If you’re looking for a quick way to get Doctrine 2 running with CodeIgniter 2, you might want to download my CodeIgniter 2/Doctrine 2 package

Overview

CodeIgniter is a great PHP framework. The codebase is clean, the documentation is fantastic, and it’s regularly updated. Doctrine is a good ORM for the same reasons: it’s very well-written, has extensive documentation, and is actively developed. A combination of these two systems makes it easy to build database-oriented web applications quicker than ever before.

To get started, download CodeIgniter 2 and Doctrine 2 – make sure you use the ‘Download Archive’ link when downloading Doctrine.

Setting up CodeIgniter

Put all of the CodeIgniter files into a directory on your web server, and configure it to your liking. If you need a guide on how to do this, the best place to look is the CodeIgniter User Guide. Keep in mind that Doctrine will load the database configuration from CodeIgniter (application/config/database.php).

Setting up Doctrine

Because Doctrine is an entire system in itself, it worked well as a plugin for CodeIgniter 1. Unfortunately one of the major changes to CodeIgniter 2 is the removal of plugins. EllisLab recommends you refactor your plugins as libraries, so that’s exactly what we will do.

Setting up Doctrine as a CodeIgniter library is fairly simple:

  1. Put the Doctrine directory into application/libraries (so that you have a application/libraries/Doctrine directory).
  2. Create the file application/libraries/Doctrine.php. This will be our Doctrine bootstrap as well as the library that CodeIgniter loads.
  3. Copy the following code into Doctrine.php:
<?php

use Doctrine\Common\ClassLoader,
    Doctrine\ORM\Tools\Setup,
    Doctrine\ORM\EntityManager;

class Doctrine
{
    public $em;

    public function __construct()
    {
        require_once __DIR__ . '/Doctrine/ORM/Tools/Setup.php';
        Setup::registerAutoloadDirectory(__DIR__);

        // Load the database configuration from CodeIgniter
        require APPPATH . 'config/database.php';

        $connection_options = array(
            'driver'        => 'pdo_mysql',
            'user'          => $db['default']['username'],
            'password'      => $db['default']['password'],
            'host'          => $db['default']['hostname'],
            'dbname'        => $db['default']['database'],
            'charset'       => $db['default']['char_set'],
            'driverOptions' => array(
                'charset'   => $db['default']['char_set'],
            ),
        );

        // With this configuration, your model files need to be in application/models/Entity
        // e.g. Creating a new Entity\User loads the class from application/models/Entity/User.php
        $models_namespace = 'Entity';
        $models_path = APPPATH . 'models';
        $proxies_dir = APPPATH . 'models/Proxies';
        $metadata_paths = array(APPPATH . 'models');

        // Set $dev_mode to TRUE to disable caching while you develop
        $config = Setup::createAnnotationMetadataConfiguration($metadata_paths, $dev_mode = true, $proxies_dir);
        $this->em = EntityManager::create($connection_options, $config);

        $loader = new ClassLoader($models_namespace, $models_path);
        $loader->register();
    }
}

There are some parts of this file that you might like to modify to suit your needs:

Lines 34-37:

$models_namespace = 'Entity';
$models_path = APPPATH . 'models';
$proxies_dir = APPPATH . 'models/Proxies';
$metadata_paths = array(APPPATH . 'models');

These lines determine where you need to put your model files, and how you instantiate your model classes. With these settings, your models must live in application/models/Entity and be in the Entity namespace. For example instantiating a new Entity\User would load the User class from application/models/Entity/User.php

Line 40

$config = Setup::createAnnotationMetadataConfiguration($metadata_paths, $dev_mode = true, $proxies_dir);

This line uses Doctrine’s Setup class to automatically create the metadata configuration. The Metadata Driver is what Doctrine uses to interpret your models and map them to the database. In this tutorial I have used the Annotations Driver. If you would like to use a different metadata driver, change createAnnotationMetadataConfiguration to one of the methods below:

Notice the $dev_mode variable. This should be true while you are developing, as it does a couple of things:

  1. Automatically generates your proxy classes
  2. Uses ArrayCache; a non-persistent caching class

Likewise, when $dev_mode is false:

  1. Prevents your proxy classes from being overwritten
  2. Attempts to use one of the following persistent caches, in this order: APC, XCache, Memcache (127.0.0.1:11211), and Redis (127.0.0.1:6379)

Advanced Configuration

For more advanced configuration, read the Configuration section of the Doctrine documentation.

Loading Doctrine

Doctrine can now be loaded in the same way as any other CodeIgniter library:

$this->load->library('doctrine');

Once the Doctrine library is loaded, you can retrieve the Entity Manage like so:

$em = $this->doctrine->em;

Defining Models

Building models using the AnnotationDriver is simple. You can build your classes as if they were regular PHP classes, and define the Doctrine metadata in DocBlock annotations.

<?php

namespace Entity;

/**
 * @Entity
 * @Table(name="user")
 */
class User
{
     // ...
}

The @Entity annotation marks this class for object-relational persistence. If the table name is not specified (using the @Table annotation), Doctrine will create a table with the same name as the class.

Annotations that you will probably use frequently are:

For a full list of the available annotations and their uses, see the Annotation Reference

Below are two sample entities that show a basic one-to-many relationship:

<?php

namespace Entity;

use Doctrine\Common\Collections\ArrayCollection;

/**
 * @Entity
 * @Table(name="user")
 */
class User
{
    /**
     * @Id
     * @Column(type="integer", nullable=false)
     * @GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @Column(type="string", length=32, unique=true, nullable=false)
     */
    protected $username;

    /**
     * @Column(type="string", length=64, nullable=false)
     */
    protected $password;

    /**
     * @Column(type="string", length=255, unique=true, nullable=false)
     */
    protected $email;

    /**
     * The @JoinColumn is not necessary in this example. When you do not specify
     * a @JoinColumn annotation, Doctrine will intelligently determine the join
     * column based on the entity class name and primary key.
     *
     * @ManyToOne(targetEntity="Group")
     * @JoinColumn(name="group_id", referencedColumnName="id")
     */
    protected $group;
}

/**
 * @Entity
 * @Table(name="group")
 */
class Group
{
    /**
     * @Id
     * @Column(type="integer", nullable=false)
     * @GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @Column(type="string", length=32, unique=true, nullable=false)
     */
    protected $name;

    /**
     * @OneToMany(targetEntity="User", mappedBy="group")
     */
    protected $users;
}

It is important to note that any mapped properties on your Entities need to be either private or protected, otherwise lazy-loading might not work as expected. This means that it is necessary to use Java-styled getter and setter methods:

public function setUsername($username)
{
    $this->username = $username;
}

public function getUsername()
{
    return $this->username;
}

Thankfully you can save a lot of time and automatically generate these methods using the orm:generate-entities command.

Setting up the Doctrine Console

This step is optional, however the Doctrine Console has some very useful commands so I highly recommend setting it up.

All you need to do is create the file application/doctrine.php and copy the following code into it:

<?php

define('APPPATH', dirname(__FILE__) . '/');
define('BASEPATH', APPPATH . '/../system/');
define('ENVIRONMENT', 'development');

chdir(APPPATH);

require __DIR__ . '/libraries/Doctrine.php';

foreach ($GLOBALS as $helperSetCandidate) {
    if ($helperSetCandidate instanceof \Symfony\Component\Console\Helper\HelperSet) {
        $helperSet = $helperSetCandidate;
        break;
    }
}

$doctrine = new Doctrine;
$em = $doctrine->em;

$helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
    'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($em->getConnection()),
    'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em)
));

\Doctrine\ORM\Tools\Console\ConsoleRunner::run($helperSet);

On a Linux or Mac, you can now run ./application/doctrine from the command line.

If you are on Windows, you can run php.exe application/doctrine.php from the command line. If you are new to running PHP scripts from the command line in Windows, you may want to read PHP’s documentation.

Using the Doctrine Console

If you run the Doctrine console with no arguments, you will be presented with a list of the available commands. For now, we are only interested in the orm:schema-tool commands. If you would like to learn about the other commands you can run a command with the --help flag or read my quick Doctrine console overview.

orm:schema-tool:create will create tables in your database based on your Doctrine models.

orm:schema-tool:drop will do the exact opposite. It is worth noting that this command will only drop tables which have correlating Doctrine models. Any tables that aren’t mapped to a Doctrine model will be left alone.

orm:schema-tool:update will determine if your database schema is out-of-date, and give you the option of updating it. You can execute this command with the --dump-sql flag to see the changes, or the --force flag to execute the changes.

Using Doctrine

Once your models are set up and your database is built, you can access your models using the Doctrine EntityManager. I like shortcuts, so I always instantiate the EntityManager in MY_Controller:

<?php

class MY_Controller extends Controller
{
    // Doctrine EntityManager
    public $em;

    function __construct()
    {
        parent::__construct();

        // Not required if you autoload the library
        $this->load->library('doctrine');

        $this->em = $this->doctrine->em;
    }
}

Instead of the longer $this->doctrine->em, this will allow you to access the EntityManager using $this->em:

$user = new Entity\User;
$user->setUsername('Joseph');
$user->setPassword('secretPassw0rd');
$user->setEmail('josephatwildlyinaccuratedotcom');

$this->em->persist($user);
$this->em->flush();

Final Words

If you have been spoiled by Doctrine 1’s magic then you will probably have a hard time wrapping your head around Doctrine 2’s strict object-oriented approach to everything. When learning your way around a new system, documentation can be your best friend or your worst enemy. Lucky for us, the Doctrine team has written some fantastic documentation. If you are having trouble getting something to work, one of the first things you should do is RTFM (Doctrine; CodeIgniter).

Good luck, I hope you enjoy using these powerful tools!