Articles

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


PHP Applications with a Persistence Layer, Dependency Injection, and Aspect Oriented Programming

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 "Creating isolated environments for PHP applications with PEAR dependencies 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 the Asterisk PBX in "Create VoIP applications for Asterisk using PHP, PAMI, and Ding".

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.

Obtaining a tiered architecture in your PHP Applications

  • 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":

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:

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):

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):

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.

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:

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:

Running your PHP Applications

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 your Application

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

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

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/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 of the Dependency Injection Container

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:

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:

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:

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:

Running the Doctrine CLI (your Persistence Layer)

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:

Running the example