Skip to content

Instantly share code, notes, and snippets.

@Ivanitch
Forked from yesnik/active_record.md
Created February 19, 2022 04:03
Show Gist options
  • Save Ivanitch/fbd3b8ceb7836695109cef5933d398d6 to your computer and use it in GitHub Desktop.
Save Ivanitch/fbd3b8ceb7836695109cef5933d398d6 to your computer and use it in GitHub Desktop.
Yii 1 Framework Cookbook

CActiveRecord methods

findAll()

Sort records

// Ascending order
Partner::model()->findAll(array('order' => 'company'));

// Descending order
Partner::model()->findAll(array('order' => 'company DESC'));

// Ascending order
$criteria = new CDbCriteria();
$criteria->order = 't.order ASC';
Partner::model()->findAll($criteria);

Find with condition

$criteria = new CDbCriteria();
$criteria->addCondition('idpartner = :partnerId');
$criteria->params[':partnerId'] = 123;
Partner::model()->findAll($criteria);

Select only one column

// Example 1
$partners = Partner::model()->findAll(array('select' => 'title'));

// Example 2
$criteria = new CDbCriteria();
$criteria->select = 'title';
$partners = Partner::model()->findAll($criteria);

Select distinct values

$criteria = new CDbCriteria();
$criteria->distinct = true;
$criteria->select = '`key`';
$criteria->addColumnCondition([
    'setting_id' => 1260,
    'status' => SettingsParams::STATUS_ACTIVE
]);

$settingParams = SettingsParams::model()->findAll($criteria);

This will produce SQL:

SELECT DISTINCT `key` FROM `settings_params` `t` 
WHERE setting_id = 1260 AND status = 2

findAllBySql()

This method finds ActiveRecord entities by raw SQL:

$sql = "SELECT * FROM products WHERE created_at > :created_at";

$products = Product::model()->findAllBySql($sql, [
  ':created_at' => '2019-01-11 00:00:00',
]);

count()

$criteria = new CDbCriteria();
$criteria->addColumnCondition([
    'user_id' => $user->id,
]);
$count = (int)Posts::model()->count($criteria); // Returns integer, e.g.: 25

Insert list of values into SQL query

We didn't find a way to do this in more beautiful way. This just works:

$statuses = [1, 2];
$statusesString = "'" . implode("', '", $statuses) . "'";

$sql = "SELECT * FROM products WHERE 
    status IN ({$statusesString})
";
echo $sql; // SELECT * FROM products WHERE status IN ('1', '2')
$products = Products::model()->findAllBySql($sql);

exists()

Method returns true if record was found with defined search criteria.

$criteria = new CDbCriteria();
$criteria->addCondition('product_id = '. $this->productOpenId);
$result = ClaimsIndeces::model()->exists($criteria); // true

updateAll()

We can use this method to update several records that meet some condition:

$criteria = new CDbCriteria();
$criteria->addInCondition('id', [1, 5]);

// This query will update `status` field to `sold` for record's ID = 1, 5
Product::model()->updateAll(
    ['status' => 'sold'],
    $criteria
);

Also it's possible to update all rows without condition:

UniversityDegree::model()->updateAll([
    'for_partners' => 0,
    'for_students' => 0,
]);

updateByPk()

Method updates defined fields for one record with defined ID.

$res = Claims::model()->updateByPk(123, ['add_status_info' => 'Pending']);
var_dump($res); // 1

save()

Method allows create or update record.

// Example 1
$product = new Product();
$product->title = 'PHP Secrets';
$product->description = 'Latest secrets about php';
$product->save();

// Example 2
$product = new Product();
$product->attributes = [
  'title' => 'PHP Secrets',
  'description' => 'Latest secrets about php'
];
$product->save();

findByAttributes

$claimsInfo = ClaimsInfo::model()->findAllByAttributes(['mobile' => '79110001155']);
var_dump($claimsInfo); // object(ClaimsInfo)[934] ...

findAllByAttributes

$claimsInfo = ClaimsInfo::model()->findAllByAttributes(['mobile' => '79110001155']);
var_dump($claimsInfo); // array (size=11) ...

deleteAll

We can delete records of some model with limit and condition:

$criteria = new CDbCriteria();
$criteria->limit = 2;
$criteria->addCondition('create_time < :createTime');
$criteria->params[':createTime'] = '2018-04-22 00:00:00';

$apiLogDeleted = ApiLog::model()->deleteAll($criteria);
var_dump($apiLogDeleted); // (int) 2

join

$criteria = new CDbCriteria();
$criteria->addCondition('t.mobile = :mobile');

$criteria->join = 'LEFT JOIN claims c ON c.id = t.claim_id';

$criteria->addColumnCondition(['c.design_id' => 29]);
$criteria->limit = 1;

$criteria->params[':mobile'] = $mobile;

$claimIndex = ClaimsIndeces::model()->find($criteria);

It will execute SQL query:

SELECT `t`.`id`, `t`.`claim_id`, `t`.`product_id`, `t`.`form_id`
FROM `claims_indeces` `t`
LEFT JOIN claims c ON c.id = t.claim_id
WHERE 
	(t.mobile = '79221111111') 
	AND (c.design_id='29')
LIMIT 1

with

$criteria = new CDbCriteria();
$criteria->with = 'product'; // relation's name in `relations()` method of ProductsRkoOptions class

$tariffs = ProductsRkoOptions::model()->findAll($criteria);

$result = [];
foreach ($tariffs as $tariff) {
    $result[] = [
	'id' => $tariff->id,
	'name' => $tariff->product->name,
    ];
}

This code will create single SQL-query that will return records from both tables:

SELECT `t`.`id` AS `t0_c0`, `product`.`name` AS `t1_c3` 
FROM `products_rko_options` `t`
LEFT OUTER JOIN `products` `product` ON (`t`.`product_id`=`product`.`id`)

It will help us to fix n + 1 query problem.

Query Builder

The Yii Query Builder provides an object-oriented way of writing SQL statements. It allows developers to use class methods and properties to specify individual parts of a SQL statement.

See documentation.

delete()

$command = Yii::app()->db->createCommand();
$claim_ids = [545, 546];
$affectedRows = $command->delete('claims_calls', ['in', 'claim_id', $claim_ids] );
var_dump($affectedRows); // (int) 2

This code will execute SQL:

DELETE FROM `claims_calls` WHERE `claim_id` IN (545, 546)

Execute raw SQL query

Without data binding

$sql = "select * from products";
$rows = Yii::app()->db->createCommand($sql)->queryAll();

foreach($rows as $row) {
    echo $row['title'];
    // ...
}

With data binding

One value to bind:

$products = Yii::app()->db->createCommand('SELECT * FROM products WHERE status = :status')
    ->bindValue(':status', $status)
    ->queryAll();

Many values to bind:

$sql = 'SELECT * FROM products WHERE status = :status AND created_at > :created_at';
$products = Yii::app()->db->createCommand($sql)
    ->bindValues([
        ':status' => $status,
        ':created_at' => '2017-01-01'
    ])
    ->queryAll();

Query one row

$sql = 'SELECT * FROM products WHERE id = :id';
$row = Yii::app()->db->createCommand($sql)
    ->bindValue(':id', 4)
    ->queryRow();

var_dump($row['id']); // '4'

Execute raw DELETE query

To get a number of deleted rows we can use execute():

$sql = "DELETE FROM api_log WHERE create_time < :createTime LIMIT 2";
$command = Yii::app()->db->createCommand($sql)
    ->bindValues([
        ':createTime' => '2018-04-22 00:00:00'
    ]);
$affectedRows = $command->execute();
var_dump($affectedRows); // (int) 2

Get text of SQL query

Suppose we want to see SQL generated by this expression:

$criteria = new CDbCriteria();
$criteria->addCondition('t.product_id = :productId');
$criteria->params = [':productId' => 1];
$result = ClaimsIndeces::model()->findAll($criteria);

We can do this via this service method, that can be placed at protected/components/Services.php:

class Services
    // ...
    
    /**
     * Method returns text of SQL-query
     * @param CActiveRecord $model
     * @param CDbCriteria $criteria
     * @return string
     * Example:
     *      $criteria = new CDbCriteria();
     *      $criteria->addCondition('t.product_id = :productId');
     *      $criteria->params = [':productId' => 1];
     *      $result = ClaimsIndeces::model()->findAll($criteria);
     *
     *      echo Services::getSQL(ClaimsIndeces::model(), $criteria);
     *      // Returns: SELECT * FROM `claims_indeces` `t` WHERE t.product_id = 1
     */
    public static function getSQL($model, $criteria)
    {
        if (is_null($criteria->params)) {
            return null;
        }

        $_criteria = $model->getCommandBuilder()->createCriteria($criteria);
        $command = $model->getCommandBuilder()->createFindCommand($model->getTableSchema(), $_criteria);

        $sql = $command->getText();

        krsort($criteria->params);

        foreach ($criteria->params as $key => $value) {
            $sql = str_replace($key, "'$value'", $sql);
        }

        return $sql;
    }
}

Queries with join

Yii doc: query builder

$result = Yii::app()->db->createCommand()
  ->select('pps.*')
  ->from('products_point_sales pps')
  ->join('products_to_point_sales ptps', 'ptps.point_sales_id = pps.sap_id')
  ->join('products p', 'p.crm_id = ptps.product_id')
  ->where('p.alias = :alias', [':alias' => 'open'])
  // ->text  - this will show SQL query
  ->queryAll();

It returns array:

array (size=2)
  0 => 
    array (size=6)
      'id' => string '427' (length=3)
      'sap_id' => string '3' (length=1)
      'name' => string 'Первый' (length=27)
      'mvz' => string '6210' (length=4)
      'open_date' => string '0000-00-00 00:00:00' (length=19)
      'close_date' => string '0000-00-00 00:00:00' (length=19)
  1 => 
    array (size=6)
      'id' => string '1822' (length=4)
      'sap_id' => string '5' (length=1)
      'name' => string 'Второй' (length=33)
      'mvz' => string '6211' (length=4)
      'open_date' => string '0000-00-00 00:00:00' (length=19)
      'close_date' => string '0000-00-00 00:00:00' (length=19)

Query with distinct

$result = Yii::app()->db->createCommand()
    ->selectDistinct('code')
    ->from('gsm_operators')
    ->queryAll();

It returns query:

array (size=79)
  0 => 
    array (size=1)
      'code' => string '900' (length=3)
  1 => 
    array (size=1)
      'code' => string '901' (length=3)

Related models

Update attribute of related model

$claim = Claims::model()->findByPk(100);
$claim->info->email = 'hello@gmail.com';
$claim->info->save(); // New email will be saved
// $claim->save(); // Note: this won't save email in related model

Additional Methods for ActiveRecord

We can add new methods to native Yii CActiveRecord class by creating new class ActiveRecord in protected/components/ActiveRecord.php. To make new methods available in our models we should extend their classes from our custom ActiveRecord class:

class Car extends ActiveRecord {
    // ...
}

findOrInitialize()

class ActiveRecord extends CActiveRecord {
	/**
	* This method find record by attributes in database or initialize new one otherwise
	* @param array $attributes
	* @return array|mixed|null
	*/
	public function findOrInitialize($attributes=array())
	{
		$object = $this->findByAttributes($attributes);

		if (!is_null($object)) {
			return $object;
		}

		$modelClassName = get_class($this);

		$object = new $modelClassName;
		$object->setAttributes($attributes, false);
		return $object;
	}
}

Usage:

$car = Car::model()->findOrInitialize(array(
	'model' => 'audi',
	'year' => 2017
));

Yii 1 Controllers

Get action id / action name

class UniversityDegreeController extends Controller
{
    protected function beforeAction($action)
    {
        $actionId = $this->getAction()->getId();
        echo $actionId; // 'index' | 'update' | 'delete'
        
        return parent::beforeAction($action);
    }
}

Yii Migrations

Yii migration column types

  • pk: an auto-incremental primary key type, will be converted into “int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY”
  • string: will be converted into varchar(255)
  • text: will be converted into text
  • integer: will be converted into int(11)
  • boolean: will be converted into tinyint(1)
  • float: will be converted into float
  • decimal: will be converted into decimal
  • datetime: will be converted into datetime
  • timestamp: will be converted into timestamp
  • time: will be converted into time
  • date: will be converted into date
  • binary: will be converted into blob

Examples of migration commands

Create table

Contents of migration protected\migrations\m200428_054554_create_claims_import_calls.php:

class m200428_054554_create_claims_import_calls extends CDbMigration
{
    const TABLE = 'claims_import_calls';
    
    public function up()
    {
        $this->createTable(self::TABLE,
            [
                'id' => 'INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT',
                'call_center_id' => "INT(11) UNSIGNED NOT NULL COMMENT 'ID in call_centers table'",
                'crm_status' => "ENUM ('', 'E0004', 'E0005', 'E0006') NOT NULL COMMENT 'Status code in CRM'",
                'call_result_id' => "INT(11) UNSIGNED COMMENT 'ID in call_results table'",

                'call_started_at' => "TIMESTAMP NOT NULL COMMENT 'Call start time'",
                'call_finished_at' => "TIMESTAMP NOT NULL COMMENT 'Call finished time'",

                'cost' => "DECIMAL(6,2) COMMENT 'Cost of the call'",

                'create_time' => 'TIMESTAMP NOT NULL DEFAULT NOW()',
                'update_time' => 'TIMESTAMP NOT NULL DEFAULT NOW() ON UPDATE NOW()',
            ]
        );

        $this->execute('set FOREIGN_KEY_CHECKS=0;');

        $this->addForeignKey('fk_claims_import_calls_call_result_id',
            self::TABLE, 'call_result_id',
            'call_results', 'id',
	    'CASCADE', 'CASCADE'
        );

        $this->execute('set FOREIGN_KEY_CHECKS=1;');
    }

    public function down()
    {
        $this->dropForeignKey('fk_claims_import_calls_call_center_id', self::TABLE);
        $this->dropForeignKey('fk_claims_import_calls_call_result_id', self::TABLE);

        $this->dropTable(self::TABLE);
    }
}

Add column

$this->addColumn('claims_info', 'products_point_sales_id', 'INT(11) UNSIGNED AFTER city_name');

Use model in migration

Way 1. Import file manually

File: /protected/migrations/m210622_124327_list_degrees_actualize.php:

Yii::import('application.models.*');

class m210622_124327_list_degrees_actualize extends CDbMigration
{
    public function up()
    {
        UniversityDegree::model()->deleteAllByAttributes(['type_id' => 1]);
    }
    // ...
}

Way 2. Configure console command to autoload classes

Add import section to app/protected/config/console.php. These lines will help to autoload classes for migrate command.

return array(
    'basePath' => dirname(__FILE__) . DIRECTORY_SEPARATOR . '..',
    'name' => 'My Console Application',

    // ...

    // autoloading model and component classes
    'import' => array(
        'application.models.*',
        'application.modules.*',
        'application.modules.questionnaires.models.*',
        'application.components.*',
	// ...
    ),
);

Yii - Run cron command from controller

class SystemController extends CController
{
  public function actionTestCron()
  {
    $cron5min = new PisendCommand('', '');
    // Our command expects an array of params
    $cron5min->run([9]);
  }
}

Yii - Run command from another command

(new SendSmsCommand('', ''))->actionIndex();

Yii - add create_time, update_time fields to model

Method 1

Do you want to track create time and update time for each record? Use Yii behaviors:

class MortgageClaim extends CActiveRecord
{
    public function behaviors()
    {
        return [
            'CTimestampBehavior' => [
                'class' => 'zii.behaviors.CTimestampBehavior',
                'createAttribute' => 'create_time',
                'updateAttribute' => 'update_time',
            ]
        ];
    }
}

Ensure that you have fields create_time, update_time. You can create them via migration:

// ...
$this->createTable(
    'mortgage_claims',
    [
        // ...
        'create_time' => 'timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP',
        'update_time' => 'timestamp NOT NULL'
    ]
);

Important: In some cases you could get an error:

include(CTimestampBehavior.php): failed to open stream: No such file or
directory (/var/www/html/vendor/yiisoft/yii/framework/YiiBase.php:427)
Stack trace:
#0 unknown(0): spl_autoload_call()
#1 /var/www/html/vendor/yiisoft/yii/framework/caching/CCache.php(108):
unserialize()

To fix error add this at config/main.php:

'import' => array(
  // ...
	'zii.behaviors.CTimestampBehavior',		
),

Method 2

Because of error in Method 1 we can implement desired behaviour manually:

class Product {
    protected function beforeSave()
    {
        if (!parent::beforeSave()) {
	    return false;
	}
	
	if ($this->isNewRecord) {
	    $this->create_time = date('Y-m-d H:i:s');
	    $this->create_ip   = Yii::app()->request->userHostAddress;
	}
	$this->update_time = date('Y-m-d H:i:s');
	$this->update_ip   = Yii::app()->request->userHostAddress;
	
	return true;
    }
}

Method 3

You can delegate this task to database. If you have MySQL >= 5.6.5, create fields with migration:

public function up() {
    $this->createTable(
        'claims_processed',
        [
	    'id' => 'pk',
	    // ...
	    'create_time' => 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP',
	    'update_time' => 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP',
        ]
    );
}

Important: Be careful! If you use this method 3 you have to mention names of attributes that you want to update:

$claim = Claim::model()->findByAttributes(['claim_id' => 123]);
$claim->status = 'SUCCESSFUL';
$claim->status_info = 'Processed';

// Won't work! This will NOT update `update_time` field
$claim->save();

// OK. This will update `update_time` field
$claim->save(true, ['status', 'status_info']);

Show SQL query in browser

If you want to see all SQL queries in web browser, add this code to config file:

// ...
'components' => [
    'log' => [
        'class'  => 'CLogRouter',
        'routes' => [
	    ['class' => 'CWebLogRoute'],
	    // or
	    // ['class'  => 'CProfileLogRoute'],
        ]
    ]
]

Add Gii generator

'modules' => [
    'gii' => [
        'class' => 'system.gii.GiiModule',
        'password' => '123',
        'ipFilters' => ['*'],
        // 'newFileMode' => 0666,
        // 'newDirMode' => 0777,
    ],
],

Yii 1 useful plugins

Yii 1 datetime picker

See CJuiDateTimePicker plugin page.

Download the code of this plugin and paste it to protected/extensions/CJuiDateTimePicker folder of your project.

Example:

Yii::import('application.extensions.CJuiDateTimePicker.CJuiDateTimePicker');

$this->widget('CJuiDateTimePicker',array(
    'model' => $model, // Model object
    'attribute' => 'publication_time', // Attribute name
    'mode' => 'datetime', // Use "time","date" or "datetime" (default)
    'options' => [
        'minDate' => 0,
        'timeText' => 'Время',
        'hourText' => 'Часы',
        'minuteText' => 'Минуты',
        'showButtonPanel' => false,
        'timeFormat' => 'hh:mm',
        'dateFormat' => "yy-mm-dd",
    ] // jquery plugin options
));

Yii Views

Display select widget for form

Write at your view file:

<?php
$products = CHtml::listData(Product::model()->findAll(), 'id', 'title');
echo $form->dropDownListRow($model, 'product_id', $products, [
    'empty' => 'Select product',
]);
?>

Add CSS file, add javascript file

You can publish js, css files from your folder to assets directory:

$assetsPath = Yii::app()->getAssetManager()->publish(__DIR__ . '/assets/', false, -1, YII_DEBUG);
Yii::app()->clientScript->registerCssFile($assetsPath . '/css/anti-mite-widget.css');
Yii::app()->clientScript->registerScriptFile($assetsPath . '/js/scripts.js');

Render Partial

We can place the fragment of HTML in a file (they call it partial) and then render this file on different pages.

File: protected/views/partnerHomeUniversity/view.php:

// Render partial 'protected/views/partnerHomeUniversity/_insurance_options.php'
$this->renderPartial('_insurance_options', ['name' => 'Kenny']);

zii.widgets.CDetailView

// some/view.php
$this->widget('zii.widgets.CDetailView', [
    'data' => $model,
    'attributes' => [
        'id',
        [
            'label' => 'Claims per day, max',
            'name' => 'daily_limit',
        ],
        [
            'name' => 'Claims per day, fact',
            'value' => function ($data) {
                return SomeService::getValue($data->id);
            }
        ],
        [
            'name' => 'password',
            'value' => function ($data) {
                return ($data->password_hash) ? 'Has saved password' : 'No password';
            }
        ],
        [
            'name' => 'status',
            'value' => CallCenters::$STATUSES[$model->status],
        ],
        'created_at',
    ],
]);

Yii::app methods

Request parameters

Retrieve GET parameter

Let's suppose that we visited page: http://site.com/?name=johny. We can get param name via this method:

echo Yii::app()->request->getParam('name'); // 'johny'

// Returns null if param doesn't exist
echo Yii::app()->request->getParam('name2'); // null

// We can define default param value
echo Yii::app()->request->getParam('name2', 'kenny'); // 'kenny'

Get URL for the page

Yii::app()->request->getUrl(); // '/open?form=new&design=newaccess&ldg=banks'

Yii::app()->request->getRequestUri(); // '/app/index.php?r=universityHome/view&id=2211'

$params = $this->getActionParams();
$params['format'] = 'pdf';
Yii::app()->createUrl($this->route, $params); // '/app/index.php?r=universityHome/view&id=2211&format=pdf'

Get domain name

Yii::app()->getBaseUrl(true); // http://hello.com

Get path of directories

Yii::app()->getRuntimePath(); // /var/www/html/sales/protected/runtime

Yii::app()->getBasePath(); // /var/www/html/sales/protected

Yii::app()->getModulePath(); // /var/www/html/sales/protected/modules

Get absolute URL for the page

We can use this at the Yii view:

Yii::app()->createAbsoluteUrl($this->getRoute(), $this->getActionParams());
// Output: "http://127.0.0.1:8090/app/index.php?r=universityHome/view&id=2211"

Yii 1 validators

Yii has many built in system validators.

Yii 1 date validator

See date validation docs.

Example of usage:

public function rules()
{
    return [
        ['published_on', 'date', 'dateFormat' => 'dd.MM.yyyy'],
    ];
}

How to create Yii custom dateValidator

Important: Don't reinvent the weel, because Yii 1 has built-in date validator.

If you want to write your own custom date validator you can create file at protected/components/validators/ folder. This validator uses DateTime::createFromFormat function, that returns false on failure.

class dateValidator extends CValidator
{
    public $dateFormat = 'd.m.Y';
    
    public $message = 'Invalid date format';

    protected function validateAttribute($object, $attribute)
    {
        // Let another validator to check empty value
        if (empty($object->$attribute)) {
            return;
        }

        $dateString = $object->$attribute;

        $date = DateTime::createFromFormat($this->dateFormat, $dateString);

        if ($date === false) {
            $this->addError($object, $attribute, $this->message);
        }
    }
}

To use this validator, just add this line to your rules() method:

    public function rules()
    {
        return [
            // By default 'dateFormat' param has value 'd.m.Y'
            ['published_on', 'dateValidator', 'dateFormat' => 'Y-m-d'],
        ];
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment