Search code examples
phpyiiyii-components

Observer design pattern in Yii 1.1.16, attaching event handlers and rising events


In Yii 1.1.16 Observer Design Pattern is implemented using events & behaivours (events can be shared between all components that extends CComponents). I have following models:

  1. User.php (see below)
  2. Work.php (see below)
  3. Activity.php

what I want to accomplish is following: lets say in DefaultController:

<?php
  public function actionExampleWork()
  {
    $work = new Work();
    $work->description = 'some random description';
    $work->created_at = new CDbException('NOW()');
    $work->user_id = 1;

    //$work->attach() INVOKE EVENT that will be handled by all models which listen to this event

    $work->save();
 }
 public function actionExampleUser()
 {
    $user = new User();
    $user->email = '[email protected]';
    $user->username = 'example';
    $user->password = '123';

    //$user->attach( something ) invoke Event that should be handled only by Activity model

    $user-> save();
 }

?>

actually I have saw lot of examples Yii doing related things, but so far no success on finding answer that fit's my needs :(

  • Is this possible in Yii?
  • How can I implement this?
  • **Is there an alternatives (except manually handling method calls in controller actions)? **

User.php

<?php class User extends CActiveRecord
{
public function tableName()
{
    //@return string the associated database table name
};

public function rules()
{
    //@return array validation rules for model attributes.
};

public function relations()
{
    return array(
        'works' => [self::HAS_MANY, 'Works', 'user_id'],
    );
}

public function attributeLabels()
{
    //@return array customized attribute labels (name=>label)
}

public function search()
{
    //@return CActiveDataProvider the data provider that can return the models
    // based on the search/filter conditions.
}
public function updateLastActivity($user_id, $activity_type){

   $user = $user->model()->findByPk($user_id);//find user
   $user->activity_type = $activity_type;
   $user->last_action = new CDbExpression('NOW()');//update DB column
   $user->save; //save user

}
} ?>

Work.php

<?php

class Work extends CActiveRecord
{
public function tableName()
{
    //@return string the associated database table name
}

public function rules()
{
    //@return array validation rules for model attributes.
}

public function relations()
{
    return array(
        'user' => [self::BELONGS_TO, 'User', 'user_id'],
    );
}

public function attributeLabels()
{
    //@return array customized attribute labels (name=>label)
}

public function search()
{
    //@return CActiveDataProvider the data provider that can return the models
    // based on the search/filter conditions.
}


?>

Solution

  • You don't need an observer class for your scenario.

    Using Built in Event Handlers The Yii model class has a few built in functions that you can overwrite in your class.

    Have a look at the official documentation for CActiveRecord for more detaiils. http://www.yiiframework.com/doc/api/1.1/CActiveRecord HINT: Search for beforeXXXX, afterXXXX, onXXXXX

    As an example, here is my pre-persist handler for my user model.

        /**
     * Runs just before the models save method is invoked. It provides a change to
     * ...further prepare the data for saving. The CActiveRecord (parent class)
     * ...beforeSave is called to process any raised events.
     *
     * @param <none> <none>
     * @return boolean the decision to continue the save or not.
     *
     * @access public
     */
    
    public function beforeSave() {
        // /////////////////////////////////////////////////////////////////////
        // Some scenarios only require certain fields to be updated. We handle
        // ...this separately.
        // /////////////////////////////////////////////////////////////////////
    if ($this->scenario == self::SCENARIO_LOGIN)
    {
        /** Login scenario */
        $this->last_login = new CDbExpression('NOW()');
    }
    
    if ($this->scenario == self::SCENARIO_ACTIVATION)
    {
        /** Account activation scenario */
    
        if ($this->activation_status == 'activated')
        {
            $this->activation_code = '';
            $this->status          = 'active';
            $this->activation_time = new CDbExpression('NOW()');
        }
    }
    
    if ( ($this->scenario == self::SCENARIO_CHANGE_PASSWORD) ||
         ($this->scenario == self::SCENARIO_REGISTER) ||
         ($this->scenario == 'insert') ||
         ($this->scenario == 'update')
       )
    {
        /** Password change scenario */
    
        // /////////////////////////////////////////////////////////////////////
        // Encrypt the password. Only do this if the password is set
        // /////////////////////////////////////////////////////////////////////
        if (isset($this->password) && (!empty($this->password)))
        {
            $this->password    =  CPasswordHelper::hashPassword($this->password);
    
        }
    }
    
    
    /** All other scenarios */
    
    // /////////////////////////////////////////////////////////////////////
    // Set the create time and user for new records
    // /////////////////////////////////////////////////////////////////////
    if ($this->isNewRecord) {
        $this->created_time = new CDbExpression('NOW()');
        $this->created_by   = '1';  // Special case for not logged in user
        $this->modified_by  = '1';
    }
    else
    {
        $this->modified_by   = isset(Yii::app()->user->id)?Yii::app()->user->id:1;
    }
    
    // /////////////////////////////////////////////////////////////////////
    // The modified log details is set for record creation and update
    // /////////////////////////////////////////////////////////////////////
    $this->modified_time = new CDbExpression('NOW()');
    
    return parent::beforeSave();
    }
    

    Notice that I have full access to the record BEFORE the save happens (ie. no access to primary-key when a new record is being processed.

    Look out also for built in scenarios 'insert' and 'update'.

    Note also that I invoke the parent beforeSave before exiting to cascade events in the parents.

    Using Custom Events

    You can create custom events.

    Create an event class eg. protected/components/Newuser.php:

    class NewUser extends CModelEvent{
        public $userRecord;
    }
    

    Then in your controller

    $userModel->attachEventHandler('onNewUser',newuserEventHandler);
    
    function newuserEventHandler($event){
          // Add custom handling code here 
          do_some_things_here($event->new, $event->id);
        }
    

    In your model, you need to raise the event

    $event=new NewUserEvent;
    // Add custom data to event.
    $event->new=$this->isNewRecord;
    $event->order=$this->id;
    $this->raiseEvent('onNewUser',$event);