Monday 30 January 2017

PHP Traits to ease dependency injection

In proramming larger applications, the DIP (Dependency inversion Pattern) is an important tool in keeping maintenance to a minimum; Munir Hassan for Codeproject has a great article that covers the topic Dependency Inversion Principle and the Dependency Injection Pattern. When we couple modules too tightly without abstraction layers or interfaces, maintenance costs go up greatly. The Codeproject article above has saved me a lot of time, and when I read about PHP Traits a while back I realised that it can be used to make this even easier.

What is a PHP Trait?

A trait is a class-like structure in PHP that many call "language assisted copy and paste". It basically means that when you import a Trait into a Class all the properties and methods defined in the Trait get copy/pasted into your Class at runtime by the PHP interpreter. Take the following example:

Without traits

<?php
  class User {

    private $connection;

    public function __construct(ConnectionInterface $connection) {
      $this->setConnection($connection);
    }

    private function setConnection(ConnectionInterface $connection) {
      $this->connection = $connection;
    }

    public function getConnection(): ConnectionInterface {
      return $this->connection;
    }

  };
?>
The above seems fine (except for the possibility of getConnection() failing if it's not set, we'll worry about that at another time), it uses a form of constructor injection to make sure the User class knows as little as possible about the Connection class. This does however create the issue where, let's say, another class (Product) now also needs a connection, now we'll have the following code too:
<?php
  class Product {

    private $connection;

    public function __construct(ConnectionInterface $connection) {
      $this->setConnection($connection);
    }

    private function setConnection(ConnectionInterface $connection) {
      $this->connection = $connection;
    }

    public function getConnection(): ConnectionInterface {
      return $this->connection;
    }

  };
?>

With traits

These two classes also have other methods and properties, but the imprtant thing to note here is that both of these now use the exact same code to set and get it's connection implementations. We can simplify this process by using a connection trait. Below is the trait we are going to use in both classes:
<?php
  trait connectionTrait {

    private $connection;

    private function setConnection(ConnectionInterface $connection) {
      $this->connection = $connection;
    }

    public function getConnection(): ConnectionInterface {
      return $this->connection;
    }

  };
?>
Now we can use our new trait in both our classes instead of re-defining the code in both classes, let's have a look at the new User class with the Trait imported/applied:
<?php
  use Connection\ConnectionInterface;
  class User {

    use \Connection\ConnectionTrait;

    public function __construct(ConnectionInterface $connection) {
      $this->setConnection($connection);
    }

  };
?>

Conclusion

In the last example our User class now imports the Trait which adds the private $connection property to our class. It also adds the setconnection() and getConnection() methods to our User class at runtime. This way we both save lines of code and we ensure that if the connection trait ever needs to change we can easily apply this change through all classes that use it.

No comments:

Post a Comment