Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions framework/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Yii Framework 2 Change Log
- Enh #4495: Added closure support in `yii\i18n\Formatter` (developeruz)
- Enh #13787: Added `yii\db\Migration::$maxSqlOutputLength` that allows limiting number of characters for outputting SQL (thiagotalma)
- Enh #13824: Support extracting concatenated strings in `yii message` (developeruz)
- Enh #14078: Allowed to use custom constructors in ActiveRecord-based classes (ElisDN)
- Enh #14089: Added tests for `yii\base\Theme` (vladis84)
- Enh #13586: Added `$preserveNonEmptyValues` property to the `yii\behaviors\AttributeBehavior` (Kolyunya)
- Bug #14192: Fixed wrong default null value for TIMESTAMP when using PostgreSQL (Tigrov)
Expand Down
3 changes: 3 additions & 0 deletions framework/UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ Upgrade from Yii 2.0.11
* Inputmask package name was changed from `jquery.inputmask` to `inputmask`. If you've configured path to
assets manually, please adjust it.

* The usage of `yii\db\BaseActiveRecord::instantiate()` was changed to more general `instantiate($row = [])`. The method is called
everywhere in framework internal code instead of `new $modelClass()` and can receive empty `$row` array in this cases.

Upgrade from Yii 2.0.10
-----------------------

Expand Down
7 changes: 6 additions & 1 deletion framework/data/ActiveDataProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,12 @@ public function setSort($value)
parent::setSort($value);
if (($sort = $this->getSort()) !== false && $this->query instanceof ActiveQueryInterface) {
/* @var $model Model */
$model = new $this->query->modelClass();
if (is_subclass_of($this->query->modelClass, 'yii\db\ActiveRecordInterface')) {
$modelClass = $this->query->modelClass;
$model = $modelClass::instantiate();
} else {
$model = new $this->query->modelClass();
}
if (empty($sort->attributes)) {
foreach ($model->attributes() as $attribute) {
$sort->attributes[$attribute] = [
Expand Down
7 changes: 5 additions & 2 deletions framework/db/ActiveQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,9 @@ private function buildJoinWith()
$join = $this->join;
$this->join = [];

$model = new $this->modelClass();
$modelClass = $this->modelClass;
$model = $modelClass::instantiate();

foreach ($this->joinWith as $config) {
list($with, $eagerLoading, $joinType) = $config;
$this->joinWithRelations($model, $with, $joinType);
Expand Down Expand Up @@ -515,7 +517,8 @@ private function joinWithRelations($model, $with, $joinType)
} else {
$relation = $relations[$fullName];
}
$primaryModel = new $relation->modelClass();
$modelClass = $relation->modelClass;
$primaryModel = $modelClass::instantiate();
$parent = $relation;
$prefix = $fullName;
$name = $childName;
Expand Down
15 changes: 15 additions & 0 deletions framework/db/ActiveRecordInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -427,4 +427,19 @@ public function unlink($name, $model, $delete = false);
* @return mixed the database connection used by this AR class.
*/
public static function getDb();

/**
* Creates an active record instance.
*
* This method is called internally by framework for instantiating new record object.
* It is not meant to be used for creating new records directly.
*
* You may override this method if the instance being created
* depends on the row data to be populated into the record.
* For example, by creating a record based on the value of a column,
* you may implement the so-called single-table inheritance mapping.
* @param array $row row data to be populated into the record (can be empty).
* @return ActiveRecordInterface the newly created active record
*/
public static function instantiate($row = []);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to have this one as is and, additionally, add another method w/o params that is creating an instance but isn't filling it. It would be used for cases when we're getting metadata such as attr. labels or joinWith relation metadata.

In 2.1 we may call the one w/o arguments instantiate() and the one w/ $row hydrate.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need suggestions for method names.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yiisoft/core-developers what do you think about naming?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @samdark ,

I think having instantiate method as is and add a new method that should simply create an empty model for metadata is the best approach right now.
Right now people use "instantiate" methods to instantiate models and don't expect changes in the method signature or it's meaning.

But these two methods have different meanings and, from my point of view, should not share "instantiate" word in names.
So I suggest having a method that creates empty models with a name such as:

  • createEmpty
  • createNew
  • createPlainModel
  • create
  • newEmptyModel
  • newPlainModel

People probably would want to use this method passing instantiation stuff so I don't think "internal" or something similar should be used in this method.

Also, if skip options with "Model", I think, it makes sense to move method to some of the base classes.
For example, createNew might be implemented in Object.
Object::createNew is pretty natural from my point of view.

So what do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

create() actually sounds good.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ElisDN what do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer Model::new() - it is closer to native PHP syntax and IMO better fits for current naming convention.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is unlikely posssible since new is PHP reserved keyword.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It works in PHP7, so we could use it if we target this to Yii 2.1.

https://3v4l.org/v8vAv

}
10 changes: 8 additions & 2 deletions framework/db/ActiveRelationTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,8 @@ private function addInverseRelations(&$result)
$relatedModel->populateRelation($this->inverseOf, $inverseRelation->multiple ? [$this->primaryModel] : $this->primaryModel);
} else {
if (!isset($inverseRelation)) {
$inverseRelation = (new $this->modelClass())->getRelation($this->inverseOf);
$modelClass = $this->modelClass;
$inverseRelation = $modelClass::instantiate()->getRelation($this->inverseOf);
}
$result[$i][$this->inverseOf] = $inverseRelation->multiple ? [$this->primaryModel] : $this->primaryModel;
}
Expand Down Expand Up @@ -298,7 +299,12 @@ private function populateInverseRelation(&$primaryModels, $models, $primaryName,
}
$model = reset($models);
/* @var $relation ActiveQueryInterface|ActiveQuery */
$relation = $model instanceof ActiveRecordInterface ? $model->getRelation($name) : (new $this->modelClass())->getRelation($name);
if ($model instanceof ActiveRecordInterface) {
$relation = $model->getRelation($name);
} else {
$modelClass = $this->modelClass;
$relation = $modelClass::instantiate()->getRelation($name);
}

if ($relation->multiple) {
$buckets = $this->buildBuckets($primaryModels, $relation->link, null, null, false);
Expand Down
14 changes: 8 additions & 6 deletions framework/db/BaseActiveRecord.php
Original file line number Diff line number Diff line change
Expand Up @@ -1148,17 +1148,17 @@ public static function populateRecord($record, $row)
/**
* Creates an active record instance.
*
* This method is called together with [[populateRecord()]] by [[ActiveQuery]].
* This method is called internally by framework for instantiating new record object.
* It is not meant to be used for creating new records directly.
*
* You may override this method if the instance being created
* depends on the row data to be populated into the record.
* For example, by creating a record based on the value of a column,
* you may implement the so-called single-table inheritance mapping.
* @param array $row row data to be populated into the record.
* @param array $row row data to be populated into the record (can be empty).
* @return static the newly created active record
*/
public static function instantiate($row)
public static function instantiate($row = [])
{
return new static();
}
Expand Down Expand Up @@ -1271,7 +1271,7 @@ public function link($name, $model, $extraColumns = [])
if (is_array($relation->via)) {
/* @var $viaClass ActiveRecordInterface */
/* @var $record ActiveRecordInterface */
$record = new $viaClass();
$record = $viaClass::instantiate();
foreach ($columns as $column => $value) {
$record->$column = $value;
}
Expand Down Expand Up @@ -1570,7 +1570,8 @@ public function getAttributeLabel($attribute)
} catch (InvalidParamException $e) {
return $this->generateAttributeLabel($attribute);
}
$relatedModel = new $relation->modelClass();
$modelClass = $relation->modelClass;
$relatedModel = $modelClass::instantiate();
}
}

Expand Down Expand Up @@ -1610,7 +1611,8 @@ public function getAttributeHint($attribute)
} catch (InvalidParamException $e) {
return '';
}
$relatedModel = new $relation->modelClass();
$modelClass = $relation->modelClass;
$relatedModel = $modelClass::instantiate();
}
}

Expand Down
14 changes: 12 additions & 2 deletions framework/grid/DataColumn.php
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,21 @@ protected function getHeaderCellLabel()
if ($this->label === null) {
if ($provider instanceof ActiveDataProvider && $provider->query instanceof ActiveQueryInterface) {
/* @var $model Model */
$model = new $provider->query->modelClass();
if (is_subclass_of($provider->query->modelClass, 'yii\db\ActiveRecordInterface')) {
$modelClass = $provider->query->modelClass;
$model = $modelClass::instantiate();
} else {
$model = new $provider->query->modelClass();
}
$label = $model->getAttributeLabel($this->attribute);
} elseif ($provider instanceof ArrayDataProvider && $provider->modelClass !== null) {
/* @var $model Model */
$model = new $provider->modelClass();
if (is_subclass_of($provider->modelClass, 'yii\db\ActiveRecordInterface')) {
$modelClass = $provider->modelClass;
$model = $modelClass::instantiate();
} else {
$model = new $provider->modelClass();
}
$label = $model->getAttributeLabel($this->attribute);
} elseif ($this->grid->filterModel !== null && $this->grid->filterModel instanceof Model) {
$label = $this->grid->filterModel->getAttributeLabel($this->attribute);
Expand Down
5 changes: 3 additions & 2 deletions tests/data/ar/Animal.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,11 @@ public function getDoes()
}

/**
* @param type $row
*
* @param array $row
* @return \yiiunit\data\ar\Animal
*/
public static function instantiate($row)
public static function instantiate($row = [])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bc break since it changes the method signature for everyone already extending this method

Copy link
Contributor Author

@ElisDN ElisDN Apr 29, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I noted about it in PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{
$class = $row['type'];
return new $class();
Expand Down
51 changes: 51 additions & 0 deletions tests/data/ar/CustomerWithConstructor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php
namespace yiiunit\data\ar;

use yiiunit\framework\db\ActiveRecordTest;

/**
* Class Customer
*
* @property int $id
* @property string $name
* @property string $email
* @property string $address
* @property int $status
*
* @property ProfileWithConstructor $profile
*/
class CustomerWithConstructor extends ActiveRecord
{
const STATUS_ACTIVE = 1;
const STATUS_INACTIVE = 2;

public static function tableName()
{
return 'customer';
}

public function __construct($name, $email, $address)
{
$this->name = $name;
$this->email = $email;
$this->address = $address;
parent::__construct();
}

public static function instantiate($row = [])
{
return (new \ReflectionClass(static::className()))->newInstanceWithoutConstructor();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not use return new static('', ''); or use default values?

Copy link
Contributor Author

@ElisDN ElisDN Apr 29, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is because normal constructor has business logic, restrictions and events:

public function __construct($name, $email)
{
    if (empty($name) || empty($email)) {
        throw new \InvalidArgumentException(...);
    }
    $this->name = $name;
    $this->email = $email;
    $this->created_at = time();
    $this->recordEvent(new CustomerCreated($this));
    parent::__construct();
}

Copy link
Contributor

@Faryshta Faryshta Apr 29, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

edit: nvm this is not going into the core so lets not discuss it.

i think thats up for the developer to know how to handle the constructor and the instantiate.

lets think it the other way around. what if the developer assumes some property, behavior or something got initialized by the constructor and start using it after a findOne() for example.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think thats up for the developer to know how to handle the constructor and the instantiate.

If developer really wants to use own customized code and knows how to override instantiate method for working with his code that the developer can do it. If developer does not need it or does not know about this method existence he do not do it.

what if the developer assumes some property, behavior or something got initialized by the constructor and start using it after a findOne() for example.

By default findOne() calls instantiate($row). Method instantiate still calls return new $class. Logically that the developer can put this initialization code into instantiate method too if he has overwritten the method by his way.

}

public function getProfile()
{
return $this->hasOne(ProfileWithConstructor::className(), ['id' => 'profile_id']);
}

public function afterSave($insert, $changedAttributes)
{
ActiveRecordTest::$afterSaveInsert = $insert;
ActiveRecordTest::$afterSaveNewRecord = $this->isNewRecord;
parent::afterSave($insert, $changedAttributes);
}
}
38 changes: 38 additions & 0 deletions tests/data/ar/OrderItemWithConstructor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace yiiunit\data\ar;

/**
* Class OrderItem
*
* @property int $order_id
* @property int $item_id
* @property int $quantity
* @property string $subtotal
*/
class OrderItemWithConstructor extends ActiveRecord
{
public static $tableName;

public static function tableName()
{
return static::$tableName ?: 'order_item';
}

public function __construct($item_id, $quantity)
{
$this->item_id = $item_id;
$this->quantity = $quantity;
parent::__construct();
}

public static function instantiate($row = [])
{
return (new \ReflectionClass(static::className()))->newInstanceWithoutConstructor();
}

public function getOrder()
{
return $this->hasOne(OrderWithConstructor::className(), ['id' => 'order_id']);
}
}
53 changes: 53 additions & 0 deletions tests/data/ar/OrderWithConstructor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

namespace yiiunit\data\ar;

/**
* Class Order
*
* @property int $id
* @property int $customer_id
* @property int $created_at
* @property string $total
*
* @property OrderItemWithConstructor $orderItems
* @property CustomerWithConstructor $customer
* @property CustomerWithConstructor $customerJoinedWithProfile
*/
class OrderWithConstructor extends ActiveRecord
{
public static $tableName;

public static function tableName()
{
return static::$tableName ?: 'order';
}

public function __construct($id)
{
$this->id = $id;
$this->created_at = time();
parent::__construct();
}

public static function instantiate($row = [])
{
return (new \ReflectionClass(static::className()))->newInstanceWithoutConstructor();
}

public function getCustomer()
{
return $this->hasOne(CustomerWithConstructor::className(), ['id' => 'customer_id']);
}

public function getCustomerJoinedWithProfile()
{
return $this->hasOne(CustomerWithConstructor::className(), ['id' => 'customer_id'])
->joinWith('profile');
}

public function getOrderItems()
{
return $this->hasMany(OrderItemWithConstructor::className(), ['order_id' => 'id'])->inverseOf('order');
}
}
29 changes: 29 additions & 0 deletions tests/data/ar/ProfileWithConstructor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace yiiunit\data\ar;

/**
* Class Profile
*
* @property int $id
* @property string $description
*
*/
class ProfileWithConstructor extends ActiveRecord
{
public static function tableName()
{
return 'profile';
}

public function __construct($description)
{
$this->description = $description;
parent::__construct();
}

public static function instantiate($row = [])
{
return (new \ReflectionClass(static::className()))->newInstanceWithoutConstructor();
}
}
Loading