深入解析PHP的Laravel框架中的event事件操作


Posted in PHP onMarch 21, 2016

 有时候当我们单纯的看 Laravel 手册的时候会有一些疑惑,比如说系统服务下的授权和事件,这些功能服务的应用场景是什么,其实如果没有经历过一定的开发经验有这些疑惑是很正常的事情,但是当我们在工作中多加思考会发现有时候这些服务其实我们一直都见过。下面就事件、事件监听举一个很简单的例子你就会发现。

​ 这个例子是关于文章的浏览数的实现,当用户查看文章的时候文章的浏览数会增加1,用户查看文章就是一个事件,有了事件,就需要一个事件监听器,对监听的事件发生后执行相应的操作(文章浏览数加1),其实这种监听机制在 Laravel 中是通过观察者模式实现的.

注册事件以及监听器
首先我们需要在 app/Providers/目录下的EventServiceProvider.php中注册事件监听器映射关系,如下:

protected $listen = [
    'App\Events\BlogView' => [
      'App\Listeners\BlogViewListener',
    ],
  ];

然后项目根目录下执行如下命令

php artisan event:generate

该命令完成后,会分别自动在 app/Events和app/Listensers目录下生成 BlogView.php和BlogViewListener.php文件。

定义事件

<?php

namespace App\Events;

use App\Events\Event;
use App\Post;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class BlogView extends Event
{
  use SerializesModels;

  /**
   * Create a new event instance.
   *
   * @return void
   */
  public function __construct(Post $post)
  {
    $this->post = $post;
  }

  /**
   * Get the channels the event should be broadcast on.
   *
   * @return array
   */
  public function broadcastOn()
  {
    return [];
  }
}

其实看到这些你会发现该事件类只是注入了一个 Post实例罢了,并没有包含多余的逻辑。

定义监听器
事件监听器在handle方法中接收事件实例,event:generate命令将会自动在handle方法中导入合适的事件类和类型提示事件。在handle方法内,你可以执行任何需要的逻辑以响应事件,我们的代码实现如下:

<?php

namespace App\Listeners;

use App\Events\BlogView;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Session\Store;

class BlogViewListener
{
  protected $session;
  /**
   * Create the event listener.
   *
   * @return void
   */
  public function __construct(Store $session)
  {
    $this->session = $session;
  }

  /**
   * Handle the event.
   *
   * @param BlogView $event
   * @return void
   */
  public function handle(BlogView $event)
  {
    $post = $event->post;
     //先进行判断是否已经查看过
    if (!$this->hasViewedBlog($post)) {
       //保存到数据库
      $post->view_cache = $post->view_cache + 1;
      $post->save();
       //看过之后将保存到 Session 
      $this->storeViewedBlog($post);
    }
  }

  protected function hasViewedBlog($post)
  {
    return array_key_exists($post->id, $this->getViewedBlogs());
  }

  protected function getViewedBlogs()
  {
    return $this->session->get('viewed_Blogs', []);
  }

  protected function storeViewedBlog($post)
  {
    $key = 'viewed_Blogs.'.$post->id;

    $this->session->put($key, time());
  }

}

注释中也已经说明了一些逻辑。

触发事件
事件和事件监听完成后,我们要做的就是实现整个监听,即触发用户打开文章事件在此我们使用和 Event提供的 fire方法,如下:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Post;
use Illuminate\Support\Facades\Event;
use App\Http\Requests;
use App\Events\BlogView;
use App\Http\Controllers\Controller;

class BlogController extends Controller
{
  
  public function showPost($slug)
  {
    $post = Post::whereSlug($slug)->firstOrFail();
    Event::fire(new BlogView($post));
    return view('home.blog.content')->withPost($post);
  }

}

现在打开页面发现数据库中的`view_cache已经正常加1了,这样整个就完成了。

事件广播
简介:
Laravel 5.1 之中新加入了事件广播的功能,作用是把服务器中触发的事件通过websocket服务通知客户端,也就是浏览器,客户端js根据接受到的事件,做出相应动作。本文会用简单的代码展示一个事件广播的过程。

依赖:

  • redis
  • nodejs, socket.io
  • laravel 5.1

配置:

  • config/broadcasting.php中,如下配置'default' => env('BROADCAST_DRIVER', 'redis'),,使用redis作为php和js的通信方式。
  • config/database.php中配置redis的连接。

定义一个被广播的事件:
根据Laravel文档的说明,想让事件被广播,必须让Event类实现一个Illuminate\Contracts\Broadcasting\ShouldBroadcast接口,并且实现一个方法broadcastOn。broadcastOn返回一个数组,包含了事件发送到的channel(频道)。如下:

namespace App\Events;

use App\Events\Event;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class SomeEvent extends Event implements ShouldBroadcast
{
  use SerializesModels;

  public $user_id;

  /**
   * Create a new event instance.
   *
   * @return void
   */
  public function __construct($user_id)
  {
    $this->user_id = $user_id;
  }

  /**
   * Get the channels the event should be broadcast on.
   *
   * @return array
   */
  public function broadcastOn()
  {
    return ['test-channel'];
  }
}

被广播的数据:
默认情况下,Event中的所有public属性都会被序列化后广播。上面的例子中就是$user_id这个属性。你也可以使用broadcastWith这个方法,明确的指出要广播什么数据。例如:

public function broadcastWith()
{
  return ['user_id' => $this->user_id];
}

Redis和Websocket服务器:
需要启动一个Redis,事件广播主要依赖的就是redis的sub/pub功能,具体可以看redis文档
需要启动一个websocket服务器来和client通信,建议使用socket.io,代码如下:

var app = require('http').createServer(handler);
var io = require('socket.io')(app);

var Redis = require('ioredis');
var redis = new Redis('6379', '192.168.1.106');

app.listen(6001, function() {
  console.log('Server is running!');
});

function handler(req, res) {
  res.writeHead(200);
  res.end('');
}

io.on('connection', function(socket) {
  console.log('connected');
});

redis.psubscribe('*', function(err, count) {
  console.log(count);
});

redis.on('pmessage', function(subscribed, channel, message) {
  console.log(subscribed);
  console.log(channel);
  console.log(message);

  message = JSON.parse(message);
  io.emit(channel + ':' + message.event, message.data);
});

这里需要注意的是redis.on方法的定义,接收到消息后,给client发送一个事件,事件名称为channel + ':' + message.event。

客户端代码:
客户端我们也使用socket.io,作为测试,代码尽量简化,仅仅打印一个接受到的数据即可。如下:

var socket = io('http://localhost:6001');
socket.on('connection', function (data) {
  console.log(data);
});
socket.on('test-channel:App\\Events\\SomeEvent', function(message){
  console.log(message);
});
console.log(socket);

服务器触发事件:
直接在router中定义个事件触发即可。如下:

Route::get('/event', function(){
  Event::fire(new \App\Events\SomeEvent(3));
  return "hello world";
});

测试:

  • 启动redis
  • 启动websocket
  • 打开带有客户端代码的页面,可以看到websocket已经连接成功。
  • 触发事件,打开另一个页面 localhost/event。

这时就可以发现,第一个页面的console中打印出了Object{user_id: 3},说明广播成功。

PHP 相关文章推荐
discuz authcode 经典php加密解密函数解析
Jul 12 PHP
PHP通过header实现文本文件下载的代码
Aug 08 PHP
关于php mvc开发模式的感想
Jun 28 PHP
php读取文件内容至字符串中,同时去除换行、空行、行首行尾空格(Zjmainstay原创)
Jul 31 PHP
apache和php之间协同工作的配置经验分享
Apr 08 PHP
PHP中判断变量为空的几种方法小结
Nov 12 PHP
php自动更新版权信息显示的方法
Jun 19 PHP
Zend Framework教程之连接数据库并执行增删查的方法(附demo源码下载)
Mar 21 PHP
mysql alter table命令修改表结构实例详解
Sep 24 PHP
ThinkPHP5.0多个文件上传后找不到临时文件的修改方法
Jul 30 PHP
PHP whois查询类定义与用法示例
Apr 03 PHP
Laravel 解决419错误 -ajax请求错误的问题(CSRF验证)
Oct 25 PHP
Android App中DrawerLayout抽屉效果的菜单编写实例
Mar 21 #PHP
PHP的Laravel框架结合MySQL与Redis数据库的使用部署
Mar 21 #PHP
PHP编写学校网站上新生注册登陆程序的实例分享
Mar 21 #PHP
调用WordPress函数统计文章访问量及PHP原生计数器的实现
Mar 21 #PHP
PHP程序中的文件锁、互斥锁、读写锁使用技巧解析
Mar 21 #PHP
PHP编程中尝试程序并发的几种方式总结
Mar 21 #PHP
PHP的Laravel框架中使用消息队列queue及异步队列的方法
Mar 21 #PHP
You might like
模仿OSO的论坛(一)
2006/10/09 PHP
支持php4、php5的mysql数据库操作类
2008/01/10 PHP
PHP insert语法详解
2008/06/07 PHP
PHP网站安装程序制作的原理、步骤、注意事项和示例代码
2010/08/01 PHP
PHP使用preg_split()分割特殊字符(元字符等)的方法分析
2017/02/04 PHP
JavaScript与函数式编程解释
2007/04/27 Javascript
jQuery中triggerHandler()方法用法实例
2015/01/19 Javascript
jQuery里filter()函数与find()函数用法分析
2015/06/24 Javascript
javascript日期操作详解(脚本之家整理)
2015/09/05 Javascript
每天一篇javascript学习小结(Boolean对象)
2015/11/12 Javascript
JavaScript 对象详细整理总结
2016/09/29 Javascript
浅谈vue.js导入css库(elementUi)的方法
2018/03/09 Javascript
手把手教你用Node.js爬虫爬取网站数据的方法
2018/07/05 Javascript
解决vue移动端适配问题
2018/12/12 Javascript
一百行JS代码实现一个校验工具
2019/04/30 Javascript
ES6 proxy和reflect的使用方法与应用实例分析
2020/02/15 Javascript
用python实现面向对像的ASP程序实例
2014/11/10 Python
对Python生成器、装饰器、递归的使用详解
2019/07/19 Python
python实现DEM数据的阴影生成的方法
2019/07/23 Python
Django框架 信号调度原理解析
2019/09/04 Python
Python时间差中seconds和total_seconds的区别详解
2019/12/26 Python
keras自定义损失函数并且模型加载的写法介绍
2020/06/15 Python
Python中bisect的用法及示例详解
2020/07/20 Python
Django配置跨域并开发测试接口
2020/11/04 Python
HTML5 Web缓存和运用程序缓存(cookie,session)
2018/01/11 HTML / CSS
Boden美国官网:英伦原创时装品牌
2017/07/03 全球购物
Otticanet英国:最顶尖的世界名牌眼镜, 能得到打折季的价格
2019/02/10 全球购物
巴基斯坦购物网站:Goto
2019/03/11 全球购物
联想印度官方网上商店:Lenovo India
2019/08/24 全球购物
2014年小学重阳节活动策划方案
2014/09/16 职场文书
大专生自我鉴定怎么写
2014/09/16 职场文书
2015中秋节晚会主持词
2015/07/01 职场文书
2015年学校少先队工作总结
2015/07/20 职场文书
2015年工会工作总结范文
2015/07/23 职场文书
PHP 对接美团大众点评团购券(门票)的开发步骤
2021/04/03 PHP
用JS实现飞机大战小游戏
2021/06/09 Javascript