Let me take you on a journey. It begins with…

Service Location

Service Location is a a design pattern in which new objects are created through a common object. This Service Locator is a factory of sorts, sometimes making new objects and other times resolving shared objects.

There are many PHP libraries which can be used for Service Location; such as Pimple\Container and Aura\Di, but my favourite is Illuminate\Container:

❯ composer require "illuminate/container:*"



./composer.json has been updated

Loading composer repositories with package information

Updating dependencies (including require-dev)

- Installing illuminate/container (v4.2.9)

Downloading: 100%



Writing lock file

Generating autoload files

This will install Illuminate\Container, so we can use it for a bit of Service Location:

interface Mailer

{

/**

* @param string $from

* @param string $to

* @param string $subject

* @param string $message

*/

public function send($from, $to, $subject, $message);

}



class SendMailer implements Mailer

{

/**

* @param string $from

* @param string $to

* @param string $subject

* @param string $message

*/

public function send($from, $to, $subject, $message)

{

// ...send some mails!

}

}



use Illuminate\Container\Container;



$container = new Container();

$container->bind("Mailer", "SendMailer");



$mailer = $container["Mailer"]; // SendMailer

Making new objects.

interface Session

{

/**

* @param string $key

* @param mixed $default

*

* @return mixed

*/

public function get($key, $default = null);

}



class CookieSession implements Session

{

/**

* @param string $key

* @param mixed $default

*

* @return mixed

*/

public function get($key, $default = null)

{

// ...get some data

}

}



use Illuminate\Container\Container;



$container = new Container();



$container->bindShared("Session", function () {

return new CookieSession();

});



$container["Session"] === $container["Session"]; // true

Resolving shared objects.

You don’t have to bind interfaces; it’s just a good habit to code against interfaces. The point is that Service Locators are nothing more than a combination of registry and factory. Then there’s…

Dependency Injection

Dependency injection is the name for when your classes have dependencies and instead of creating them inside the class, you create them outside and pass them to the constructor or a setter. Given:

interface Database

{

/**

* @param array $credentials

*

* @return bool

*/

public function connect(array $credentials);

}



class PDODatabase implements Database

{

/**

* @param array $credentials

*

* @return bool

*/

public function connect(array $credentials)

{

// ...connect to a database

}

}

Consider the difference between the following:

class UserRepository

{

/**

* @var Database

*/

protected $database;



/**

* @return UserRepository

*/

public function __construct()

{

$this->database = new PDODatabase();



$this->database->connect([

"username" => "phillip",

"password" => "MyXKiDj8cvwA3y"

]);

}

}



$users = new UserRepository(); class NewUserRepository

{

/**

* @var Database

*/

protected $database;



/**

* @param Database $database

*

* @return NewUserRepository

*/

public function __construct(Database $database)

{

$this->database = $database;

}

}



$database = new PDODatabase();



$database->connect([

"username" => "phillip",

"password" => "MyXKiDj8cvwA3y"

]);



$users = new NewUserRepository($database);

On the one hand, it’s slightly easier to create a new object from the first UserRepository class. But what about when you want to control which database the repository connects to? How about when you need to unit test with a Test Double of Database?

The benefit of injecting dependencies is that you get to control how and when dependencies are created. This is greatly enriched by depending on interfaces rather than concrete implementations.

Dependency Resolution

The fancier Service Locator libraries do this thing called Dependency Resolution. Let me show you what that is, with the help of Illuminate\Container:

interface Database

{

// ...

}



class PDODatabase implements Database

{

// ...

}



interface UserRepository

{

// ...

}



class DatabaseUserRepository implements UserRepository

{

/**

* @var Database

*/

protected $database;



/**

* @param Database $database

*/

public function __construct(Database $database)

{

$this->database = $database;

}

}



class UserController

{

/**

* @var UserRepository

*/

protected $user;



/**

* @param UserRepository $user

*/

public function __construct(UserRepository $user)

{

$this->user = $user;

}

}



use Illuminate\Container\Container;



$container = new Container();



$container->bind(

Database::class,

PDODatabase::class

);



$container->bind(

UserRepository::class,

DatabaseUserRepository::class

);



$container[UserController::class]; // UserController

This code actually works! Illuminate\Container uses PHP reflection classes to discover the whole dependency graph, and build it automatically. This is used all the time, in Laravel.

Proxies

So I was building a service class, using this very principle. The code I had was similar to the following:

namespace ThirdParty

{

interface UserRepository

{

// ...

}



class DatabaseUserRepository implements UserRepository

{

/**

* @var int

*/

protected $state = 0;



/**

* @param int $state

*/

public function setState($state)

{

$this->state = $state;

}



/**

* @return int

*/

public function getState()

{

return $this->state;

}

}

}



namespace Acme

{

use ThirdParty\UserRepository;



class UserService

{

/**

* @var UserRepository

*/

protected $repository;



/**

* @param UserRepository $repository

*

* @return UserService

*/

public function __construct(UserRepository $repository)

{

$this->repository = $repository;

}



/**

* @return UserRepository

*/

public function getRepository()

{

return $this->repository;

}

}

}



namespace

{

$container = new Illuminate\Container\Container();



$container->bindShared(

"ThirdParty\\UserRepository",

function() {

return new ThirdParty\DatabaseUserRepository();

}

);



$container["ThirdParty\\UserRepository"]->setState(1);

}

I have combined all three contexts into a single file, for the purpose of demonstration. I don’t usually bundle multiple namespaces in the same file, in-case you were worried about it.

The code in the global namespace, was being done before the Acme\UserService class was called, as part of framework initialisation. In addition, I was requiring interfaces/classes from the ThirdParty namespace in almost every single file in the application. Even files with nothing besides business logic actually depended on the ThirdParty namespace stuff.

What I wanted to do was remove the huge dependency on external namespaces, so I tried something like the following:

namespace Acme

{

use ThirdParty\DatabaseUserRepository;



class ProxyUserRepository extends DatabaseUserRepository

{

// ...

}

}



namespace

{

$container = new Illuminate\Container\Container();



$container["Acme\\UserService"]

->getRepository()

->getState(); // 0

}

Though this code executed (because of the Dependency Resolution), it didn’t have the correct state. Because the name of the dependency (Acme\ProxyUserRepository) is different from the original dependency (ThirdParty\DatabaseUserRepository), the shard instance wasn’t being used to retrieve state. Instead, a new ThirdParty\UserRepository subclass was being created. This still fulfilled the inheritance restrictions, yet the state was not what was expected.

Then I tried this:

namespace Acme

{

use ThirdParty\UserRepository;



class ProxyUserRepository

{

/**

* @var UserRepository

*/

protected $repository;



/**

* @param UserRepository $repository

*

* @return ProxyUserRepository

*/

public function __construct(UserRepository $repository)

{

$this->repository = $repository;

}



/**

* @param string $method

* @param array $parameters

*

* @return mixed

*/

public function __call($method, $parameters)

{

return call_user_func_array(

[$this->repository, $method],

$parameters

);

}

}



class UserService

{

/**

* @var ProxyUserRepository

*/

protected $repository;



/**

* @param ProxyUserRepository $repository

*

* @return UserService

*/

public function __construct(ProxyUserRepository $repository)

{

$this->repository = $repository;

}



/**

* @return ProxyRepository

*/

public function getRepository()

{

return $this->repository;

}

}

}



namespace

{

$container = new Illuminate\Container\Container();



$container->bindShared(

"ThirdParty\\UserRepository",

function() {

return new ThirdParty\DatabaseUserRepository();

}

);



$container["ThirdParty\\UserRepository"]->setState(1);



$container["Acme\\UserService"]

->getRepository()

->getState(); // 1

}

This works because the dependency (ThirdParty\UserRepository) is moved from Acme\UserService to Acme\ProxyUserRepository. Acme\ProxyUserRepository gets the shared state, and all calls are directed through to ThirdParty\UserRepository, so the service continues to work as expected.

Should you use this trick, so that your domain classes/intefaces depend only on other things in the same namespace? Who knows? It will definitely add overhead (the extra method calls) and be slightly harder to debug (PHP Notice: Undefined property: Acme\ProxyUserRepository::$…).

I didn’t really have a problem with using a third-party user repository. I was trying to use a shared Illuminate\Http\Request object, in my own namespace, when I discovered this little hack. Ok, it’s a big hack!

Edit:

ThePsion5 suggests that this would be even better if the proxy class implements an interface and the consumers depend on that interface rather than on the concrete proxy. That’s absolutely right. The more you depend on interfaces and less on concrete implementations the more decoupled your code becomes.

This proxy thing is by no means the best/only/whatever way to make your code cleaner. It’s just one way to reduce third-party namespace references while still being able to share state through a Dependency Injection container.