Yii 1.1: models

Extension contains commonly used, extended models base classes, which is so lacking in Yii for developing good MVC application.
22 followers

This extensions contains commonly used, extended models for working with database.

Requirements

...requirements of using this extension (e.g. Yii 1.1.10 or above)...

Usage

To use this extension, please, import as needed aliases in application import property.

1. SearchModel base class for modeling select data

Example:

class MySearchModel extends SearchModel
{
    const DEFAULT_PAGE_SIZE = 20;
 
    const SCOPE_ONLY_ACTIVE = 'only_active';
    const SCOPE_WITH_COMMENTS = 'with_comments';
 
    public $field_name;
 
    public function getModelClass()
    {
        return 'Post';
    }
 
    protected function build()
    {
       $criteria = new CDbCriteria();
       if (isset($this->field_name)) {
           $criteria->compare('t.name', $this->field_name, true);
       }
       if ($this->hasScope(self::SCOPE_ONLY_ACTIVE)) {
           $criteria->compare('t.status', 'active');
       }
       if ($this->hasScope(self::SCOPE_WITH_COMMENTS)) {
           $criteria->with[] = 'comments';
       }
       $criteria->together = true;
       $config = array('criteria' => $criteria);
       return new CActiveDataProvider($this->getModelClass(), $config);
    }
 
    public function getDefaultPageSize()
    {
       return self::DEFAULT_PAGE_SIZE;
    }
 
    public function getDefualtSortMode()
    {
       return self::SORT_MODE_ALPHABETICAL;
    }
 
    protected function applySort($criteria)
    {
        switch ($this->sort) {
        case self::SORT_MODE_ALPHABETICAL:
            $criteria->order .= 't.name';
            break;
        case self::SORT_MODE_DATE:
            $criteria->order .= 't.time';
            break;
        }
        parent::applySort($criteria);
    }
}

SearchModel usage:

class MyController extends CController
{
    //...
 
    public function actionList()
    {
        $model = new MySearchModel();
        $model->addScope(MySearchModel::SCOPE_WITH_COMMENTS);
        if (isset($_POST['MySearchModel'])) {
            $model->attributes = $_POST['MySearchModel'];
        }
        $viewData = array(
            'dataProvider' => $model->search(),
        );
        $this->render('list', $viewData);
    }
 
    //...
}

2. FilterModel base class for additional filtering search models' criteria.

Example:

class MyTermFilter extends FilterModel
{
    public $term;
 
    public $poi_table_alias;
 
    public function rules()
    {
        return array(
            array('poi_table_alias', 'default', 'value' => 't'),
            array('poi_table_alias, term', 'required'),
            array('term', 'length', 'max' => 512),
            array('term', 'safe', 'on' => self::SCENARIO_FILTERING),
        );
    }
 
    public function applyAttributeFilter($criteria, $attribute)
    {
        switch ($attribute) {
        case 'term':
            $items = explode(',', $this->term);
            $queryCriteria = new CDbCriteria;
            $value = $this->$attribute;
            foreach ($terms as $key => $value) {
                $termCriteria = new CDbCriteria();
                $termCriteria->compare($this->poi_table_alias . '.name', $value, true, 'OR');
                $termCriteria->compare($this->poi_table_alias . '.description', $value, true, 'OR');
                $queryCriteria->mergeWith($termCriteria);
            }
            if ($this->poi_table_alias != 't') {
                $queryCriteria->with = array($this->poi_table_alias);
                $queryCriteria->together = true;
            }
            $criteria->mergeWith($queryCriteria);
            break;
        }
    }
}

Using term filter in SearchModel:

class MySearchModel extends SearchModel
{
    public $term;
 
    //...
 
    protected function build()
    {
       $criteria = new CDbCriteria();
       //...
       if (isset($this->term)) {
           $filter = new MyTermFilter();
           $filter->term = $this->term;
           $filter->apply($criteria);
       }
       //...
       $config = array('criteria' => $criteria);
       return new CActiveDataProvider($this->getModelClass(), $config);
    }
}

3. RatingModel base class for modeling coefficient based ratings with calculations caching.

Example:

class UserRating extends RatingModel
{
    const RATING_CACHE_PREFIX = 'user_rating_';
    const DEFAULT_CACHE_DURATION = 1000;
    const DEFAULT_OUTPUT_MULTIPLIER = 1;
 
    public $user;
 
    public $cacheDuration = self::DEFAULT_CACHE_DURATION;
 
    public $ratio;
 
    public $multiplier = self::DEFAULT_OUTPUT_MULTIPLIER;
 
    public function rules()
    {
        return array(
            array('user, ratio, multiplier, cacheDuration', 'required'),
        );
    }
 
    public function getDataCacheId()
    {
        return self::RATING_CACHE_PREFIX . $this->user->id;
    }
 
    public function getDataCacheDuration()
    {
        return $this->cacheDuration;
    }
 
    protected function calc()
    {
        $ret = 0;
        $ret += $this->_calcFeedbackRating();
        $ret += $this->_calcPhotoRating();
        return round($ret * $this->multiplier);
    }
 
    /**
     * Saves calculated rating into database after calculating
     * 
     * @return void
     * @see RatingModel::afterCalculate()
     */
    protected function afterCalculate()
    {
        parent::afterCalculate();
        $this->user->rating = $this->getCachedValue();
        $this->user->save(true, array('rating'));
    }
 
    /**
     * Calculates user rating based on user's photo rating
     * 
     * @return double poi votes rating
     */
    private function _calcFeedbackRating()
    {
        $connection = $this->user->getDbConnection();
        $query = "
            SELECT SUM(`marks`.value) as `vote` FROM poi_visitor `t`
            INNER JOIN feedback `feedback` ON (`feedback`.user_id = `t`.user_id AND `feedback`.poi_id = `t`.poi_id)
            LEFT JOIN object_mark `marks` ON (`feedback`.id = `marks`.object_id)
            WHERE `t`.user_id = :user_id  AND `marks`.object_type = :object_type;
        ";
        $command = $connection->createCommand($query);
        $params = array(':user_id' => $this->user->id, ':object_type' => ObjectMark::OBJECT_TYPE_FEEDBACK);
        $votesSummary = $command->queryScalar($params);
        $ratio = $this->ratio['feedback'];
        return $ratio * $votesSummary;
    }
 
 
    /**
     * Calculates user rating based on user's photo rating
     * 
     * @return double poi votes rating
     */
    private function _calcPhotoRating()
    {
        $connection = $this->user->getDbConnection();
        $query = "
            SELECT SUM(`marks`.value) as `vote` FROM poi_visitor `t`
            INNER JOIN poi_photo `poi_photo` ON (`poi_photo`.poi_id = `t`.poi_id)
            INNER JOIN photo `photo` ON (`photo`.user_id = `t`.user_id AND `photo`.id = `poi_photo`.photo_id)
            LEFT JOIN object_mark `marks` ON (`photo`.id = `marks`.object_id)
            WHERE `t`.user_id = :user_id  AND `marks`.object_type = :object_type;
        ";
        $command = $connection->createCommand($query);
        $params = array(':user_id' => $this->user->id, ':object_type' => ObjectMark::OBJECT_TYPE_PHOTO);
        $votesSummary = $command->queryScalar($params);
        $ratio = $this->ratio['photo'];
        return $ratio * $votesSummary;
    }
}

Example usage:

class User extends CActiveRecord
{
    //...    
 
    /**
     * Returns user's rating value
     * 
     * @param boolean $refresh whether required to refresh cached rating value (recalculate)
     * 
     * @return integer user's rating value
     */
    public function getRating($refresh = false)
    {
        if (!$this->isNewRecord) {
            $model = new UserRating;
            $model->attributes = Yii::app()->params['rating']['user'];
            $model->user = $this;
            $value = $model->getValue($refresh);
            return $value;
        }
        return $this->getAttribute('rating');
    }
 
    //...
}

4. FormSubmitModel base class specially for encapsulating form submitting logic into it.

Example:

class LoginForm extends FormSubmitModel
{
    public $username;
 
    public $password;
 
    public $rememberMe;
 
    private $_identity;
 
    public function rules()
    {
        return array(
            array('username, password', 'required'),
            array('rememberMe', 'boolean'),
            array('password', 'authenticate'),
        );
    }
 
    public function attributeLabels()
    {
        return array(
            'username' => Yii::t('app', 'User Name'),
            'password' => Yii::t('app', 'Password'),
            'rememberMe' => Yii::t('app', 'Remember me next time'),
        );
    }
 
 
    public function authenticate($attribute, $params)
    {
        if (!$this->hasErrors()) {
            $this->_identity = new UserIdentity($this->username, $this->password);
            $this->_identity->authenticate();
            switch($this->_identity->errorCode) {
            case UserIdentity::ERROR_EMAIL_INVALID:
                $this->addError('username', Yii::t('app', 'Email is incorrect.'));
                break;
            case UserIdentity::ERROR_PASSWORD_INVALID:
                $this->addError('password', Yii::t('app', 'Password is incorrect.'));
                break;
            }
        }
    }
 
    /**
     * Logs in the user using the given username and password in the model.
     *
     * @return boolean whether login is successful
     */
    protected function onSubmit()
    {
        if ($this->_identity->errorCode === UserIdentity::ERROR_NONE) {
            $duration = $this->rememberMe ? 3600 * 24 * 365 : 0; // 30 days
            Yii::app()->user->login($this->_identity, $duration);
            return true;
        } else {
            return false;
        }
    }
}

Example usage:

class AuthController extends CController
{
    //...    
 
    /**
     * Handles login form
     *
     * @return void
     */
    public function actionLogin()
    {
        $model = new LoginForm;    
        if (isset($_POST['ajax']) && $_POST['ajax'] === 'login-form') {
            echo CActiveForm::validate($model);
            Yii::app()->end();
        }
        if (isset($_POST['LoginForm'])) {
            $model->attributes = $_POST['LoginForm'];
            if ($model->submit()) {
                $this->redirect(Yii::app()->user->homeUrl);
            }
        }
        $this->render('login', array('model' => $model));
    }
 
    //...
}

5. TransactionalFormModel base class used for submitting data into database under transaction. Automatic rollback transaction on exception throw or on submitting error.

Example:

class RegistrationForm extends TransactionalFormModel
{   
    public $username;
 
    public $email;
 
    public $password;
 
    public $verifyPassword;
 
    public $verifyCode;
 
    public function rules()
    {
        return array(
            array('username, password, verifyPassword, email', 'required'),
            array('email', 'email'),
            array('verifyPassword', 'compare', 'compareAttribute' => 'password',
                'message' => Yii::t('app', 'Retype Password is incorrect.'),
            ),
            array('verifyCode', 'captcha', 'allowEmpty' => false),
        );
    }
 
    /**
     * Registers new user under database transaction.
     * If something would be wrong, model rollbacks
     * transaction automatically. For manual rollback
     * transaction return false.
     * 
     * @return whether user registered successfully
     * @see FormSubmitModel::onSubmit()
     */
    protected function onSubmit()
    {
        $user = new User();
        $user->username = $this->username;
        $user->email = $this->email;
        $user->password = md5($this->password);
        $user->status = User::STATUS_NOT_VERIFIED;
        if ($user->save()) {
            $profile = new UserProfile();
            $profile->attributes = $this->getAttributes();
            $profile->user_id = $user->id;
            if ($profile->save()) {
                return true;
            } else {
                $this->addErrors($profile->getErrors());
            }
        } else {
            $this->addErrors($user->getErrors());
        }
        return false;
    }
 
    //...
}

6. DAOModel base class used for submitting data into database under transaction or selecting data. Model implements handling data access code for each access scenario.

Examples in preparing stage

Resources

...external resources for this extension...

Total 2 comments

#15355 report it
djvibegga at 2013/10/31 05:55am
Re: Just like yii's search or Google's site search?

Yes, you can achieve that by overriding build() method and merge results of other search models, than you can pass merged data into CArrayDataProvider constructor.

Thanks for question.

#8454 report it
jiaming at 2012/06/05 09:44am
Very nice!

Thanks for your extension. That is great.

Just one question, can your SearchModel do multiple models search?

Is there a way to achieve that? Like i can do free search in both post, comment, and photo models....

Just like yii's search or Google's site search?

Cheers.

Leave a comment

Please to leave your comment.

Create extension