Building a PHP ORM: Deploying a Blog

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.

Let’s face it: building a full-featured ORM in PHP (or in any other programming language) is a challenging and difficult process that requires careful planning. You’ll need a solid background in some popular data persistence patterns, such as Active Record and Data Mapper. That’s a tall order, especially since this kind of package must be able to handle all of the possible relationships between the objects that compose an application, not to mention the validation that must be performed on them.

Fortunately, there’s a healthy variety of ORMs available that will do the hard work for you. What’s more, Doctrine, RedBeanPHP and Propel (just to name a few) are good examples of well-trusted ORMs that provide a remarkable set of features right out of the box.

While it’s fair to admit that these libraries (and others) boast many happy users in active and vibrant communities, in some use cases, using them may be overkill. Perhaps there are times when you only need to define some common relationships between your domain objects and use some clever approaches (like proxy objects) to load them on request from the database, and nothing else.

If at this moment you’re facing a situation like this, hopefully this tutorial will catch your attention. It will show you in a step-by-step manner how to build a small ORM, which you’ll be able to tweak and customize at will to fit your own requirements. Moreover, if you’ve already read the two installments that precede this one, it’s probable that you’re familiar with the inner workings of this sample ORM. In those chapters I implemented the ORM’s data access and mapping layers, along with a simple domain model.

To be frank, the development of this last tier is entirely optional; however, it’s useful for demonstrating the ORM’s actual functionality in the deployment of a blog program, which naturally will handle some “typical” domain objects, namely blog entries, comments and authors.

With the aforementioned layers ready to be put into action, it’s time to give to the project its final touches. In line with this idea, in this last episode I’ll be adding to the ORM the remaining components that it needs to work as intended. These include a mixture of proxy classes, dependency injection containers and a few basic services.

Want to see how all these pieces can be put to work side by side in a productive way? Then keep reading!

Creating Proxy Classes

In consonance with the concepts deployed in the introduction, it’s necessary to add to the ORM’s existing structure a set of proxy classes. These will be responsible for lazy-loading domain objects from the underlying storage (in this particular case a MySQL database). 

With that said, here’s the definition of these proxies, along with the interface that they implement. Check them out:

(Blog/Model/Proxy/ProxyInterface.php)

<?php

namespace BlogModelProxy;

interface ProxyInterface
{
    public function load();
}

 

(Blog/Model/Proxy/AbstractProxy.php)

<?php

namespace BlogModelProxy;
use BlogModelMapper;

abstract class AbstractProxy
{
    protected $_mapper;
    protected $_params;

    /**
     * Constructor
     */
    public function __construct(MapperAbstractMapper $mapper, $params)
    {
        if (!is_string($params) || empty($params)) {
            throw new InvalidArgumentException(‘The mapper parameters are invalid.’);
        }
        $this->_mapper = $mapper;
        $this->_params = $params;
    }

    /**
     * Get the mapper
     */
    public function getMapper()
    {
        return $this->_mapper;
    }

    /**
     * Get the mapper parameters
     */
    public function getParams()
    {
        return $this->_params;
    }
}

 

(Blog/Model/Proxy/EntityProxy.php)

<?php

namespace BlogModelProxy;
use BlogModel;

class EntityProxy extends AbstractProxy implements ProxyInterface
{
    protected $_entity;

    /**
     * Load an entity via the mapper’s ‘findById()’ method
     */
    public function load()
    {
        if ($this->_entity === null) {
            $this->_entity = $this->_mapper->findById($this->_params);
            if (!$this->_entity instanceof ModelAbstractEntity) {
                throw new RuntimeException(‘Unable to load the related entity.’);
            }
        }
        return $this->_entity;
    }
}

 

(Blog/Model/Proxy/CollectionProxy.php)

<?php

namespace BlogModelProxy;
use BlogModelCollection;

class CollectionProxy extends AbstractProxy implements ProxyInterface, Countable, IteratorAggregate
{
    protected $_collection;

    /**
     * Load the entity collection via the mapper’s ‘find()’ method
     */
    public function load()
    {
        if ($this->_collection === null) {
            $this->_collection = $this->_mapper->find($this->_params);
            if (!$this->_collection instanceof CollectionEntityCollection) {
                throw new RuntimeException(‘Unable to load the related collection.’);
            }
        }
        return $this->_collection;
    }

    /**
     * Count the number of elements in the collection
     */
    public function count()
    {
        return count($this->load());
    }

    /**
     * Load the entity collection when the proxy is used in a ‘foreach’ construct
     */
    public function getIterator()
    {
        return $this->load();
    }
}

From the above code snippet, it’s clear to see how the aforementioned proxy classes do their thing. Simply put, they act like “stand-in” elements, which use the functionality of an injected mapper (notice the implementation of the constructor in the abstract proxy) to fetch on request from the database either a single entity or a collection of them.

Although the use of proxies might seem somewhat confusing and even irrelevant at this point, don’t worry. When I show you how to retrieve from storage some blog entries and lazy-load the related comments and authors, you’ll realize how useful they are in the implementation of more efficient model persistence strategies.

So far, so good. Having shown you the previous proxy classes, this sample ORM is now a bit more functional. However, there’s no need to rush, as there are still some additional components that must be constructed to improve the project’s overall efficiency.

Object instantiation should be separated as much as possible from application logic. To achieve this goal, in the next section I’ll be defining a couple of dependency injection containers, which will neatly hide from client code the creation of some complex object graphs.    

To see how this will be done, click on the link below and read the lines to come.

{mospagebreak title=Building Dependency Injection Containers and Service Locators}

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 BlogInjector;

interface InjectorInterface
{
    public function create();
}

 

(Blog/Injector/MysqlAdapterInjector.php)

<?php

namespace BlogInjector;
use BlogLibraryDatabase;

class MysqlAdapterInjector implements InjectorInterface
{
    protected static $_mysqlAdapter;

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

 

(Blog/Injector/EntryServiceInjector.php)

<?php

namespace BlogInjector;
use BlogLibraryDatabase,
    BlogModelMapper,
    BlogService;

class EntryServiceInjector implements InjectorInterface
{
    /**
     * Create the entry service
     */
    public function create()
    {
        $mysqlInjector = new MysqlAdapterInjector;
        $mysqlAdapter = $mysqlInjector->create();
        return new ServiceEntryService(
            new MapperEntryMapper(
                $mysqlAdapter, new MapperCommentMapper(
                    $mysqlAdapter, new MapperAuthorMapper($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 BlogService;
use BlogModelMapper,
    BlogModel;

abstract class AbstractService
{
    protected $_mapper;

    /**
     * Constructor
     */
    public function __construct(MapperAbstractMapper $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 BlogService;
use BlogModelMapper,
    BlogModel;

class EntryService extends AbstractService
{
    /**
     * Constructor
     */
    public function __construct(MapperEntryMapper $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 BlogService;
use BlogInjector;

class ServiceLocator
{
    protected $_injectors = array();
    protected $_services = array();
   
    /**
     * Add a single injector
     */
    public function addInjector($key, InjectorInjectorInterface $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 BlogLibraryLoaderAutoloader as Autoloader,
    BlogServiceServiceLocator as ServiceLocator,
    BlogInjectorEntryServiceInjector as EntryServiceInjector,
    BlogModelEntry 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!     

[gp-comments width="770" linklove="off" ]

antalya escort bayan antalya escort bayan Antalya escort diyarbakir escort