Code snippets for symfony 1.x

Navigation

Refine Tags

Snippets tagged "doctrine updated"

Automatically updated 'updated_by' and 'created_by' doctrine columns

Our sf1.4 project required recording the user_id of the person who updated or created a record as well as the timestamps and was using the sfDoctrineGuard plugin.

There are several ways of achieving this but the additional requirement of having constraints on these fields on a db level, being able to load fixtures and have the whole code properly unit testable made it a bit more difficult. why-sfcontextgetinstance-is-bad

After experimenting with other methods like custom save method and the following snippet snippet 281 I came to the following code:

Using actAs Timestampable was an option for the timestamps, but I have chosen to add a new Doctrine template and listener that has the timestamp incorporated so that every update/save only triggers one listener instead of two.

config/doctrine/schema.yml:

Address:
  columns:
    id:
      type: integer(4)
      primary: true
      autoincrement: true
    address_1:
      type: varchar(50)
    address_2:
      type: varchar(50)
    postal_code:
      type: varchar(6)
  relations:
    userCreate:
      class: sfGuardUser
      local: creater_id
      foreign: id
      foreignType: one
      onDelete: RESTRICT
      onUpdate: CASCADE
    userUpdate:
      class: sfGuardUser
      local: updater_id
      foreign: id
      foreignType: one
      onDelete: RESTRICT
      onUpdate: CASCADE
  actAs:
    Auditable:
  options:
    type: INNODB
    collate: utf8_unicode_ci
    charset: utf8
 

The relations part of the schema is only needed if you want to maintain data integrity.

The names of the creater and updater fields are defaulted to 'creater_id' and 'updater_id' but can be changed in the schema as per normal.

The template looks as follows and copies the logic from the timestampable template:

lib/doctrine/Doctrine/Template/Auditable.php:

<?php
class Doctrine_Template_Auditable extends Doctrine_Template
{
    /**
     * Array of userid options
     *
     * @var string
     */
    protected $_options = array(
        'creater' =>  array(
            'name'          => 'creater_id',
            'alias'         => null,
            'type'          => 'int',
            'disabled'      => false,
            'expression'    => false,
            'options'       =>  array('notnull' => true)
        ),
        'updater' =>  array(
            'name'          => 'updater_id',
            'alias'         => null,
            'type'          => 'int',
            'disabled'      => false,
            'expression'    => false,
            'onInsert'      => true,
            'options'       =>  array('notnull' => true)
        ),
        'created' =>  array(
            'name'          =>  'created_at',
            'alias'         =>  null,
            'type'          =>  'timestamp',
            'format'        =>  'Y-m-d H:i:s',
            'disabled'      =>  false,
            'expression'    =>  false,
            'options'       =>  array('notnull' => true)
        ),
        'updated' =>  array(
            'name'          =>  'updated_at',
            'alias'         =>  null,
            'type'          =>  'timestamp',
            'format'        =>  'Y-m-d H:i:s',
            'disabled'      =>  false,
            'expression'    =>  false,
            'onInsert'      =>  true,
            'options'       =>  array('notnull' => true)
        )
    );
 
 
    /**
     * setTableDefinition
     *
     * @return void
     */
    public function setTableDefinition()
    {
        if(!$this->_options['creater']['disabled']) {
            $name = $this->_options['creater']['name'];
            if ($this->_options['creater']['alias']) {
                $name .= ' as ' . $this->_options['creater']['alias'];
            }
            $this->hasColumn(
                $name,
                $this->_options['creater']['type'],
                null,
                $this->_options['creater']['options']
            );
        }
        if(!$this->_options['updater']['disabled']) {
            $name = $this->_options['updater']['name'];
            if ($this->_options['updater']['alias']) {
                $name .= ' as ' . $this->_options['updater']['alias'];
            }
            $this->hasColumn(
                $name,
                $this->_options['updater']['type'],
                null,
                $this->_options['updater']['options']
            );
        }
        if ( ! $this->_options['created']['disabled']) {
            $name = $this->_options['created']['name'];
            if ($this->_options['created']['alias']) {
                $name .= ' as ' . $this->_options['created']['alias'];
            }
            $this->hasColumn(
                $name,
                $this->_options['created']['type'],
                null,
                $this->_options['created']['options']
            );
        }
        if ( ! $this->_options['updated']['disabled']) {
            $name = $this->_options['updated']['name'];
            if ($this->_options['updated']['alias']) {
                $name .= ' as ' . $this->_options['updated']['alias'];
            }
            $this->hasColumn(
                $name,
                $this->_options['updated']['type'],
                null,
                $this->_options['updated']['options']);
        }
 
        $this->addListener(new Doctrine_Template_Listener_Auditable($this->_options));
    }
}
 

The listener that makes sure the fields are set before saving looks like this:

lib/doctrine/Doctrine/Template/Listener/Auditable.php:

<?php
class Doctrine_Template_Listener_Auditable extends Doctrine_Record_Listener
{
    /**
     * Array of userid options
     *
     * @var string
     */
    protected $_options = array();
 
    /**
     * __construct
     *
     * @param string $options.
     * @return void
     */
    public function __construct(array $options)
    {
        $this->_options = $options;
    }
 
    /**
     * preInsert
     *
     * @param Doctrine_Event $event.
     * @return void
     */
    public function preInsert(Doctrine_Event $event)
    {
        if(!$this->_options['creater']['disabled']) {
            $createrName = $this->_options['creater']['name'];
            $event->getInvoker()->$createrName = $this->getUserId($event);
        }
 
        if(!$this->_options['updater']['disabled'] && $this->_options['updater']['onInsert']) {
            $updaterName = $this->_options['updater']['name'];
            $event->getInvoker()->$updaterName = $this->getUserId($event);
        }
 
        if ( ! $this->_options['created']['disabled']) {
            $createdName = $event->getInvoker()->getTable()->getFieldName($this->_options['created']['name']);
            $modified = $event->getInvoker()->getModified();
            if ( ! isset($modified[$createdName])) {
                $event->getInvoker()->$createdName = $this->getTimestamp('created', $event->getInvoker()->getTable()->getConnection());
            }
        }
 
        if ( ! $this->_options['updated']['disabled'] && $this->_options['updated']['onInsert']) {
            $updatedName = $event->getInvoker()->getTable()->getFieldName($this->_options['updated']['name']);
            $modified = $event->getInvoker()->getModified();
            if ( ! isset($modified[$updatedName])) {
                $event->getInvoker()->$updatedName = $this->getTimestamp('updated', $event->getInvoker()->getTable()->getConnection());
            }
        }
    }
 
    /**
     * preUpdate
     *
     * @param Doctrine_Event $event.
     * @return void
     */
    public function preUpdate(Doctrine_Event $event)
    {
        if( ! $this->_options['updater']['disabled']) {
            $updaterName = $this->_options['updater']['name'];
            $event->getInvoker()->$updaterName = $this->getUserId($event);
        }
 
        if ( ! $this->_options['updated']['disabled']) {
            $updatedName = $event->getInvoker()->getTable()->getFieldName($this->_options['updated']['name']);
            $modified = $event->getInvoker()->getModified();
            if ( ! isset($modified[$updatedName])) {
                $event->getInvoker()->$updatedName = $this->getTimestamp('updated', $event->getInvoker()->getTable()->getConnection());
            }
        }
    }
 
    /**
     * getUserId
     *
     * Gets the userid
     *
     * @param Doctrine_Event $event.
     * @return int
     */
    public function getUserId(Doctrine_Event $event)
    {
      if (!$event->getInvoker()->getSystemUser())
      {
        throw new LogicException('The user must be set before saving');
      }
      return $event->getInvoker()->getSystemUser();
    }
 
    /**
     * Set the updated field for dql update queries
     *
     * @param Doctrine_Event $event
     * @return void
     */
    public function preDqlUpdate(Doctrine_Event $event)
    {
        if ( ! $this->_options['updated']['disabled']) {
            $params = $event->getParams();
            $updatedName = $event->getInvoker()->getTable()->getFieldName($this->_options['updated']['name']);
            $field = $params['alias'] . '.' . $updatedName;
            $query = $event->getQuery();
 
            if ( ! $query->contains($field)) {
                $query->set($field, '?', $this->getTimestamp('updated', $event->getInvoker()->getTable()->getConnection()));
            }
        }
    }
 
    /**
     * Gets the timestamp in the correct format based on the way the behavior is configured
     *
     * @param string $type
     * @return void
     */
    public function getTimestamp($type, $conn = null)
    {
        $options = $this->_options[$type];
 
        if ($options['expression'] !== false && is_string($options['expression'])) {
            return new Doctrine_Expression($options['expression'], $conn);
        } else {
            if ($options['type'] == 'date') {
                return date($options['format'], time());
            } else if ($options['type'] == 'timestamp') {
                return date($options['format'], time());
            } else {
                return time();
            }
        }
    }
}
 

As you can see, the user_id is retrieved from the object that is saved via the getSystemUser() method.

To not have to duplicate this method on each of the model classes, I made my base classes extend a custom class. This can to be done by adding a configureDoctrine function to the ProjectConfiguration class

confid/ProjectConfiguration.class.php:

public function configureDoctrine(Doctrine_Manager $manager)
  {
    // use a custom base class that has the system user methods used for updating
    // the creater and updater.
    $options = array('baseClassName' => 'MyDoctrineRecord');
    sfConfig::set('doctrine_model_builder_options', $options);
 
    // user id that is used when no logged in user is present. This is required
    // to be able to load fixtures.
    sfConfig::set('default_updater_id',1);
  }
 

After making these changes and regenerating the sql and model, all your base classes will now extend MyDoctrineRecord which is automatically created if not present already.

The created class is empty and we still need to add the custom functions to it:

lib/model/doctrine/MyDoctrineRecord.class:

<?php
 
abstract class MyDoctrineRecord extends sfDoctrineRecord
{
  protected $systemUserId = null;
 
  public function preSave($event)
  {
    if (!$this->getSystemUser())
    {
      if(!sfContext::hasInstance() || get_called_class()=='sfGuardUser')
      {
        $userId = sfConfig::get('default_updater_id');
        $this->setSystemUser($userId);
      }
      else
      {
        $this->setSystemUser(sfContext::getInstance()->getUser()->getGuardUser()->getId());
      }
    }
  }
 
  public function setSystemUser($userId)
  {
    $this->systemUserId = $userId;
  }
 
  public function getSystemUser()
  {
    return $this->systemUserId;
  }
}
?>
 

The above code allows for insertion of a userId for testing perposes. It will use the default_updater_id that is set in the projectConfiguration. This default is required for the field restrictions to play nice. Without this, it would try and save null which is not accepted by our database because it is not a valid user.

When a user is logged in, the userId of that user will be used.

note: If you add actAs: Auditable to the sfDoctrineGuard schema you even can track who made changes to the permissions tables.

2010-08-04: Updated the MyDoctrineRecord.class code to see if it was called by sfGuardUser. Not sure what goes wrong but I am sure it has to do with the fact that you can't get the logged in user before you are completely logged in.

2011-06-14: Updated the MyDoctrineRecord.class and replaced the save() function with preSave() as the system user was not being set in certain instances.

by Marcel Berteler on 2010-04-16, tagged created  doctrine  sfdoctrineguard  template  updated  userid  username 
(2 comments)

Add `created_by` and `updated_by` columns to Doctrine

I wanted to add created_by and updated_by columns to my Doctrine schema. But the existing templates doesn't support that, so i've rewritten the Timestampable template.

Now Doctrine can automatically update these fields to the specific username oder userid.

config/doctrine/schema.yml:

ExampleUser:
  tableName: example_user
  columns:
    id:
      primary: true
      autoincrement: true
      type: integer(4)
    name:
      type: string(255)
    password:
      type: string(255)
  actAs:
    Userid:
      created:
        name: created_by
        type: integer
      updated:
        name: updated_by
        type: string
 

If you are writing integer as type the template will call $sf_user->getId(), if you write string it will call $sf_user->getUsername().

So the above example will write the ID to created_by column and the username to updated_by column.

lib/doctrine/Doctrine/Template/Userid.php:

<?php
/*
 *  $Id$
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * and is licensed under the LGPL. For more information, see
 * <http://www.phpdoctrine.org>.
 */
 
/**
 * Doctrine_Template_Userid
 *
 * Easily add created and updated by ids or usernames to your doctrine records
 *
 * @package     Doctrine
 * @subpackage  Template
 * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
 * @link        www.phpdoctrine.org
 * @since       1.0
 * @version     $Revision$
 * @author      Thomas Boerger <tb@mosez.net>
 */
class Doctrine_Template_Userid extends Doctrine_Template
{
    /**
     * Array of userid options
     *
     * @var string
     */
    protected $_options = array(
        'created' =>  array(
            'name'          =>  'created_by',
            'type'          =>  'integer',
            'disabled'      => false,
            'expression'    => false,
            'options'       =>  array()
        ),
        'updated' =>  array(
            'name'          =>  'updated_by',
            'type'          =>  'integer',
            'disabled'      => false,
            'expression'    => false,
            'onInsert'      => true,
            'options'       =>  array()
        )
    );
 
    /**
     * __construct
     *
     * @param string $array.
     * @return void
     */
    public function __construct(array $options)
    {
        $this->_options = Doctrine_Lib::arrayDeepMerge($this->_options, $options);
    }
 
    /**
     * setTableDefinition
     *
     * @return void
     */
    public function setTableDefinition()
    {
        if(!$this->_options['created']['disabled']) {
            $this->hasColumn(
                $this->_options['created']['name'],
                $this->_options['created']['type'],
                null,
                $this->_options['created']['options']
            );
        }
        if(!$this->_options['updated']['disabled']) {
            $this->hasColumn(
                $this->_options['updated']['name'],
                $this->_options['updated']['type'],
                null,
                $this->_options['updated']['options']
            );
        }
        $this->addListener(new Doctrine_Template_Listener_Userid($this->_options));
    }
}
 

lib/doctrine/Doctrine/Template/Listener/Userid.php:

<?php
/*
 *  $Id$
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * and is licensed under the LGPL. For more information, see
 * <http://www.phpdoctrine.org>.
 */
 
/**
 * Doctrine_Template_Listener_Userid
 *
 * @package     Doctrine
 * @subpackage  Template
 * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
 * @link        www.phpdoctrine.org
 * @since       1.0
 * @version     $Revision$
 * @author      Thomas Boerger <tb@mosez.net>
 */
class Doctrine_Template_Listener_Userid extends Doctrine_Record_Listener
{
    /**
     * Array of userid options
     *
     * @var string
     */
    protected $_options = array();
 
    /**
     * __construct
     *
     * @param string $options.
     * @return void
     */
    public function __construct(array $options)
    {
        $this->_options = $options;
    }
 
    /**
     * preInsert
     *
     * @param object $Doctrine_Event.
     * @return void
     */
    public function preInsert(Doctrine_Event $event)
    {
        if(!$this->_options['created']['disabled']) {
            $createdName = $this->_options['created']['name'];
            $event->getInvoker()->$createdName = $this->getUserId('created');
        }
 
        if(!$this->_options['updated']['disabled'] && $this->_options['updated']['onInsert']) {
            $updatedName = $this->_options['updated']['name'];
            $event->getInvoker()->$updatedName = $this->getUserId('updated');
        }
    }
 
    /**
     * preUpdate
     *
     * @param object $Doctrine_Event.
     * @return void
     */
    public function preUpdate(Doctrine_Event $event)
    {
        if( ! $this->_options['updated']['disabled']) {
            $updatedName = $this->_options['updated']['name'];
            $event->getInvoker()->$updatedName = $this->getUserId('updated');
        }
    }
 
    /**
     * getUserId
     *
     * Gets the userid or username depending on field format
     *
     * @param string $type.
     * @return void
     */
    public function getUserId($type)
    {
        $options = $this->_options[$type];
 
        if ($options['expression'] !== false && is_string($options['expression'])) {
            return new Doctrine_Expression($options['expression']);
        } elseif (!class_exists("pakeApp")) {
            switch($options['type']) 
            {
                case 'integer':
                    if (class_exists('sfGuardUser')) {
                      return sfContext::getInstance()->getUser()->getAttribute('user_id', null, 'sfGuardSecurityUser');
                    } else {
                      return sfContext::getInstance()->getUser()->getId();
                    }
                    break;
                case 'string':
                    return sfContext::getInstance()->getUser()->getUsername();
                    break;
                default:
                    return 'n/a';
                    break;
            }
        }
    }
}
 
by Thomas Boerger on 2008-03-10, tagged created  doctrine  template  updated  userid  username 
(6 comments)