PHP Observer Design Pattern Поведенческий паттерн

Паттерн Наблюдатель (Observer) в PHP

Короткий скринкаст: паттерн «Наблюдатель» (Observer) — издатель уведомляет подписчиков об изменениях, не зная их конкретных классов. Реализуем рассылку новостей по email и SMS.

Observer Pattern — открыть страницу урока

Описание

Наблюдатель задаёт зависимость «один ко многим»: при изменении состояния издателя все подписчики получают уведомление. Интерфейс Observer описывает метод update(), который должен уметь каждый подписчик. Базовый класс Subject хранит список наблюдателей: attach() добавляет подписчика, detach() убирает, а notify() проходит по списку и вызывает у каждого update(). Конкретный издатель NewsAgency наследует Subject и через publish() запускает рассылку. Конкретные подписчики EmailSubscriber и SmsSubscriber по-своему реагируют на новость, но издатель не знает ни их типов, ни того, что именно они делают.

UML-диаграмма

classDiagram class Observer { <<interface>> +update(news: string): void } class Subject { -observers: array +attach(observer: Observer): void +detach(observer: Observer): void #notify(news: string): void } class NewsAgency { +publish(news: string): void } class EmailSubscriber { -email: string +update(news: string): void } class SmsSubscriber { -phone: string +update(news: string): void } Observer <|.. EmailSubscriber Observer <|.. SmsSubscriber Subject <|-- NewsAgency Subject o-- Observer : notifies

Код

Observer.php

interface Observer
{
    public function update(string $news): void;
}

Subject.php

abstract class Subject
{
    /** @var Observer[] */
    private array $observers = [];

    public function attach(Observer $observer): void
    {
        $this->observers[] = $observer;
    }

    public function detach(Observer $observer): void
    {
        $this->observers = array_filter(
            $this->observers,
            fn (Observer $item): bool => $item !== $observer
        );
    }

    protected function notify(string $news): void
    {
        foreach ($this->observers as $observer) {
            $observer->update($news);
        }
    }
}

EmailSubscriber.php

class EmailSubscriber implements Observer
{
    public function __construct(private readonly string $email) {}

    public function update(string $news): void
    {
        echo "Email to {$this->email}: {$news}".PHP_EOL;
    }
}

SmsSubscriber.php

class SmsSubscriber implements Observer
{
    public function __construct(private readonly string $phone) {}

    public function update(string $news): void
    {
        echo "SMS to {$this->phone}: {$news}".PHP_EOL;
    }
}

NewsAgency.php

class NewsAgency extends Subject
{
    public function publish(string $news): void
    {
        $this->notify($news);
    }
}

demo.php

require_once 'Observer.php';
require_once 'Subject.php';
require_once 'EmailSubscriber.php';
require_once 'SmsSubscriber.php';
require_once 'NewsAgency.php';

$agency = new NewsAgency;

$email = new EmailSubscriber('user@example.com');
$sms = new SmsSubscriber('+1 555 0100');

$agency->attach($email);
$agency->attach($sms);

$agency->publish('New PHP release!');

$agency->detach($sms);
$agency->publish('Second news');

Пример результата

После php demo.php первую новость получают оба подписчика, а после detach — только email:

Email to user@example.com: New PHP release!
SMS to +1 555 0100: New PHP release!
Email to user@example.com: Second news

Преимущества

  • Издатель и подписчики слабо связаны: издатель не знает конкретных классов наблюдателей.
  • Новых подписчиков можно добавлять, не меняя код издателя.
  • Подписку можно включать и выключать во время выполнения.
  • Работает принцип открытости/закрытости: расширение без правки существующих классов.

Когда использовать

  • Когда при изменении одного объекта должны обновляться другие, и их число заранее неизвестно.
  • Когда объекты должны уведомлять других, не зная, кто они.
  • Когда часть объектов должна следить за состоянием другого объекта.
  • Когда нужна событийная модель с подпиской и отпиской.

Использование в Laravel

В Laravel Наблюдатель используется повсюду. События и слушатели: Event::dispatch() рассылает событие, а слушатели в app/Listeners реагируют на него через метод handle(). Eloquent-наблюдатели app/Observers подписываются на события модели (creating, saved, deleted) и срабатывают автоматически. Механизм Illuminate\Support\Facades\Notification рассылает уведомления по нескольким каналам (mail, SMS, database) — канал выбирается уже при отправке, а не в коде отправителя.