在之前的文章中我们谈过php的几种设计模式
浅谈PHP中常用的3种设计模式
今天我们继续谈另外一种设计模式-----中介者模式(Mediator pattern)
什么是中介者模式
中介者模式是 PHP 中的一种行为设计模式,它通过将对象之间的通信封装在中介者对象中来促进对象之间的松散耦合。它允许对象通过中介间接地相互通信,而不是直接相互交互。这有助于减少依赖性并简化对象之间的通信流程。
Mediator 设计模式在以下几种情况下很有用:
- 减少依赖:中介者模式通过消除直接依赖来促进对象之间的松散耦合。它们不是通过对象直接相互通信,而是通过中介进行通信,这减少了对象之间的依赖关系。
- 集中控制:Mediator模式提供了一种集中控制机制,用于协调对象之间的交互。中介者不是让多个对象相互通信和协调,而是充当通信和协调的单一联系点。
- 简化通信:通过将通信逻辑封装在中介中,降低了对象到对象通信的复杂性。对象只需要知道中介接口,不需要知道其他对象或它们的接口。这简化了通信流程,使其更易于理解和维护。
- 促进可维护性:借助中介者模式,可以在中介者内部处理通信行为的更改或新对象的引入,而不会影响单个对象。这使得系统更加灵活和可维护,因为更改可以本地化到中介,不需要跨多个对象进行修改。
- 增强可重用性:中介者模式通过解耦对象并使它们更加独立来提升可重用性。对象可以在不同的上下文中或与不同的中介重用,只要它们遵守中介接口。
- 促进复杂的交互:在多个对象需要相互通信和协调的复杂系统中,中介者模式提供了一种结构化的方法来管理交互。调解器充当了解不同对象之间的关系和职责的中心枢纽,从而更易于管理和推理复杂的交互。
中介者模式的PHP实现
下面是 PHP 中中介者模式的一个示例实现:
// Mediator interface
interface Mediator
{
public function notify($sender, $event);
}
// Concrete Mediator
class ConcreteMediator implements Mediator
{
private $component1;
private $component2;
public function __construct(Component1 $component1, Component2 $component2)
{
$this->component1 = $component1;
$this->component1->setMediator($this);
$this->component2 = $component2;
$this->component2->setMediator($this);
}
public function notify($sender, $event)
{
if ($sender === $this->component1) {
echo "Component 1 triggered event: $event\n";
echo "Mediator reacts and triggers the following action in Component 2\n";
$this->component2->doAction();
} elseif ($sender === $this->component2) {
echo "Component 2 triggered event: $event\n";
echo "Mediator reacts and triggers the following action in Component 1\n";
$this->component1->doAction();
}
}
}
// Components
class Component1
{
private $mediator;
public function setMediator(Mediator $mediator)
{
$this->mediator = $mediator;
}
public function triggerEvent($event)
{
$this->mediator->notify($this, $event);
}
public function doAction()
{
echo "Component 1 performs its action.\n";
}
}
class Component2
{
private $mediator;
public function setMediator(Mediator $mediator)
{
$this->mediator = $mediator;
}
public function triggerEvent($event)
{
$this->mediator->notify($this, $event);
}
public function doAction()
{
echo "Component 2 performs its action.\n";
}
}
// Usage
$component1 = new Component1();
$component2 = new Component2();
$mediator = new ConcreteMediator($component1, $component2);
// Triggering events
$component1->triggerEvent('Event A');
$component2->triggerEvent('Event B');
在此示例中,我们有一个 Mediator 接口,它定义了方法 notify() 用于对象之间的通信。 ConcreteMediator 类实现 Mediator 接口并维护对 Component1 和 Component2 对象的引用。它通过响应每个组件触发的事件来处理它们之间的通信。
Component1 和 Component2 类都引用了 Mediator 接口。他们可以通过调用 triggerEvent() 来触发事件并通知中介。中介者通过调用组件中的相应操作来对事件作出反应。
当组件触发事件时,中介者接收通知并通过触发其他组件中的动作做出相应的反应。
这样,对象可以通过中介间接通信,组件之间不需要知道彼此,从而实现松散耦合和代码更易于维护。
中介者模式在PHP框架中的应用
在php的流行框架thinkphp、laravel、hyperf中,都能发现中介者模式的身影,而且几乎都用在**事件调度器(EventDispatcher)**中。只不过事件调度器一般都会再增加一个观察者模式。
在Thinkphp中,\think\Event类就是中介者,相当于ConcreteMediator的角色。
namespace think;
use ReflectionClass;
use ReflectionMethod;
/**
* 事件管理类
* @package think
*/
class Event
{
/**
* 监听者
* @var array
*/
protected $listener = [];
/**
* 事件别名
* @var array
*/
protected $bind = [
'AppInit' => event\AppInit::class,
'HttpRun' => event\HttpRun::class,
'HttpEnd' => event\HttpEnd::class,
'RouteLoaded' => event\RouteLoaded::class,
'LogWrite' => event\LogWrite::class,
'LogRecord' => event\LogRecord::class,
];
/**
* 应用对象
* @var App
*/
protected $app;
public function __construct(App $app)
{
$this->app = $app;
}
/**
* 批量注册事件监听
* @access public
* @param array $events 事件定义
* @return $this
*/
public function listenEvents(array $events)
{
foreach ($events as $event => $listeners) {
if (isset($this->bind[$event])) {
$event = $this->bind[$event];
}
$this->listener[$event] = array_merge($this->listener[$event] ?? [], $listeners);
}
return $this;
}
/**
* 注册事件监听
* @access public
* @param string $event 事件名称
* @param mixed $listener 监听操作(或者类名)
* @param bool $first 是否优先执行
* @return $this
*/
public function listen(string $event, $listener, bool $first = false)
{
if (isset($this->bind[$event])) {
$event = $this->bind[$event];
}
if ($first && isset($this->listener[$event])) {
array_unshift($this->listener[$event], $listener);
} else {
$this->listener[$event][] = $listener;
}
return $this;
}
/**
* 是否存在事件监听
* @access public
* @param string $event 事件名称
* @return bool
*/
public function hasListener(string $event): bool
{
if (isset($this->bind[$event])) {
$event = $this->bind[$event];
}
return isset($this->listener[$event]);
}
/**
* 移除事件监听
* @access public
* @param string $event 事件名称
* @return void
*/
public function remove(string $event): void
{
if (isset($this->bind[$event])) {
$event = $this->bind[$event];
}
unset($this->listener[$event]);
}
/**
* 指定事件别名标识 便于调用
* @access public
* @param array $events 事件别名
* @return $this
*/
public function bind(array $events)
{
$this->bind = array_merge($this->bind, $events);
return $this;
}
/**
* 注册事件订阅者
* @access public
* @param mixed $subscriber 订阅者
* @return $this
*/
public function subscribe($subscriber)
{
$subscribers = (array) $subscriber;
foreach ($subscribers as $subscriber) {
if (is_string($subscriber)) {
$subscriber = $this->app->make($subscriber);
}
if (method_exists($subscriber, 'subscribe')) {
// 手动订阅
$subscriber->subscribe($this);
} else {
// 智能订阅
$this->observe($subscriber);
}
}
return $this;
}
/**
* 自动注册事件观察者
* @access public
* @param string|object $observer 观察者
* @param null|string $prefix 事件名前缀
* @return $this
*/
public function observe($observer, string $prefix = '')
{
if (is_string($observer)) {
$observer = $this->app->make($observer);
}
$reflect = new ReflectionClass($observer);
$methods = $reflect->getMethods(ReflectionMethod::IS_PUBLIC);
if (empty($prefix) && $reflect->hasProperty('eventPrefix')) {
$reflectProperty = $reflect->getProperty('eventPrefix');
$reflectProperty->setAccessible(true);
$prefix = $reflectProperty->getValue($observer);
}
foreach ($methods as $method) {
$name = $method->getName();
if (0 === strpos($name, 'on')) {
$this->listen($prefix . substr($name, 2), [$observer, $name]);
}
}
return $this;
}
/**
* 触发事件
* @access public
* @param string|object $event 事件名称
* @param mixed $params 传入参数
* @param bool $once 只获取一个有效返回值
* @return mixed
*/
public function trigger($event, $params = null, bool $once = false)
{
if (is_object($event)) {
$params = $event;
$event = get_class($event);
}
if (isset($this->bind[$event])) {
$event = $this->bind[$event];
}
$result = [];
$listeners = $this->listener[$event] ?? [];
if (strpos($event, '.')) {
[$prefix, $event] = explode('.', $event, 2);
if (isset($this->listener[$prefix . '.*'])) {
$listeners = array_merge($listeners, $this->listener[$prefix . '.*']);
}
}
$listeners = array_unique($listeners, SORT_REGULAR);
foreach ($listeners as $key => $listener) {
$result[$key] = $this->dispatch($listener, $params);
if (false === $result[$key] || (!is_null($result[$key]) && $once)) {
break;
}
}
return $once ? end($result) : $result;
}
/**
* 触发事件(只获取一个有效返回值)
* @param $event
* @param null $params
* @return mixed
*/
public function until($event, $params = null)
{
return $this->trigger($event, $params, true);
}
/**
* 执行事件调度
* @access protected
* @param mixed $event 事件方法
* @param mixed $params 参数
* @return mixed
*/
protected function dispatch($event, $params = null)
{
if (!is_string($event)) {
$call = $event;
} elseif (strpos($event, '::')) {
$call = $event;
} else {
$obj = $this->app->make($event);
$call = [$obj, 'handle'];
}
return $this->app->invoke($call, [$params]);
}
}
而trigger就相当于示例中的triggerEvent,
在Laravel中,Illuminate\Events\Dispatcher类就是中介者,dispatch方法就是案例中的triggerEvent,
use Closure;
use Exception;
use Illuminate\Container\Container;
use Illuminate\Contracts\Broadcasting\Factory as BroadcastFactory;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Contracts\Container\Container as ContainerContract;
use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\Macroable;
use Illuminate\Support\Traits\ReflectsClosures;
use ReflectionClass;
class Dispatcher implements DispatcherContract
{
use Macroable, ReflectsClosures;
/**
* The IoC container instance.
*
* @var \Illuminate\Contracts\Container\Container
*/
protected $container;
/**
* The registered event listeners.
*
* @var array
*/
protected $listeners = [];
/**
* The wildcard listeners.
*
* @var array
*/
protected $wildcards = [];
/**
* The cached wildcard listeners.
*
* @var array
*/
protected $wildcardsCache = [];
/**
* The queue resolver instance.
*
* @var callable
*/
protected $queueResolver;
/**
* Create a new event dispatcher instance.
*
* @param \Illuminate\Contracts\Container\Container|null $container
* @return void
*/
public function __construct(ContainerContract $container = null)
{
$this->container = $container ?: new Container;
}
/**
* Register an event listener with the dispatcher.
*
* @param \Closure|string|array $events
* @param \Closure|string|array|null $listener
* @return void
*/
public function listen($events, $listener = null)
{
if ($events instanceof Closure) {
return collect($this->firstClosureParameterTypes($events))
->each(function ($event) use ($events) {
$this->listen($event, $events);
});
} elseif ($events instanceof QueuedClosure) {
return collect($this->firstClosureParameterTypes($events->closure))
->each(function ($event) use ($events) {
$this->listen($event, $events->resolve());
});
} elseif ($listener instanceof QueuedClosure) {
$listener = $listener->resolve();
}
foreach ((array) $events as $event) {
if (str_contains($event, '*')) {
$this->setupWildcardListen($event, $listener);
} else {
$this->listeners[$event][] = $listener;
}
}
}
/**
* Setup a wildcard listener callback.
*
* @param string $event
* @param \Closure|string $listener
* @return void
*/
protected function setupWildcardListen($event, $listener)
{
$this->wildcards[$event][] = $listener;
$this->wildcardsCache = [];
}
/**
* Determine if a given event has listeners.
*
* @param string $eventName
* @return bool
*/
public function hasListeners($eventName)
{
return isset($this->listeners[$eventName]) ||
isset($this->wildcards[$eventName]) ||
$this->hasWildcardListeners($eventName);
}
/**
* Determine if the given event has any wildcard listeners.
*
* @param string $eventName
* @return bool
*/
public function hasWildcardListeners($eventName)
{
foreach ($this->wildcards as $key => $listeners) {
if (Str::is($key, $eventName)) {
return true;
}
}
return false;
}
/**
* Register an event and payload to be fired later.
*
* @param string $event
* @param object|array $payload
* @return void
*/
public function push($event, $payload = [])
{
$this->listen($event.'_pushed', function () use ($event, $payload) {
$this->dispatch($event, $payload);
});
}
/**
* Flush a set of pushed events.
*
* @param string $event
* @return void
*/
public function flush($event)
{
$this->dispatch($event.'_pushed');
}
/**
* Register an event subscriber with the dispatcher.
*
* @param object|string $subscriber
* @return void
*/
public function subscribe($subscriber)
{
$subscriber = $this->resolveSubscriber($subscriber);
$events = $subscriber->subscribe($this);
if (is_array($events)) {
foreach ($events as $event => $listeners) {
foreach (Arr::wrap($listeners) as $listener) {
if (is_string($listener) && method_exists($subscriber, $listener)) {
$this->listen($event, [get_class($subscriber), $listener]);
continue;
}
$this->listen($event, $listener);
}
}
}
}
/**
* Resolve the subscriber instance.
*
* @param object|string $subscriber
* @return mixed
*/
protected function resolveSubscriber($subscriber)
{
if (is_string($subscriber)) {
return $this->container->make($subscriber);
}
return $subscriber;
}
/**
* Fire an event until the first non-null response is returned.
*
* @param string|object $event
* @param mixed $payload
* @return array|null
*/
public function until($event, $payload = [])
{
return $this->dispatch($event, $payload, true);
}
/**
* Fire an event and call the listeners.
*
* @param string|object $event
* @param mixed $payload
* @param bool $halt
* @return array|null
*/
public function dispatch($event, $payload = [], $halt = false)
{
// When the given "event" is actually an object we will assume it is an event
// object and use the class as the event name and this event itself as the
// payload to the handler, which makes object based events quite simple.
[$event, $payload] = $this->parseEventAndPayload(
$event, $payload
);
if ($this->shouldBroadcast($payload)) {
$this->broadcastEvent($payload[0]);
}
$responses = [];
foreach ($this->getListeners($event) as $listener) {
$response = $listener($event, $payload);
// If a response is returned from the listener and event halting is enabled
// we will just return this response, and not call the rest of the event
// listeners. Otherwise we will add the response on the response list.
if ($halt && ! is_null($response)) {
return $response;
}
// If a boolean false is returned from a listener, we will stop propagating
// the event to any further listeners down in the chain, else we keep on
// looping through the listeners and firing every one in our sequence.
if ($response === false) {
break;
}
$responses[] = $response;
}
return $halt ? null : $responses;
}
/**
* Parse the given event and payload and prepare them for dispatching.
*
* @param mixed $event
* @param mixed $payload
* @return array
*/
protected function parseEventAndPayload($event, $payload)
{
if (is_object($event)) {
[$payload, $event] = [[$event], get_class($event)];
}
return [$event, Arr::wrap($payload)];
}
/**
* Determine if the payload has a broadcastable event.
*
* @param array $payload
* @return bool
*/
protected function shouldBroadcast(array $payload)
{
return isset($payload[0]) &&
$payload[0] instanceof ShouldBroadcast &&
$this->broadcastWhen($payload[0]);
}
/**
* Check if the event should be broadcasted by the condition.
*
* @param mixed $event
* @return bool
*/
protected function broadcastWhen($event)
{
return method_exists($event, 'broadcastWhen')
? $event->broadcastWhen() : true;
}
/**
* Broadcast the given event class.
*
* @param \Illuminate\Contracts\Broadcasting\ShouldBroadcast $event
* @return void
*/
protected function broadcastEvent($event)
{
$this->container->make(BroadcastFactory::class)->queue($event);
}
/**
* Get all of the listeners for a given event name.
*
* @param string $eventName
* @return array
*/
public function getListeners($eventName)
{
$listeners = array_merge(
$this->prepareListeners($eventName),
$this->wildcardsCache[$eventName] ?? $this->getWildcardListeners($eventName)
);
return class_exists($eventName, false)
? $this->addInterfaceListeners($eventName, $listeners)
: $listeners;
}
/**
* Get the wildcard listeners for the event.
*
* @param string $eventName
* @return array
*/
protected function getWildcardListeners($eventName)
{
$wildcards = [];
foreach ($this->wildcards as $key => $listeners) {
if (Str::is($key, $eventName)) {
foreach ($listeners as $listener) {
$wildcards[] = $this->makeListener($listener, true);
}
}
}
return $this->wildcardsCache[$eventName] = $wildcards;
}
/**
* Add the listeners for the event's interfaces to the given array.
*
* @param string $eventName
* @param array $listeners
* @return array
*/
protected function addInterfaceListeners($eventName, array $listeners = [])
{
foreach (class_implements($eventName) as $interface) {
if (isset($this->listeners[$interface])) {
foreach ($this->prepareListeners($interface) as $names) {
$listeners = array_merge($listeners, (array) $names);
}
}
}
return $listeners;
}
/**
* Prepare the listeners for a given event.
*
* @param string $eventName
* @return \Closure[]
*/
protected function prepareListeners(string $eventName)
{
$listeners = [];
foreach ($this->listeners[$eventName] ?? [] as $listener) {
$listeners[] = $this->makeListener($listener);
}
return $listeners;
}
/**
* Register an event listener with the dispatcher.
*
* @param \Closure|string|array $listener
* @param bool $wildcard
* @return \Closure
*/
public function makeListener($listener, $wildcard = false)
{
if (is_string($listener)) {
return $this->createClassListener($listener, $wildcard);
}
if (is_array($listener) && isset($listener[0]) && is_string($listener[0])) {
return $this->createClassListener($listener, $wildcard);
}
return function ($event, $payload) use ($listener, $wildcard) {
if ($wildcard) {
return $listener($event, $payload);
}
return $listener(...array_values($payload));
};
}
/**
* Create a class based listener using the IoC container.
*
* @param string $listener
* @param bool $wildcard
* @return \Closure
*/
public function createClassListener($listener, $wildcard = false)
{
return function ($event, $payload) use ($listener, $wildcard) {
if ($wildcard) {
return call_user_func($this->createClassCallable($listener), $event, $payload);
}
$callable = $this->createClassCallable($listener);
return $callable(...array_values($payload));
};
}
/**
* Create the class based event callable.
*
* @param array|string $listener
* @return callable
*/
protected function createClassCallable($listener)
{
[$class, $method] = is_array($listener)
? $listener
: $this->parseClassCallable($listener);
if (! method_exists($class, $method)) {
$method = '__invoke';
}
if ($this->handlerShouldBeQueued($class)) {
return $this->createQueuedHandlerCallable($class, $method);
}
$listener = $this->container->make($class);
return $this->handlerShouldBeDispatchedAfterDatabaseTransactions($listener)
? $this->createCallbackForListenerRunningAfterCommits($listener, $method)
: [$listener, $method];
}
/**
* Parse the class listener into class and method.
*
* @param string $listener
* @return array
*/
protected function parseClassCallable($listener)
{
return Str::parseCallback($listener, 'handle');
}
/**
* Determine if the event handler class should be queued.
*
* @param string $class
* @return bool
*/
protected function handlerShouldBeQueued($class)
{
try {
return (new ReflectionClass($class))->implementsInterface(
ShouldQueue::class
);
} catch (Exception $e) {
return false;
}
}
/**
* Create a callable for putting an event handler on the queue.
*
* @param string $class
* @param string $method
* @return \Closure
*/
protected function createQueuedHandlerCallable($class, $method)
{
return function () use ($class, $method) {
$arguments = array_map(function ($a) {
return is_object($a) ? clone $a : $a;
}, func_get_args());
if ($this->handlerWantsToBeQueued($class, $arguments)) {
$this->queueHandler($class, $method, $arguments);
}
};
}
/**
* Determine if the given event handler should be dispatched after all database transactions have committed.
*
* @param object|mixed $listener
* @return bool
*/
protected function handlerShouldBeDispatchedAfterDatabaseTransactions($listener)
{
return ($listener->afterCommit ?? null) && $this->container->bound('db.transactions');
}
/**
* Create a callable for dispatching a listener after database transactions.
*
* @param mixed $listener
* @param string $method
* @return \Closure
*/
protected function createCallbackForListenerRunningAfterCommits($listener, $method)
{
return function () use ($method, $listener) {
$payload = func_get_args();
$this->container->make('db.transactions')->addCallback(
function () use ($listener, $method, $payload) {
$listener->$method(...$payload);
}
);
};
}
/**
* Determine if the event handler wants to be queued.
*
* @param string $class
* @param array $arguments
* @return bool
*/
protected function handlerWantsToBeQueued($class, $arguments)
{
$instance = $this->container->make($class);
if (method_exists($instance, 'shouldQueue')) {
return $instance->shouldQueue($arguments[0]);
}
return true;
}
/**
* Queue the handler class.
*
* @param string $class
* @param string $method
* @param array $arguments
* @return void
*/
protected function queueHandler($class, $method, $arguments)
{
[$listener, $job] = $this->createListenerAndJob($class, $method, $arguments);
$connection = $this->resolveQueue()->connection(method_exists($listener, 'viaConnection')
? (isset($arguments[0]) ? $listener->viaConnection($arguments[0]) : $listener->viaConnection())
: $listener->connection ?? null);
$queue = method_exists($listener, 'viaQueue')
? (isset($arguments[0]) ? $listener->viaQueue($arguments[0]) : $listener->viaQueue())
: $listener->queue ?? null;
isset($listener->delay)
? $connection->laterOn($queue, $listener->delay, $job)
: $connection->pushOn($queue, $job);
}
/**
* Create the listener and job for a queued listener.
*
* @param string $class
* @param string $method
* @param array $arguments
* @return array
*/
protected function createListenerAndJob($class, $method, $arguments)
{
$listener = (new ReflectionClass($class))->newInstanceWithoutConstructor();
return [$listener, $this->propagateListenerOptions(
$listener, new CallQueuedListener($class, $method, $arguments)
)];
}
/**
* Propagate listener options to the job.
*
* @param mixed $listener
* @param \Illuminate\Events\CallQueuedListener $job
* @return mixed
*/
protected function propagateListenerOptions($listener, $job)
{
return tap($job, function ($job) use ($listener) {
$data = array_values($job->data);
$job->afterCommit = property_exists($listener, 'afterCommit') ? $listener->afterCommit : null;
$job->backoff = method_exists($listener, 'backoff') ? $listener->backoff(...$data) : ($listener->backoff ?? null);
$job->maxExceptions = $listener->maxExceptions ?? null;
$job->retryUntil = method_exists($listener, 'retryUntil') ? $listener->retryUntil(...$data) : null;
$job->shouldBeEncrypted = $listener instanceof ShouldBeEncrypted;
$job->timeout = $listener->timeout ?? null;
$job->tries = $listener->tries ?? null;
$job->through(array_merge(
method_exists($listener, 'middleware') ? $listener->middleware(...$data) : [],
$listener->middleware ?? []
));
});
}
/**
* Remove a set of listeners from the dispatcher.
*
* @param string $event
* @return void
*/
public function forget($event)
{
if (str_contains($event, '*')) {
unset($this->wildcards[$event]);
} else {
unset($this->listeners[$event]);
}
foreach ($this->wildcardsCache as $key => $listeners) {
if (Str::is($event, $key)) {
unset($this->wildcardsCache[$key]);
}
}
}
/**
* Forget all of the pushed listeners.
*
* @return void
*/
public function forgetPushed()
{
foreach ($this->listeners as $key => $value) {
if (str_ends_with($key, '_pushed')) {
$this->forget($key);
}
}
}
/**
* Get the queue implementation from the resolver.
*
* @return \Illuminate\Contracts\Queue\Queue
*/
protected function resolveQueue()
{
return call_user_func($this->queueResolver);
}
/**
* Set the queue resolver implementation.
*
* @param callable $resolver
* @return $this
*/
public function setQueueResolver(callable $resolver)
{
$this->queueResolver = $resolver;
return $this;
}
/**
* Gets the raw, unprepared listeners.
*
* @return array
*/
public function getRawListeners()
{
return $this->listeners;
}
}
同样在,Hyperf框架中,Hyperf\Event\EventDispatcher类是中介者,dispatch方法是triggerEvent。
namespace Hyperf\Event;
use Hyperf\Contract\StdoutLoggerInterface;
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\EventDispatcher\ListenerProviderInterface;
use Psr\EventDispatcher\StoppableEventInterface;
class EventDispatcher implements EventDispatcherInterface
{
/**
* @var ListenerProviderInterface
*/
private $listeners;
/**
* @var null|StdoutLoggerInterface
*/
private $logger;
public function __construct(
ListenerProviderInterface $listeners,
?StdoutLoggerInterface $logger = null
) {
$this->listeners = $listeners;
$this->logger = $logger;
}
/**
* Provide all listeners with an event to process.
*
* @param object $event The object to process
* @return object The Event that was passed, now modified by listeners
*/
public function dispatch(object $event)
{
foreach ($this->listeners->getListenersForEvent($event) as $listener) {
$listener($event);
$this->dump($listener, $event);
if ($event instanceof StoppableEventInterface && $event->isPropagationStopped()) {
break;
}
}
return $event;
}
/**
* Dump the debug message if $logger property is provided.
* @param mixed $listener
*/
private function dump($listener, object $event)
{
if (! $this->logger instanceof StdoutLoggerInterface) {
return;
}
$eventName = get_class($event);
$listenerName = '[ERROR TYPE]';
if (is_array($listener)) {
$listenerName = is_string($listener[0]) ? $listener[0] : get_class($listener[0]);
} elseif (is_string($listener)) {
$listenerName = $listener;
} elseif (is_object($listener)) {
$listenerName = get_class($listener);
}
$this->logger->debug(sprintf('Event %s handled by %s listener.', $eventName, $listenerName));
}
}
总结
中介者(Mediator) 模式通过促进松散耦合、简化通信并为对象交互提供集中控制机制,有助于提高系统的灵活性、可维护性和可理解性。