Don’t Hard-Code Yourself. Make your Code Framework-Agnostic

When working on a project, we use a framework. Let’s admit it – we don’t write code from scratch (I’m not talking about short, “few-hours” projects). We have a framework we like, know and are used to use frequently.

This framework enslaves you. Why? Because it offers so much shortcuts / methods / helpers you can use instantly, without any effort. You don’t have to write any additional code, no new libraries, no new getters or anything. You just use it and, hey, it works!

Why is using framework-given functionality a bad thing?

I’m not saying it’s actually a bad thing. You should use it because this functionality is (probably and hopefully) tested well enough, the code is efficient and it will speed up your work. The thing is, it makes you lazy. And this is a bad kind of “lazy”. It binds the code to a specific framework. Take a look at the code below:

namespace News;

class NewsController extends \FrameworkController {
  
  public function getRecentNews() {
    return \View::make('news.recent', array(
      'news' => \News\Model::get()
    ));
  }
}

Let’s say the client want’s to use the same functionality in a number of other places on the website. You can copy the code and paste it into other controllers – it’s fast and simple. But after a while the client wants you to change the sorting of the news. Now that’s a bit of a problem, since you have to alter all occurrences of this code.

We can go on and on multiplying the client’s demands. The real problem is the shortcut, that was made.

How to fix this?

Now, take a look at the code below:

namespace News;

class NewsController extends \AppController {
  
  protected $viewFactory;
  protected $newsRepository;
  
  public function __construct($viewFactory, $newsRepository) {
    $this->viewFactory = $viewFactory;
    $this->newsRepository = $newsRepository;
  }
  
  public function getRecentNews() {
    return $this->viewFactory->make('news.recent', array(
      'news' => $this->newsRepository->getRecent()
    ));
  }
  
}

class AppController extends \FrameworkController {
  
  // ...
  
}

It provides the same functionality with some additional features:

  • it does not rely on any framework,
  • it utilizes Dependency Injection (testable, mockable code = win),
  • its behavior can be easily altered.

Let’s examine the code step-by-step.

1. AppController instead of a FrameworkController. If you use an additional layer for controller classes you can easily adapt your code to any framework. This makes your code portable. It also gives you the ability to move some commonly used functionality to the AppController. Now, moving from one framework to another should be easy since you have to adapt only the AppController and not your actual controller logic.

2. By injecting class instances into other classes you gain flexibility. If you want to change the behavior of a specific method (e.g. $newsRepository->getRecent()) you don’t have to alter any code in the controllers. You only need to change the getRecent() method. Furthermore, this code is testable – you can mock the injected objects! This is a topic for a different article 🙂

One more step – type hinting

You can do one more thing to enhance your code. Since it now allows injecting class instances you can type hint classes that can be injected.

Instead of:

public function foo($bar)

You can use:

public function foo(\Baz $bar)

This means that the injected object $bar has to be an instance of \Baz. This is great because you can restrict users to use a certain class for injection. But let’s consider this situation:

protected function fetchLog(\VCS\Driver\Svn $log)

You have a method that fetches the commit log from an SVN repository. But time moves on and you switch to GIT. So, obviously, you want your code to support GIT along with SVN. And we have a problem since the fetchLog method accepts only an instance of the SVN driver class. You have to change one or more classes so they will accept GIT drivers. But then again you lose the SVN functionality. Crap.

The best answer for that is interface type hinting. Take a look at the following code:

file: log.php

protected function fetchLog(\VCS\Driver\DriverInterface $log)

file: VCS/Driver/Svn.php

namespace VCS\Driver;
class Svn implements DriverInterface {}

file: VCS/Driver/Git.php

namespace VCS\Driver;
class Git implements DriverInterface {}

This means that the object injected into the fetchLog method has to implement an interface called DriverInterface. Interface type hinting does not force you to use a specific class, so you can inject anything you want as long as it implements this specific interface.

Protip: you can mock an interface so it pretends to be an instance of a class that implements the desired interface. So it’s testable! 🙂

Summary

With only few things in mind you can make your code portable and framework-agnostic. It will be also flexible (interface type hinting, altering one method instead of its dozen occurrences) and testable (injecting mocks). This will be rewarding in the long run.

You May Also Like

Take a free consultation

Get an online consultation or workshop session. Let's estimate costs!