Posts tagged Doctrine

Doctrine – Fetching Objects

2

Last night I was reading up on doctrine and came across a link to some basic, yet helpful info.

You should always use array hydration when you only need to data for access-only purposes, whereas you should use the record hydration when you need to change the fetched data.

Interesting. I’ve always tried to always use objects for reading data etc, but. but apparently doctrine has optimized the array population algorithm.

FYI, their call to

$q->execute(array(), Doctrine::HYDRATE_ARRAY);

Appears to be the same as:

$q->fetchArray();

Symfony – Form Save Processes Outlined

1

In the never ending quest to figure out how to successfully embed symfony forms I found myself always opening /symfony/lib/plugins/sfDoctrinePlugin/lib/form/sfFormDoctrine.class.php to study the order of operations for saving data against a form object. Finally figured it was time to post it somewhere where I could easily get to it and may help other devs along the way.

Note: This is a living doc and may be updated over time.

  1. sfFormDoctrine::save($con)
    1. $con->beingTransaction()
    2. $this->doSave($con)
      1. $this->updateObject()
        1. $this->processValues()
        2. $this->object->fromArray($values)
        3. $this->updateObjectEmbeddedForms($values)
      2. $this->object->save($con)
      3. $this->saveEmbeddedForms($con)
    3. $con->commit()
Author_Book Many-To-Many

Symfony – Doctrine – Saving Many-To-Many (M:M) relationships

72

Pretty much one of the hardest things I had to get my head around, saving many-to-many (M:M) relationships in Symfony (using Doctrine), but after some mindset adjustment and a lot of googling, I found it to be very easy to implement. It took several weeks and perusing several different google searches to piece together enough information to finally get my head around this process, hopefully this post will save some babies from being punched due to frustration.

Schema first. Say you have a M:M relationship between books and authors, meaning that an author can write more than one book (many) and a book can have more than one author (many). The schema to support this requires that we have a 3rd table, to which I refer as a linking table. The linking table’s main purpose is to link data from the author’s table to the data from the book’s table, which forces us into 2NF (second normal form), by ensuring we are not saving duplicate data in either the author table or the book table.

Your schema should look something like the following (remember that your table engines must be innodb to establish a FK relationship):

Author_Book Many-To-Many

Author_Book Many-To-Many

Now we need to rebuild the schema so we can rebuild the model to take advantage of these relationships established by the schema.

php symfony doctrine:build-schema

Now that the schema has been rebuilt, we need to do some manual edits to ensure the model files are created correctly. So open /config/doctrine/schema.yml and you should see something like:

[yaml]
Author:
tableName: author
columns:

relations:
LinkingAuthorBook:
local: id
foreign: book_id
type: many
Book:
tableName: book
columns:

relations:
LinkingAuthorBook:
local: id
foreign: author_id
type: many
[/yaml]

After

[yaml highlight="7, 8, 9, 10, 17, 18, 19, 20"]
Author:
tableName: author
columns:

relations:
Book:
class: Book
refClass: LinkingAuthorBook #This will allow you to reference Book rows from an Author object
local: author_id #Local value refers to the current object, in this case Author
foreign: book_id #Foreign value refers to the object you wish to link to from Author, in this case Book
Book:
tableName: book
columns:

relations:
Author:
class: Author
refClass: LinkingAuthorBook #This will allow you to reference Author rows from a Book object
local: book_id #Local value refers to the current object, in this case Author
foreign: author_id #Foreign value refers to the object you wish to link to from Book, in this case Author
[/yaml]

Note the changes made to the after schema.yml file. We added ‘class’ and most importantly, ‘refClass’. You can read more information about refClass @ doctrine’s website.

Now rebuild the model:

php symfony doctrine:build-model

The auto-generated classes created after rebuilding the model will be aware of the M:M relationship you have established in your schema which results in saving these with minimal effort.

As part of the rebuild “stack”, you must also rebuild your filters and forms, and clear your cache. So be sure to do the following as well:

php symfony doctrine:build-filters
php symfony doctrine:build-forms
php symfony cc

Now lets see saving a M:M relationship in action, lets add a select double list to your Author form (assuming you have sfFormExtraPlugin installed), just add the following to /lib/form/doctrine/AuthorForm.class.php:

$this->widgetSchema['book_list']->setOption('renderer_class', 'sfWidgetFormSelectDoubleList');

When you render your Author form, you should now have a double list with all the books you have in your book table. Select a few, then click save, you should see that they were saved against the Author you are currently editing. You can remove, add, etc as you see fit.

That’s it! You set up your app to handle the saving of M:M relationships with (hopefully) minimal effort.

—Edit 5.25.2010

Some people are having a problem with this example with respect to the linking table in the given example. As far as I know, the linking table MUST be present in your schema and available via the resulting model class, otherwise doctrine will not know how to store the link relationship between book and author.

Symfony /w Doctrine – Many to Many (M:M) – Primary Keys are Your Friend

2

If you are having problems with embedded forms for linking tables between many to many relationships, try adding a primary key to the linking table. At first I was using a composite primary key on my linking table and trying to figure out how to get it to work properly with form embedding, but was unable to. So I added a primary key, dropped the composite primary key and everything works as expected. This works well for forms you plan on embedding, if you just want to save M:M relationships via admin double list please visit my post on the topic: http://melikedev.com/2009/12/09/symfony-w-doctrine-saving-many-to-many-mm-relationships/

Symfony – Serializing Data for Save

0

There are times when form data must be serialized before being saved in a database. An example would be saving multiple parameters within an array, then serializing the array, which converts the array into one long string.

Working within Symfony the best place to add the serialize() method is as close to the data as possible, so any calling code will be forced to work the same way, serialize upon create/update, unserialize upon retrieve. To do this you will need to overload the setters in the object class file.

Before

class Object extends BaseObject
{
}

After

class Object extends BaseObject
{
    public function setData($data)
    {
        serialize($data);
        parent::setData($data);
    }

    public function getData()
    {
        return unserialize($this->data);
    }
}

As you can see, in the ‘after’ example, we have overloaded the setData() and getData() methods in the BaseObject class with our own custom methods, which serialize/unserialize the data. We call on the BaseObject::setData() b/c there is some extra data handling that is conducted and we want to maintain that behavior.

One last thing, if you submitted data via a form,  you will need to give the serialized data candidate a special validator:

Before

class BaseForm
{
     $this->setValidators(array(
         'formElement' => new sfValidatorString()
     );
}

class Form extends BaseForm
{
}

After

class BaseForm
{
    $this->setValidators(array(
        'formElement' => new sfValidatorString()
    );
}

class Form extends BaseForm
{
    public function configure()
    {
        $this->validatorSchema['formElement'] = new sfValidatorPass();
    }
}

Without making this change, your ‘formElement’ will be typecast as a string and you will see the word ‘Array’ in your database upon viewing saved records.

Symfony w/ Doctrine Package Over-ride in schema.yml

2

So doctrine allows for the ability to flag a table schema in schema.yml as a package using the following notation:

[yaml]
Table:
tableName:
package:
package_custom_path:
[/yaml]

For some reason Symfony does not allow this type of behavior via the /config/doctrine/schema.yml file. When you try to package your model files you get a “Cannot use package parameter in symfony Doctrine schema files.” error message. You may be asking “Why do I care if I can package model files?”, read on. By default, all the model files get stuffed in /lib/model/doctrine/…

Table1Object.class.php
Table2Object.class.php
Table3Object.class.php
base
  BaseTable1Object.class.php
  BaseTable2Object.class.php
  BaseTable3Object.class.php
sfDoctrineGuardPlugin
  sfGuardFiles
  ...
  base
    BasesfGuardFiles
    ...

Notice that TableXObject files are kind of free floating at the root of /lib/model/doctrine/ whereas the sfGuard files are tucked neatly within their own directory. Now this may seem like a nit-picky sort of situation, but it does make a big diff when you start adding more tables and more plug-ins. IMO it’s much easier if you can package your files accordingly (inside of /lib/model/doctrine/):

<package_name>
  Table1Object.class.php
  Table2Object.class.php
  Table3Object.class.php
  base
    BaseTable1Object.class.php
    BaseTable2Object.class.php
    BaseTable3Object.class.php
sfDoctrineGuardPlugin
  sfGuardFiles
  ...
  base
    BasesfGuardFiles
    ...

Much easier to see follow the logic behind the grouping of files. If you view the source code for sfDoctrineBuildModelTask::_checkForPackageParameter() which controls this behavior, you will see the comments:

/**
 * Check for package parameter in main schema files.
 * sfDoctrinePlugin uses the package feature of Doctrine
 * for plugins and cannot be used by the user
 *
 * @param string $path
 * @return void
 */

Why not? Why just for plug-ins? so I simply edited the method:

Before

protected function _checkForPackageParameter($path)
 {
 $files = sfFinder::type('file')->name('*.yml')->in($path);
 foreach ($files as $file)
 {
 $array = sfYaml::load($file);
 if (is_array($array) AND !empty($array))

After

protected function _checkForPackageParameter($path)
 {
  return true;
 $files = sfFinder::type('file')->name('*.yml')->in($path);
 foreach ($files as $file)
 {
 $array = sfYaml::load($file);
 if (is_array($array) AND !empty($array))

This will allow you to now edit your schema.yml file to group your files as described above. For example:

Before

[yaml]
Table1:
tableName:
columns:

[/yaml]

After

[yaml highlight="3"]
Table1:
tableName:
package:
columns:

[/yaml]

When I rebuilt model, all my files were correctly stored as expected… I’m not sure why Symfony devs would restrict this behavior for doctrine whereas propel allows it, and it’s available to vanilla Doctrine.

Symfony (Admin Generator) with Doctrine using Checkboxes Pitfall

4

In working with Symfony’s Admin Generator I was having one hell of a time trying to get a boolean column (tinyint(1)) to show up on the form as a checkbox. At first I was trying to over-ride the behavior in the lib/form/doctrine/<object>Form.class.php file, which worked on render but failed on submit.

After asking in #symfony on irc.freenode.net I was informed I needed to change the data type in config/doctrine/schema.yml file from the exact mapping via the mysql data type. Check it out:

vi config/doctrine/schema.yml

Before

[yaml]
Object:
tableName: someName
columns:
some_boolean_column:
type: integer(1)
default: ’0′
notnull: true
[/yaml]

After

[yaml highlight="5"]
Object:
tableName: someName
columns:
some_boolean_column:
type: boolean
[/yaml]

After this I ran the following:

php symfony doctrine:build-model
php symfony doctrine:build-filters
php symfony doctrine:build-forms
php symfony cc

After that the form rendered and validated as expected.

Doctrine (within Symfony) Timestampable

0

For most tables there is usually a need to track when a row was created or updated. Doctrine allows for this type of behavior but in a different way. If your table has created_at and updated_at columns (or just one or the other) you can tell doctrine to automatically set the correct timestamp. The datatype I use for my created_at and updated_at columns are ‘datetime’ so my schema would look like:

...
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL
...

To ensure that your columns are updated you will need to edit your config/doctrine/schemal.yml file and add the following lines:

Before

[yaml]
tableName: objects
columns:
id:
type: integer(4)
primary: true
autoincrement: true
name:
type: string(50)
notnull: true
[/yaml]

After

[yaml highlight="2,3,4,5,6,7,8,9"]
tableName: objects
actAs:
Timestampable:
created:
name: created_at
type: timestamp
updated:
name: updated_at
type: timestamp
columns:
id:
type: integer(4)
primary: true
autoincrement: true
name:
type: string(50)
notnull: true
[/yaml]

Now that you edited your schema file you will need to rebuild your model so that the php model files know that you will be updating these columns during saves:

php symfony doctrine:build-model

And as always, clear your cache so that your code will be using the latest version of model files:

php symfony cc

For more information on timestample /w doctrine, visit their website.

Doctrine vs Propel

0

Given 1 table that houses a particular object /w related attributes, say something like car; color, num_of_doors, fuel_type.

Doctrine = only 3 Files

  • Car.class
  • CarTable.class.php
  • base/CarBase.class.php

Propel = 5 Files

  • Car.php
  • CarPeer.php
  • map/CarMapBuilder.php
  • om/CarObject.php
  • om/CarObjectPeer.php

Symfony /w Doctrine Setup Guide

0

Abstract

A quick cheat guide for setting up symfony (/w doctrine). This assumes you already have your html being served from respective dir.

Setup Symfony Environment

cd /path/to/where/you/want/symfony
wget http://www.symfony-project.org/get/symfony-1.2.9.tgz
tar -xvpf symfony-1.2.9.tgz
ln -s /path/to/symfony/symfony-1.2.9 symfony

Generate  Symfony Project

cd /path/to/html
php /path/to/symfony/data/bin/symfony generate:project <project_name>

Generate Symfony Project App

–escaping-strategy prevents XSS (cross site scripting) attacks, and  –csrf-secret prevents CSRF (Cross Site Request Forgery) attacks. CSRF uses <super_secret_salt> to hash out values on all your form pages as hidden elements so we enforce that any requests made to form actions orgininated from our site, and not someone elses.

php symfony generate:app --escaping-strategy=on --csrf-secret=<super_secret_salt> <app_name>

Enable Doctrine for Symfony

vi config/ProjectConfiguration.class.php
$this->enablePlugins(array('sfDoctrinePlugin'));

OR

$this->enableAllPluginsExcept(array('sfPropelPlugin', 'sfCompat10Plugin'));

Clear Cache

php symfony cc

Publish Assets

This step lets symfony know that you have some plugins you are ready to use.

php symfony plugin:publish-assets

Remove Propel Artifacts

rm web/sfPropelPlugin
rm config/propel.ini config/schema.yml config/databases.yml

Add Doctrine Artifacts

mkdir config/doctrine

Create databases.yml File

php symfony configure:database --name=doctrine --class=sfDoctrineDatabase "mysql:host=<host_name>;dbname=<db_name>" <db_user> <db_password>

Secure databases.yml File

chmod 600 config/databases.yml

Build Schema

Building schema will create a config/doctrine/schema.yml file. It is basically a text version of your database schema. You can edit as you see fit, but for most users it isn’t necessary, but you should at least check it out to see what’s going on.

php symfony doctrine:build-schema
vi config/doctrine/schema.yml

Doctrine Options Configuration (optional)

You can over-ride doctrine model generation options by create a configureDoctrin() method in config/ProjectConfiguration.class.php file. These settings allow you to control elements of the model creation, for example; I dont like creating the extraneous ‘*Tables’ model files so I set ‘generateTableClasses’ to false, like so:

vi config/ProjectConfiguration.class.php
public function configureDoctrine()
{

    $options = array(
        'generateTableClasses' => FALSE
    );

    sfConfig::set('doctrine_model_builder_options', $options);

}

You can find more information and configuration options on the Symfony Website under Doctrine Configuration.

Symfony Model Generation

The ‘model’ refers to the files that allow access to data stored within your database. These files are created automatically based on your config/doctrine/schema.yml file and placed in /lib/model/doctrine/

php symfony doctrine:build-model

Setup Symfony Module

Only use this if you DO NOT PLAN on using Symfony’s Admin Generator, otherwise you may run into issues. In order to start editing actions and template scripts, you need to run the following command in /path/to/project/apps/<app_name>:

php symfony generate:module <app_name> <module_name>

Setup Symfony default css

This will give access to the symfony default css so that web toolbar and default symfony pages look nice.

cd web
ln -s /path/to/symfony/symfony/data/web/sf sf

Enable Login Authentication (optional)

This will protect your app by forcing a user to login to view any app files. You will have to add code to handle login authentication and is covered in my sfGuardDoctrine plugin post.

vi apps/<app_name>/config/security.yml

Before

[yaml]
default:
is_secure: off
[/yaml]

After

[yaml highlight="2"]
default:
is_secure: on
[/yaml]

Allow Access to Dev Controller

If you are developing on an remote environment and you need access to the dev controller (<app>_dev.php),  you will have to add your IP address to <app>_dev.php, as it defaults to localhost.

EOF

If you followed each of the above steps, you have successfully created a symfony project, app, module. You can access your project using the following links:

App: http://yourdomain.com/<app_name>_dev.php (should see success project landing page)

Module: http://yourdomain.com/<app_name>_dev.php/<module_name> (should see success module landing page)

Forms, Actions, etc will be discussed in other setup guides.

Go to Top