Silex + Doctrine 2 ORM

Silex is great for its simplicity, however for larger projects it's probably better to use some ORM like Doctrine but there's build-in service provider only for Doctrine DBAL and not ORM.

Fortunately, implementing Doctrine 2 ORM is not that hard.

Update (Sep 27th 2012): Switched to composer and to a different ORM provider.

1. Doctrine 2 ORM Service Provider

Note: I rewrote this tutorial to be based only on composer. I think it's the easiest way to handle dependecies and it's much easier to use than cloning each repo with git or downloading libraries on your own.

Clone (or download) Doctrine 2 ORM and service provider and register them:. The old provider is not maintained any more so I recommend this forked version.

Add dependency to your composer.json. Actually, taluu/doctrine-orm-provider (Doctrine 2 ORM Service Provider) already defines dependencies on Doctrine so you don't have to define them again.

{
    "require": {
        "silex/silex": "1.0.*",
        "taluu/doctrine-orm-provider": "1.0.*"
    },
    "minimum-stability": "dev"
}

Let composer download all libraries and generate autoload.php.

composer.phar install

Then register ORM provider for Silex:

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
use \Doctrine\Common\Cache\ApcCache;
use \Doctrine\Common\Cache\ArrayCache;

// Register Doctrine DBAL
$app->register(new Silex\Provider\DoctrineServiceProvider(), array(
    // Doctrine DBAL settings goes here
));

// Register Doctrine ORM
$app->register(new Nutwerk\Provider\DoctrineORMServiceProvider(), array(
    'db.orm.proxies_dir'           => __DIR__ . '/cache/doctrine/proxy',
    'db.orm.proxies_namespace'     => 'DoctrineProxy',
    'db.orm.cache'                 => 
        !$app['debug'] && extension_loaded('apc') ? new ApcCache() : new ArrayCache(),
    'db.orm.auto_generate_proxies' => true,
    'db.orm.entities'              => array(array(
        'type'      => 'annotation',       // entity definition 
        'path'      => __DIR__ . '/src',   // path to your entity classes
        'namespace' => 'MyWebsite\Entity', // your classes namespace
    )),
));

I think it's more or less obvious what each parameter means. Just make sure you set db.orm.cache properly. $app['debug'] variable means if you're in development or production environment. During development it's better not to use any caching mechanism, while in production it tries to check whether you have APC module enabled.

2. Create your entity classes

For example, create an entity Article in src/MyWebsite/Entity.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
<?php
// src/MyWebsite/Entity/Article.php
namespace MyWebsite\Entity;

/**
 * @Entity
 */
class Article {

    /** 
     * @Id @Column(type="integer")
     * @GeneratedValue
     */
    private $id;

    /** @Column(type="text") */
    private $content;
}

3. Mind Doctrine DBAL and Doctrine ORM versions

If you're using composer to handle all dependencies you don't need to care about this. Composer does this for you.

One a bit confusing thing is that Doctrine ORM service provider uses Doctrine DBAL connection $app['db']. You probably already have cloned (or downloaded) doctrine-dbal and doctrine-common repos, but doctrine-orm has its own doctrine-dbal and doctrine-common in doctrine2-orm/lib/vendor and here comes the problem.

Doctrine ORM thinks your $app['db'] is an instance from doctrine2-orm/lib/vendor/doctrine-dbal/lib/Doctrine/DBAL/Connection.php. Everything is fine if you have the same version of Doctrine DBAL in vendor/doctrine-dbal as Doctrine ORM expects, but it's easier to have just one copy of all Doctrine libraries.

Therefore, I think it's better to delete both doctrine-dbal and doctrine-common from your vendor directory and use only those cloned as submodule in doctrine-orm.

Because of this, change Doctrine DBAL and Doctrine Common class paths:

4. Doctrine CLI

Important part of Doctrine is its command line interface. I'm not sure if there is some best practice for Doctrine CLI but to use it you need just two files from vendor/doctrine/orm/tools/sandbox.
Copy doctrine.php and cli-config.php to /path/to/my/project/cli and in both of them change all paths for ClassLoader to correspond to your directory structure and then in cli-config.php configure your database settings.

I haven't tried it yet, but maybe you can just include your vendor/autoload.php and then remove all the $classLoader mess.

Now when you call from cli directory php doctrine.php you should see some help generated by Doctrine.

To generate entity properties and setter/getter methods call:
php doctrine.php orm:generate-entities ../src

5. Working with Doctrine ORM

If everything works fine, you should be able to call standard Doctrine EntityManager in $app['db.orm.em'] and do everything you want like persisting:

 1 
 2 
 3 
 4 
$article = new MyWebsite\Entity\Article();
$article->setContent('Hello world!');
$app['db.orm.em']->persist($article);
$app['db.orm.em']->flush();

or querying:

 1 
$app['db.orm.em']->createQuery('SELECT a FROM MyWebsite\Entity\Article a');

Just don't forget, Doctrine ORM should use caching in production environment and when you modify table structure you have to clear the APC cache or it won't take any effect.

Well... it's a bit long

I haven't find any shorter solution. If you were more successful than me, please, leave me a comment :).

I think with composer everything is much easier because you don't have to worry about different versions of Doctrine's and yours components and still if there's a conflict, composer will rise an error that something is probably wrong.

blog comments powered by Disqus