【问题标题】:Event-driven architecture and hooks in PHPPHP 中的事件驱动架构和钩子
【发布时间】:2011-10-14 08:06:50
【问题描述】:

我正计划开发一款具有 PHP 后端以与数据存储库通信的游戏。我正在考虑并得出结论,我们的游戏要遵循的最佳设计范式将是事件驱动的。我希望有一个成就系统(类似于本网站的徽章系统),基本上我希望能够将这些“成就检查”与游戏中发生的许多不同事件挂钩。即:

当用户执行操作时,X 钩子 Y 被触发并调用所有附加函数来检查成就要求。

在构建这样的架构时,我将允许轻松添加新成就,因为我所要做的就是将检查功能添加到适当的挂钩中,其他一切都会到位。

我不确定这是否是对我打算做的事情的一个很好的解释,但无论如何我正在寻找以下内容:

  1. 关于如何编写事件驱动应用程序的良好参考资料
  2. 代码 sn-p(s) 展示了如何在 PHP 中的函数中放置“钩子”
  3. 代码 sn-p(s) 显示如何将函数附加到第 2 点中提到的“钩子”

我对如何完成 2) 和 3) 有一些想法,但我希望精通此问题的人能够阐明最佳实践。

提前谢谢你!

【问题讨论】:

  • 我知道 wordpress 在他们的插件中使用了钩子。你可以看看here
  • WordPress 钩子 API 的链接并不是我真正想要的。
  • 好吧,你实际上可以检查一下 wordpress 如何使用钩子。有很好的文档,源代码清晰。
  • 嗯,wordpress 钩子实现并不是一个很好的例子,它没有清晰的接口,实现是错误的,并且代码没有很好的文档记录。然而,它显示的是您可以将回调分配给变量,包括。数组,您可以对这些数组进行排序,以及在调用回调和取消注册回调时可能会做错什么。然而,这些信息并不是真正的可访问格式。

标签: php hook event-driven-design


【解决方案1】:

关于如何编写事件驱动应用程序的良好参考资料

您可以使用"dumb" callbacks (Demo) 执行此操作:

class Hooks
{
    private $hooks;
    public function __construct()
    {
        $this->hooks = array();
    }
    public function add($name, $callback) {
        // callback parameters must be at least syntactically
        // correct when added.
        if (!is_callable($callback, true))
        {
            throw new InvalidArgumentException(sprintf('Invalid callback: %s.', print_r($callback, true)));
        }
        $this->hooks[$name][] = $callback;
    }
    public function getCallbacks($name)
    {
        return isset($this->hooks[$name]) ? $this->hooks[$name] : array();
    }
    public function fire($name)
    {
        foreach($this->getCallbacks($name) as $callback)
        {
            // prevent fatal errors, do your own warning or
            // exception here as you need it.
            if (!is_callable($callback))
                continue;

            call_user_func($callback);
        }
    }
}

$hooks = new Hooks;
$hooks->add('event', function() {echo 'morally disputed.';});
$hooks->add('event', function() {echo 'explicitly called.';});
$hooks->fire('event');

或者实现事件驱动应用程序中经常使用的模式:Observer Pattern

代码 sn-p(s) 显示如何在 PHP 中的函数中放置“钩子”

上面的手动链接(回调可以存储到变量中)和一些PHP code examples for the Observer Pattern

【讨论】:

  • 太棒了。这或多或少是我关于钩子实现的想法,但想确保我没有遗漏任何东西。此外,非常酷的 PHP 演示网站。
  • @MoarCodePlz:这个实现缺少事件/钩子上下文。例如,您也可以将事件封装到它自己的类中,以便它们可以被触发(接口它们)。然而,这会增加另一层。在这种情况下,另一个有用的函数是is_callable
  • @MoarCodePlz:为类添加了一些清理以显示这一点。您可能需要一些扩展来再次删除回调(取消注册)。
  • 什么是“停止”下一个事件运行的好方法,比如我是否会输出响应或其他什么?
  • @John:看看call_user_func_array() 而不是call_user_func() - 它允许您传递零个或多个参数的数组。还有func_get_args()
【解决方案2】:

对于 PHP,我经常集成 Symfony 事件组件:http://components.symfony-project.org/event-dispatcher/

下面是一个简短的示例,您可以在 Symfony 的 Recipe section 中找到扩展。

<?php

class Foo
{
  protected $dispatcher = null;

    // Inject the dispatcher via the constructor
  public function __construct(sfEventDispatcher $dispatcher)
  {
    $this->dispatcher = $dispatcher;
  }

  public function sendEvent($foo, $bar)
  {
    // Send an event
    $event = new sfEvent($this, 'foo.eventName', array('foo' => $foo, 'bar' => $bar));
    $this->dispatcher->notify($event);
  }
}


class Bar
{
  public function addBarMethodToFoo(sfEvent $event)
  {
    // respond to event here.
  }
}


// Somewhere, wire up the Foo event to the Bar listener
$dispatcher->connect('foo.eventName', array($bar, 'addBarMethodToFoo'));

?>

这是我们集成到购物车中的系统,用于创建类似游戏的购物体验,将用户操作与游戏事件挂钩。当用户执行特定操作时,php 触发事件导致触发奖励。

示例 1:如果用户点击特定按钮 10 次,他们会收到一颗星。

示例 2:当用户推荐朋友并且该朋友注册时,触发事件以奖励原始推荐人积分。

【讨论】:

    【解决方案3】:

    查看CodeIgniter,因为它有hooks built right in

    只需启用钩子:

    $config['enable_hooks'] = TRUE;
    

    然后定义你的钩子:

     $hook['post_controller_constructor'] = array(
                                    'class'    => 'Hooks',
                                    'function' => 'session_check',
                                    'filename' => 'hooks.php',
                                    'filepath' => 'hooks',
                                    'params'   => array()
                                    ); 
    

    然后在你的课堂上使用它:

    <?php
    
        class Hooks {
            var $CI;
    
            function Hooks() {
                $this->CI =& get_instance();
            }
    
            function session_check() {
                if(!$this->CI->session->userdata("logged_in") && $this->CI->uri->uri_string != "/user/login")
                    redirect('user/login', 'location');
            }
        }
    
    ?> 
    

    【讨论】:

    • 虽然我很欣赏这些反馈,并且肯定要查看 CodeIgniter,但我更多的是寻找有关如何从头开始编写这类东西的解释。我也在寻找有关开发事件驱动应用程序的良好参考,以便我可以使用自己的用户制作的钩子开发自己的事件驱动应用程序。
    • 花时间编写游戏逻辑,而不是重新发明经过尝试、真实和测试的*。用示例用法更新了我的答案。
    • 虽然我完全同意“不重新发明*的论点”,但我也认为,如果我只需要一个非常简单的事件驱动钩子系统,那么使用具有许多铃铛的外部库与 hakre 所示的编写单个类相比,口哨是一种更糟糕的方法。如果我正在寻找更复杂、更强大的东西,那么我认为使用外部库会更合适。
    • 真实数据。我知道这种感觉。需要考虑的一件事是,社区驱动的开源框架总是比单独的努力更好。学习如何做某事就像查看源代码并对其进行逆向工程一样简单。这样,您就可以在当前的最佳实践中奠定基础,然后从那里开始。
    • CodeIgniter 与事件驱动设计相去甚远。