Home arrow MySQL arrow Page 2 - Building a PHP ORM: Deploying a Blog

Building Dependency Injection Containers and Service Locators - MySQL

In this conclusion to a three-part tutorial, you'll see that implementing a customizable ORM using modern development techniques is a fairly straightforward process. We'll use the dependency injection technique, the data mapper pattern and a domain model. This powerful combination allows you to easily manipulate relationships between entities.

TABLE OF CONTENTS:
  1. Building a PHP ORM: Deploying a Blog
  2. Building Dependency Injection Containers and Service Locators
By: Alejandro Gervasio
Rating: starstarstarstarstar / 0
December 08, 2011

print this article
SEARCH DEV SHED

TOOLS YOU CAN USE

advertisement

If you’ve been an insightful observer from the very first article, then you've realized that the brave warrior driving the forces of this ORM is…yes, dependency injection. To keep all of the code clean and uncluttered, however, the creation of object graphs must be encapsulated behind some low-level factories and dependency injection containers, thus keeping object instantiation isolated from application logic. 

To accomplish this objective, I’m going to appeal to the functionality of a manual dependency injection container (AKA a poor man’s DIC) and the one provided by a simple factory class, to create the previous MySQL adapter and a service that will run CRUD operations in domain objects.

Here are the classes that perform all of these tasks, along with the interface that they implement: 

(Blog/Injector/InjectorInterface.php)

<?php

namespace Blog\Injector;

interface InjectorInterface
{
    public function create();
}

 

(Blog/Injector/MysqlAdapterInjector.php)

<?php

namespace Blog\Injector;
use Blog\Library\Database;

class MysqlAdapterInjector implements InjectorInterface
{
    protected static $_mysqlAdapter;

    /**
     * Create an instance of the MysqlAdapter class
     */
    public function create()
    {
        if (self::$_mysqlAdapter === null) {
           self::$_mysqlAdapter = new Database\MysqlAdapter(array(
                'host',
                'user',
                'password',
                'database'
            ));
        }
        return self::$_mysqlAdapter;
    }
}

 

(Blog/Injector/EntryServiceInjector.php)

<?php

namespace Blog\Injector;
use Blog\Library\Database,
    Blog\Model\Mapper,
    Blog\Service;

class EntryServiceInjector implements InjectorInterface
{
    /**
     * Create the entry service
     */
    public function create()
    {
        $mysqlInjector = new MysqlAdapterInjector;
        $mysqlAdapter = $mysqlInjector->create();
        return new Service\EntryService(
            new Mapper\EntryMapper(
                $mysqlAdapter, new Mapper\CommentMapper(
                    $mysqlAdapter, new Mapper\AuthorMapper($mysqlAdapter)
                )
            )
        ); 
    }
}

The code fragment above speaks for itself. It defines a low-level factory, which is responsible for creating a Singleton instance of the aforementioned database adapter (in fact, this factory is optional, as it’s possible to eliminate this Singleton by using only dependency injection). After doing that, it shows the implementation of a simple DIC, which gathers in one single place all the collaborators required for building a service layer that handles blog entries.

Of course, at this time you may be wondering how this still-undefined service looks. Check the following snippet, which shows the originating classes of the service:

(Blog/Service/AbstractService.php)

<?php

namespace Blog\Service;
use Blog\Model\Mapper,
    Blog\Model;

abstract class AbstractService
{
    protected $_mapper;

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

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

    /**
     * Find the entities that meet the specified conditions
     * (find all entities if no conditions are specified)
     */
    public function find($conditions = '')
    {
        return $this->_mapper->find($conditions);
    }

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

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

    /**
     * Delete one or more entities
     */
    public function delete($id)
    {
        return $this->_mapper->delete($id);
    }
}

 

(Blog/Service/EntryService.php)

<?php

namespace Blog\Service;
use Blog\Model\Mapper,
    Blog\Model;

class EntryService extends AbstractService
{
    /**
     * Constructor
     */
    public function __construct(Mapper\EntryMapper $entryMapper)
    {
        parent::__construct($entryMapper);
    }
}

There you have it. As you can see above, the service layer is composed of a simple hierarchy of classes that act like wrappers for the mappers defined previously. This provides a single “catch-all” point, which allows you to encapsulate all of the functionality required for manipulating blog entries and their associated entities -- that is, comments and authors.

Finally, to provide the ORM with a more flexible structure that enables us to work with multiple services (aside from the one that you just saw), below I included a simple service locator class. It allows you to create on request a specified service by using its associated dependency injection container. Check it out:

(Blog/Service/ServiceLocator.php)

<?php

namespace Blog\Service;
use Blog\Injector;

class ServiceLocator
{
    protected $_injectors = array();
    protected $_services = array();
   
    /**
     * Add a single injector
     */
    public function addInjector($key, Injector\InjectorInterface $injector)
    {
        if (!isset($this->_injectors[$key])) {
            $this->_injectors[$key] = $injector;
            return true;
        }
        return false;
    }
   
    /**
     * Add multiple injectors
     */
    public function addInjectors(array $injectors)
    {
        foreach ($injectors as $key => $injector) {
            $this->addInjector($key, $injector);
        }
        return $this;
    }
   
    /**
     * Check if the specified injector exists
     */
    public function injectorExists($key)
    {
        return isset($this->_injectors[$key]);
    }
   
    /**
     * Get the specified injector
     */
    public function getInjector($key)
    {
        return isset($this->_injectors[$key])
            ? $this->_injectors[$key] : null;
    }
   
    /**
     * Add a single service
     */
    public function addService($key, AbstractService $service)
    {
        if (!isset($this->_services[$key])) {
            $this->_services[$key] = $service;
            return true;
        }
        return false;
    }
   
    /**
     * Add multiple services
     */
    public function addServices(array $services)
    {
        foreach ($services as $key => $service) {
            $this->addService($key, $service);
        }
        return $this;
    }
   
    /**
     * Check if the specified service exists
     */
    public function serviceExists($key)
    {
        return isset($this->_services[$key]);
    }
   
    /**
     * Get the specified service.
     * If the service has been previously injected or created, get it from the $_services array;
     * Otherwise, make the associated injector create the service and save it to the $_services array
     */
    public function getService($key)
    {
        if (isset($this->_services[$key])) {
            return $this->_services[$key];
        }
        if (!isset($this->_injectors[$key])) {
            throw new \RuntimeException('The specified service cannot be created because the associated injector does not exist.');
        }
        $service = $this->getInjector($key)->create();
        $this->addService($key, $service);
        return $service;
    }
}

Mission accomplished. With this service locator up and running, we’ve come to the long-awaited moment when we’ll be able to see the ORM in action! Of course, first it’s necessary to create some MySQL tables and populate them with some sample blog entries, their associated comments and the corresponding authors. So, here are the tables:

As shown above, each blog post has effectively caught the attention of a few picky users, even though at disparate levels. Naturally, the most interesting facet of this process is demonstrating whether or not the ORM is capable of handling the data stored in the tables and maintaining the relationships that exist between the involved entities.

Well, the following script hopefully will make your doubts disappear: 

<?php

use \Blog\Library\Loader\Autoloader as Autoloader,
    \Blog\Service\ServiceLocator as ServiceLocator,
    \Blog\Injector\EntryServiceInjector as EntryServiceInjector,
    \Blog\Model\Entry as Entry;

// include the autoloader
require_once __DIR__ . '/Library/Loader/Autoloader.php';
$autoloader = new Autoloader;

// create the service locator
$serviceLocator = new ServiceLocator;

// add the entry service injector to the service locator
$serviceLocator->addInjector('entry', new EntryServiceInjector);

// get the entry service via the associated service injector
$entryService = $serviceLocator->getService('entry');

// display all the entries along with their associated comments (comments are lazy-loaded from the storage)
$entries = $entryService->find();
foreach ($entries as $entry) {
    echo '<h2>' . $entry->title . '</h2>';
    echo '<p>' . $entry->content . '</p>';
    foreach ($entry->comments as $comment) {
        echo '<p>' . $comment->content . ' ' . $comment->author->name . '</p>';
    }
}

// add a new entry to the storage
$entry = new Entry(array(
    'title'   => 'My fourth blog post',
    'content' => 'This is the content of the fourth blog post'
));
$entryService->insert($entry);

// delete an entry from the storage
$entryService->delete(1);

That worked like a charm! As the above code fragment shows, running CRUD operations on the tables created before is a breeze, thanks to the functionality provided by the blog entry service. Moreover, if you test the script on your own machine (provided that you’ve defined the sample tables, too), you should get the following output:

Not too bad at all, right? Not only have the blog entries been properly fetched from the database, but the related comments and authors have been loaded on request. Even though this example is somewhat contrived, it demonstrates that building an extendable, object-oriented ORM in PHP is much simpler than one might think. So, feel free to play with it for a while and tweak it to suit your own requirements.

Final Thoughts

In this tutorial, I showed that the implementation of a customizable ORM using modern development techniques is a fairly straightforward process that can be tackled with minor efforts. As you just saw, exploiting the benefits brought by dependency injection, and the use of the data mapper pattern along with a domain model (even an anemic one) is a powerful combination that allows you to manipulate relationships between entities with relative ease.

Naturally, if your needs are closer to the level of an enterprise, a full-blown ORM like Doctrine can be the best solution. In either case, learning how to create a library like the one above, even in its simplest version, has hopefully been a didactic, and why not, fun experience.

See you in the next PHP tutorial!     



 
 
>>> More MySQL Articles          >>> More By Alejandro Gervasio
 

blog comments powered by Disqus
escort Bursa Bursa escort Antalya eskort
   

MYSQL ARTICLES

- Oracle Unveils MySQL 5.6
- MySQL Vulnerabilities Threaten Databases
- MySQL Cloud Options Expand with Google Cloud...
- MySQL 5.6 Prepped to Handle Demanding Web Use
- ScaleBase Service Virtualizes MySQL Databases
- Oracle Unveils MySQL Conversion Tools
- Akiban Opens Database Software for MySQL Use...
- Oracle Fixes MySQL Bug
- MySQL Databases Vulnerable to Password Hack
- MySQL: Overview of the ALTER TABLE Statement
- MySQL: How to Use the GRANT Statement
- MySQL: Creating, Listing, and Removing Datab...
- MySQL: Create, Show, and Describe Database T...
- MySQL Data and Table Types
- McAfee Releases Audit Plugin for MySQL Users

Developer Shed Affiliates

 


Dev Shed Tutorial Topics: