Переглянути джерело

[DoctrineBundle] Enhancing the Doctrine 2 integration further to better handle multiple connections/entity managers

Jonathan H. Wage 15 роки тому
батько
коміт
1a45bb6d63

+ 139 - 0
src/Symfony/Framework/DoctrineBundle/Command/BuildDoctrineCommand.php

@@ -0,0 +1,139 @@
+<?php
+
+namespace Symfony\Framework\DoctrineBundle\Command;
+
+use Symfony\Components\Console\Input\InputArgument;
+use Symfony\Components\Console\Input\InputOption;
+use Symfony\Components\Console\Input\InputInterface;
+use Symfony\Components\Console\Output\OutputInterface;
+use Symfony\Components\Console\Output\Output;
+use Symfony\Framework\WebBundle\Util\Filesystem;
+use Doctrine\Common\Cli\Configuration;
+use Doctrine\Common\Cli\CliController as DoctrineCliController;
+use Doctrine\DBAL\Connection;
+
+/*
+ * This file is part of the symfony framework.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * Build command allows you to easily build and re-build your Doctrine development environment
+ *
+ * @package    symfony
+ * @subpackage console
+ * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @author     Jonathan H. Wage <jonwage@gmail.com>
+ * @author     Kris Wallsmith <kris.wallsmith@symfony-project.org>
+ */
+class BuildDoctrineCommand extends DoctrineCommand
+{
+  const
+    BUILD_ENTITIES   = 1,
+    BUILD_DB         = 16,
+
+    OPTION_ENTITIES    = 1,
+    OPTION_DB          = 16,
+    OPTION_ALL         = 31;
+
+  /**
+   * @see Command
+   */
+  protected function configure()
+  {
+    $this
+      ->setName('doctrine:build')
+      ->setDescription('Build task for easily re-building your Doctrine development environment.')
+      ->addOption('all', null, null, 'Build everything and reset the database')
+      ->addOption('entities', null, null, 'Build model classes')
+      ->addOption('db', null, null, 'Drop database, create database and create schema.')
+      ->addOption('and-load', null, InputOption::PARAMETER_OPTIONAL | InputOption::PARAMETER_IS_ARRAY, 'Load data fixtures')
+      ->addOption('and-append', null, InputOption::PARAMETER_OPTIONAL | InputOption::PARAMETER_IS_ARRAY, 'Load data fixtures and append to existing data')
+      ->addOption('and-update-schema', null, null, 'Update schema after rebuilding all classes')
+      ->addOption('connection', null, null, 'The connection to use.')
+    ;
+  }
+
+  /**
+   * @see Command
+   */
+  protected function execute(InputInterface $input, OutputInterface $output)
+  {
+    if (!$mode = $this->calculateMode($input))
+    {
+      throw new \InvalidArgumentException(sprintf("You must include one or more of the following build options:\n--%s\n\nSee this task's help page for more information:\n\n  php console help doctrine:build", join(', --', array_keys($this->getBuildOptions()))));
+    }
+
+    if (self::BUILD_ENTITIES == (self::BUILD_ENTITIES & $mode))
+    {
+      $this->runCommand('doctrine:build-entities');
+    }
+
+    if (self::BUILD_DB == (self::BUILD_DB & $mode))
+    {
+      $this->runCommand('doctrine:schema-tool', array('--re-create' => true, '--connection' => $input->getOption('connection')));
+    }
+
+    if ($input->getOption('and-update-schema'))
+    {
+      $this->runCommand('doctrine:schema-tool', array('--update' => true, '--connection' => $input->getOption('connection')));
+      $this->runCommand('doctrine:schema-tool', array('--complete-update' => true,  '--connection' => $input->getOption('connection')));
+    }
+
+    if ($input->hasOption('and-load'))
+    {
+      $dirOrFile = $input->getOption('and-load');
+      $this->runCommand('doctrine:load-data-fixtures', 
+        array('--dir_or_file' => $dirOrFile, '--append' => false)
+      );
+    }
+    else if ($input->hasOption('and-append'))
+    {
+      $dirOrFile = $input->getOption('and-append');
+      $this->runCommand('doctrine:load-data-fixtures', array('--dir_or_file' => $dirOrFile, '--append' => true));
+    }
+  }
+
+  /**
+   * Calculates a bit mode based on the supplied options.
+   *
+   * @param InputInterface $input
+   * @return integer
+   */
+  protected function calculateMode(InputInterface $input)
+  {
+    $mode = 0;
+    foreach ($this->getBuildOptions() as $name => $value)
+    {
+      if ($input->getOption($name) === true)
+      {
+        $mode = $mode | $value;
+      }
+    }
+
+    return $mode;
+  }
+
+  /**
+   * Returns an array of valid build options.
+   *
+   * @return array An array of option names and their mode
+   */
+  protected function getBuildOptions()
+  {
+    $options = array();
+    foreach ($this->getDefinition()->getOptions() as $option)
+    {
+      if (defined($constant = __CLASS__.'::OPTION_'.str_replace('-', '_', strtoupper($option->getName()))))
+      {
+        $options[$option->getName()] = constant($constant);
+      }
+    }
+
+    return $options;
+  }
+}

+ 66 - 0
src/Symfony/Framework/DoctrineBundle/Command/BuildEntitiesDoctrineCommand.php

@@ -0,0 +1,66 @@
+<?php
+
+namespace Symfony\Framework\DoctrineBundle\Command;
+
+use Symfony\Components\Console\Input\InputArgument;
+use Symfony\Components\Console\Input\InputOption;
+use Symfony\Components\Console\Input\InputInterface;
+use Symfony\Components\Console\Output\OutputInterface;
+use Symfony\Components\Console\Output\Output;
+use Symfony\Framework\WebBundle\Util\Filesystem;
+use Doctrine\Common\Cli\Configuration;
+use Doctrine\Common\Cli\CliController as DoctrineCliController;
+
+/*
+ * This file is part of the symfony framework.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * Build all Bundle entity classes from mapping information.
+ *
+ * @package    symfony
+ * @subpackage console
+ * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @author     Jonathan H. Wage <jonwage@gmail.com>
+ */
+class BuildEntitiesDoctrineCommand extends DoctrineCommand
+{
+  /**
+   * @see Command
+   */
+  protected function configure()
+  {
+    $this
+      ->setName('doctrine:build-entities')
+      ->setDescription('Build all Bundle entity classes from mapping information.')
+    ;
+  }
+
+  /**
+   * @see Command
+   */
+  protected function execute(InputInterface $input, OutputInterface $output)
+  {
+    foreach ($this->container->getParameter('kernel.bundle_dirs') as $bundle => $path)
+    {
+      $bundles = glob($path.'/*Bundle');
+      foreach ($bundles as $p)
+      {
+        if (!is_dir($metadataPath = $p.'/Resources/config/doctrine/metadata'))
+        {
+          continue;
+        }
+        $opts = array();
+        $opts['--from'] = $metadataPath;
+        $opts['--to'] = 'annotation';
+        $opts['--dest'] = realpath($path.'/..');
+        $this->runCommand('doctrine:convert-mapping', $opts);
+      }
+    }
+  }
+}

+ 127 - 0
src/Symfony/Framework/DoctrineBundle/Command/DatabaseToolDoctrineCommand.php

@@ -0,0 +1,127 @@
+<?php
+
+namespace Symfony\Framework\DoctrineBundle\Command;
+
+use Symfony\Components\Console\Input\InputArgument;
+use Symfony\Components\Console\Input\InputOption;
+use Symfony\Components\Console\Input\InputInterface;
+use Symfony\Components\Console\Output\OutputInterface;
+use Symfony\Components\Console\Output\Output;
+use Symfony\Framework\WebBundle\Util\Filesystem;
+use Doctrine\Common\Cli\Configuration;
+use Doctrine\Common\Cli\CliController as DoctrineCliController;
+use Doctrine\DBAL\Connection;
+
+/*
+ * This file is part of the symfony framework.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * Database tool allows you to easily drop and create your configured databases.
+ *
+ * @package    symfony
+ * @subpackage console
+ * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @author     Jonathan H. Wage <jonwage@gmail.com>
+ */
+class DatabaseToolDoctrineCommand extends DoctrineCommand
+{
+  /**
+   * @see Command
+   */
+  protected function configure()
+  {
+    $this
+      ->setName('doctrine:database-tool')
+      ->setDescription('Create and drop the configured databases.')
+      ->addOption('re-create', null, null, 'Drop and re-create your databases.')
+      ->addOption('drop', null, null, 'Drop your databases.')
+      ->addOption('create', null, null, 'Create your databases.')
+      ->addOption('connection', null, null, 'The connection name to work on.')
+    ;
+  }
+
+  /**
+   * @see Command
+   */
+  protected function execute(InputInterface $input, OutputInterface $output)
+  {
+    if ($input->getOption('re-create'))
+    {
+      $input->setOption('drop', true);
+      $input->setOption('create', true);
+    }
+    if (!$input->getOption('drop') && !$input->getOption('create'))
+    {
+      throw new \InvalidArgumentException('You must specify one of the --drop and --create options or both.');
+    }
+    $found = false;
+    $connections = $this->getDoctrineConnections();
+    foreach ($connections as $name => $connection)
+    {
+      if ($input->getOption('connection') && $name != $input->getOption('connection'))
+      {
+        continue;
+      }
+      if ($input->getOption('drop'))
+      {
+        $this->dropDatabaseForConnection($connection, $output);
+      }
+      if ($input->getOption('create'))
+      {
+        $this->createDatabaseForConnection($connection, $output);
+      }
+      $found = true;
+    }
+    if ($found === false)
+    {
+      if ($input->getOption('connection'))
+      {
+        throw new \InvalidArgumentException(sprintf('<error>Could not find a connection named <comment>%s</comment></error>', $input->getOption('connection')));
+      }
+      else
+      {
+        throw new \InvalidArgumentException(sprintf('<error>Could not find any configured connections</error>', $input->getOption('connection')));        
+      }
+    }
+  }
+
+  protected function dropDatabaseForConnection(Connection $connection, OutputInterface $output)
+  {
+    $params = $connection->getParams();
+    $name = isset($params['path']) ? $params['path']:$params['dbname'];
+
+    try {
+      $connection->getSchemaManager()->dropDatabase($name);
+      $output->writeln(sprintf('<info>Dropped database for connection named <comment>%s</comment></info>', $name));
+    } catch (\Exception $e) {
+      $output->writeln(sprintf('<error>Could not drop database for connection named <comment>%s</comment></error>', $name));
+      $output->writeln(sprintf('<error>%s</error>', $e->getMessage()));
+    }
+  }
+
+  protected function createDatabaseForConnection(Connection $connection, OutputInterface $output)
+  {
+    $params = $connection->getParams();
+    $name = isset($params['path']) ? $params['path']:$params['dbname'];
+
+    unset($params['dbname']);
+
+    $tmpConnection = \Doctrine\DBAL\DriverManager::getConnection($params);
+
+    try {
+      $tmpConnection->getSchemaManager()->createDatabase($name);
+      $output->writeln(sprintf('<info>Created database for connection named <comment>%s</comment></info>', $name));
+    } catch (\Exception $e) {
+      $output->writeln(sprintf('<error>Could not create database for connection named <comment>%s</comment></error>', $name));
+      $output->writeln(sprintf('<error>%s</error>', $e->getMessage()));
+    }
+
+    $tmpConnection->close();
+  }
+}

+ 53 - 3
src/Symfony/Framework/DoctrineBundle/Command/DoctrineCommand.php

@@ -3,11 +3,13 @@
 namespace Symfony\Framework\DoctrineBundle\Command;
 
 use Symfony\Framework\WebBundle\Command\Command;
+use Symfony\Components\Console\Input\ArrayInput;
 use Symfony\Components\Console\Input\InputArgument;
 use Symfony\Components\Console\Input\InputOption;
 use Symfony\Components\Console\Input\InputInterface;
 use Symfony\Components\Console\Output\OutputInterface;
 use Symfony\Components\Console\Output\Output;
+use Symfony\Framework\WebBundle\Console\Application;
 use Symfony\Framework\WebBundle\Util\Filesystem;
 use Doctrine\Common\Cli\Configuration;
 use Doctrine\Common\Cli\CliController as DoctrineCliController;
@@ -30,16 +32,19 @@ use Doctrine\Common\Cli\CliController as DoctrineCliController;
  */
 abstract class DoctrineCommand extends Command
 {
-  protected $cli;
+  protected
+    $cli,
+    $em;
 
   protected function getDoctrineCli()
   {
     if ($this->cli === null)
     {
       $configuration = new Configuration();
-      $configuration->setAttribute('em', $this->container->getDoctrine_Orm_EntityManagerService());
       $this->cli = new DoctrineCliController($configuration);
     }
+    $em = $this->em ? $this->em : $this->container->getDoctrine_Orm_EntityManagerService();
+    $this->cli->getConfiguration()->setAttribute('em', $em);
     return $this->cli;
   }
 
@@ -60,7 +65,7 @@ abstract class DoctrineCommand extends Command
     return $this->getDoctrineCli()->run(array_merge(array('doctrine', $name), $builtOptions));
   }
 
-  public function buildDoctrineCliTaskOptions(InputInterface $input, array $options)
+  protected function buildDoctrineCliTaskOptions(InputInterface $input, array $options)
   {
     $taskOptions = array();
     foreach ($options as $option)
@@ -72,4 +77,49 @@ abstract class DoctrineCommand extends Command
     }
     return $options;
   }
+
+  protected function runCommand($name, array $input = array())
+  {
+    $arguments = array();
+    $arguments = array_merge(array($name), $input);
+    $input = new ArrayInput($arguments);
+    $application = new Application($this->container->getKernelService());
+    $application->setAutoExit(false);
+    $application->run($input);
+  }
+
+  /**
+   * TODO: Better way to do these functions?
+   */
+  protected function getDoctrineConnections()
+  {
+    $connections = array();
+    $ids = $this->container->getServiceIds();
+    foreach ($ids as $id)
+    {
+      preg_match('/doctrine.dbal.(.*)_connection/', $id, $matches);
+      if ($matches)
+      {
+        $name = $matches[1];
+        $connections[$name] = $this->container->getService($id);
+      }
+    }
+    return $connections;
+  }
+
+  protected function getDoctrineEntityManagers()
+  {
+    $entityManagers = array();
+    $ids = $this->container->getServiceIds();
+    foreach ($ids as $id)
+    {
+      preg_match('/doctrine.orm.(.*)_entity_manager/', $id, $matches);
+      if ($matches)
+      {
+        $name = $matches[1];
+        $entityManagers[$name] = $this->container->getService($id);
+      }
+    }
+    return $entityManagers;
+  }
 }

+ 73 - 36
src/Symfony/Framework/DoctrineBundle/Command/LoadDataFixturesDoctrineCommand.php

@@ -41,7 +41,7 @@ class LoadDataFixturesDoctrineCommand extends DoctrineCommand
     $this
       ->setName('doctrine:load-data-fixtures')
       ->setDescription('Load data fixtures to your database.')
-      ->addOption('dir_or_file', null, null, 'The directory or file to load data fixtures from.')
+      ->addOption('dir_or_file', null, InputOption::PARAMETER_OPTIONAL | InputOption::PARAMETER_IS_ARRAY, 'The directory or file to load data fixtures from.')
       ->addOption('append', null, InputOption::PARAMETER_OPTIONAL, 'Whether or not to append the data fixtures.', false)
     ;
   }
@@ -51,37 +51,11 @@ class LoadDataFixturesDoctrineCommand extends DoctrineCommand
    */
   protected function execute(InputInterface $input, OutputInterface $output)
   {
-    $em = $this->container->getDoctrine_ORM_EntityManagerService();
-    if (!$input->getOption('append'))
-    {
-      $classes = array();
-      $metadatas = $em->getMetadataFactory()->getAllMetadata();
-
-      foreach ($metadatas as $metadata)
-      {
-        if (!$metadata->isMappedSuperclass)
-        {
-          $classes[] = $metadata;
-        }
-      }
-      $cmf = $em->getMetadataFactory();
-      $classes = $this->getCommitOrder($em, $classes);
-      for ($i = count($classes) - 1; $i >= 0; --$i)
-      {
-        $class = $classes[$i];
-        if ($cmf->hasMetadataFor($class->name))
-        {
-          try {
-            $em->createQuery('DELETE FROM '.$class->name.' a')->execute();
-          } catch (Exception $e) {}
-        }
-      }
-    }
-
+    $defaultEm = $this->container->getDoctrine_ORM_EntityManagerService();
     $dirOrFile = $input->getOption('dir_or_file');
     if ($dirOrFile)
     {
-      $paths = $dirOrFile;
+      $paths = is_array($dirOrFile) ? $dirOrFile : array($dirOrFile);
     } else {
       $paths = array();
       $bundleDirs = $this->container->getKernelService()->getBundleDirs();
@@ -112,23 +86,86 @@ class LoadDataFixturesDoctrineCommand extends DoctrineCommand
       $files = array_merge($files, $found);
     }
 
+    $ems = array();
+    $emEntities = array();
     $files = array_unique($files);
-
     foreach ($files as $file)
     {
+      $em = $defaultEm;
+      $output->writeln(sprintf('<info>Loading data fixtures from <comment>"%s"</comment></info>', $file));
+
       $before = array_keys(get_defined_vars());
       include($file);
       $after = array_keys(get_defined_vars());
       $new = array_diff($after, $before);
-      $entities = array_values($new);
-      unset($entities[array_search('before', $entities)]);
-      foreach ($entities as $entity) {
-        $em->persist($$entity);
+      $params = $em->getConnection()->getParams();
+      $emName = isset($params['path']) ? $params['path']:$params['dbname'];
+
+      $ems[$emName] = $em;
+      $emEntities[$emName] = array();
+      $variables = array_values($new);
+
+      foreach ($variables as $variable)
+      {
+        $value = $$variable;
+        if (!is_object($value) || $value instanceof \Doctrine\ORM\EntityManager)
+        {
+          continue;
+        }
+        $emEntities[$emName][] = $value;
+      }
+      foreach ($ems as $emName => $em)
+      {
+        if (!$input->getOption('append'))
+        {
+          $output->writeln(sprintf('<info>Purging data from entity manager named <comment>"%s"</comment></info>', $emName));
+          $this->purgeEntityManager($em);
+        }
+
+        $entities = $emEntities[$emName];
+        $numEntities = count($entities);
+        $output->writeln(sprintf('<info>Persisting "%s" '.($numEntities > 1 ? 'entities' : 'entity').'</info>', count($entities)));
+
+        foreach ($entities as $entity)
+        {
+          $output->writeln(sprintf('<info>Persisting "%s" entity:</info>', get_class($entity)));
+          $output->writeln('');
+          $output->writeln(var_dump($entity));
+
+          $em->persist($entity);
+        }
+        $output->writeln('<info>Flushing entity manager</info>');
+        $em->flush();
       }
-      $em->flush();
     }
   }
   
+  protected function purgeEntityManager(EntityManager $em)
+  {
+    $classes = array();
+    $metadatas = $em->getMetadataFactory()->getAllMetadata();
+
+    foreach ($metadatas as $metadata)
+    {
+      if (!$metadata->isMappedSuperclass)
+      {
+        $classes[] = $metadata;
+      }
+    }
+    $cmf = $em->getMetadataFactory();
+    $classes = $this->getCommitOrder($em, $classes);
+    for ($i = count($classes) - 1; $i >= 0; --$i)
+    {
+      $class = $classes[$i];
+      if ($cmf->hasMetadataFor($class->name))
+      {
+        try {
+          $em->createQuery('DELETE FROM '.$class->name.' a')->execute();
+        } catch (Exception $e) {}
+      }
+    }
+  }
+
   protected function getCommitOrder(EntityManager $em, array $classes)
   {
     $calc = new CommitOrderCalculator;
@@ -154,4 +191,4 @@ class LoadDataFixturesDoctrineCommand extends DoctrineCommand
 
     return $calc->getCommitOrder();
   }
-}
+}

+ 1 - 0
src/Symfony/Framework/DoctrineBundle/Command/RunDqlDoctrineCommand.php

@@ -40,6 +40,7 @@ class RunDqlDoctrineCommand extends DoctrineCommand
       ->setDescription('Executes arbitrary DQL directly from the command line.')
       ->addOption('dql', null, null, 'The DQL query to run.')
       ->addOption('depth', null, null, 'The depth to output the data to.')
+      ->addOption('connection', null, null, 'The connection to use.')
     ;
   }
 

+ 1 - 0
src/Symfony/Framework/DoctrineBundle/Command/RunSqlDoctrineCommand.php

@@ -41,6 +41,7 @@ class RunSqlDoctrineCommand extends DoctrineCommand
       ->addOption('sql', null, null, 'The SQL query to run.')
       ->addOption('file', null, null, 'Path to a SQL file to run.')
       ->addOption('depth', null, null, 'The depth to output the data to.')
+      ->addOption('connection', null, null, 'The connection to use.')
     ;
   }
 

+ 27 - 1
src/Symfony/Framework/DoctrineBundle/Command/SchemaToolDoctrineCommand.php

@@ -44,6 +44,7 @@ class SchemaToolDoctrineCommand extends DoctrineCommand
       ->addOption('complete-update', null, null, 'Complete update and drop anything that is not in your schema.')
       ->addOption('re-create', null, null, 'Drop and re-create your database schema.')
       ->addOption('dump-sql', null, null, 'Dump the SQL instead of executing it.')
+      ->addOption('connection', null, null, 'The connection to use.')
     ;
   }
 
@@ -55,8 +56,33 @@ class SchemaToolDoctrineCommand extends DoctrineCommand
     $options = $this->buildDoctrineCliTaskOptions($input, array(
       'create', 'drop', 'update', 'complete-update', 're-create', 'dump-sql'
     ));
+
     $entityDirs = $this->container->getParameter('doctrine.orm.entity_dirs');
     $options['class-dir'] = implode(', ', $entityDirs);
-    $this->runDoctrineCliTask('orm:schema-tool', $options);
+
+    $found = false;
+    $ems = $this->getDoctrineEntityManagers();
+    foreach  ($ems as $name => $em)
+    {
+      if ($input->getOption('connection') && $name !== $input->getOption('connection'))
+      {
+        continue;
+      }
+      $this->em = $em;
+      $this->runDoctrineCliTask('orm:schema-tool', $options);
+      $found = true;
+    }
+
+    if ($found === false)
+    {
+      if ($input->getOption('connection'))
+      {
+        $output->writeln(sprintf('<error>Could not find a connection named <comment>%s</comment></error>', $input->getOption('connection')));
+      }
+      else
+      {
+        $output->writeln(sprintf('<error>Could not find any configured connections</error>', $input->getOption('connection')));        
+      }
+    }
   }
 }

+ 191 - 0
src/Symfony/Framework/DoctrineBundle/README

@@ -0,0 +1,191 @@
+DoctrineBundle
+--------------
+
+This document describes some of the functionality provided by the 
+**DoctrineBundle**. Doctrine 2 is a first class citizen in Symfony 2 and is 
+tightly integrated. Continue reading to learn how to use Doctrine 2 with the
+latest Symfony 2!
+
+## Configuration
+
+This section will help you with configuring your Symfony 2 project to enable
+the Doctrine DBAL and ORM services.
+
+### Database Abstraction Layer
+
+You can configure your database abstraction layer simply configuration a few 
+pieces of information about your database. If you only have one database you can
+do the following:
+
+    [yml]
+    doctrine.dbal:
+      dbname: symfony_guestbook
+      user: root
+      password: ~
+
+Or if you have multiple connections and want to customize the configuration of the
+connection further you can use the following:
+
+    [yml]
+    doctrine.dbal:
+      default_connection:       default
+      connections:
+        default:
+          driver:               PDOSqlite     # PDOSqlite, PDOMySql, PDOMsSql, PDOOracle, PDOPgSql, OCI8
+          dbname:               symfony
+          user:                 root
+          password:             null
+          host:                 localhost
+          port:                 ~
+          path:                 %kernel.data_dir%/symfony.sqlite
+          event_manager_class:  Doctrine\Common\EventManager
+          configuration_class:  Doctrine\DBAL\Configuration
+          wrapper_class:        ~
+          options:              []
+
+### Object Relational Mapper
+
+If you want to enable the Doctrine 2 ORM you can do so with the following:
+
+    doctrine.orm:
+      default_entity_manager:   default
+      metadata_driver:          xml           # xml, yml, annotation
+      cache_driver:             apc           # array, apc, memcache, xcache
+      entity_managers:
+        default:
+          connection:           default
+
+It's pretty simple, you can specify which entity managers you want to instantiate
+for which connections and also configure some other information about the ORM
+like what type of mapping files to use or what cache driver to use.
+
+## Creating a Bundle
+
+To get started we need to create a new bundle:
+
+    $ php console init:bundle "Bundle\\GuestbookBundle"
+    Initializing bundle "GuestbookBundle" in "/path/to/symfony-sandbox/src/Bundle"
+
+Now basically the most important thing to know about using Doctrine 2 with Symfony
+is where to put your mapping information files, where your entity classes are and 
+a few commands to help move things faster!
+
+## Mapping Information
+
+You can place all your mapping information inside a bundle. Below is an example
+path for the **GuestbookBundle** we created above:
+
+    /path/to/symfony-sandbox/src/Bundle/GuestbookBundle/Resources/config/doctrine/metadata
+
+Any files found in here that have a suffix of **.dcm.xml** (or whatever
+mapping_driver you picked) are used as your entities mapping information.
+
+In the **GuestbookBundle** we have a file named **Bundle.GuestbookBundle.Entities.Entry.dcm.xml**
+which contains the following XML:
+
+    <?xml version="1.0" encoding="UTF-8"?>
+
+    <doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
+                              http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
+
+        <entity name="Bundle\GuestbookBundle\Entities\Entry" table="guestbook_entry">
+
+            <id name="id" type="integer" column="id">
+                <generator strategy="AUTO"/>
+            </id>
+
+            <field name="createdAt" column="created_at" type="datetime" />
+            <field name="name" column="name" type="string" length="255" />
+            <field name="emailAddress" column="email_address" type="string" length="255" />
+            <field name="body" column="body" type="text" />
+
+        </entity>
+
+    </doctrine-mapping>
+
+## Building Entities
+
+Doctrine can help you a little bit by generating the entity classes for your 
+mapping information with the command:
+
+    $ php console doctrine:build-entities
+
+Now if you have a look in the bundles **Entities** directory you will see a new
+file named **Entry.php** with some code like the following:
+
+    [php]
+    // Bundle/GuestbookBundle/Entities/Entry.php
+
+    namespace Bundle\GuestbookBundle\Entities;
+
+    /**
+     * @Entity
+     * @Table(name="guestbook_entry")
+     */
+    class Entry
+    {
+        /**
+         * @Column(name="created_at", type="datetime")
+         */
+        private $createdAt;
+
+        /**
+         * @Column(name="name", type="string", length=255)
+         */
+        private $name;
+
+    // ...
+
+> **NOTE**
+> If you modify your mapping information and re-run the build entities command
+> it will modify the classes and update them based on the mapping information.
+
+## Commands
+
+The Doctrine 2 CLI is integrated with the Symfony 2 CLI so we have all the common
+commands we need to make working with Doctrine 2 just as easy and fast as before!
+
+    $ php console list doctrine
+
+    Available commands for the "doctrine" namespace:
+      :build                       Build task for easily re-building your Doctrine development environment.
+      :build-entities              Build all Bundle entity classes from mapping information.
+      :clear-cache                 Clear cache from configured query, result and metadata drivers. (doctrine:cc)
+      :convert-mapping             Convert mapping information between supported formats.
+      :database-tool               Create and drop the configured databases.
+      :ensure-production-settings  Verify that Doctrine is properly configured for a production environment.
+      :generate-proxies            Generates proxy classes for entity classes.
+      :load-data-fixtures          Load data fixtures to your database.
+      :run-dql                     Executes arbitrary DQL directly from the command line.
+      :run-sql                     Executes arbitrary SQL from a file or directly from the command line.
+      :schema-tool                 Processes the schema and either apply it directly on EntityManager or generate the SQL output.
+      :version                     Displays the current installed Doctrine version.
+
+The development workflow is very similar to how it is in Symfony 1.4. You can modify 
+your mapping information and use **doctrine:build --all** to re-build your 
+environment:
+
+    php console doctrine:build --all
+
+The recommend way to work is to re-build your entities and update your database
+schema:
+
+    php console doctrine:build --entities --and-update-schema
+
+Now any changes you made in your mapping information will be reflected in the 
+according databases! Here are all the available options for the **build** task:
+
+    $ php console help doctrine:build
+    Usage:
+     Symfony doctrine:build [--all] [--all-classes] [--entities] [--db] [--and-load[="..."]] [--and-append[="..."]] [--and-update-schema] [--dump-sql] [--connection]
+
+    Options:
+     --all   Build everything and reset the database
+     --entities Build model classes
+     --db    Drop database, create database and create schema.
+     --and-load Load data fixtures (multiple values allowed)
+     --and-append Load data fixtures and append to existing data (multiple values allowed)
+     --and-update-schema Update schema after rebuilding all classes
+     --connection The connection to use.