文档章节

基于Model Event模型事件的Laravel实时APP

botkenni
 botkenni
发布于 2016/10/11 09:16
字数 2160
阅读 35
收藏 0

说明:本文主要来源于real-time-apps-laravel-5-1-event-broadcasting

本文主要基于Laravel的Model Event介绍该框架的实时通信功能,Laravel模型的生命周期中包含事件:createdcreatingsavedsavingupdatedupdatingdeleteddeletingrestoredrestoring,同时结合了Pusher包,有关Pusher的注册和使用相关信息可以参考:(基于 Pusher 驱动的 Laravel 事件广播)(上)。
备注:Laravel对Model的CRUD操作都会触发对应的事件,如create操作会在创建前触发creating事件,创建后触发created事件,即Model Event。

Non Real-time App

Laravel程序安装

先全局安装composer:

    curl -sS https://getcomposer.org/installer | php
    mv composer.phar /usr/local/bin/composer

新建一个空文件夹,在文件夹下,再使用composer安装Laravel项目:

composer create-project laravel/laravel mylaravelapp --prefer-dist

写一个TODO APP

写路由Route

在app/Http/routes.php中写上资源型路由:

Route::get('/', function () {
    return view('index');
});
Route::resource('items', 'ItemController', ['except' => ['create', 'edit']]);//排除掉create和edit操作

写个Model

先建个迁移文件:

php artisan make:migration create_items_table --create=items

在迁移文件database/migrations/*_create_items_table.php中写上:

/**
     * 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:

php artisan make:model Item

别忘了配置下数据库,我用的是MAMP集成环境,数据库服务是MySQL。数据库配置主要在config/database.php和.env文件中,在.env文件中写上对应的host,database,user,password:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_DATABASE=model_event
DB_USERNAME=root
DB_PASSWORD=model_event

写控制器Controller

首先在项目根目录下输入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;
    }
}

写个View视图

建个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: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完成时触发

在项目根目录依次输入:

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'];
    }
}

创建Model Event

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的作用、注册和安装可参考:基于 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容器事件新开篇文章,到时见。

© 著作权归作者所有

共有 人打赏支持
botkenni
粉丝 18
博文 406
码字总数 433886
作品 0
西城
程序员
私信 提问
基于 Pusher 驱动的 Laravel 事件广播(上)

说明:本文主要来源于Building Real-Time Laravel Apps with Pusher。 本文主要介绍使用Pusher包来开发带有实时通信功能的Laravel APP,整个教程只需要两个小时就能顺利走一遍。同时,作者会...

botkenni
2016/10/10
65
0
laravel中的错误与日志

日志 laravel中的日志是基于monolog而封装的。laravel在它上面做了几个事情: 把monolog中的addInfo等函数简化成为了info这样的函数 增加了useFiles和useDailyFiles两个参数,使得做日志管理...

王二狗子11
2018/01/07
0
0
Laravel框架一:原理机制篇

http://www.cnblogs.com/XiongMaoMengNan/p/6644892.html Laravel作为在国内国外都颇为流行的PHP框架,风格优雅,其拥有自己的一些特点。 一. 请求周期   Laravel 采用了单一入口模式,应用...

hisense20112784
2017/08/11
0
0
Laravel 5.1 LTS 发布,支持 PSR-2

Laravel 5.1 是 Laravel 首个 LTS 版本,包含很多新特性。 Laravel 5.1 现在开始会包括 3 年的安全修复。此版本也重新编写了文档,而且添加了实时搜索的自动完成功能。 应用和生成器转换为 ...

raykwok
2015/06/09
2.3K
9
Laravel4.1--Laravel 应用程序的体系结构(二)

 在一切环境就绪了,当然就要开始了解框架了。   站在巨人的肩膀上,学东西会事半功倍。我在网上找到一篇好文章,正好可以让我轻松了解Laravel应用程序的体系结构。因此借来直接用了。  ...

botkenni
2016/11/16
10
0

没有更多内容

加载失败,请刷新页面

加载更多

Nginx反向代理

Nginx反向代理 应用场景 A 机器运行的nginx提供的web服务,只有一个内网地址192.168.254.128(内网) B机器有两块网卡,一个地址是192.168.254.137(内网),另一个是192.168.79.128(外网)...

李超小牛子
今天
2
0
数据库事务隔离级别

当数据库上有多个事务同时执行的时候,可能出现下面问题: 脏读(dirty read):指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访...

Jacktanger
今天
1
0
4.61 - 第二个JAVA应用 4.62/63 - Tomcat的管理功能

4.61 - 第二个JAVA应用 方法一:配置文件: /usr/local/tomcat/conf/server.xml <Host name="www.aminglinux.cc" appBase="/data/wwwroot/www.aminglinux.cc" unpackWARs="tr......

Champin
今天
0
0
MariaDB密码重置

MariaDB密码重置 如果记得root的密码: mysqladmin -uroot -paminglinux password "aming-linux" //用此方式将原密码aminglinux重置为aming-linux 如果不记得原密码: # vi /etc/my.cnf......

wzb88
昨天
1
0
印度封禁抖音,称导致该国年轻人“文化堕落”!

本文经授权转载自顶级程序员 (ID:TopCoding) 作者 | 江户川雨 责编 | https://weavi.com/13775725 https://weavi.com/13775726 https://weavi.com/13775724 https://weavi.com/13775723 ......

陈刚生
昨天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部