Do not unset primary key fields in object forms!

September 17, 2009

The issue

Well I thought I can just unset the primary key field in a Doctrine object form (for security reasons for example). This field didn’t seem to be necessary  if you want to update an object because the object is retrieved via a Doctrine route and provided to the form (in the generated modules). I have to add that my object has a unique property that the user is allowed to change. The curious thing is that the form is valid if I provide a new value for this property but that I get an error for this field if I don’t change it.

Investigating…

Suppose you have a form that enables the user to change a unique property of an object. To ensure that the value is unique, an ‘sfValidatorDoctrineUnique’ is set for the form field. How does this validator work? It tries to get one object with the provided value as you can see here:

<p>class sfValidatorDoctrineUnique extends sfValidatorSchema
{
  //...
  protected function doClean($values)
  {
    //...
    $table = Doctrine::getTable($this->getOption('model'));
    //...
    $q = Doctrine_Query::create()
    ->from($this->getOption('model') . ' a');
    foreach ($this->getOption('column') as $column)
    {
      $colName = $table->getColumnName($column);
       //...
      $q->addWhere('a.' . $colName . ' = ?', $values[$column]);
    }
    $object = $q->fetchOne();
    // if no object or if we're updating the object, it's ok
    if (!$object || $this->isUpdate($object, $values))
    {
      return $originalValues;
    }
   //...
 }

If no object is returned (!$object) or if the method isUpdate() returns true, the value is valid.
Obviously the field is valid if we provide a new unique value for this column (which results in an empty $object). But what happens if we don’t want to change that value? Lets have a look at the isUpdate() method:

protected function isUpdate(Doctrine_Record $object, $values)
{
  // check each primary key column
  foreach ($this->getPrimaryKeys() as $column)
  {
     if (!isset($values[$column]) || $object->$column != $values[$column])
     {
        return false;
     }
 }
return true;
}

As you can see the method checks for every primary key column if a value is provided for it via a form field. If not, the objects are not considered to be the same.

Conclusion

If you have an edit form for an object that has a unique field and a ‘sfValidatorDoctrineUnique’ object applied to it, don’t unset the primary key fields. They are needed to verify that the objects are the same.

For those who really want to do it

To make it work it would be necessary to provide the the current object (retrieved via a Doctrine route) to the validator (via options) and then compare the primary keys of the provided object with those of the found object. Something like this:

<p>protected function isUpdate(Doctrine_Record $object)
{
  // check each primary key column
  foreach ($this->getPrimaryKeys() as $column)
  {
    if ($object->$column != $this->getOption('object')->$column)
    {
      return false;
    }
  }
  return true;
}

But that would result in a custom validator and you have to set it manually.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s

%d bloggers like this: