read

In the last weekend I come out with a great presentation by Fabien Potencier, the author of Symfony Framework - Decouple your PHP code for reusability. This presentaion is from 2008 but still very up to date and touches important points about how and why its important to have a decoupled code base. It tackle concepts like Loose Coupling, Dependency Injection and how these concepts can be applied in a real world example to make your code more maintainable, testable and change friendly.

In a agile and fast evolving world, its important to build scalable applications that are easier to change. The concepts of loose coupling and dependency injection play a central role in this.

A loosely coupled system is a system where each component have very little or no knowledge at all about other components. These components communicate with each other via well defined interfaces and could be easily replaced by other components without affecting the global system, as long as they provide the same interface. Each component should be responsible by just a single funcionality in the application and nothing more.

Dependency injection is another buzzword but its concept is very easy to understand. When one class depends on another class, instead of create that dependent object inside the class, it should be injected, using a setter (setter injection) or directly in the constructor (constructor injection).

So, now that you understood the adavantages of loose coupling and dependency injection, lets do this:

{<1>}

Not quite. ;) I read some articles about loose coupling and domain driven design taken to the extreme of decouple everything including decoupling your application code from the framework. If you want to decouple all your code from your framework, then why use a framework in the first place? In my opinion, too much abstraction is bad and will make your system more complex and your code harder to understand in the long run, mostly for newcomers.

You should focus on your core business and let the framework do the "dirty" job for you. Be pragmatic.

Remember:

  • We cannot predict or prepare for every change
  • Premature optimizaion is evil
  • Dont reinvent the wheel.

You cant predict all the changes but you can identify what components of your application are more likely to change in the future. Next I will present some tips to help identify these components and make them decoupled. Its focused on Symfony framework but the ideas can be applied to any framework or language.

No business logic in controllers or views.

Your business logic should all live in a Model/Service layer. This implies, no logic at all at Controllers or Views level. A controller should only be responsible to process the request, call some Service to get the requested data and pass that data to the view. Nothing more. If you make your controllers "thin and stupid" it will be much easier to refactor to a new framework or language. Also I have seen same cases of creating "private" methods in controllers and I have done that some myself too. Its better that having an action with 100 lines of code, but if your extracting code to a new method, instead of polutting the controller with private methods, create a service, helper, handler or whatever you want to call instead.

In the case of views, there might be same cases you need some presentation logic. For that use Twig Extensions. No big chunks of code in Twig Templates.

Use dependency injection / inversion of control.

If you are using the "new" keywoard inside a class to create an object from another class you are creating a dependency between these two classes. Thats a good indicator to use Dependency Injection. Extract that object creation to outside the class and then pass the object to the class either via constructuor or setter method.. Symfony makes this extremely easy with their Dependency Injection component. Of course, some "new" calls make sense indide a class, like create a new instance of some domain model object for example. Loggers, Database helpers, External services clients etc are good candidates for being set via dependency injection.

Abstract external libraries

If you use a library that interacts with external systems, like an HttpClient, a Logger or a AnalyticsClient that is be a good candidate to be abstracted. For example, you might want to track your users activities in your application and you decide to use Mixpanel. Instead of building your application around this particular library, you could abstract in by creating an Interface and a set of Adapters. Your application should then interact with that interface and not with a real implementation. By doiung this, if you decide to change your Analytics tool from Mixpanel to Intercom for example, you just need to create a new Adapter that implements your interface and configure your application to use your new adapter (which should be only in one place).

Here is very basic code snippet to give you an idea:

<?php
interface TrackerInterface {
    public function trackPageView($page);
    public function trackNewUser(array $userData);
}
<?php
class MixPanelAdapter implements TrackerIinterface
{
    /**
      * client to interact with mixpanel api
      */
    private $client;

    public function trackPageView($pageId)
    {
        // connect to mixpanel api to track a page view.
    }
}

You will then have a service in your application that we would call evrywhere you want to track something.

<?php
class AnalyticsService
{
    private $adapter;

    public function setAdapter(TrackerInterface $adapter)
    {
        $this->adapter = $adapter;
    }

    public function trackPageView($page)
    {
        $this->adapter->trackPageView($page);
    }
}

FileSystem access is also a great canditate to be abstracted. You might start using Local filesystem but then your application grows and you need to scale to Amazon S3 for example. Take a look a Gaufrette for a great implementation of an FileSystem abstraction library.

Database Layer

There is a chance that you decide to change your database from lets say a relational database like MySql to a document store like mongo. In Symfony and Doctrine this is relatively simple because doctrine also have a ODM. In general, you should make sure that the logic to connect to your database is centralized in one place, and that your model is independent of your persistence layer, meaning that as long as data returned from your persistence layer is in the same format all your application should continue to work if you change your persistence system. See below a possible approach to work with Doctrine repositories.

Doctrine Repositories

Its common to see Doctrine Entity Manager to get called directly in the Controller like this:

<?php
$this->container->get('doctrine')->getManager();

By doing this we are coupling our controllers with doctrine which belongs to the persistence layer. The controller should only talk to the service layer and not to the persistence layer directly.

Example:

<?php
class CustomersController extends Controller
{
    public function __construct(CustomerManager $customerManager)
    {
        $this->customerManager = $customerManager;
    }

    public function indexAction()
    {
        $customers = $this->customerManager->getAll();
    }

}

Here, the controller only knows about your CustomerManager service and its completely agnostic about the persistence layer.

The customerManager class would have the following code:

<?php
class CustomerManager
{
    protected $customerRepository;

    public function __construct(CustomerRepositoryInterface $customerRepository)
    {
        $this->customerRepository = $customerRepository;
    }

    public function getAll()
    {
        return $this->customerRepository->getAll();
    }
}

The CustomerManager knows about the persistence layer by the customerRepository property, but dont need to know if the persistence layer is a MySQL database or a MongoDB database. We use an interface for this. This will allow to replace our persistence layer without the need to change our CustomerManager implementation. Our repository just need to implement the CustomerRepositoryInterface and return the data in the same format.

And thats it for today.

Hope you find this usefull. Please feel free to comment this article and give your opinion about the topic.

Also I recommend you to see this great presentation Jakub Zalas about "The dependency Trap". It explains very well this concepts.

Good programming!

Blog Logo

Bruno Paz


Published

Image

Bruno Paz

Web Application Engineer. Expert in PHP and Symfony Framework. Enthusiast about new technologies. Sports and FCPorto fan!

Back to Home