Joomla is a decent CMS with very nice features. It works great for the end user and has many components ready to use from the online world. What I don’t like personally, as a developer, is the model implementation in Joomla. For me the way ‘ model and table’ classes are implemented, just doesn’t feel right to me. Also it is very difficult to get other models in a controller or an other model class.
With Symfony I have worked with Doctrine regularly and in this blog I’ll show you how to use Doctrine for your own component in Joomla. Doctrine is an Object Relational Mapping framework and offers you a persistence library. This not the holy grail and you should determine if you need the extra overhead and if you are comfortable with it.
The first step
I’ll start with an clean Joomla 1.6 install from the Joomla website. Also I installed Doctrine ORM from the Doctrine website. I unpacked the archive in a new folder in the Joomla folder libraries.The structure in this new folder would be the same as the archive, so you get a Doctrine folder and a bin folder in the libraries/doctrine folder.
By the way, we live in 2011 and the current release of PHP is 5.3.5. Doctrine and the implementation I show you here assumes you use PHP 5.3. If you still use PHP 5.2 you seriously need to work on the upgrade of that projects.
The example
I’m going to provide you a very simple example. The most important thing I want to show you is how to implement Doctrine in your Joomla project, not how to use Doctrine. The documentation on the Doctrine website is of high quality and should give you enough information to get going.
We are writing a new component called Bugs end hold one entity called ‘Bug’. It has five attributes. ID, title, description, notificationDate and solvedDate. In the controller I’ll show you some examples how to use it.
Setting up the defaults
It is possible that you want to use Doctrine on multiple components you are going to write. This is the reason why I choose to add Doctrine to the libraries folder. In this folder we are going to add two files. One is an interface to provide a generic interface, the other is a bootstrapper to get Doctrine going. First the easy stuff, the interface.
The interface looks like this:
<?php interface JoomlaDoctrineController { public function setEntityManager(Doctrine\ORM\EntityManager $entityManager); } ?>
Nothing spectacular here, lets move on.
Doctrine needs to have some basic configuration to get going. We need to tell Doctrine where it can find the entities and where it needs to place the generated proxies. Here is the implementation of JoomlaDoctrineBootstrapper:
<?php /** * Configuration class to integrate Doctrine into Joomla. * * @author pderaaij <paul@paulderaaij.nl> */ use Doctrine\Common\ClassLoader, Doctrine\ORM\EntityManager, Doctrine\ORM\Configuration, Doctrine\Common\Cache\ArrayCache; if( !class_exists('\Doctrine\Common\Classloader')) { require_once dirname(__FILE__) . '/../doctrine/Doctrine/Common/ClassLoader.php'; } class JoomlaDoctrineBootstrapper { const APP_MODE_DEVELOPMENT = 1; const APP_MODE_PRODUCTION = 2; private $applicationMode; private $cache; private $entityLibrary; private $proxyLibrary; private $proxyNamespace; private $entityManager; private $connectionOptions; public function __construct($applicationMode) { $this->applicationMode = $applicationMode; } public function getConnectionOptions() { return $this->connectionOptions; } public function setConnectionOptions($connectionOptions) { $this->connectionOptions = $connectionOptions; } public function getProxyLibrary() { return $this->proxyLibrary; } public function setProxyLibrary($proxyLibrary) { $this->proxyLibrary = $proxyLibrary; } public function getProxyNamespace() { return $this->proxyNamespace; } public function setProxyNamespace($proxyNamespace) { $this->proxyNamespace = $proxyNamespace; } public function getCache() { return $this->cache; } public function setCache($cache) { $this->cache = $cache; } public function getEntityLibrary() { return $this->entityLibrary; } public function setEntityLibrary($entityLibrary) { $this->entityLibrary = $entityLibrary; } public function getApplicationMode() { return $this->applicationMode; } public function setApplicationMode($applicationMode) { $this->applicationMode = $applicationMode; } public function getEntityManager() { return $this->entityManager; } public function setEntityManager($entityManager) { $this->entityManager = $entityManager; } /** * Bootstrap Doctrine, setting the libraries and namespaces and creating * the entitymanager */ public function bootstrap() { $this->registerClassLoader(); // Load cache if ($this->getApplicationMode() == self::APP_MODE_DEVELOPMENT) { $this->cache = new ArrayCache; } else { $this->cache = new ApcCache; } /** @var $config Doctrine\ORM\Configuration */ $config = new Configuration; $config->setMetadataCacheImpl($this->cache); $driverImpl = $config->newDefaultAnnotationDriver($this->getEntityLibrary()); $config->setMetadataDriverImpl($driverImpl); $config->setQueryCacheImpl($this->cache); $config->setProxyDir($this->getProxyLibrary()); $config->setProxyNamespace($this->getProxyNamespace()); if ($this->applicationMode == self::APP_MODE_DEVELOPMENT) { $config->setAutoGenerateProxyClasses(true); } else { $config->setAutoGenerateProxyClasses(false); } $this->entityManager = EntityManager::create($this->getConnectionOptions(), $config); } /** * Register the different classloaders for each type. */ private function registerClassLoader() { // Autoloader for all the Doctrine library files $classLoader = new ClassLoader('Doctrine', dirname(__FILE__) . '/'); $classLoader->register(); // Autoloader for all Entities $modelLoader = new ClassLoader('Entities', $this->getEntityLibrary()); $modelLoader->register(); // Autoloader for all Proxies $proxiesClassLoader = new ClassLoader('Proxies', $this->getProxyLibrary()); $proxiesClassLoader->register(); } } ?>
Creating the component
Now we can start with setting up our component. Create a com_bugs folder in components with a models folder in it. In the models folder you should create an Entities folder (mind the capital, it’s important!).
Create the ‘Bug’ entity next. Copy the text below and place it in Bug.php in the models/Entities folder. Do not forget to set the namespace it is essential. If you forget it the entity can’t be found by the classloader. This is also the reason why the capitalization is so important.
<?php namespace Entities; /** * @Entity @Table(name="bugs") */ class Bug { /** @Id @Column(type="integer") @GeneratedValue */ private $id; /** @Column(type="string") */ private $title; /** @Column(type="string") */ private $description; /** @Column(type="datetime") */ private $notificationDate; /** @Column(type="datetime") */ private $solvedDate; public function getId() { return $this->id; } public function setId($id) { $this->id = $id; } public function getTitle() { return $this->title; } public function setTitle($title) { $this->title = $title; } public function getDescription() { return $this->description; } public function setDescription($description) { $this->description = $description; } public function getNotificationDate() { return $this->notificationDate; } public function setNotificationDate($notificationDate) { $this->notificationDate = $notificationDate; } public function getSolvedDate() { return $this->solvedDate; } public function setSolvedDate($solvedDate) { $this->solvedDate = $solvedDate; } } ?>
There is our entity! Now we need a way to get that entity definition in our database. Doctrine has command-line tools for us to do that, but they need the same configuration as we give to our controller in the front-end. So, lets set-up that configuration and use it on the command line.
The configuration
In order to use Doctrine within our controllers we have to create the configuration and use our created bootstrapper to get Doctrine going. At the moment I chose the component initialization file for our initialization work on Doctrine. I’m looking for a way to extract this initialization in to a nicer place, but the options I’ve tried didn’t made me happy at all.
What do we need to do? Well, we need to tell where our entities are stored on the file system and where we want to store our proxy files. also we need to pass our database parameters to Doctrine so it has authorization to the database. Here is my init file (bugs.php) of the component:
<?php // no direct access defined('_JEXEC') or die; // Include dependancies jimport('joomla.application.component.controller'); require_once(JPATH_LIBRARIES . '/doctrine/bootstrap.php'); $controller = JController::getInstance('Bugs'); // configure Doctrine thru the bootstrapper $controller->setEntityManager(bootstrapDoctrine()); $controller->execute(JRequest::getCmd('task', 'index')); $controller->redirect(); /** * Initialize doctrine by setting the entities and proxies locaties. Also define * a default namespace for the proxies. */ function bootstrapDoctrine() { $doctrineProxy = new JoomlaDoctrineBootstrapper( JoomlaDoctrineBootstrapper::APP_MODE_DEVELOPMENT ); $doctrineProxy->setEntityLibrary(dirname(__FILE__) . '/models'); $doctrineProxy->setProxyLibrary(dirname(__FILE__) . '/proxies'); $doctrineProxy->setProxyNamespace('Joomla\Proxies'); $doctrineProxy->setConnectionOptions(getConfigurationOptions()); $doctrineProxy->bootstrap(); return $doctrineProxy->getEntityManager(); } function getConfigurationOptions() { // Define database configuration options $joomlaConfig = JFactory::getConfig(); return array( 'driver' => 'pdo_mysql', 'path' => 'database.mysql', 'dbname' => $joomlaConfig->get('db'), 'user' => $joomlaConfig->get('user'), 'password' => $joomlaConfig->get('password') ); }
What do we do here. We retrieve an instance of our controller and our going to inject an EntityManager to this controller. In order to this we are going to call the JoomlaDoctrineBootstrapper, set the configuration parameters and it will return us the EntityManager. To make the configuration and use a little easier we are using the database credentials from the Joomla configuration.
That’s nice for the front-end, but how about that command line tool? Well Doctrine will search for a file called ‘cli-config.php’ on the location you call the doctrine command line tool. The file ‘cli-config.php’ will return an EntityManager using the configuration we set from our bootstrap file.
Before we start sorting out this file, a little notice. This file is somewhat strange. We have to perform a number of tricks so we can make use of the Joomla framework on the command line. The way we do use led to the fact we need a separate configuration file containing the database credentials and other configuration options. I’m sure that a number of trick I use here aren’t necessary, but I just can’t find a better way to do this. If you know the trick, please let me know. I, for sure, don’t like the way this file has been built up and would love to improve it.
Now with that out, I’ll show you the file.
<?php // We are a valid Joomla entry point. define('_JEXEC', 1); // Setup the path related constants. define('DS', DIRECTORY_SEPARATOR); define('JPATH_BASE', __DIR__ . '/../../'); define('JPATH_ROOT', JPATH_BASE); define('JPATH_CONFIGURATION', JPATH_BASE); define('JPATH_LIBRARIES', JPATH_BASE.DS.'libraries'); define('JPATH_METHODS', JPATH_ROOT.DS.'methods'); // Load the library importer. require_once (JPATH_LIBRARIES.'/joomla/import.php'); require_once (JPATH_CONFIGURATION.'/configuration.php'); // Import library dependencies. jimport('joomla.application.application'); jimport('joomla.utilities.utility'); jimport('joomla.language.language'); jimport('joomla.utilities.string'); jimport('joomla.factory'); require_once __DIR__ . '/../../libraries/doctrine/bootstrap.php'; /** * Initialize doctrine by setting the entities and proxies locaties. Also define * a default namespace for the proxies. */ function bootstrapDoctrine() { $doctrineProxy = new JoomlaDoctrineBootstrapper( JoomlaDoctrineBootstrapper::APP_MODE_DEVELOPMENT ); $doctrineProxy->setEntityLibrary(dirname(__FILE__) . '/models'); $doctrineProxy->setProxyLibrary(dirname(__FILE__) . '/proxies'); $doctrineProxy->setProxyNamespace('Joomla\Proxies'); $doctrineProxy->setConnectionOptions(getConfigurationOptions()); $doctrineProxy->bootstrap(); return $doctrineProxy->getEntityManager(); } function getConfigurationOptions() { // Define database configuration options $joomlaConfig = JFactory::getConfig(JPATH_BASE . '/cli-configuration.php'); return array( 'driver' => 'pdo_mysql', 'path' => 'database.mysql', 'dbname' => $joomlaConfig->get('db'), 'user' => $joomlaConfig->get('user'), 'password' => $joomlaConfig->get('password') ); } $helperSet = new \Symfony\Component\Console\Helper\HelperSet(array( 'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper(bootstrapDoctrine()) ));
The first part of the file is just bootstrapping of the Joomla framework. We determine the location of some core folders, we import core classes and all that will bootstrap the Joomla Framework. Then two function follow that we have seen before. The only difference is that we can’t use the default configuration, which will give us an blank configuration class, but an alternative file. Why not the default file? Well the default file is of the type JConfig and the factory expects an JFrameworkConfig to import.
The bottom three lines are the default code of Doctrine to get the command line tools going. You see, the only thing we do here is setting the EntityManager. More information about this file can be found on the Doctrine website.
The last mile
It has been a long way already, but we are getting there.The last thing I’m going to show you is how to make use of the Doctrine. If you remember we have created an interface earlier in this blog. Just implement this on your controller and implement the setEntityManager() method as required by the interface.
If you look back in the init file of the component we are using this method to set the EntityManager on the controller. We use the interface to make sure we use an generic method on all the controllers. In the example below you see how I created a simple example which makes use of Doctrine and outputs some basic information.
<?php // No direct access defined('_JEXEC') or die; jimport('joomla.application.component.controller'); require_once(JPATH_LIBRARIES . '/doctrine/JoomlaDoctrineController.php'); /** * Description of BugsController * * @author pderaaij */ class BugsController extends JController implements JoomlaDoctrineController { /** * * @var Doctrine\ORM\EntityManager */ private $em; public function setEntityManager(Doctrine\ORM\EntityManager $entityManager) { $this->em = $entityManager; } public function index() { // add a default bug every refresh $newBug = new Entities\Bug(); $newBug->setTitle('Doctrine Test'); $newBug->setDescription('test'); $newBug->setNotificationDate(new DateTime()); $newBug->setSolvedDate(new DateTime()); $this->em->persist($newBug); $this->em->flush(); $dql = "SELECT b FROM Entities\Bug b"; $query = $this->em->createQuery($dql); $query->setMaxResults(30); $bugs = $query->getResult(); echo '<h1>Bug overview</h1>'; echo '<table width="100%">'; echo '<tr>'; echo '<th>#</th>'; echo '<th>Title</th>'; echo '<th>Notification date</th>'; echo '<th>Date solved</th>'; echo '</tr>'; foreach( $bugs as $bug ) { /* @var $bug Bug */ echo '<tr>'; echo '<td>' . $bug->getId() . '</td>'; echo '<td>' . $bug->getTitle() . '</td>'; echo '<td>' . $bug->getNotificationDate()->format("Y-m-d H:i:s") . '</td>'; echo '<td>' . $bug->getSolvedDate()->format("Y-m-d H:i:s") . '</td>'; echo '</tr>'; } echo '</table>'; echo '<h1>Bug #1</h1>'; $bug = $this->em->find("Entities\Bug", 1); echo '<pre>'; var_dump($bug); echo '</pre>'; } } ?>
It’s a wrap
That’s it, now you can make use of Doctrine within your component. It was a long blog post, but I hope I’ve shown you an easy way to get going with Doctrine within Joomla.
I hope you liked this article. You can leave your feedback behind on this blog, since I’m always interested in new insights which I can use to learn and grow as an developer.
I am very interested in getting Doctrine to work in joomla. I followed the instructions on a joomla 1.7 installation on localhost using doctrine 2.1.0 and i can not get it working – i get error when i display com_bugs —- 1056 – An error has occurred.
( one thing i did notice – command line interface file mentioned does not exist on my installation )
Invalid controller: name=”, format=”
If you could give me any asistance to get this working it would be most appreciated.
thanks
Bill
Hi Bill,
I’m working on using Doctine in Joomla (and Nooku) too, so I would like to keep in touch about that very much! I’ve sent you an email with some more ideas.
I’d love to work on this with others: to stimulate and help each other and to exchange information. If you’re interested in cooperation, please send me a mail: herman at yepr.nl
Ciao,
Herman
I’ll try to take a look at it this week. I’m working on my graduation thesis, so time is spare at the moment. But will do my best!
paul
i tested again on a fresh install, and with a little help from Herman, i have got your example working.
Thank you for publishing this article.
Bill
Bill,
Could you send me an email containing the changes for the 1.7 branch? I’ll update my post with the changes and credits for you and Herman.
Thanks in advance
Hi Paul,
Your example works perfectly and without changes in 1.7. Thanks for writing it down; it is a great starter and appetiser. There are however some features in 1.7 that might be useful, like the easier installation of libraries; we can use that to make a nice installable package to use Doctrine in Joomla even easier.
I’m also investigating putting the initialisation code in a system plugin (like how the koowa-plugin is used to install Nooku Framework in Joomla). By default the Entities folder would then be on a level for the whole application and not in the models folder of just one component.
I am also playing around with extending the JController to a DoctrineController (with the setEntityManager()-method implemented in it) and extending all controllers from that Doctrinecontroller instead of JController when using Doctrine. Eventually an Entities folder could be limited to a specific component in there, if the objects should only be available to one component (I’ll first have a look how Symfony does that).
I’ll put it all in one installable package, “Jooctrine” (of course with all credits to you and a link when using code from this article) and send you the results. I allready wanted to have this ready, but well you know: time… I’ll do it the coming days … if enough customers stay on holiday for a little while ;-). I like the Dommelsch (a Dutch beer) advertisement slogan: “you don’t HAVE time, you MAKE it…”
Some next steps I have in mind ater that:
* work out the bugtracker-example from the Doctrine (and Zend) documentation
* work on some objects in the domain of content management (I think that with a good DDD we don’t need no CCKs)
* integrate Doctrine also with Nooku Framework (“Nooctrine”)
First steps first.
O yeah, could be interesting too, when using the Doctrine command line tools: the new CLI-possibilities in the Joomla Platform 11.2. See for instance http://docs.joomla.org/index.php?title=How_to_create_a_stand-alone_application_using_the_Joomla!_Framework&oldid=61249
Havn’t looked at it yet.