说明:本文主要来源于real-time-apps-laravel-5-1-event-broadcastingphp
本文主要基于Laravel的Model Event介绍该框架的实时通讯功能,Laravel模型的生命周期中包含事件:created
、creating
、saved
、saving
、updated
,updating
、deleted
、deleting
、restored
、restoring
,同时结合了Pusher包,有关Pusher的注册和使用相关信息能够参考:基于 Pusher 驱动的 Laravel 事件广播(上)。同时,做者会将开发过程当中的一些截图和代码黏上去,提升阅读效率。备注:Laravel对Model的CRUD操做都会触发对应的事件,如create操做会在建立前触发creating事件,建立后触发created事件,即Model Event。
css
先全局安装composer:html
curl -sS https://getcomposer.org/installer | php mv composer.phar /usr/local/bin/composer
新建一个空文件夹,在文件夹下,再使用composer安装Laravel项目:html5
composer create-project laravel/laravel mylaravelapp --prefer-dist
在app/Http/routes.php中写上资源型路由:mysql
Route::get('/', function () { return view('index'); }); Route::resource('items', 'ItemController', ['except' => ['create', 'edit']]);//排除掉create和edit操做
先建个迁移文件:jquery
php artisan make:migration create_items_table --create=items
在迁移文件database/migrations/*_create_items_table.php中写上:laravel
/** * Run the migrations. * * @return void */ public function up() { Schema::create('items', function (Blueprint $table) { $table->increments('id'); $table->string('title'); $table->boolean('isCompleted')->default(false); $table->timestamps(); }); }
新建一个Eloquent Model:ajax
php artisan make:model Item
别忘了配置下数据库,我用的是MAMP集成环境,数据库服务是MySQL。数据库配置主要在config/database.php和.env文件中,在.env文件中写上对应的host,database,user,password:sql
DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_DATABASE=model_event DB_USERNAME=root DB_PASSWORD=model_event
首先在项目根目录下输入artisan命令建立个ItemController:数据库
php artisan make:controller ItemController
在ItemController中写上增删改查:
class ItemController extends Controller { /** * Display a listing of the resource. * * @return Response */ public function index() { $uncompletedItems = Item::where('isCompleted', 0)->get(); $completedItems = Item::where('isCompleted', 1)->get(); $data = ['uncompletedItems' => $uncompletedItems, 'completedItems' => $completedItems]; return view('item.index', $data); } /** * Store a newly created resource in storage. * * @return Response */ public function store(Request $request) { $item = new Item; $item->title = $request->title; $item->save(); return response()->json(['id' => $item->id]); } /** * Display the specified resource. * * @param int $id * @return Response */ public function show($id) { $item = Item::find($id); return view('item.show', ['item' => $item]); } /** * Update the specified resource in storage. * * @param int $id * @return Response */ public function update(Request $request, $id) { $item = Item::find($id); $item->isCompleted = (bool) $request->isCompleted; $item->save(); return; } /** * Remove the specified resource from storage. * * @param int $id * @return Response */ public function destroy($id) { $item = Item::find($id); $item->delete(); return; } }
建个reources/views/index.php:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="csrf-token" content="{{ csrf_token() }}"> <title>Todo App</title> <!-- Bootstrap --> {{--<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">--}} <link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css"> <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries --> <!-- WARNING: Respond.js doesn't work if you view the page via file:// --> <!--[if lt IE 9]> <!--<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>--> {{--<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>--}} {{--<![endif]-->--}} </head> <body> <div class="container"> <div class="row"> <div class="col-sm-offset-4 col-sm-4"> <h1 class="text-center">Todo App</h1> <form id="addFrm" role="form"> <div class="form-group"> <input type="text" class="form-control" name="title" id="title" required="required" placeholder="Enter title"> </div> <div class="form-group"> <input type="submit" class="btn btn-default" name="submit" value="Add"> </div> </form> <hr> <div id="itemsList"> </div> </div> </div> </div> <!-- jQuery (necessary for Bootstrap's JavaScript plugins) --> <!-- 新 Bootstrap 核心 CSS 文件 --> <!-- jQuery文件。务必在bootstrap.min.js 以前引入 --> <script src="//cdn.bootcss.com/jquery/1.11.3/jquery.min.js"></script> <!-- 最新的 Bootstrap 核心 JavaScript 文件 --> <script src="//cdn.bootcss.com/bootstrap/3.3.5/js/bootstrap.min.js"></script> {{--<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>--}} {{--<script src="//cdn.bootcss.com/jquery/1.11.3/jquery.min.js"></script>--}} <!-- Include all compiled plugins (below), or include individual files as needed --> {{--<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>--}} <script> $.ajaxSetup({ headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') } }); </script> <script> //renders item's new state to the page function addItem(id, isCompleted) {//根据状态添加item $.get("/items/" + id, function(data) { if (isCompleted) { $("#completedItemsList").append(data); } else { $("#uncompletedItemsList").append(data); } }); } //removes item's old state from the page function removeItem(id) { $('li[data-id="' + id + '"').remove(); } (function($, addItem, removeItem) { $.get( "/items", function( data ) {//DOM加载后,AJAX请求数据,进入ItemController::index() $( "#itemsList" ).html( data ); }); $( "#addFrm" ).submit(function() {//回车或点击提交按钮时,AJAX post到ItemController::store()方法,json返回保存的'id'=>$item->id console.log($(this).serialize()); $.post( "/items", $(this).serialize(), function( data ) { addItem(data.id, false); $( "#title" ).val(''); }); return false; }); $(document).on("change", ".isCompleted", function() { var id = $(this).closest('li').data('id'); var isCompleted = $(this).prop("checked") ? 1 : 0;//获取该item的完成状态 $.ajax('/items/' + id, {//进入ItemController::update(),更细下item状态 data: {"isCompleted": isCompleted}, method: 'PATCH', success: function() {//根据状态变化删除增长item removeItem(id); addItem(id, isCompleted); } }); }); $(document).on("click", ".deleteItem", function() { var id = $(this).closest('li').data('id'); $.ajax('/items/' + id, {//进入ItemController::destroy()删除数据库中item method: 'DELETE', success: function() {//UI删除该item removeItem(id); } }); }); })(jQuery, addItem, removeItem); </script> </body> </html>
ItemController控制器中返回两个子视图item.index、item.show,在resources/views/item中建两个:
//item.index <legend>未完成的Items</legend> <ul id="uncompletedItemsList" class="list-group"> @foreach ($uncompletedItems as $item) @include('item.show') @endforeach </ul> <hr> <legend>完成的Items</legend> <ul id="completedItemsList" class="list-group"> @foreach ($completedItems as $item) @include('item.show') @endforeach </ul>
//item.show <li class="list-group-item {{ ($item->isCompleted) ? 'text-muted' : '' }}" data-id="{{ $item->id }}"> <span class="badge"> <span class="deleteItem glyphicon glyphicon-remove" aria-hidden="true"></span> </span> <span class="checkbox-inline"> <label> <input type="checkbox" class="isCompleted" value="1" {{ ($item->isCompleted) ? 'checked="checked"' : '' }}> {{ $item->title }} </lable> </span> </li>
一切准备就OK了,个人在MAMP环境输入路由:http://laravelmodelevent.app:...,新开AB两个页面,而后在输入框里提交文本后:
A页面输入后B页面只有刷新才能看到最新输入的文本,不能实时显示,固然,输入的文本已经保存在model_event.items表里了:
页面里改变每个item的checkbox后,该item的状态将会互换,在UI上显示也是上下位置互换,具体逻辑能够看views/index.blade.php的JS逻辑,这不是本文的重点,故不详述。
重点是:在A页面写入新文本,B页面不能实时显示。这还不是个实时APP。
建立三个广播事件:
ItemCreated:
当新建一个item完成时触发
ItemUpdated:
当更新一个item完成时触发(isCompleted=0或1)
ItemDeleted:
当删除一个item完成时触发
在项目根目录依次输入:
php artisan make:event ItemCreated php artisan make:event ItemUpdated php artisan make:event ItemDeleted
Laravel事件广播须要实现ShouldBroadcast接口而且在broadcastOn()方法中写上广播频道:
class ItemCreated extends Event implements ShouldBroadcast { use SerializesModels; public $id; /** * Create a new event instance. * * @return void */ public function __construct(Item $item) { $this->id = $item->id; } /** * Get the channels the event should be broadcast on. * * @return array */ public function broadcastOn() { return ['itemAction']; } }
class ItemDeleted extends Event implements ShouldBroadcast { use SerializesModels; public $id; /** * Create a new event instance. * * @return void */ public function __construct(Item $item) { $this->id = $item->id; } /** * Get the channels the event should be broadcast on. * * @return array */ public function broadcastOn() { return ['itemAction']; } }
class ItemUpdated extends Event implements ShouldBroadcast { use SerializesModels; public $id; public $isCompleted; /** * Create a new event instance. * * @return void */ public function __construct(Item $item) { $this->id = $item->id; $this->isCompleted = (bool)$item->isCompleted; } /** * Get the channels the event should be broadcast on. * * @return array */ public function broadcastOn() { return ['itemAction']; } }
Laravel的Eloquent每一CRUD操做都会触发Model事件,能够在service provider里监听这些事件从而触发新建的三个广播事件,在AppServiceProvider中:
class AppServiceProvider extends ServiceProvider { /** * Bootstrap any application services. * * @return void */ public function boot() { Item::created(function($item){ event(new ItemCreated($item)); }); Item::deleted(function($item){ event(new ItemDeleted($item)); }); Item::updated(function($item){ event(new ItemUpdated($item)); }); } /** * Register any application services. * * @return void */ public function register() { // } }
Pusher的做用、注册和安装可参考:
基于 Pusher 驱动的 Laravel 事件广播(上)
注册安装也比较简单,总之使用Pusher能作个实时APP。
更新resources/views/index.blade.php文件:
... <title>Todo App</title> <!-- Bootstrap --> <link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css"> <script src="//js.pusher.com/3.0/pusher.min.js"></script>//引入pusherJS文件 ... $.post( "/items", $(this).serialize(), function( data ) { // addItem(data.id, false);//注销掉 $( "#title" ).val(''); }); ... $.ajax('/items/' + id, {//进入ItemController::update(),更细下item状态 data: {"isCompleted": isCompleted}, method: 'PATCH', success: function() {//根据状态变化删除增长item // removeItem(id);//注销掉 // addItem(id, isCompleted);//注销掉 } }); ... $(document).on("click", ".deleteItem", function() { var id = $(this).closest('li').data('id'); $.ajax('/items/' + id, {//进入ItemController::destroy()删除数据库中item method: 'DELETE', success: function() {//UI删除该item // removeItem(id);//注销掉 } }); }); })(jQuery, addItem, removeItem); //新加代码 var pusher = new Pusher("{{env("PUSHER_KEY")}}"); var itemActionChannel = pusher.subscribe('itemAction'); itemActionChannel.bind('App\\Events\\ItemCreated', function (data) { console.log(data.id); addItem(data.id, false); }); itemActionChannel.bind('App\\Events\\ItemDeleted', function (data) { console.log(data.id); removeItem(data.id); }); itemActionChannel.bind('App\\Events\\ItemUpdated', function (data) { removeItem(data.id); addItem(data.id, data.isCompleted); });
新加代码主要用pusher对象注册三个事件广播的频道'itemAction',并分别绑定三个事件,成功后回调执行对应的UI操做。想要了解更多能够参考这篇文章:基于 Pusher 驱动的 Laravel 事件广播(下)
刷新AB页面,并观察数据库model_event.items。
测试实时建立功能。
A页面输入文本后发现B页面不用刷新就实时显示对应内容,且数据库已经保存刚刚建立的文本:
测试实时更新功能。
B页面点击状态更新checkbox后,A页面该item状态也实时更新,且数据库isCompleted字段变为1:
测试实时删除功能。
A页面点击删除按钮后,B页面也实时删除对应的item,且数据库该item也删除:
OK,It is working!!!
总结:本节主要利用Laravel的Model Event来建立一个实时WEB APP,挺好玩的,能够玩一玩哦。有问题可留言。嘛,过两天还想结合Laravel的Container Event容器事件新开篇文章,到时见。
欢迎关注Laravel-China。