Joel Verhagen

a computer programming blog

Setting up CodeIgniter 2 with Doctrine 2 the right way

So you've discovered the awesomeness of CodeIgniter and you need an ORM. You've read about people around StackOverflow and the CodeIgniter forums recommending Doctrine as an object mapper. You've tried and tried but Doctrine and CodeIgniter are just not playing nicely together. Well http://www.phpandstuff.com/ has a stupendous tutorial... for CodeIgniter < 2.0 and Doctrine < 2.0. We need something more recent, eh?

Well, I walked the same rocky road and decided to make my own tutorial. Yah, yah, there's already a bunch out there. But they either suck or don't apply to CodeIgniter 2 or Doctrine 2. Mine doesn't suck. I hope.

So for future reference, here are the versions I am working with during this tutorial, but similarly versions should work just fine:

  • CodeIgniter 2.1.0
  • Doctrine 2.2.1
  • Also, I am running Apache 2.2.20, PHP 5.3.6 (with APC), MySQL 5.1.58, all on Ubuntu Server 11.10.
  • I am assuming you already have a MySQL database (with valid a user) set up.

Versions

The following versions have been tested to make sure they fully work with this tutorial:

  • CodeIgniter 2.1.0 and Doctrine 2.2.1
  • CodeIgniter 2.0.2 and Doctrine 2.0.5

If you want to work with a different version of CodeIgniter or Doctrine, this tutorial should still be totally fine as long as the version of CodeIgniterand Doctrine are version 2.x.x! You may have to change the paths of downloads a bit (i.e. changing a CodeIgniter_2.1.0.zip in a URL to CodeIgniter_2.0.2.zip, but everything else will be exactly the same. If you can't handle that, I'd recommend firing up a WordPress instance and going from there...

Tutorial Context

Also, here are some names I will be using throughout the tutorial. Replace them with whatever paths or names that are relevant to your project.

  • Project Name: Emma Watson Shrine
  • Project Directory: /var/www/emma_watson_shrine/

I am going to try to make this as easy on you as possible, so I will give all of the steps necessary to get this stack up and running. In other words, you shouldn't even have to read the Doctrine and CodeIgniter documentation, although it is HIGHLY recommended.

"And away we go..."

Download and Configure CodeIgniter

  1. Download the CodeIgniter archive, extract it, and move it to the location that you'd like your project to reside.
    ### go to the web directory
    cd /var/www
    
    ### download CodeIgniter
    wget http://www.codeigniter.com/download_files/reactor/CodeIgniter_2.1.0.zip
    
    ### unzip the archive
    unzip CodeIgniter_2.1.0.zip
    
    ### remove the archive
    rm CodeIgniter_2.1.0.zip
    
    ### rename the extracted archive to the desired project directory
    mv CodeIgniter_2.1.0 emma_watson_shrine
  2. Make sure your project's directory has proper permissions. This kind of depends on your server's setup, but I set my project's directory permissions to 755. If you're not sure about this, skip to the next step. It's most likely that the permissions are okay if you used an FTP program to upload the CodeIgniter files.
    chmod -R 755 emma_watson_shrine
  3. CodeIgniter still needs some configuration, but it's running! When you view your project directory in your browser, it should look something like this: screenshot.
    http://www.example.com/emma_watson_shrine/
  4. Now let's take some time to configure CodeIgniter. Don't worry, it's really quick!
    1. Open CodeIgniter's config file in the text editor of your choice.
      vim /var/www/emma_watson_shrine/application/config/config.php
    2. Set the base URL.
      <?php
      $config['base_url'] = 'http://www.example.com/emma_watson_shrine/';
    3. Open CodeIgniter's database config file.
      vim /var/www/emma_watson_shrine/application/config/database.php
    4. Set up the database information. This is the database that Doctrine will use.
      <?php 
      $db['default']['hostname'] = 'localhost';
      $db['default']['username'] = 'emmawatson';
      $db['default']['password'] = 'ronweasley';
      $db['default']['database'] = 'emmawatson';
And that's really all there is to it! CodeIgniter, for the win!

Download Doctrine

  1. Download the Doctrine archive, extract it, and move it to your CodeIgniter application's library directory. Keep in mind that the archive you download has two sub-directories: bin and Doctrine. The Doctrine directory is what you actually need.
    ### go to the libraries directory
    cd /var/www/emma_watson_shrine/application/libraries
    
    ### download Doctrine
    wget http://www.doctrine-project.org/downloads/DoctrineORM-2.2.1-full.tar.gz
    
    ### extract the archive
    tar xfz DoctrineORM-2.2.1-full.tar.gz
    
    ### pull out the directory we need
    mv DoctrineORM-2.2.1/Doctrine Doctrine
    
    ### remove the other stuff we don't need
    rm -rf DoctrineORM-2.2.1 DoctrineORM-2.2.1-full.tar.gz

Add Doctrine as a CodeIgniter library

This is when things start getting a bit more confusing. So read carefully!

  1. Go to your CodeIgniter applications's library directory. We need to make a PHP class that will help our CodeIgniter controllers talk use Doctrine's all-powerful Entity Manager. Create a new file at the following path and open it up in a text editor:
    vim /var/www/emma_watson_shrine/application/libraries/Doctrine.php
  2. Paste this code in. Read the in-code comments for explanation, if you want to learn a thing or two. Here's a link to Doctrine.php if you just want to download it to the right location.
    <?php
    
    class Doctrine
    {
        // the Doctrine entity manager
        public $em = null;
    
        public function __construct()
        {
            // include our CodeIgniter application's database configuration
            require_once APPPATH.'config/database.php';
            
            // include Doctrine's fancy ClassLoader class
            require_once APPPATH.'libraries/Doctrine/Common/ClassLoader.php';
    
            // load the Doctrine classes
            $doctrineClassLoader = new \Doctrine\Common\ClassLoader('Doctrine', APPPATH.'libraries');
            $doctrineClassLoader->register();
            
            // load Symfony2 helpers
            // Don't be alarmed, this is necessary for YAML mapping files
            $symfonyClassLoader = new \Doctrine\Common\ClassLoader('Symfony', APPPATH.'libraries/Doctrine');
            $symfonyClassLoader->register();
    
            // load the entities
            $entityClassLoader = new \Doctrine\Common\ClassLoader('Entities', APPPATH.'models');
            $entityClassLoader->register();
    
            // load the proxy entities
            $proxyClassLoader = new \Doctrine\Common\ClassLoader('Proxies', APPPATH.'models');
            $proxyClassLoader->register();
    
            // set up the configuration 
            $config = new \Doctrine\ORM\Configuration;
        
            if(ENVIRONMENT == 'development')
                // set up simple array caching for development mode
                $cache = new \Doctrine\Common\Cache\ArrayCache;
            else
                // set up caching with APC for production mode
                $cache = new \Doctrine\Common\Cache\ApcCache;
            $config->setMetadataCacheImpl($cache);
            $config->setQueryCacheImpl($cache);
    
            // set up proxy configuration
            $config->setProxyDir(APPPATH.'models/Proxies');
            $config->setProxyNamespace('Proxies');
            
            // auto-generate proxy classes if we are in development mode
            $config->setAutoGenerateProxyClasses(ENVIRONMENT == 'development');
    
            // set up annotation driver
            $yamlDriver = new \Doctrine\ORM\Mapping\Driver\YamlDriver(APPPATH.'models/Mappings');
            $config->setMetadataDriverImpl($yamlDriver);
    
            // Database connection information
            $connectionOptions = array(
                'driver' => 'pdo_mysql',
                'user' => $db['default']['username'],
                'password' => $db['default']['password'],
                'host' => $db['default']['hostname'],
                'dbname' => $db['default']['database']
            );
            
            // create the EntityManager
            $em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config);
            
            // store it as a member, for use in our CodeIgniter controllers.
            $this->em = $em;
        }
    }
  3. Open CodeIgniter's autoload config file.
    vim /var/www/emma_watson_shrine/application/config/autoload.php
    Add doctrine to the autoloaded libraries.
    <?php
    $autoload['libraries'] = array('doctrine');
  4. You should be able to view your project directory in your browser at this point without any errors coming up. If you have, double back and make sure you've followed all the steps. Furthermore, the page should look exactly as it did before you added Doctrine as a library.
    http://www.example.com/emma_watson_shrine/

Add Models to Doctrine

From here on, what you need to do really depends on what you would like your website to do. Since I am making a shrine dedicated to Emma Watson, I think I'll need two models (represented by two tables): a User object and an Article object. The User object will hold information about an individual user. The Article object will have an author (linking to a User object) and other information for displaying an article. Something we need to get straight first is that the models generated by Doctrine are going to be different than CodeIgniter models (i.e. they will not extend the CI_Model class).
  1. Create a Mappings directory in your application’s models directory.
    cd /var/www/emma_watson_shrine/application/models
    mkdir Mappings
    cd Mappings
  2. Create some YAML mapping files and put them in the Mappings directory. How to do this is outside the scope of this tutorial, but I will provide mine as examples. Here is a link to the Doctrine documentation on how to format these file. Make sure the mapping files have the ".dcm.yml" file extension and the "Entities." prefix. The extension tells Doctrine that this file is a YAML mapping file, and the prefix tells Doctrine to put the models in the Entity namespace.
    • When declaring the name of your object, make sure to prefix the name with Entities\ to put it in the Entities namespace.
    • Here is the YAML file for my User object. The file name should be Entities.User.dcm.yml.
      Entities\User:
        type: entity
        table: users
        uniqueConstraints:
          email_index:
            columns:
              - email
        fields:
          id:
            type: integer
            id: true
            generator:
              strategy: AUTO
          password:
            type: string
            length: 32
            nullable: false
          firstName:
            type: string
            length: 255
            nullable: false
            column: first_name
          lastName:
            type: string
            length: 255
            nullable: false
            column: last_name
          email:
            type: string
            length: 255
            nullable: false
          website:
            type: string
            length: 255
            nullable: true
          created:
            type: datetime
            nullable: false
    • And here is the YAML file for my Article object. The file name should be Entities.Article.dcm.yml.
      Entities\Article:
        type: entity
        table: articles
        fields:
          id:
            type: integer
            id: true
            generator:
              strategy: AUTO
          title:
            type: string
            length: 255
            nullable: false
          content:
            type: text
            nullable: false
          created:
            type: datetime
            nullable: false
        manyToOne:
          user:
            targetEntity: User
            joinColumns:
              user_id:
                referencedColumnName: id
  3. Go back to the models directory and create two more directories: "Entities" and "Proxies". Proxies is the directory that will hold the proxy classes that Doctrine uses. If you're really smart, you should be able to figure out what the Entities directory is for. Since this is the Internet, I think it's safe to assume that I should explain it to you. It's the directory that holds the Entities!
    cd /var/www/emma_watson_shrine/application/models
    mkdir Entities Proxies

Configure Doctrine Command Line Tool

To use Doctrine, you need set up the command line tool, which helps you with a lot of tasks, like automatically creating the schema in your database, generating proxy classes, etc.
  1. Go to your application's application directory and create a file called doctrine-cli.php. This is a PHP file that you will need to run through the command line PHP program. Alternatively, download this file to your application directory and skip the next step. Make sure you change the APPPATH definition in the code to fit your needs!
    cd /var/www/emma_watson_shrine/application
    vim doctrine-cli.php
  2. Put the following contents in your doctrine-cli.php file. As I said above, make sure you change the APPPATH in the code definition to fit your needs!
    <?php
    
    // trailing slash is important!
    define('APPPATH', dirname(__FILE__).DIRECTORY_SEPARATOR);
    define('BASEPATH', APPPATH);
    define('ENVIRONMENT', 'production');
    
    require APPPATH.'libraries/Doctrine.php';
    
    $doctrine = new Doctrine();
     
    $helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
        'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($doctrine->em->getConnection()),
        'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($doctrine->em)
    ));
    
    $cli = new \Symfony\Component\Console\Application('Doctrine Command Line Interface (CodeIgniter integration by Joel Verhagen)', Doctrine\ORM\Version::VERSION);
    $cli->setCatchExceptions(true);
    $cli->setHelperSet($helperSet);
    $cli->addCommands(array(
        // DBAL Commands
        new \Doctrine\DBAL\Tools\Console\Command\RunSqlCommand(),
        new \Doctrine\DBAL\Tools\Console\Command\ImportCommand(),
     
        // ORM Commands
        new \Doctrine\ORM\Tools\Console\Command\ClearCache\MetadataCommand(),
        new \Doctrine\ORM\Tools\Console\Command\ClearCache\ResultCommand(),
        new \Doctrine\ORM\Tools\Console\Command\ClearCache\QueryCommand(),
        new \Doctrine\ORM\Tools\Console\Command\SchemaTool\CreateCommand(),
        new \Doctrine\ORM\Tools\Console\Command\SchemaTool\UpdateCommand(),
        new \Doctrine\ORM\Tools\Console\Command\SchemaTool\DropCommand(),
        new \Doctrine\ORM\Tools\Console\Command\EnsureProductionSettingsCommand(),
        new \Doctrine\ORM\Tools\Console\Command\ConvertDoctrine1SchemaCommand(),
        new \Doctrine\ORM\Tools\Console\Command\GenerateRepositoriesCommand(),
        new \Doctrine\ORM\Tools\Console\Command\GenerateEntitiesCommand(),
        new \Doctrine\ORM\Tools\Console\Command\GenerateProxiesCommand(),
        new \Doctrine\ORM\Tools\Console\Command\ConvertMappingCommand(),
        new \Doctrine\ORM\Tools\Console\Command\RunDqlCommand(),
        new \Doctrine\ORM\Tools\Console\Command\ValidateSchemaCommand(),
     
    ));
    $cli->run();
  3. Now run this script through the PHP command-line and should see a list of commands available to you.
    php doctrine-cli.php
    The output should look something like this:
    Doctrine Command Line Interface (CodeIgniter integration by Joel Verhagen) version 2.0.5
    
    Usage:
      [options] command [arguments]
    
    Options:
      --help           -h Display this help message.
      --quiet          -q Do not output any message.
      --verbose        -v Increase verbosity of messages.
      --version        -V Display this program version.
      --ansi           -a Force ANSI output.
      --no-interaction -n Do not ask any interactive question.
    
    Available commands:
      help                         Displays help for a command (?)
      list                         Lists commands
    dbal
      :import                      Import SQL file(s) directly to Database.
      :run-sql                     Executes arbitrary SQL directly from the command line.
    orm
      :convert-d1-schema           Converts Doctrine 1.X schema into a Doctrine 2.X schema.
      :convert-mapping             Convert mapping information between supported formats.
      :ensure-production-settings  Verify that Doctrine is properly configured for a production environment.
      :generate-entities           Generate entity classes and method stubs from your mapping information.
      :generate-proxies            Generates proxy classes for entity classes.
      :generate-repositories       Generate repository classes from your mapping information.
      :run-dql                     Executes arbitrary DQL directly from the command line.
      :validate-schema             Validate that the mapping files.
    orm:clear-cache
      :metadata                    Clear all metadata cache of the various cache drivers.
      :query                       Clear all query cache of the various cache drivers.
      :result                      Clear result cache of the various cache drivers.
    orm:schema-tool
      :create                      Processes the schema and either create it directly on EntityManager Storage Connection or generate the SQL output.
      :drop                        Drop the complete database schema of EntityManager Storage Connection or generate the corresponding SQL output.
      :update                      Processes the schema and either update the database schema of EntityManager Storage Connection or generate the SQL output.

Setup Entities, Proxies, and Database Schema

We're getting close here. Really the last thing you need to do before you can jump into developing your Facebook killer.
  1. For all of the steps in this section, you'll need to simply call the Doctrine command line tool that you got working in the previous section. The order of these steps does matter!
    cd /var/www/emma_watson_shrine/application
  2. Let's go ahead and generate the entity classes. Note that we are creating them in the models directory. Doctrine detects that they are in the Entities namespace and automatically puts them in the Entities subdirectory. Keep in mind that these entity classes come with getters/setters for all of the fields you defined in your YAML file but can be modified to add custom methods and such.
    php doctrine-cli.php orm:generate-entities models
  3. Now the proxy classes.
    php doctrine-cli.php orm:generate-proxies
  4. If you've tried this before and your database is not empty, you'll need to do this step. Otherwise, skip it.
    php doctrine-cli.php orm:schema-tool:drop --force
  5. And finally, let's create the database tables for Doctrine to store our objects in.
    php doctrine-cli.php orm:schema-tool:create

Using Doctrine in a CodeIgniter Controller

Believe it or not, you've just made it through all the hard stuff. With all that stupid configuration stuff out of the way, you can get to coding. Yay! Since we added Doctrine as an autoloaded library, we don't even need to fool with loading Doctrine every time you create a new controller. Also, the library file (libraries/Doctrine.php) has a auto-load call that makes it so all of the entities you put in the models/Entities directory are automatically available to your controllers! If you know what you're doing, go ahead and leave this page. Get coding! Otherwise, I'm going to give you a few code samples so you can start using Doctrine.
  1. How do I create a new entity and save it into the database?

    Easy.
    <?php
    public function createObjects()
    {
        // create a new user object
        $user = new Entities\User;
        $user->setFirstName('Joel');
        $user->setLastName('Verhagen');
        $user->setPassword(md5('Emma Watson'));
        $user->setEmail('[email protected]');
        $user->setWebsite('https://www.joelverhagen.com');
        $user->setCreated(new DateTime());
        
        // standard way in CodeIgniter to access a library in a controller: $this->library_name->member->memberFunction()
        // save the object to database, so that it can obtain its ID
        $this->doctrine->em->persist($user);
        
        // create a new article object
        $article = new Entities\Article;
        $article->setTitle('Emma Watson is extremely talented, no?');
        $article->setContent('By talented, I mean good at lookin\' good.');
        $article->setCreated(new DateTime());
        // the user object you pass must be persisted first!
        $article->setUser($user);
        
        // save the article object to the database
        $this->doctrine->em->persist($article);
        $this->doctrine->em->flush();
        
        echo "<b>Success!</b>";
    }
  2. How do I get an entity from the database?

    Child's play.
    <?php
    public function getObjects()
    {
        // get an object by ID
        $user = $this->doctrine->em->find('Entities\User', 1);
        echo $user->getFirstName().' '.$user->getLastName().' is a real chill guy.<br />';
        
        // get a related object
        $article = $this->doctrine->em->find('Entities\Article', 1);
        $user = $article->getUser();
        echo $user->getFirstName().' '.$user->getLastName().' thinks CodeIgniter + Doctrine is real slick. <br />';
        
        // what happens when we try to get an object that doesn't exist?
        $article = $this->doctrine->em->find('Entities\Article', 9001);
        if(is_null($article))
        {
            // the "find" call returns NULL!
            echo 'Dude, that article number 9001 doesn\'t even exist yet.<br />';
        }
        
        // get an object by another field
        $user = $this->doctrine->em->getRepository('Entities\User')->findOneBy(array('email' => '[email protected]'));
        echo $user->getFirstName().' '.$user->getLastName().' is glad he isn\'t dealing with Symfony.<br />';
    }
  3. Can I add custom methods to my entities?

    Yes!
    <?php
    
    namespace Entities;
    
    /**
     * Entities\User
     */
    class User
    {
        ...
    
        // my custom function :)
        public function getFullName()
        {
            return $this->firstName.' '.$this->lastName;
        }
    
        ...
    }