Using Dependency Injection ************************** The following example is a bit lengthy, but explains why using a service container, 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; practically we cannot change the connection parameters or the type of database system because the component only works as created. .. code-block:: php "localhost", "username" => "root", "password" => "secret", "dbname" => "invo" )); // ... } } $some = new SomeComponent(); $some->someDbTask(); To solve this, we create a setter that injects the dependency externally before using it. For now, this seems to be a good solution: .. code-block:: php _connection = $connection; } public function someDbTask() { $connection = $this->_connection; // ... } } $some = new SomeComponent(); //Create the connection $connection = new Connection(array( "host" => "localhost", "username" => "root", "password" => "secret", "dbname" => "invo" )); //Inject the connection in the component $some->setConnection($connection); $some->someDbTask(); 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: .. code-block:: php "localhost", "username" => "root", "password" => "secret", "dbname" => "invo" )); } } class SomeComponent { protected $_connection; /** * Sets the connection externally */ public function setConnection($connection){ $this->_connection = $connection; } public function someDbTask() { $connection = $this->_connection; // ... } } $some = new SomeComponent(); //Pass the connection defined in the registry $some->setConnection(Registry::getConnection()); $some->someDbTask(); Now, let's imagine that we must implement two methods in the component, the first always need to create a new connection and the second always need to use a shared connection: .. code-block:: php "localhost", "username" => "root", "password" => "secret", "dbname" => "invo" )); } /** * Creates a connection only once and returns it */ public static function getSharedConnection() { if (self::$_connection===null){ $connection = self::_createConnection(); self::$_connection = $connection; } return self::$_connection; } /** * Always returns a new connection */ public static function getNewConnection() { return self::_createConnection(); } } class SomeComponent { protected $_connection; /** * Sets the connection externally */ public function setConnection($connection){ $this->_connection = $connection; } /** * This method always needs the shared connection */ public function someDbTask() { $connection = $this->_connection; // ... } /** * This method always needs a new connection */ public function someOtherDbTask($connection) { } } $some = new SomeComponent(); //This injects the shared connection $some->setConnection(Registry::getSharedConnection()); $some->someDbTask(); //Here, we always pass a new connection as parameter $some->someOtherDbTask(Registry::getConnection()); 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, to long-term, this form of dependency injection have 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 maintainable as we would like: .. code-block:: php setConnection($connection); $some->setSession($session); $some->setFileSystem($fileSystem); $some->setFilter($filter); $some->setSelector($selector); Think we had to create this object in many parts of our application. If you ever do not require any of the dependencies, we need to go everywhere to remove the parameter in the constructor or the 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: .. code-block:: php _di = $di; } public function someDbTask() { // Get the connection service // Always returns a new connection $connection = $this->_di->get('db'); } public function someOtherDbTask() { // Get a shared connection service, // this will return the same connection everytime $connection = $this->_di->getShared('db'); //This method also requires a input filtering service $filter = $this->_db->get('filter'); } } $di = new Phalcon\DI(); //Register a "db" service in the container $di->set('db', function() { return new Connection(array( "host" => "localhost", "username" => "root", "password" => "secret", "dbname" => "invo" )); }); //Register a "filter" service in the container $di->set('filter', function() { return new Filter(); }); //Register a "session" service in the container $di->set('session', function() { return new Session(); }); //Pass the service container as unique parameter $some = new SomeComponent($di); $some->someTask(); The component now simply access the service it requires when it needs it, if it does not require a service that 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. Our approach ============ Phalcon\\DI is a component implementing Dependency Injection and Location of services and it's itself a container for them. Since Phalcon is highly decoupled, Phalcon\\DI is essential to integrate the different components of the framework. The developer can also use this component to inject dependencies and manage global instances of the different classes used in the application. Basically, this component implements the `Inversion of Control`_ pattern. Applying this, the objects do not receive their dependencies using setters or constructors, but requesting a service dependency injector. This reduces the overall complexity since there is only one way to get the required dependencies within a component. Additionally, this pattern increases testability in the code, thus making it less prone to errors. Registering services in the Container ===================================== The framework itself or the developer can register services. When a component A requires component B (or an instance of its class) to operate, it can request component B from the container, rather than creating a new instance component B. This way of working gives us many advantages: * We can replace a component by one created by ourselves or a third party one easily. * We have full control of the object initialization, allowing us to set these objects, as you need before delivering them to components. * We can get global instances of components in a structured and unified way Services can be registered using several types of definitions: .. code-block:: php set("request", 'Phalcon\Http\Request'); //Using an anonymous function, the instance will lazy loaded $di->set("request", function() { return new Phalcon\Http\Request(); }); //Registering directly an instance $di->set("request", new Phalcon\Http\Request()); //Using an array definition $di->set("request", array( "className" => 'Phalcon\Http\Request' )); The array syntax is also allowed to register services: .. code-block:: php 'Phalcon\Http\Request' ); In the examples given above, when the framework needs to access the request data, it will ask for the service identified as ‘request’ in the container. The container in turn will return an instance of the required service. A developer might eventually replace a component when he/she needs. Each of the methods (demonstrated in the example given above) used to set/register a service has advantages and disadvantages. It is up to the developer and the particular requirements that will designate which one is used. Setting a service by a string is simple, but lacks flexibility. Setting services using an array offers a lot more flexibility, but makes the code more complicated. The lambda function is a good balance between the two, but could lead to more maintenance than one would expect. Phalcon\\DI offers lazy loading for every service it stores. Unless the developer chooses to instantiate an object directly and store it in the container, any object stored in it (via array, string, etc.) will be lazy loaded i.e. instantiated only when requested. Simple Registration ------------------- As seen before, there are several ways to register services. These are we call simple: String ^^^^^^ This type expects the name of a valid class, returning an object of the specified class, if the class is not loaded is loaded using an auto-loader. This type of definition does not allow to define arguments for the class constructor or parameters: .. code-block:: php set('request', 'Phalcon\Http\Request'); Object ^^^^^^ This type expects an object because the object does not need to be resolved because it is already an object, one could say that there is not really dependency injection here, but it is useful if you want to force that the returned dependency will always the same object/value: .. code-block:: php set('request', new Phalcon\Http\Request()); Closures/Anonymous functions ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This method offers greater freedom to build the dependency as desired, however, it is difficult to change some of the parameters externally without having to completely change the definition of dependency: .. code-block:: php set("db", function() { return new \Phalcon\Db\Adapter\Pdo\Mysql(array( "host" => "localhost", "username" => "root", "password" => "secret", "dbname" => "blog" )); }); Some of the limitations can be overcome by passing additional variables to the closure's environment: .. code-block:: php set("db", function() use ($config) { return new \Phalcon\Db\Adapter\Pdo\Mysql(array( "host" => $config->host, "username" => $config->username, "password" => $config->password, "dbname" => $config->name )); }); Complex Registration -------------------- If it is required to change the definition of a service without instantiating/resolving the service, then, we need to define the services using the array syntax. Define a service using an array definition can be a little more verbose: .. code-block:: php set('logger', array( 'className' => 'Phalcon\Logger\Adapter\File', 'arguments' => array( array( 'type' => 'parameter', 'value' => '../apps/logs/error.log' ) ) )); //Using an anonymous function $di->set('logger', function() { return new \Phalcon\Logger\Adapter\File('../apps/logs/error.log'); }); Both service registrations above produce the same result. The array definition however, allows for alteration of the service parameters if needed: .. code-block:: php getService('logger')->setClassName('MyCustomLogger'); //Change the first parameter without instantiate the logger $di->getService('logger')->setParameter(0, array( 'type' => 'parameter', 'value' => '../apps/logs/error.log' )); In addition by using the array syntax you can use three types of dependency injection: Constructor Injection ^^^^^^^^^^^^^^^^^^^^^ This injection type passes the dependencies/arguments to the class constructor. Let's pretend we have the following component: .. code-block:: php _response = $response; $this->_someFlag = $someFlag; } } The service can be registered this way: .. code-block:: php set('response', array( 'className' => 'Phalcon\Http\Response' )); $di->set('someComponent', array( 'className' => 'SomeApp\SomeComponent', 'arguments' => array( array('type' => 'service', 'name' => 'response'), array('type' => 'parameter', 'value' => true) ) )); The service "response" (Phalcon\\Http\\Response) is resolved to be passed as the first argument of the constructor, while the second is a boolean value (true) that is passed as it is. Setter Injection ^^^^^^^^^^^^^^^^ Classes may have setters to inject optional dependencies, our previous class can be changed to accept the dependencies with setters: .. code-block:: php _response = $response; } public function setFlag($someFlag) { $this->_someFlag = $someFlag; } } A service with setter injection can be registered as follows: .. code-block:: php set('response', array( 'className' => 'Phalcon\Http\Response' )); $di->set('someComponent', array( 'className' => 'SomeApp\SomeComponent', 'calls' => array( array( 'method' => 'setResponse', 'arguments' => array( array('type' => 'service', 'name' => 'response'), ) ), array( 'method' => 'setFlag', 'arguments' => array( array('type' => 'parameter', 'value' => true) ) ) ) )); Properties Injection ^^^^^^^^^^^^^^^^^^^^ A less common strategy is to inject dependencies or parameters directly in public attributes of the class: .. code-block:: php set('response', array( 'className' => 'Phalcon\Http\Response' )); $di->set('someComponent', array( 'className' => 'SomeApp\SomeComponent', 'properties' => array( array( 'name' => 'response', 'value' => array('type' => 'service', 'name' => 'response') ), array( 'name' => 'someFlag', 'value' => array('type' => 'parameter', 'value' => true) ) ) )); Supported parameter types include the following: +-------------+----------------------------------------------------------+------------------------------------------------------------------------------------+ | Type | Description | Example | +=============+==========================================================+====================================================================================+ | parameter | Represents a literal value to be passed as parameter | array('type' => 'parameter', 'value' => 1234) | +-------------+----------------------------------------------------------+------------------------------------------------------------------------------------+ | service | Represents another service in the services container | array('type' => 'service', 'name' => 'request') | +-------------+----------------------------------------------------------+------------------------------------------------------------------------------------+ | instance | Represents a object that must be built dynamically | array('type' => 'service', 'className' => 'DateTime', 'arguments' => array('now')) | +-------------+----------------------------------------------------------+------------------------------------------------------------------------------------+ Resolving a service whose definition is complex may be slightly slower than previously seen simple definitions. However, these provide a more robust approach to define and inject services. Mixing different types of definitions is allowed, everyone can decide what is the most appropriate way to register the services according to the application needs. Resolving Services ================== Obtaining a service from the container is a matter of simply calling the “get” method. A new instance of the service will be returned: .. code-block:: php get("request"); Or by calling through the magic method: .. code-block:: php getRequest(); Or using the array-access syntax: .. code-block:: php get("MyComponent", array("some-parameter", "other")); Shared services =============== Services can be registered as "shared" services this means that they always will act as singletons_. Once the service is resolved for the first time the same instance it's returned every time a consumer retrieve the service from the container: .. code-block:: php setShared('session', function() { $session = new Phalcon\Session\Adapter\Files(); $session->start(); return $session; }); $session = $di->get('session'); // Locates the service for the first time $session = $di->getSession(); // Returns the first instantiated object An alternative way to register services is pass "true" as third parameter of "set": .. code-block:: php set('session', function() { //... }, true); If a service isn't registered as shared and you want to be sure that a shared instance will be accessed every time the service is obtained from the DI, you can use the 'getShared' method: .. code-block:: php getShared("request"); Manipulating services individually ================================== Once a service is registered in services container, you can retrieve it to manipulate it individually: .. code-block:: php set('request', 'Phalcon\Http\Request'); //Get the service $requestService = $di->getService('request'); //Change its definition $requestService->setDefinition(function() { return new Phalcon\Http\Request(); }); //Change it to shared $request->setShared(true); //Resolve the service (return a Phalcon\Http\Request instance) $request = $requestService->resolve(); Instantiating classes via the Services Container ================================================ When you request a service to the services container, if it can't find out a service with the same name it'll try to load a class with the same name. With this behavior we can replace any class by another simply by registering a service with its name: .. code-block:: php set('IndexController', function() { $component = new Component(); return $component; }, true); //Register a controller as a service $di->set('MyOtherComponent', function() { //Actually returns another component $component = new AnotherComponent(); return $component; }); //Create a instance via the services container $myComponent = $di->get('MyOtherComponent'); You can take advantage of this, always instantiating your classes via the services container (even if they aren't registered as services). The DI will fallback to a valid autoloader to finally load the class. By doing this, you can easily replace any class in the future by implementing a definition for it. Automatic Injecting of the DI itself ==================================== If a class or component requires the DI itself to locate services, the DI can automatically inject itself to the instances creates by it, to do this, you need to implement the :doc:`Phalcon\\DI\\InjectionAwareInterface <../api/Phalcon_DI_InjectionAwareInterface>` in your classes: .. code-block:: php _di = $di; } public function getDi() { return $this->_di; } } Then once the service is resolved, the $di will be passed to setDi automatically: .. code-block:: php set('myClass', 'MyClass'); //Resolve the service (also $myClass->setDi($di) is automatically called) $myClass = $di->get('myClass'); Avoiding service resolution =========================== Some services are used in each of the requests made to the application, eliminate the process of resolving the service could add some small improvement in performance. .. code-block:: php set('router', $router); Organizing services in files ============================ You can better organize your application by moving the service registration to individual files instead of doing everything in the application's bootstrap: .. code-block:: php set('router', function() { return include ("../app/config/routes.php"); }); Then in the file ("../app/config/routes.php") return the object resolved: .. code-block:: php post('/login'); return $router; Accessing the DI in a static way ================================ If needed you can access the latest DI created in a static function in the following way: .. code-block:: php getSession(); } } Factory Default DI ================== Although the decoupled character of Phalcon offers us great freedom and flexibility, maybe we just simply want to use it as a full-stack framework. To achieve this, the framework provides a variant of Phalcon\\DI called Phalcon\\DI\\FactoryDefault. This class automatically registers the appropriate services bundled with the framework to act as full-stack. .. code-block:: php ` | Yes | +---------------------+---------------------------------------------+----------------------------------------------------------------------------------------------------+--------+ | router | Routing Service | :doc:`Phalcon\\Mvc\\Router <../api/Phalcon_Mvc_Router>` | Yes | +---------------------+---------------------------------------------+----------------------------------------------------------------------------------------------------+--------+ | url | URL Generator Service | :doc:`Phalcon\\Mvc\\Url <../api/Phalcon_Mvc_Url>` | Yes | +---------------------+---------------------------------------------+----------------------------------------------------------------------------------------------------+--------+ | request | HTTP Request Environment Service | :doc:`Phalcon\\Http\\Request <../api/Phalcon_Http_Request>` | Yes | +---------------------+---------------------------------------------+----------------------------------------------------------------------------------------------------+--------+ | response | HTTP Response Environment Service | :doc:`Phalcon\\Http\\Response <../api/Phalcon_Http_Response>` | Yes | +---------------------+---------------------------------------------+----------------------------------------------------------------------------------------------------+--------+ | filter | Input Filtering Service | :doc:`Phalcon\\Filter <../api/Phalcon_Filter>` | Yes | +---------------------+---------------------------------------------+----------------------------------------------------------------------------------------------------+--------+ | flash | Flash Messaging Service | :doc:`Phalcon\\Flash\\Direct <../api/Phalcon_Flash_Direct>` | Yes | +---------------------+---------------------------------------------+----------------------------------------------------------------------------------------------------+--------+ | flashSession | Flash Session Messaging Service | :doc:`Phalcon\\Flash\\Session <../api/Phalcon_Flash_Session>` | Yes | +---------------------+---------------------------------------------+----------------------------------------------------------------------------------------------------+--------+ | session | Session Service | :doc:`Phalcon\\Session\\Adapter\\Files <../api/Phalcon_Session_Adapter_Files>` | Yes | +---------------------+---------------------------------------------+----------------------------------------------------------------------------------------------------+--------+ | eventsManager | Events Management Service | :doc:`Phalcon\\Events\\Manager <../api/Phalcon_Events_Manager>` | Yes | +---------------------+---------------------------------------------+----------------------------------------------------------------------------------------------------+--------+ | db | Low-Level Database Connection Service | :doc:`Phalcon\\Db <../api/Phalcon_Db>` | Yes | +---------------------+---------------------------------------------+----------------------------------------------------------------------------------------------------+--------+ | security | Security helpers | :doc:`Phalcon\\Security <../api/Phalcon_Security>` | Yes | +---------------------+---------------------------------------------+----------------------------------------------------------------------------------------------------+--------+ | escaper | Contextual Escaping | :doc:`Phalcon\\Escaper <../api/Phalcon_Escaper>` | Yes | +---------------------+---------------------------------------------+----------------------------------------------------------------------------------------------------+--------+ | annotations | Annotations Parser | :doc:`Phalcon\\Annotations\\Adapter\\Memory <../api/Phalcon_Annotations_Adapter_Memory>` | Yes | +---------------------+---------------------------------------------+----------------------------------------------------------------------------------------------------+--------+ | modelsManager | Models Management Service | :doc:`Phalcon\\Mvc\\Model\\Manager <../api/Phalcon_Mvc_Model_Manager>` | Yes | +---------------------+---------------------------------------------+----------------------------------------------------------------------------------------------------+--------+ | modelsMetadata | Models Meta-Data Service | :doc:`Phalcon\\Mvc\\Model\\MetaData\\Memory <../api/Phalcon_Mvc_Model_MetaData_Memory>` | Yes | +---------------------+---------------------------------------------+----------------------------------------------------------------------------------------------------+--------+ | transactionManager | Models Transaction Manager Service | :doc:`Phalcon\\Mvc\\Model\\Transaction\\Manager <../api/Phalcon_Mvc_Model_Transaction_Manager>` | Yes | +---------------------+---------------------------------------------+----------------------------------------------------------------------------------------------------+--------+ | modelsCache | Cache backend for models cache | None | - | +---------------------+---------------------------------------------+----------------------------------------------------------------------------------------------------+--------+ | viewsCache | Cache backend for views fragments | None | - | +---------------------+---------------------------------------------+----------------------------------------------------------------------------------------------------+--------+ Implementing your own DI ======================== The :doc:`Phalcon\\DiInterface <../api/Phalcon_DiInterface>` interface must be implemented to create your own DI replacing the one provided by Phalcon or extend the current one. .. _`Inversion of Control`: http://en.wikipedia.org/wiki/Inversion_of_control .. _Singletons: http://en.wikipedia.org/wiki/Singleton_pattern