Doctrine
Doctrine – PHP – Compare two Doctrine_Record Objects
0As part of a recent project to reduce the number of “rogue” update statements issued when a Symfony form (with embedded forms) was saved I had to create an algorithm to compare two Doctrine_Record objects, to determine if an update should occur or not, so I thought I would share with the community.
Below is the logic, however it does not follow the DRY (Don’t Repeat Yourself) principle, I did it this way on purpose so you can easily implement into your codebase.
$array1 = array('id' => 1, 'name' => 'element1');
$array2 = array('id' => 1, 'name' => 'element1');
// For this too work, these objects must extend from Doctrine_Record class
$object1 = new Object();
$object2 = new Object();
// fromArray will hydrate the object with values in the array
$object1->fromArray($array1);
$object2->fromArray($array2);
// Due to PHP being a loosely typed language, there were instances where, after hydration, some is_numeric scalars were strings or ints
// So this will ensure all is_numeric scalars are ints, which ensures an apples to apples comparison
$objectArray1 = array_map(function($value) { return (is_numeric($value)) ? (int) $value : $value; }, $object1->toArray(false));
$objectArray2 = array_map(function($value) { return (is_numeric($value)) ? (int) $value : $value; }, $object2->toArray(false));
$hash1 = md5(serialize($objectArray1));
$hash2 = md5(serialize($objectArray2));
$compareStatus = ($hash1 === $hash2) ? true : false;
Symfony – Doctrine – Call to a member function evictAll on a non-object
1If you are working with Symfony and Doctrine and encounter a “Call to a member function evictAll() on a non-object” error message, check to see if you are using DQL for any delete queries. I just came across this error and after some googling (mostly about bad schema files) I couldn’t fix the issue. I then checked a custom DQL statement which unlinks rows and saw that I had aliased the main table with ‘caa’, but the other references were ‘ca’ (only one a, instead of two). Once I fixed the alias issue, everything worked as expected.
Doctrine – Fetching Objects
2Last 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
1In 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.
- sfFormDoctrine::save($con)
- $con->beingTransaction()
- $this->doSave($con)
- $this->updateObject()
- $this->processValues()
- $this->object->fromArray($values)
- $this->updateObjectEmbeddedForms($values)
- $this->object->save($con)
- $this->saveEmbeddedForms($con)
- $this->updateObject()
- $con->commit()
Symfony – Doctrine – Saving Many-To-Many (M:M) relationships
75Pretty 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
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:
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
After
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
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
4If 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
0There 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 (Admin Generator) with Doctrine using Checkboxes Pitfall
4In 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
Object:
tableName: someName
columns:
some_boolean_column:
type: integer(1)
default: '0'
notnull: true
After
Object:
tableName: someName
columns:
some_boolean_column:
type: boolean
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.