Development

ConvertingPropelProjectToDoctrine

You must first sign up to be able to contribute.

I encourage everyone to add / edit this page to make it more useful to others. In this page I have used and linked to other sources. By no means do I claim ownership/credit or anything else about this page, it's simply a collection of information.

First make sure you understand the doctrine related stuff in the official docs The symfony and Doctrine book.

Starting point: A symfony project with a propel/mysql database.

End point same project functioning with Doctrine/mysql database.

The Symfony documentation is pretty good on describing how to start with doctrine: The symfony and Doctrine book. But when you have an existing project there are some caveats.

Before you begin make a good backup of all your code and data! And be sure you know how to get back to your starting point! Check the compatibility of plugins you may have installed. When there are incompatible plugins you cannot do without, converting is not an option. If doctrine compatibility it is not mentioned try it out, and see. Please note some doctrine built in 'tricks', that you may have installed as plugin for Propel: core behaviours and Doctrine extensions

Change the configuration from Propel to Doctrine (the easy part)

Step 1: Enable Doctrine

In order to begin using Doctrine you must first enable it by editing your config/ProjectConfiguration.class.php setup() method to enable sfDoctrinePlugin and disable sfPropelPlugin.

public function setup()
{
  $this->enablePlugins(array('sfDoctrinePlugin'));
  $this->disablePlugins(array('sfPropelPlugin'));
}

If you prefer to have all plugins enabled by default, you can do the following:

public function setup()
{
  $this->enableAllPluginsExcept(array('sfPropelPlugin', 'sfCompat10Plugin'));
}

When you want to use a different version of Doctrine than the one that came with your Symfony, follow this guide: Symfony Blog

Step 2: change the project configuration

Run the following command:

./symfony configure:database --name=doctrine --class=sfDoctrineDatabase "mysql:host=localhost;dbname=dbname" user secret

Obviously you need to replace dbname, user and secret with your own database information.

step 3: schema files

We now need to get the propel schema into Doctrine. Assuming your project has an existing database with some (testdata) you use the Doctrine task

$ ./symfony doctrine:build-schema

This will convert the database into a schema.yml in /config/doctrine/. The generated schema.yml will have rather meaningless relation names for your tables when a foreign table is referenced more than once. To keep things meaningful, be sure to go through the schema.yml to change the relation names like these:

 relations:
    User:
      local: user_id
      foreign: id
      type: one
    User_2:
      local: updated_by_user_id
      foreign: id
      type: one
    User_3:
      local: deleted_by_user_id
      foreign: id
      type: one

to:

 relations:
    User:
      local: user_id
      foreign: id
      type: one
    UpdatedByUser:
      local: updated_by_user_id
      foreign: id
      type: one
    DeletedByUser:
      local: deleted_by_user_id
      foreign: id
      type: one

You will use the names of your table relations extensively in Doctrine like this: $article->getDeletedByUser()

With schema.yml in place you can generate all your model/form files like this:

$ ./symfony doctrine:build-model
$ ./symfony doctrine:build-forms
$ ./symfony doctrine:build-filters

When you don't care about your data you can also run "./symfony doctrine:build-all"

the not so easy part: converting propel code to doctrine code

At this point you have a Doctrine database connection to you database, all your models, forms and filters have been rewritten in Doctrine. All your old model/form/filter code is still there''' The new files have been placed in a folder named "doctrine" the respective (lib/...) folders.

step 4: the web folder

You are now ready to remove the sfPropelPlugin folder from your /web folder. After that, we have to create a "sfDoctrinePlugin" folder in /web. on *nix you can link to /lib/vendor/symfony/lib/plugins/sfDoctrinePlugin/web by typing (from your project root):

$ ln -s lib/vendor/symfony/lib/plugins/sfDoctrinePlugin/web web/sfDoctrinePlugin

on Windows you need to create the "web/sfDoctrinePlugin" folder and copy the contents of "lib/vendor/symfony/lib/plugins/sfDoctrinePlugin/web" into it.

step 5: Model classes

This is a case of rewriting all the Propel code into Doctrine code. Be sure to read the information in chapter 6 so you know how to retrieve and modify data in Doctrine language. Obviously every project has it's own business logic, but I will state an example.

Propel:

  public function getAverageRating()
  {
    $c = new Criteria;
    $c->addSelectColumn('avg(' . RatingPeer::RATING . ')');
    $c->add(RatingPeer::ID, $this->getId());
    
    $stmt = RatingPeer::doSelectStmt($c);    
    $average = $stmt->fetchAll(PDO::FETCH_COLUMN);
    if(array_key_exists(0, $average)) {
      return floatval($average[0]);
    }
    else
    {
      return 0;
    }
  }

Doctrine:

  public function getAverageRating()
  {
    $q = Doctrine_Query::create()
        ->select('AVG(i.rating) AS rating_avg')
        ->from('Rating i')
        ->where('i.id = ?', $this->getId());
    $average = $q->fetchOne();
    return $average['rating_avg'];
  }

Be careful when you're retrieving data from your models in your actions etc - in Propel, you could retrieve related models by:

  $myModel->getRelatedModel();

Doing this in Doctrine will return an empty Doctrine record if the related record doesn't exist. If you have validation set up on the related model, then doing something similar to the following will fail:

  $obj = $myModel;
  $related = $myModel->getRelatedModel();
  $obj->field = "data";
  $obj->save();

Doctrine will try and save the related model's empty record, which will cause validation to fail. Use the Doctrine method of retrieving data:

  $obj = $myModel;
  $related = $myModel->RelatedModel;
  $obj->field = "data";
  $obj->save();

which won't create an empty record.

step 6: Form classes

The form classes will have things like "sfWidgetFormPropelChoiceMany" in them. Just a matter of doing search/replace of the word "Propel" with the word "Doctrine". I also had some "sfWidgetFormPropelChoice" in there with a Criteria added to filter the choices. You need to change that to be an instance of "Doctrine_Query". You obviously have to change your model method to return this Doctrine_Query instead of the Criteria it did before. forex crescendo review

In the following examples "$customerCriteria" and "$customerQuery" are not magic methods, you need to set these somewhere before in your form class!

Propel:

'type_id'        => new sfValidatorPropelChoice(array('model' => 'Type', 'column' => 'id', 'criteria' => $customerCriteria)),

Doctrine:

'type_id'        => new sfValidatorDoctrineChoice(array('model' => 'Type', 'column' => 'id', 'query' => $customerQuery)),

step 7: admin generator

The admin generator in propel used a propel class to generate your admins:

generator:
  class: sfPropelGenerator
  param:
    model_class:           Brand
    theme:                 admin
    ....

You need to change these to Doctrine like so:

generator:
  class: sfDoctrineGenerator
  param:
    model_class:           Brand
    theme:                 admin
    ...

To test if it works be sure to run ./symfony cc first (also in 'dev' environment)!

Naturally when you have untouched admin modules you can just regenerate them from the CLI.

step 8: routing

In my routing.yml I had some references to the

class: sfPropelRouteCollection

they cannot be found now so they need to be changed to:

class: sfDoctrineRouteCollection

Make sure the case of all references is correct since you generated the model from the database, UpperCase? may have changed into lowerCase.

Last step: Cleanup

You should make sure all the Propel form/filter/model classes are out of the way of the auto-loading mechanism. Since the 'developer-defined' classes are called the same and are in a lower hierarchical part of the folder structure, Symfony will take those first and ignore the Doctrine classes!