I code a class for Hook system. But this is outdated. I want to use splObserver to code it.
<?php
class Event
{
private static $filters = [];
private static $actions = [];
public static function addAction($name, $callback, $priority = 10)
{
if (! isset(static::$actions[$name])) {
static::$actions[$name] = [];
}
static::$actions[$name][] = [
'priority' => (int)$priority,
'callback' => $callback,
];
}
public function doAction($name, ...$args)
{
$actions = isset(static::$actions[$name]) ? static::$actions[$name] : false;
if (! $actions) {
return;
}
// sort actions by priority
$sortArr = array_map(function ($action) {
return $action['priority'];
}, $actions);
\array_multisort($sortArr, $actions);
foreach ($actions as $action) {
\call_user_func_array($action['callback'], $args);
}
}
}
Event::addAction('action1', function(){
echo 'balabala1';
});
Event::addAction('action1', function(){
echo 'balabala2';
});
Event::doAction('action1');
Output: balabala1 balabala2
It works good. I want to use SplObserver to re-code it and try to code but no idea.
I don't really know whether this implementation could be useful in a real life application or not but, for the sake of answering your question, here we go...
Let's imagine we have a User class that we'd like to hook with our custom functions.
First, we create a reusable trait containing the Subject logic, capable of managing "event names" to whom we can hook our actions.
trait SubjectTrait {
private $observers = [];
// this is not a real __construct() (we will call it later)
public function construct()
{
$this->observers["all"] = [];
}
private function initObserversGroup(string $name = "all")
{
if (!isset($this->observers[$name])) {
$this->observers[$name] = [];
}
}
private function getObservers(string $name = "all")
{
$this->initObserversGroup($name);
$group = $this->observers[$name];
$all = $this->observers["all"];
return array_merge($group, $all);
}
public function attach(\SplObserver $observer, string $name = "all")
{
$this->initObserversGroup($name);
$this->observers[$name][] = $observer;
}
public function detach(\SplObserver $observer, string $name = "all")
{
foreach ($this->getObservers($name) as $key => $o) {
if ($o === $observer) {
unset($this->observers[$name][$key]);
}
}
}
public function notify(string $name = "all", $data = null)
{
foreach ($this->getObservers($name) as $observer) {
$observer->update($this, $name, $data);
}
}
}
Next, we use the trait in our SplSubject User class:
class User implements \SplSubject
{
// It's necessary to alias construct() because it
// would conflict with other methods.
use SubjectTrait {
SubjectTrait::construct as protected constructSubject;
}
public function __construct()
{
$this->constructSubject();
}
public function create()
{
// User creation code...
$this->notify("User:created");
}
public function update()
{
// User update code...
$this->notify("User:updated");
}
public function delete()
{
// User deletion code...
$this->notify("User:deleted");
}
}
The last step is to implement a reusable SplObserver. This observer is able to bind himself to a Closure (anonymous function).
class MyObserver implements SplObserver
{
protected $closure;
public function __construct(Closure $closure)
{
$this->closure = $closure->bindTo($this, $this);
}
public function update(SplSubject $subject, $name = null, $data = null)
{
$closure = $this->closure;
$closure($subject, $name, $data);
}
}
Now, the test:
$user = new User;
// our custom functions (Closures)
$function1 = function(SplSubject $subject, $name, $data) {
echo $name . ": function1\n"; // we could also use $data here
};
$function2 = function(SplSubject $subject, $name, $data) {
echo $name . ": function2\n";
};
// subscribe the first function to all events
$user->attach(new MyObserver($function1), 'all');
// subscribe the second function to user creations only
$user->attach(new MyObserver($function2), 'User:created');
// run a couple of methods to see what happens
$user->create();
$user->update();
The output will be:
User:created: function2
User:created: function1
User:updated: function1
NOTE: we could use SplObjectStorage instead of an array, to store observers in the trait.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With