说明:本文主要来源于real-time-apps-laravel-5-1-event-broadcasting
本文主要基于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。
Non Real-time App
Laravel程序安装
先全局安装composer:
代码语言:javascript复制 curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer
新建一个空文件夹,在文件夹下,再使用composer安装Laravel项目:
代码语言:javascript复制composer create-project laravel/laravel mylaravelapp --prefer-dist
写一个TODO APP
写路由Route
在app/Http/routes.php中写上资源型路由:
代码语言:javascript复制Route::get('/', function () {
return view('index');
});
Route::resource('items', 'ItemController', ['except' => ['create', 'edit']]);//排除掉create和edit操作
写个Model
先建个迁移文件:
代码语言:javascript复制php artisan make:migration create_items_table --create=items
在迁移文件database/migrations/*_create_items_table.php中写上:
代码语言:javascript复制/**
* 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:
代码语言:javascript复制php artisan make:model Item
别忘了配置下数据库,我用的是MAMP集成环境,数据库服务是MySQL。数据库配置主要在config/database.php和.env文件中,在.env文件中写上对应的host,database,user,password:
代码语言:javascript复制DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_DATABASE=model_event
DB_USERNAME=root
DB_PASSWORD=model_event
写控制器Controller
首先在项目根目录下输入artisan命令创建个ItemController:
代码语言:javascript复制php artisan make:controller ItemController
在ItemController中写上增删改查:
代码语言:javascript复制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;
}
}
写个View视图
建个reources/views/index.php:
代码语言:javascript复制<!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中建两个:
代码语言:javascript复制//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>
代码语言:javascript复制//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:8888/,新开AB两个页面,然后在输入框里提交文本后:
A页面输入后B页面只有刷新才能看到最新输入的文本,不能实时显示,当然,输入的文本已经保存在model_event.items表里了:
页面里改变每一个item的checkbox后,该item的状态将会互换,在UI上显示也是上下位置互换,具体逻辑可以看views/index.blade.php的JS逻辑,这不是本文的重点,故不详述。
重点是:在A页面写入新文本,B页面不能实时显示。这还不是个实时APP。
Real-time App
创建三个广播事件
创建三个广播事件:
-
ItemCreated:
当新建一个item完成时触发 -
ItemUpdated:
当更新一个item完成时触发(isCompleted=0或1) -
ItemDeleted:
当删除一个item完成时触发
在项目根目录依次输入:
代码语言:javascript复制php artisan make:event ItemCreated
php artisan make:event ItemUpdated
php artisan make:event ItemDeleted
Laravel事件广播需要实现ShouldBroadcast接口并且在broadcastOn()方法中写上广播频道:
代码语言:javascript复制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'];
}
}
代码语言:javascript复制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'];
}
}
代码语言:javascript复制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'];
}
}
创建Model Event
Laravel的Eloquent每一CRUD操作都会触发Model事件,可以在service provider里监听这些事件从而触发新建的三个广播事件,在AppServiceProvider中:
代码语言:javascript复制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的作用、注册和安装可参考:
基于 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容器事件新开篇文章,到时见。