3 Symfony and Laravel Patterns that Make Code Easy to Extend Without Modification

Do you write open-source? If so, you probably get many PR and issues about adding new feature, that people miss. You can add them and increase project complexity or deny them and increase people's frustration. Both sucks for somebody. I prefer win-win situations: keep complexity low and add new features.

Magic? No, just patterns! Today we look on 3 of them I found and fond in Symfony and Laravel world.

How is Open-Source Different from Private Code

There is a big mind-shift from closed-source to open-source. To make it really work, you need to move from my ego first to other people's feelings first.

It is like building Matrix open to everybody. You have to predict future and unexpected use cases. Your code have to be extendable without making any changes in it.

"Opened for Extension, Closed for Modification"

Now, I can refer to Open/closed principle on Wikipedia, which is the worst way to explain it.

Instead, I took a time to find simple example (pro tip: Google with "simple") and actually found one - go check it, it clearly shows wrong approach.

Today I will show you 3 ways to create such entrances.

1. Interfaces for Everything

When I first saw Laravel, I've noticed one big difference to other PHP projects I've seen. There is Contracts directory, that contains interface for every service there is in Laravel. And they're not only in this directory, but used everywhere in the code. Crazy move by Taylor Otwell in that times, but very useful.

Why is this Useful?

Don't forget to have Final Word

To promote using interfaces instead of extending your classes like this:

class BoothCallEntrance implements MatrixEntranceInterface
{

}

class ComputerEntrance extends BoothCallEntrance
{

}

always mark your classes final. There is event sniff for that. Use it.

final class BoothCallEntrance implements MatrixEntranceInterface
{

}

final ComputerEntrance implements MatrixEntranceInterface
{

}

Programmers won't have to think about raping your classes in the night - they just use the interface you provide.

This is code-embodied composition over inheritance. No documentation nor Wikipedia links required.

2. Go to Party Events, when in the Mood

Back to Matrix world: imagine you can listen to every phone booth. Let's say you write a script, that sends you sms with geo location of the booth everytime it gets called (favorite tool for agent Smith ;-)).

This approach is implemented in PHP under name of EventDispatcher. While working with Symfony, events gave me very similar feeling of freedom - in docs as well in small book A Year with Symfony.

Do you want simple example of such listening script? Check this tested post with all code snippets you need.

While on Event, Listen Carefully

Matrix situation above would look like this:

Code of your package:

// some CRON script checking all booths are working

if ($booth->isCalled()) {
    // you with people could get here without sending you a PR for everything they might need? Easy! ↓

    // this is the entry point, just listen to 'boothCall'
    $this->eventDispatcher->dispatch('boolCall', $booth->getLocation();
    // ...
}

And custom script listening:

final class BoothSpy
{
    public function listenTo()
    {
        return 'boothCall';
    }

    public function runOnPing($location)
    {
        $this->smsSender->sendABoothAlert($location);
    }
}

Why is this Useful?

It might be confusing while using at first, but after few weeks I get used to it. Trust me, it's the best.

3. Like Collecting stamps, just on Steroids

This is most powerful and less known architecture pattern.

1 service collects all services of specific type

Where it came from?

Do you know service tagging in Symfony?

services:
    app.custom_subscriber:
        class: AppBundle\EventListener\CustomSubscriber
        tags:
            - { name: kernel.event_subscriber }

All services of EventSubscriber type are collected by EventDispatcher.

You Probably Already Use It

As for tags - they often promote bad practise of duplicated information. Don't use it if you don't have to.

Why is this Useful?

services:
    - YourSubscriber

Back to the Matrix

Let's say we have a service to render Matrix. It might look like this:

final class MatrixRenderer()
{
    public function render()
    {
        $this->agents->render();
        $this->environment->render();
        $this->people->render();
    }
}

Later, one customer wants to render night clubs. Another customer wants to add weather. How to do that without modifying the code? Like this:

final class MatrixRenderer()
{
    /**
     * @var LayerRendererInterface[]
     */
    private $layerRenderers = [];

    public function addLayerRenderer(LayerRendererInterface $layerRenderer)
    {
        $this->layerRenderers[] = $layerRenderer;
    }

    public function render()
    {
        foreach ($this->layerRenderers as $layerRenderer) {
            $layerRenderer->render();
        }
    }
}

And decouple to services:

services:
    - AgentLayerRenderer
    - EnvironmentLayerRenderer
    - PeopleLayerRenderer

    # added by customers
    - NightClubsLayerRenderer
    - WeatherLayerRenderer

Do you want to use collectors without pain? In case you use Symfony, Nette or Laravel, here is PackageBuilder that makes it simple.

How do You Make your Packages Easy to Extend?

Let me know if you use any of these patterns. Or do you use something else? I'd love to hear about that!

I Care about Your Opinion