Previous topic

Next topic

This Page

The following example is a bit lengthy, but it attempts to explain why Phalcon uses service location and dependency injection.
First, let’s pretend we are developing a component called SomeComponent. This performs a task that is not important now.
Our component has some dependency that is a connection to a database.

In this first example, the connection is created inside the component. This approach is impractical; due to the fact
we cannot change the connection parameters or the type of database system because the component only works as created.

<?phpclassSomeComponent{/** * The instantiation of the connection is hardcoded inside * the component, therefore it's difficult replace it externally * or change its behavior */publicfunctionsomeDbTask(){$connection=newConnection(["host"=>"localhost","username"=>"root","password"=>"secret","dbname"=>"invo",]);// ...}}$some=newSomeComponent();$some->someDbTask();

To solve this, we have created a setter that injects the dependency externally before using it. For now, this seems to be
a good solution:

Now consider that we use this component in different parts of the application and
then we will need to create the connection several times before passing it to the component.
Using some kind of global registry where we obtain the connection instance and not have
to create it again and again could solve this:

Now, let’s imagine that we must implement two methods in the component, the first always needs to create a new connection and the second always needs to use a shared connection:

<?phpclassRegistry{protectedstatic$_connection;/** * Creates a connection */protectedstaticfunction_createConnection(){returnnewConnection(["host"=>"localhost","username"=>"root","password"=>"secret","dbname"=>"invo",]);}/** * Creates a connection only once and returns it */publicstaticfunctiongetSharedConnection(){if(self::$_connection===null){self::$_connection=self::_createConnection();}returnself::$_connection;}/** * Always returns a new connection */publicstaticfunctiongetNewConnection(){returnself::_createConnection();}}classSomeComponent{protected$_connection;/** * Sets the connection externally */publicfunctionsetConnection($connection){$this->_connection=$connection;}/** * This method always needs the shared connection */publicfunctionsomeDbTask(){$connection=$this->_connection;// ...}/** * This method always needs a new connection */publicfunctionsomeOtherDbTask($connection){}}$some=newSomeComponent();// This injects the shared connection$some->setConnection(Registry::getSharedConnection());$some->someDbTask();// Here, we always pass a new connection as parameter$some->someOtherDbTask(Registry::getNewConnection());

So far we have seen how dependency injection solved our problems. Passing dependencies as arguments instead
of creating them internally in the code makes our application more maintainable and decoupled. However, in the long-term,
this form of dependency injection has some disadvantages.

For instance, if the component has many dependencies, we will need to create multiple setter arguments to pass
the dependencies or create a constructor that pass them with many arguments, additionally creating dependencies
before using the component, every time, makes our code not as maintainable as we would like:

<?php// Create the dependencies or retrieve them from the registry$connection=newConnection();$session=newSession();$fileSystem=newFileSystem();$filter=newFilter();$selector=newSelector();// Pass them as constructor parameters$some=newSomeComponent($connection,$session,$fileSystem,$filter,$selector);// ... Or using setters$some->setConnection($connection);$some->setSession($session);$some->setFileSystem($fileSystem);$some->setFilter($filter);$some->setSelector($selector);

Think if we had to create this object in many parts of our application. In the future, if we do not require any of the dependencies,
we need to go through the entire code base to remove the parameter in any constructor or setter where we injected the code. To solve this,
we return again to a global registry to create the component. However, it adds a new layer of abstraction before creating
the object:

Now we find ourselves back where we started, we are again building the dependencies inside of the component! We must find a solution that
keeps us from repeatedly falling into bad practices.

A practical and elegant way to solve these problems is using a container for dependencies. The containers act as the global registry that
we saw earlier. Using the container for dependencies as a bridge to obtain the dependencies allows us to reduce the complexity
of our component:

<?phpusePhalcon\Di;usePhalcon\DiInterface;classSomeComponent{protected$_di;publicfunction__construct(DiInterface$di){$this->_di=$di;}publicfunctionsomeDbTask(){// Get the connection service// Always returns a new connection$connection=$this->_di->get("db");}publicfunctionsomeOtherDbTask(){// Get a shared connection service,// this will return the same connection every time$connection=$this->_di->getShared("db");// This method also requires an input filtering service$filter=$this->_di->get("filter");}}$di=newDi();// Register a "db" service in the container$di->set("db",function(){returnnewConnection(["host"=>"localhost","username"=>"root","password"=>"secret","dbname"=>"invo",]);});// Register a "filter" service in the container$di->set("filter",function(){returnnewFilter();});// Register a "session" service in the container$di->set("session",function(){returnnewSession();});// Pass the service container as unique parameter$some=newSomeComponent($di);$some->someDbTask();

The component can now simply access the service it requires when it needs it, if it does not require a service it is not even initialized,
saving resources. The component is now highly decoupled. For example, we can replace the manner in which connections are created,
their behavior or any other aspect of them and that would not affect the component.