Articles

Writing PHP applications with Doctrine2 as ORM and Ding as DI container


Introduction

This article will show how we can develop software in php with a nifty design and architecture, and very much like other languages like java, using an ORM and an AOP, DI, Events container. I will assume you've read (or at least took a quick look) at this article that explains the tree layout used throughout the code, and that you have some basic knowledge of Doctrine2 and used it before on your own.

You can find a similar article with a telephony application example for asterisk here.

Doctrine2 has proven to be a great thing to have around for your applications. I'd like to show how Ding can also be one of those good things to use in your projects. So in this example, we'll use Doctrine2 as the orm of a sample application, and use Ding as a container to provide the glue (events, aspect oriented programming, dependency injection, and inversion of control) throughout all of the code.

You can find the complete source code for this article at GitHub.

I'll try to go step by step. Please bare with me as I go along the article trying to explain what is going on. Hopefully, the pieces will fit together as you go down the article. It's recommended that you read this if you dont know how to download and install ding.

First, the result

  • A software architecture based on PHP beans, leading to very decoupled code, orchestrated by the container (ding).
  • Lots of boiler plate procedural code transformed into declarative code, in the bean definitions (like doctrine setup and aquirement of needed dependencies)
  • Our resulting application will have 1 entity, the User.
  • A "User" domain service, that can return a User given an id, and also can create a "User" with a given username and password.
  • We will use an event driven (let's call our events, "domain events"), approach to extend our application, in this case we will create a domain event "newUserCreated", dispatched for each "User" created in the system.
  • A listener of "newUserCreated" that will log the username created via debug level.
  • We will apply AOP to create 2 aspects in the system, a "Profiler" aspect that can keep track of the time spent in each domain service, and a "Transactional" aspect, that will call beginTransaction()-flush()-commit()-rollback() on the doctrine entity manager in nested domain services calls, so our domain services wont have to.

A sample log of the creation of the username "john":

$userDomainService->createUser('john', 'pass');
$user = $userDomainService->getById(1);
| DEBUG | Execution of Domain\Service\AbstractService::setLogger took: 0.00006
| DEBUG | Execution of Domain\Service\User::setContainer took: 0.00005
| DEBUG | Serving for: Domain\Service\User::createUser
| DEBUG | Beginning transaction
| DEBUG | Dispatching event: newUserCreated
| DEBUG | New user: john
| DEBUG | Commiting transaction
| DEBUG | Execution of Domain\Service\User::createUser took: 0.01331
| DEBUG | Serving for: Domain\Service\User::getById
| DEBUG | Beginning transaction
| DEBUG | Commiting transaction
| DEBUG | Execution of Domain\Service\User::getById took: 0.00467
| DEBUG | Dispatching event: dingShutdown

You can see the "Transactional" aspect managing the per-domain-service-method-call transactions. Also, you can spot 2 transactions being made: one is for the call to createUser, and the other one is for the call to getById (we can see that because of the "Profiler" aspect accounting the time spent in each one of the calls). These calls are not nested, they are issued per separate.

Also, notice the "New user: john" message, logged by the event listener for the event "newUserCreated" after the container logged a debug message when dispatching the event.

The code

Let's take a quick review of the resuling code first, the code is kind of self explanatory and will let you discover "the magic" behind it before I get into any gory details. If I can still have you interested after that, I'll show how to setup the container and the orm later :)

The User entity can be something standard, you can find it here.

Our beans

So whenever a User is persist()ed, we would have this bean listening for the domain event "newUserCreated". We declare beans with annotations like @Component, @Aspect, @Configuration, @Controller, etc:

namespace Listeners;

use Ding\Logger\ILoggerAware;

/**
 * @Component
 * @ListensOn(value=newUserCreated)
 */
class NewUser implements ILoggerAware
{
    /**
     * @var \Logger
     */
    private $_logger;

   /**
     * Called by the container.
     *
     * @param string $name The created username
     *
     * @return void
     */
    public function onNewUserCreated($name)
    {
        $this->_logger->debug("New user: $name");
    }

    public function setLogger(\Logger $logger)
    {
        $this->_logger = $logger;
    }

}

Another bean: the user domain service, that will create User entities, persist them, and dispatch the "newUserCreated" event. This one is also a @Component, but it specifies a name for itself, "userDomainService" (note the @Resource annotation, that wires-by-name the indicated dependency):

namespace Domain\Service;

use Domain\Entity\User as UserEntity;
use Ding\Container\IContainerAware;
use Ding\Container\IContainer;

/**
 * @Component(name="userDomainService")
 */
class User extends AbstractService implements IContainerAware
{
    /**
     * @Resource
     * @var \Domain\Repository\User
     */
    protected $userRepository;

    /**
     * @var \Ding\Container\IContainer
     */
    private $_container;

    public function createUser($username, $password)
    {
        $user = new UserEntity($username, $password);
        $this->entityManager->persist($user);
        $this->_container->eventDispatch('newUserCreated', $username);
        return $user;
    }

    public function getById($id)
    {
        return $this->userRepository->find($id);
    }

    public function setContainer(IContainer $container)
    {
        $this->_container = $container;
    }
}

Components can extend other components, so the bean definitions can extend other bean definitions. The parent class for all domain services will ensure that every domain service has a logger and the "entityManager" always injected (note again the @Resource annotation, that wires-by-name the indicated dependency):

namespace Domain\Service;

use Ding\Logger\ILoggerAware;

/**
 * @Component
 */
class AbstractService implements ILoggerAware
{
    /**
     * @Resource
     * @var \Doctrine\ORM\EntityManager
     */
    protected $entityManager;

    /**
     * @var \Logger
     */
    protected $logger;

    public function setLogger(\Logger $logger)
    {
        $this->logger = $logger;
    }
}

A programatically bean definition for the repository. All components can be a source for bean definitions. @Configuration annotated classes are specifically used for this purpose. It will be injected with the entityManager itself. Each @Bean annotated method is a bean whose name is the method's name.

namespace Domain\Repository;

/**
 * @Configuration
 */
class Configuration
{
    /**
     * @Resource
     * @var \Doctrine\ORM\EntityRepository
     */
    protected $entityManager;

    protected function getRepo($entityName)
    {
        return $this->entityManager
               ->getRepository("\\Domain\Entity\\$entityName");
    }

    /** @Bean */
    public function userRepository()
    {
        return $this->getRepo('User');
    }
}

The method "userRepository" is a bean definition that will be injected in the user domain service, in the "userRepository" property (because of the @Resource annotation that wires by name). You can find the code for the repository here.

The Profiler aspect mentioned above, just another component (bean). Notice the @Aspect and @MethodInterceptor annotations:

namespace Aspect;

use Ding\Aspect\MethodInvocation;
use Ding\Logger\ILoggerAware;

/**
 * @Aspect
 */
class Profiler implements ILoggerAware
{
    protected $logger;

    public function setLogger(\Logger $logger)
    {
        $this->logger = $logger;
    }

    /**
     * @MethodInterceptor(class-expression=.*Service.*,expression=.*)
     */
    public function profileDomainServices(MethodInvocation $invocation)
    {
        $time = microtime(true);
        $originalInvocation = $invocation->getOriginalInvocation();
        $ret = $invocation->proceed();
        $total = microtime(true) - $time;
        $this->logger->debug(
            "Execution of "
            . $originalInvocation->getClass()
            . "::" . $originalInvocation->getMethod()
            . " took: " . sprintf('%.5f', $total)
        );
        return $ret;
    }
}

And last but not least, the "Transactional" aspect, that will call flush() in the entityManager to commit changes to DB, mantaining transactions when having nested domain services calls. To do it, it will count the number of nested calls, on the first one, the transaction will be started. The counter will decrease with every return'ing function, so when this counter goes to 1 again, the transactions is commited:

namespace Aspect;

use Ding\Aspect\MethodInvocation;
use Ding\Logger\ILoggerAware;

/**
 * @Aspect
 */
class Transactional implements ILoggerAware
{
    /**
     * @Resource
     * @var \Doctrine\ORM\EntityRepository
     */
    protected $entityManager;
    protected static $count = 0;
    protected $logger;

    public function setLogger(\Logger $logger)
    {
        $this->logger = $logger;
    }

    /**
     * @MethodInterceptor(class-expression=AbstractService,expression=.*)
     */
    public function manageTransaction(MethodInvocation $methodInvocation)
    {
        $method = $methodInvocation->getMethod();
        $class = $methodInvocation->getClass();
        if ((strncmp($method, '_', 1) === 0) || (strncmp($method, 'set', 3) === 0)) {
            return $methodInvocation->proceed();
        }
        $this->logger->debug("Serving for: $class::$method");
        self::$count++;
        try
        {
            if (self::$count === 1) {
                $this->logger->debug('Beginning transaction');
                $this->entityManager->beginTransaction();
            }
            $result = $methodInvocation->proceed();
            if (self::$count === 1) {
                $this->logger->debug('Commiting transaction');
                $this->entityManager->flush();
                $this->entityManager->commit();
            }
            self::$count--;
            return $result;
        } catch(\Exception $exception) {
            if (self::$count === 1) {
                $this->logger->error(
                    'Exception: '
                    . $exception->getMessage()
                    . ' occurred, rollbacking transaction'
                );
                $this->entityManager->rollback();
            }
            self::$count--;
            throw $exception;
        }
    }
}

Conclusions

Well, that really is all. Just wanted to make a point on how we can actually do better applications using PHP. As you can see, the container handles everything needed to glue the application together, and every part of the system can do just what it needs to do, leading to more concise software and better practices over time. Of course this is a trivial example, but it can be applied without effort to more complex software.

You can get the complete source code here. What follows, is an overview of how to structure this kind of applications, and where everything goes. The config details and bootstrapping stuff, and basically, how everything works. I hope you liked it and that you're still interested in continue your reading :)

Setting up

So we'll start with the very same application mentioned earlier, here.

Our config/setup/dependencies file will need to look like:

pear.doctrine-project.org/DoctrineORM-2.1.0
pear.marcelog.name/Ding-1.4.2
pear.apache.org/log4php/Apache_log4php-2.1.0

Log4php is needed by Ding, it will be used to log messages in your application and the container itself.

Also, let's add a new directory to the tree layout, the runtime directory:

|~runtime/
| |~cache/
| | |~ding/
| | `~doctrine/
| |   |~hydrators/
| |   `~proxies/
| |~db/
| `~log/

runtime/log will of course hold the log files. runtime/db will hold the database (I've used sqlite3 in this particular application).

runtime/cache will hold all the files that ding and doctrine might need to generate (also, we could store our generated repositories and entities).

The bootstrap

Now we have to bootstrap the container for our application, this of course will be done in the bin/bootstrap.php file of the sample tree layout. The comments will explain what is going on:

// Establish absolute application root path
$myDir = realpath(__DIR__ . '/..');
define('ROOT_APPLICATION_PATH', $myDir);
$confDir = $myDir . '/config';
$vendorsDir = $myDir . '/vendor';
$srcDir = $myDir . '/src/php';

// The ding autoloader needs to be registered. Any
// PSR-0 compliant autoloader will also do fine.
require_once 'Ding/Autoloader/Autoloader.php';
Ding\Autoloader\Autoloader::register();
use Ding\Container\Impl\ContainerImpl as DingContainer;

// We will be using annotations throughout all of
// the code, so let's tell ding to traverse and discover
// the annotations in these directories.
$annotationDirectories = array(
    $srcDir . '/Listeners',
    $srcDir . '/Domain/Service',
    $srcDir . '/Domain/Repository',
    $srcDir . '/Aspect'
);

/*
 * Container options:
 *   - The log4php configuration file will
 *     be config/application.properties
 *   - Dont use any cache (you should have cache for production
 *     environments).
 *   - The container will have an additional properties file,
 *     specified also as config/application.properties
 *   - Bean providers: Not only we will use annotations in the
 *     classes "living" in the above specified directories, but also
 *     xml definitions residing in config/support/beans.xml.
 *   - Also, a single property is injected in the container, the
 *     'config.dir' property will have the absolute path to our /config
 *     directory.
 */
$options = array('ding' => array(
    'log4php.properties'
        => $confDir . '/application.properties',
    'cache' => array(
        'aspect' => array('impl' => 'dummy'),
        'annotations' => array('impl' => 'dummy'),
        'bdef' => array('impl' => 'dummy'),
        'autoloader' => array('impl' => 'dummy'),
        'proxy' => array('impl' => 'dummy')
    ),
    'factory' => array(
        'properties' => array('config.dir' => $confDir),
        'bdef' => array(
            'xml' => array(
                'filename' => 'beans.xml',
                'directories' => array($confDir . '/support')
            ),
            'annotation' => array(
                'scanDir' => $annotationDirectories
            )
        )
    )
));
// Get the container instance.
$container = DingContainer::getInstance($options);

Application Configuration

So I've mentioned a config/application.properties file, used to configure the application. Let's take a look at it, it should be self explanatory:

; ROOT_APPLICATION_PATH is defined in bootstrap.php
; Database (doctrine2) configuration
doctrine.db[driver]=pdo_sqlite
doctrine.db[path]=ROOT_APPLICATION_PATH "/runtime/db/db.sqlite"
doctrine.db[charset]=UTF8
doctrine.proxy.dir=ROOT_APPLICATION_PATH "/runtime/cache/doctrine/proxies"
doctrine.proxy.namespace="Proxies"
doctrine.hydrator.dir=ROOT_APPLICATION_PATH "/runtime/cache/doctrine/hydrators"
doctrine.hydrator.namespace="Hydrators"
doctrine.entity.path=ROOT_APPLICATION_PATH "/src/php/Domain/Entity"
doctrine.cache.query="Doctrine\Common\Cache\ApcCache"
doctrine.cache.metadata="Doctrine\Common\Cache\ApcCache"

; Logging (log4php) configuration
log4php.appender.default = LoggerAppenderDailyFile
log4php.appender.default.layout = LoggerLayoutPattern
log4php.appender.default.layout.ConversionPattern = "%d{ISO8601} | %p | %m%n"
log4php.appender.default.file = ROOT_APPLICATION_PATH "/runtime/log/log.log"
log4php.rootLogger = DEBUG, default

; PHP options that will override php.ini stuff. Ding will autoconfigure these.
php.error_reporting=E_ALL
php.display_errors=1
php.date.timezone=America/Buenos_Aires

Ding and Doctrine Configuration

So how do these properties end up configuring everything? This is where the container comes into action. Remember we specified that we're going to be using a file, config/support/beans.xml as a bean provider. Let's take a look at that file:

<?xml version="1.0" encoding="UTF-8"?>
<beans>
  <!-- The Doctrine2 configuration is a separated xml file -->
  <import resource="doctrine.xml"/>

  <!-- The PropertiesHolder bean is what makes it possible for the
    container to read properties from a standard php.ini file, so we
    can use them in other bean configurations.  -->
  <bean id="PropertiesHolder" class="Ding\Helpers\Properties\PropertiesHelper">
    <property name="locations">
      <array>
        <entry><value>
            resource://file://${config.dir}/application.properties
        </value></entry>
      </array>
    </property>
  </bean>
</beans>

Let's now take a look at the config/support/doctrine.xml file. This will configure doctrine not programatically but as a series of bean definitions. This is the equivalent of the this code.Everything like ${..} is replaced by the container with the appropiate values coming in from the config/application.properties file:

<?xml version="1.0" encoding="UTF-8"?>
<beans>
  <!--
    The entityManager will be used in our domain service to persist
    and work with the entities.
    -->
  <bean id="entityManager"
    class="Doctrine\ORM\EntityManager"
    factory-method="create">
    <constructor-arg><value>${doctrine.db}</value></constructor-arg>
    <constructor-arg><ref bean="doctrine-config" /></constructor-arg>
    <constructor-arg><ref bean="eventManager" /></constructor-arg>
  </bean>

  <bean id="eventManager" class="Doctrine\Common\EventManager"/>

  <bean id="doctrine-config" class="\Doctrine\ORM\Configuration">
    <property name="metaDataCacheImpl">
      <ref bean="doctrine-metadata-cache"/>
    </property>
    <property name="metadataDriverImpl">
      <ref bean="doctrine-metadata-driver"/>
    </property>
    <property name="queryCacheImpl">
      <ref bean="doctrine-query-cache"/>
    </property>
    <property name="proxyDir">
      <value>${doctrine.proxy.dir}</value>
    </property>
    <property name="proxyNamespace">
      <value>${doctrine.proxy.namespace}</value>
    </property>
    <!-- Optional sql logger -->
    <!--
      <property name="sqlLogger">
        <ref bean="sqlLogger"/>
      </property>
    -->
  </bean>

  <!-- Optional sql logger -->
  <!--
    <bean id="sqlLogger"
    class="\Doctrine\DBAL\Logging\EchoSQLLogger"/>
  -->

  <bean id="doctrine-metadata-cache"
    class="${doctrine.cache.query}"/>

  <bean id="doctrine-query-cache"
    class="${doctrine.cache.metadata}"/>

  <bean id="doctrine-config-dummy"
    class="\Doctrine\ORM\Configuration"/>

  <bean id="doctrine-metadata-driver"
    factory-bean="doctrine-config-dummy"
    factory-method="newDefaultAnnotationDriver">
    <constructor-arg>
      <array>
        <entry><value>
          ${doctrine.entity.path}
        </value></entry>
      </array>
    </constructor-arg>
  </bean>
</beans>

Running the doctrine cli

Since our application has its own isolated environment, we need our own shell script to call the right doctrine cli (the one in our vendors directory). So we run bin/doctrine.sh:

marcelog@host ~/tmp/test $ bin/doctrine.sh orm:schema-tool:create
ATTENTION: This operation should not be executed in a production environment.

Creating database schema...
Database schema created successfully!
marcelog@host ~/tmp/test $ bin/doctrine.sh orm:validate-schema
[Mapping]  OK - The mapping files are correct.
[Database] OK - The database schema is in sync with the mapping files.

Running the example

marcelog@host ~/tmp/test $ bin/run.sh createUser john doe
[User: 1: john ]