Zdarzyło Ci się kiedyś dopisać sendEmail() bezpośrednio po save()? A może logujesz akcję użytkownika zaraz po zarejestrowaniu?
A teraz wyobraź sobie, że rejestrujesz użytkownika. Po zapisaniu go do bazy chcesz:
- wysłać powitalnego maila,
- zapisać wpis w logach,
- przypisać domyślną rolę,
- dodać go do systemu CRM.
Na początku robisz to wszystko w jednym kontrolerze, jeden po drugim:
$userRepository->save($user);
$mailer->sendWelcomeEmail($user);
$logger->info('User registered', [...]);
$crm->addContact($user);Działa? Działa. Ale po tygodniu ktoś dorzuca kolejną akcję. Po miesiącu jeszcze jedną.
Zamiast prostego “zapisz użytkownika“, masz pętlę zależności i żadnej elastyczności.
A teraz wyobraź sobie, że chcesz:
- zapisać użytkownika w innym kontekście (np. rejestracja przez API),
- zrezygnować z integracji z CRM-em,
- testować to bez side-effectów.
Twój kod zaczyna przypominać spaghetti z efektami ubocznymi, których nie da się śledzić.
To właśnie przypadki, w których aż prosi się o użycie wzorca Obserwator, jednego z najczęściej nadużywanych i… jednocześnie niedocenianych wzorców w PHP.
W tym wpisie pokażę Ci:
- Czym jest wzorzec Obserwator i jak działa
- Jak wdrożyć go w Symfony i Laravelu
- Jak uniknąć wyżej wspomnianego spaghetti kodu przez eventy i listenerów
Czym jest wzorzec Obserwator?
Zgodnie z definicją z Gang of Four (GoF):
To wzorzec, który pozwala powiązać obiekty w relacji 1:N, tak że kiedy jeden obiekt zmienia stan, inne są o tym automatycznie powiadamiane.
Najprościej:
- Subject np. użytkownik został zarejestrowany
- Observer np. wyślij maila, zaloguj, dodaj punkty
Zamiast ręcznie wywoływać każdą akcję po kolei, po prostu… emitujesz zdarzenie. Reszta dzieje się sama.
Symfony – przykład użycia wzorca Obserwator
Użytkownik się rejestruje → chcemy:
- Wysłać powitalnego maila
- Zapisać log do bazy
- Nadać domyślną rolę
Tworzymy zdarzenie (event)
// ../Event/UserRegisteredEvent.php
namespace App\Event;
use App\Entity\User;
use Symfony\Contracts\EventDispatcher\Event;
class UserRegisteredEvent extends Event
{
public function __construct(public readonly User $user) {...}
}Dispatchujemy event w kontrolerze lub serwisie
// src/Controller/RegisterController.php $this->dispatcher->dispatch(new UserRegisteredEvent($user));
Tworzymy listener
// ../EventListener/WelcomeEmailListener.php
namespace App\EventListener;
use App\Event\UserRegisteredEvent;
use App\Service\Mailer;
class WelcomeEmailListener
{
public function __construct(private Mailer $mailer) {...}
public function __invoke(UserRegisteredEvent $event): void
{
$this->mailer->sendWelcomeEmail($event->user);
}
}Rejestrujemy listener (autowiring działa out-of-the-box od Symfony 4.3+)
# config/services.yaml App\EventListener\WelcomeEmailListener: ~
Gotowe! Wysyłka maila jest teraz luźno powiązana z rejestracją użytkownika.
Bonus!
Symfony domyślnie nie gwarantuje kolejności listenerów z __invoke().
Jeśli potrzebujesz kolejności, użyj tagów i definiuj priorytet:
App\EventListener\WelcomeEmailListener:
tags:
- { name: kernel.event_listener, event: App\Event\UserRegisteredEvent, priority: 100 }
App\EventListener\AssignDefaultRoleListener:
tags:
- { name: kernel.event_listener, event: App\Event\UserRegisteredEvent, priority: 50 }
App\EventListener\LogUserRegistrationListener:
tags:
- { name: kernel.event_listener, event: App\Event\UserRegisteredEvent, priority: 10 }Laravel – przykład użycia wzorca Obserwator
Tworzymy obserwatora
php artisan make:observer UserObserver --model=User
Implementujemy metody zdarzeń
// app/Observers/UserObserver.php
public function created(User $user)
{
Mail::to($user->email)->send(new WelcomeEmail($user));
}Rejestrujemy obserwatora
// app/Providers/EventServiceProvider.php
use App\Models\User;
use App\Observers\UserObserver;
public function boot()
{
User::observe(UserObserver::class);
}To wszystko! Laravel sam odpala metodę created() przy dodaniu użytkownika do bazy. Możesz też dodać inne metody (updated, deleted, restored, forceDeleted itd.).
Kiedy stosować wzorzec Obserwator?
✅ Chcesz rozszerzyć zachowanie modelu bez modyfikowania go
✅ Potrzebujesz luźnego powiązania logiki (np. akcje po rejestracji)
✅ W Twoim kodzie jest wiele reakcji na jedno zdarzenie
A kiedy NIE stosować?
❌ Masz tylko jedną akcję – overengineering
❌ Potrzebujesz potwierdzenia, że coś się udało (brak feedbacku z listenera)
❌ Twój kod to wydajność krytyczna – eventy mogą być wolniejsze niż bezpośrednie wywołania
Kiedy event listener nie jest wzorcem Observer?
Jeśli listener jest twardo zakodowany w subjekcie np.
class User
{
public function save(): void
{
$this->logger->log('user saved');
}
}Podsumowanie
Wzorzec Obserwator to potężne narzędzie, jeśli stosujesz go świadomie.
W Symfony opiera się na EventDispatcherze, a w Laravelu masz go wbudowanego jako Observer.
To świetny sposób, by:
- unikać „Boga kontrolera”,
- rozdzielić odpowiedzialności,
- zachować czysty, testowalny kod.
Masz w swoim projekcie jakiś “brzydki if po zapisie”? Może da się to rozwiązać przez eventy. Napisz do mnie, pomogę!
A jeśli chcesz więcej takich wpisów, zostaw maila. 🚀

Nikt jeszcze nie komentował. Bądź pierwszy!