http://vria.eu SL

Wellcome to my professional blog dedicated to interesting things in programming, web-development and design patterns.

Observer pattern and the use of Symfony form events. Part 1

Jan 1, 2017 php design patterns

The first part of this article is about the Observer pattern itself. Two implementations are provided in order to depict the pattern in detail and to give its general concepts.

Observer.png

Observer pattern is the one I encounter the most. In all object-oriented frameworks I have seen an Observer pattern implemented either in its pure form or by means of the Mediator, also known as event dispatcher. It is one of behavioral patterns therefore it defines the interaction between the objects. It does it in the following way: the Subject keeps the list of Observers and notifies them when some event happens. In order to be notified all observers must implement unique interface, usually called Observer with single method notify() – that is the only thing that subject requires from its observers. So Subject class gets decoupled from the classes of its Observers that ensures better reusability. Here is a classic class diagram:

In simple terms when the object of type A (Subject) wants to notify objects of type B, C or D (Concrete Observers) it can do it without knowing what particular classes they represent. The only condition is that Observers must implement the Observer interface only with the method called notify. The class A is only aware of the Observer interface and not of classes B, C or D. The subject has to be positive that his observers have the method notify to call if needed. This need usually comes as some event: a user clicks the interface element, the form construction finishes, the new request appears, the entity is fetched from database (or is about to insert, or get inserted, or is about to update or get updated) and so forth. The Subject can be anything: the form, the form builder, Doctrine framework, Symfony kernel. It can run third-party code plugged in it as an Observer at specific point of its lifecycle (the events). This allows the subject to send notifications to whatever observer. On the other hand, the observer can listen to events from any subject. Both become decoupled, reusable and separately distributable. You are welcome to explore my implementation of observer pattern here. The illustration of this article is the class diagram of the solution.

As you could see, the Subject uses all its Observers whenever it throws any notification. Whether the Observers interested or not in this particular notification, they are notified anyway. It is often useful when particular event happens to notify only the Observers interested in this event and ignore others. The solution is to turn Observer into EventListener and subscribe them to particular Event of Subject. In this case the code would be similar to :


/**
 * All subjects must implement this interface.
 * Two events are defined INITIALIZED and FINALIZED. Event listeners
 * can subscribe to listen one of them by means of `addEventListener` method.
 * The only defined method is `addEventListener` the analog of `attach` method in `\SplSubject`
 * This interface can be extended by `removeEventListener` method
 */
interface SubjectInterface
{
    const INITIALIZED = "subject_initialized";
    const FINALIZED = "subject_finalized";

    /**
     * Attach the listener to the event.
     * Afterwards it will be called when this event happens
     *
     * @param $event Event to listen to
     * @param ListenerInterface $listener Listener that listens to the specified event
     */
    public function addEventListener($event, ListenerInterface $listener);
}

/**
 * The listener interface. `onEvent` method is called from inside the subject.
 * It permits execute arbitrary logic at some point of subject's lifecycle:
 * when the subject gets initialized or the subject gets finalized.
 * Concrete listeners are usually interested only one event.
 */
interface ListenerInterface
{
    /**
     * Method that is called by subject when the particular event happens
     *
     * @param Event $event The object containing all necessary information
     */
    public function onEvent(Event $event);
}

/**
 * Event interface represents the generic event. It is created by the subject
 * and passed to listener when to latter is called.
 * It contains all information that is interesting to Listener.
 */
interface Event
{
    /**
     * Get the subject itself
     *
     * @return SubjectInterface
     */
    public function getSubject();

    /**
     * Get whatever variable associated with the event
     *
     * @return mixed
     */
    public function getWhatever();
}

// Create new object implementing SubjectInterface
$order = new Subject();

// Add some listeners: SendConfirmationEmailListener, LogOrder and LogSuccessfulCommand.
// All implement ListenerInterface
$order->addEventListener(SubjectInterface::INITIALIZED, new SendConfirmationEmailListener());
$order->addEventListener(SubjectInterface::INITIALIZED, new LogOrder());
$order->addEventListener(SubjectInterface::FINALIZED, new LogSuccessfulOrder());

// Evolve subject
$order->init(); // SendConfirmationEmailListener and LogOrder get called
$order->finalize(); // LogSuccessfulOrder gets called

In the second part, I will show you how to use the event system of Symfony form component to make use of dependent selects.