PHP Service Layers: Dependency Injection

In this penultimate part of the series, I put the finishing touches on the previous service layer by adding a couple of basic dependency injection containers along with a static helper to it. These can be used by client code to construct a specified service in a valid state.

Although its name may sound intimidating, a service is nothing but an additional abstraction layer (usually placed on top of the mapping one). It permits you to encapsulate application logic behind a single entry-point interface that can be consumed by several different clients, without having to deal with frustrating code duplication issues.

As with any other design pattern, the creation of a service isn’t tied to a particular programming language or platform. This means that it’s perfectly possible to implement it in a fairly straightforward fashion with PHP and enjoy the benefits that it provides, especially when developing middle/large-scale applications.

In line with this concept, in earlier installments of this series I showed how to construct a basic application, which used a service layer to perform CRUD operations on a domain model made up of a few user entities. Moreover, to demonstrate the flexible structure of this service, I enabled it to fetch the aforementioned user entities from the persistence layer in XML format. Not too bad, right?

While the pertinent service in its current state is entirely functional, there are a few extra steps we must take before seeing it in action. Since its object graph is slightly complex, we need first to create some dependencies (including a MySQL adapter, a user mapper and an array collection). In this penultimate chapter of the series I’ll be developing a couple of simple dependency injection containers, which will take care of spawning these collaborators in the background and returning to client code a ready-to-use user service object.

To learn how these DI containers will be created, keep reading. 

Recap time: the previous service classes

If you missed the preceding part of this series, where I implemented the user service mentioned in the introduction, below I included the full source code corresponding to this layer. This way, you can dissect it and understand its underlying logic.

So here is an abstract service, which is responsible for running CRUD functions on generic entities. Take a close look at it:

(MyApplication/Service/AbstractService.php)

<?php

namespace MyApplicationService;
use MyApplicationMapper,
    MyApplicationEntity;

abstract class AbstractService
{
    protected $_mapper;

    /**
     * Constructor
     */
    public function  __construct(MapperAbstractDataMapper $mapper)
    {
        $this->_mapper = $mapper;
    }

    /**
     * Find an entity by their ID
     */
    public function findById($id)
    {
        return $this->_mapper->findById($id);
    }

    /**
     * Find all the entities
     */
    public function findAll()
    {
        return $this->_mapper->findAll();
    }

    /**
     * Insert a new entity
     */
    public function insert(EntityEntityAbstract $entity)
    {
        return $this->_mapper->insert($entity);
    }

    /**
     * Update an entity
     */
    public function update(EntityEntityAbstract $entity)
    {
        return $this->_mapper->update($entity);
    }

    /**
     * Delete an entity
     */
    public function delete($id)
    {
        return $this->_mapper->delete($id);
    }
}

As seen in the above code fragment, the “AbstractService” class accepts in its constructor a data mapper, which is used internally to retrieve, save and remove entities from the persistence layer. Since in this  case, I want to perform these operations only on user entities, it’s necessary to spawn a subclass that works specifically with these kinds of domain objects.

Well, the following derivative does that. Check it out:

(MyApplication/Service/UserService.php)

<?php

namespace MyApplicationService;
use MyApplicationMapper,
    MyApplicationEntity;

class UserService extends AbstractService
{
    /**
     * Constructor
     */
    public function  __construct(MapperUserMapper $mapper)
    {
        parent::__construct($mapper);
    }

    /**
     * Save a user to persistence layer
     */
    public function save(EntityUser $user)
    {
        return $user->id === null ?
               $this->insert($user) :
               $this->update($user);
    }

    /**
     * Fetch all users in XML format
     */
    public function toXML()
    {
        $users = $this->_mapper->findAll();
        $xml = "<?xml version="1.0" encoding="UTF-8"?>n<users>n";
        foreach($users as $user) {
            $xml .= "<user>n<fname>$user->fname</fname>n"
                  . "<lname>$user->lname</lname>n"
                  . "<email>$user->fname</email>n</user>n";
        }
        $xml .= "</users>";
        return $xml;
    }
}

Mission accomplished. Thanks to the functionality implemented by the prior abstract service, it’s really simple to create a child class that manipulates user entities exclusively. And to prove that a service layer can be extended easily, I also enabled the previous one to return the pertinent entities in XML. Now do you see how easy it is to build a service in PHP? I guess you do!

But before you launch your code editor and start adding your own tweaks to the previous class, it’s necessary to do some more work. As I said in the introduction, the service’s object graph is made up of a few dependencies which are injected in turn into the corresponding constructors, right?

Well, to prevent client code from constructing the graph at runtime, in the following segment I’ll be creating a couple of dependency injection containers. They will perform this task behind the scenes.

To learn how this will be done, click on the link below and keep reading. 

{mospagebreak title=The service’s object graph: basic DI containers}

Since I want to keep things clear and easy to follow, the DI containers that I plan to build in the next few lines will be simple factories. They will implement the following interface:

(MyApplication/Injector/InjectorInterface.php)

<?php

namespace MyApplicationInjector;

interface InjectorInterface
{
    public function create();
}

Definitely, the contract defined by the “InjectorInterface” interface doesn’t bear any further discussion. So move on and take a close look at the implementer below. It is a factory that creates a Singleton instance of the previous MySQL adapter (you remember that one, right?):  

(MyApplication/Injector/MysqlAdapterInjector.php)

<?php

namespace MyApplicationInjector;
use MyApplicationDatabase;

class MysqlAdapterInjector implements InjectorInterface
{
    protected $_mysqlAdapter;

    /**
     * Create an instance of the MysqlAdapter class
     */
    public function create()
    {
        if ($this->_mysqlAdapter === null) {
            $this->_mysqlAdapter = new DatabaseMysqlAdapter(array(‘host’, ‘user’, ‘password’, ‘database’));
        }
        return $this->_mysqlAdapter;
    }
}

There’s no need to be a rocket scientist to grasp the logic behind the “MysqlAdapterInjector” class. As you can see, it simply implements the “create()” method defined by the earlier interface to return to client code a Singleton of the aforementioned MySQL adapter.

With this low-level factory already up and running, it’s really easy to create another one that spawns valid user service objects. Skeptical? Then take a peek at the following class, which should change your mind quickly: 

(MyApplication/Injector/UserServiceInjector.php)

<?php

namespace MyApplicationInjector;
use MyApplicationDatabase,
    MyApplicationCollection,
    MyApplicationMapper,
    MyApplicationService;

class UserServiceInjector implements InjectorInterface
{
    /**
     * Create a user service
     */
    public function create()
    {
        $mysqlInjector = new MysqlAdapterInjector;
        $adapter = $mysqlInjector->create();
        return new ServiceUserService(
            new MapperUserMapper($adapter,
                new CollectionUserCollection
            )
        );
    }
}

There you have it. In a few easy steps, I implemented a basic DI container, which is capable of creating instances of the previous user service class by injecting the corresponding dependencies at the appropriate places. Of course, similar results can be obtained with an “automated” container that uses  reflection internally to figure out what collaborators must be injected into a specific object. In this case, though, I decided to stick with the so-called poor man’s dependency injection, since the number of object graphs that need to be created is really small.

So far, so good. At this point, I’m sure that you’ve grasped how the pair of DI containers do their things. So, the next step is to build a helper that permits us to abstract away the creation of a specified service (including the one that you saw in the preceding section).

The definition of this brand new helper will be shown in the next segment, so keep reading. 

Giving the final touches to the service layer: creating a service helper

Frankly speaking, the creation of a helper class that allows you to retrieve (and also inject) a specified service is entirely optional. However, the convenience of having such a class at our disposal is that it can be used either for pulling in or out model data in an action controller or in a view, when working with an MVC stack.

Having clarified that point, here’s the code for this helper:

(MyApplication/Service/ServiceLocator.php)

<?php

namespace MyApplicationService;
use MyApplicationInjector;

class ServiceLocator
{
    protected static $_services = array();
    protected static $_injectors = array();

    /**
     * Protected constructor
     */
    protected function  __construct(){}

    /**
     * Add a single injector
     */
    public static function addInjector($name, InjectorInjectorInterface $injector)
    {
        if (!isset(self::$_injectors[$name])) {
            self::$_injectors[$name] = $injector;
            return true;
        }
        return false;
    }

    /**
     * Add multiple injectors
     */
    public static function addInjectors(array $injectors)
    {
        foreach ($injectors as $injector) {
            self::addInjector($injector);
        }
    }

    /**
     * Remove an existing injector
     */
    public static function removeInjector($name)
    {
        if (isset(self::$_injectors[$name])) {
            unset(self::$_injectors[$name]);
            return true;
        }
        return false;
    }
   
    /**
     * Get the specified injector
     */
    public static function getInjector($name)
    {
        return isset(self::$_injectors[$name]) ?
               self::$_injectors[$name] :
               null;
    }
   
    /**
     * Check if the specified injector exists
     */
    public static function injectorExists($name)
    {
        return isset(self::$_injectors[$name]);
    }

    /**
     * Add a service
     */
    public static function addService($name, AbstractService $service)
    {
        if (!isset(self::$_services[$name])) {
            self::$_services[$name] = $service;
            return true;
        }
        return false;
    }

    /**
     * Add multiple services
     */
    public static function addServices(array $services)
    {
        foreach($services as $service) {
            self::addService($service);
        }
    }

    /**
     * Remove an existing service
     */
    public static function removeService($name)
    {
        if (isset(self::$_services[$name])) {
            unset(self::$_services[$name]);
            return true;
        }
        return false;
    }

    /**
     * Get the specified service (if not created already, the associated injector builds the service)
     */
    public static function getService($name)
    {
        // if the service has been added and cached, get it from the cache
        if (isset(self::$_services[$name])) {
            return self::$_services[$name];
        }
        // otherwise, attempt to build the service via the associated injector and save it to the cache
        if (!$injector = self::getInjector($name)) {
            throw new ServiceLocatorException(‘Unable to get the injector associated to the specified service.’);
        }
        $service = $injector->create();
        self::addService($name, $service);
        return $service;
    }

    /**
     * Check if the specified service exists
     */
    public static function serviceExists($name)
    {
        return isset(self::$_services[$name]);
    }
}

 

(MyApplication/Service/ServiceLocatorException.php)

<?php

namespace MyApplicationService;

class ServiceLocatorException extends Exception{}

From the above code fragment, it’s clear to see that the previous “ServiceLocator” class behaves like a static registry. This permits us to save and retrieve services and DI containers (called injectors in this case) via a set of convenient methods.

Nevertheless, the most interesting aspect of this class is the implementation of its “getService()” method. It follows a simple naming convention to retrieve a specified service. For instance, if the requested service is called “user,” the method will first look in the registry to see if the service has been created, and will return it to client code. Otherwise, it’ll attempt to find an associated injector called “UserServiceInjector,” which will be used (when possible) to create the pertinent service. Do you understand the inner workings of the helper? Great.

And now that you’ve grasped how this class functions, we’re finally ready to put this sample user service to work for us. However, to see the service’s actual functionality, you’ll have to wait until the next tutorial.    

Final Thoughts

In this penultimate chapter of the series, I put the final touches on the previous service layer. I added to it a couple of basic dependency injection containers, along with a static helper. These can be used by client code — and by client code, I mean anything that can consume the layer in question, including action controllers and views in an MVC schema — to construct a specified service in a valid state.

With this additional step, we’ve finally set the scene to see the service’s abilities in action. So, make sure to tighten your seatbelts and be ready to be confronted with a bunch of code samples, as this process will be conducted in the last tutorial.

Don’t miss it!

[gp-comments width="770" linklove="off" ]
antalya escort bayan antalya escort bayan