// $project->company = ['id' => 3, 'name' => 'GiHub', 'description' => 'Awesome']; // Will update an existing company record
// $project->company = ['id' => 3, 'name' => 'GiHub', 'description' => 'Awesome']; // Will update an existing company record
$project->save();
$project->save();
```
```
Attributes of the related model will be massively assigned using the `load() method. So remember to declare the according attributes as safe in the rules of the related model.
Attributes of the related model will be massively assigned using the `load() method. So remember to declare the according attributes as safe in the rules of the related model.
> **Notes:**
> **Notes:**
@ -153,6 +171,7 @@ Attributes of the related model will be massively assigned using the `load() met
> See the PHPUnit tests for more examples.
> See the PHPUnit tests for more examples.
Populate additional junction table columns in a many-to-many relation
Populate additional junction table columns in a many-to-many relation
In a many-to-many relation involving a junction table additional column values can be saved to the junction table for each model.
In a many-to-many relation involving a junction table additional column values can be saved to the junction table for each model.
@ -162,6 +181,7 @@ See the configuration section for examples.
> If junction table properties are configured for a relation the rows associated with the related models in the junction table will be deleted and inserted again on each saving
> If junction table properties are configured for a relation the rows associated with the related models in the junction table will be deleted and inserted again on each saving
> to ensure that changes to the junction table properties are saved too.
> to ensure that changes to the junction table properties are saved too.
Validation
Validation
----------
----------
Every declared related models will be validated prior to be saved. If any validation fails, for each related model attribute in error, an error associated with the named relation will be added to the owner model.
Every declared related models will be validated prior to be saved. If any validation fails, for each related model attribute in error, an error associated with the named relation will be added to the owner model.
@ -170,6 +190,7 @@ For `hasMany()` relations, the index of the related model will be used to identi
It is possible to specify the validation scenario for each relation by declaring an associative array in which the `scenario` key must contain the needed scenario value.
It is possible to specify the validation scenario for each relation by declaring an associative array in which the `scenario` key must contain the needed scenario value.
For instance, in the following configuration, the `links ` related records will be validated using the `Link::SOME_SCENARIO` scenario:
For instance, in the following configuration, the `links ` related records will be validated using the `Link::SOME_SCENARIO` scenario:
```php
```php
...
...
public function behaviors()
public function behaviors()
@ -191,12 +212,37 @@ For instance, in the following configuration, the `links ` related records will
> An error message will be attached to the relation attribute of the owner model.
> An error message will be attached to the relation attribute of the owner model.
> In order to be able to handle these cases in a user-friendly way, one will have to catch `yii\db\Exception` exceptions.
> In order to be able to handle these cases in a user-friendly way, one will have to catch `yii\db\Exception` exceptions.
Delete related records when the main model is deleted
* Load existing model or create one if no key was provided and data is not empty
* Delete related models marked as to be deleted
* @param $data
* @throws Exception
* @param $fks
* @param $modelClass
* @return BaseActiveRecord
*/
*/
private function _loadOrCreateRelationModel($data, $fks, $modelClass)
public function afterDelete()
{
{
/** @var BaseActiveRecord $relationModel */
/** @var BaseActiveRecord $modelToDelete */
$relationModel = null;
foreach ($this->_relationsToDelete as $modelToDelete) {
if (!empty($fks)) {
try {
$relationModel = $modelClass::findOne($fks);
if (!$modelToDelete->delete()) {
}
throw new DbException('Could not delete the related record: ' . $modelToDelete::className() . '(' . VarDumper::dumpAsString($modelToDelete->primaryKey) . ')');
if (!($relationModel instanceof BaseActiveRecord) && !empty($data)) {
}
$relationModel = new $modelClass;
} catch (Exception $e) {
}
Yii::warning(get_class($e) . ' was thrown while deleting related records during afterDelete event: ' . $e->getMessage(), __METHOD__);
if (($relationModel instanceof BaseActiveRecord) && is_array($data)) {
$this->_rollback();
$relationModel->setAttributes($data);
throw $e;
}
}
}
return $relationModel;
}
}
/**
/**
* Get the related model foreign keys
* Populates relations with input data
* @param $data
* @param array $data
* @param $relation
* @param BaseActiveRecord $modelClass
* @return array
*/
*/
private function _getRelatedFks($data, $relation, $modelClass)
public function loadRelations($data)
{
{
$fks = [];
/** @var BaseActiveRecord $model */
if (is_array($data)) {
$model = $this->owner;
// search PK
foreach ($this->_relations as $relationName) {
foreach ($modelClass::primaryKey() as $modelAttribute) {
/** @var ActiveQuery $relation */
if (array_key_exists($modelAttribute, $data) && !empty($data[$modelAttribute])) {
$relation = $model->getRelation($relationName);
$fks[$modelAttribute] = $data[$modelAttribute];
$modelClass = $relation->modelClass;
} else {
/** @var ActiveQuery $relationalModel */
$fks = [];
$relationalModel = new $modelClass;
break;
$formName = $relationalModel->formName();
}
if (array_key_exists($formName, $data)) {
}
$model->{$relationName} = $data[$formName];
if (empty($fks)) {
// Get the right link definition
if ($relation->via instanceof BaseActiveRecord) {
$link = $relation->via->link;
} elseif (is_array($relation->via)) {
list($viaName, $viaQuery) = $relation->via;
$link = $viaQuery->link;
} else {
$link = $relation->link;
}
foreach ($link as $relatedAttribute => $modelAttribute) {
if (array_key_exists($modelAttribute, $data) && !empty($data[$modelAttribute])) {
[1, 'Steven Paul Jobs (February 24, 1955 – October 5, 2011) was an American entrepreneur, business magnate, inventor, and industrial designer. He was the chairman, chief executive officer (CEO), and co-founder of Apple Inc.; CEO and majority shareholder of Pixar; a member of The Walt Disney Company\'s board of directors following its acquisition of Pixar; and the founder, chairman, and CEO of NeXT.'],
[2, 'William Henry Gates III (born October 28, 1955) is an American business magnate, investor, author, philanthropist, and co-founder of the Microsoft Corporation along with Paul Allen.'],
[3, 'Timothy Donald Cook (born November 1, 1960) is an American business executive, industrial engineer, and developer. Cook is the Chief Executive Officer of Apple Inc., previously serving as the company\'s Chief Operating Officer, under its founder Steve Jobs.'],
[4, 'Sir Jonathan Paul "Jony" Ive, KBE (born 27 February 1967), is an English industrial designer who is currently the chief design officer (CDO) of Apple and chancellor of the Royal College of Art in London.']
public function testSaveHasOneReplaceRelatedWithNewRecord()
{
$profile = UserProfile::findOne(1);
$this->assertEquals('Steven Paul Jobs (February 24, 1955 – October 5, 2011) was an American entrepreneur, business magnate, inventor, and industrial designer. He was the chairman, chief executive officer (CEO), and co-founder of Apple Inc.; CEO and majority shareholder of Pixar; a member of The Walt Disney Company\'s board of directors following its acquisition of Pixar; and the founder, chairman, and CEO of NeXT.', $profile->bio, "Profile bio is wrong");
$data = [
'User' => [
'username' => 'Someone Else',
'company_id' => 1
]
];
$profile->loadRelations($data);
$this->assertEquals('Someone Else', $profile->user->username, "User name should be 'Someone Else'");
$this->assertTrue($profile->user->isNewRecord, "User should be a new record");
$profile->save();
$this->assertTrue($profile->save(), 'Profile could not be saved');
$this->assertEquals('Someone Else', $profile->user->username, "User name should be 'Someone Else'");
}
public function testSaveNestedModels()
public function testSaveNestedModels()
{
{
$project = new Project();
$project = new Project();
@ -725,4 +751,46 @@ class SaveRelationsBehaviorTest extends \PHPUnit_Framework_TestCase
$user->userProfile->bio = 'Lawrence Edward Page (born March 26, 1973) is an American computer scientist and Internet entrepreneur who co-founded Google with Sergey Brin.';
$user->userProfile->bio = 'Lawrence Edward Page (born March 26, 1973) is an American computer scientist and Internet entrepreneur who co-founded Google with Sergey Brin.';
$this->assertTrue($user->save(), 'User could not be saved');
$this->assertTrue($user->save(), 'User could not be saved');
}
}
public function testDeleteRelatedHasOneShouldSucceed()
{
User::findOne(1)->delete();
$this->assertNull(UserProfile::findOne(1), 'Related user profile was not deleted');
$this->assertNotNull(UserProfile::findOne(2), 'Unrelated user profile was deleted');
}
public function testDeleteRelatedHasManyShouldSucceed()
{
Project::findOne(1)->delete();
$this->assertCount(0, ProjectLink::find()->where(['project_id' => 1])->all(), 'Related project links were not deleted');
}
public function testDeleteRelatedWithErrorShouldThrowAnException()
{
$this->setExpectedException('\yii\db\Exception');
$project = Project::findOne(1);
foreach ($project->projectLinks as $projectLink) {
$projectLink->blockDelete = true;
}
$this->assertFalse($project->delete(), 'Project could be deleted');
}
public function testSaveProjectWithCompanyWithUserShouldSucceed()
{
// Test for cascading save relations
$project = new Project();
$project->name = "Cartoon";
$company = new Company();
$company->name = 'ACME';
$user = new User();
$user->username = "Bugs Bunny";
$company->users = $user;
$project->company = $company;
$this->assertTrue($project->save(), 'Project could not be saved');
$this->assertEquals('ACME', $project->company->name, 'Project company\'s name is wrong');
$this->assertCount(1, $project->company->users, 'Count of related users is wrong');
$this->assertEquals('Bugs Bunny', $project->company->users[0]->username, 'Company user\'s name is wrong');
$this->assertFalse($project->company->isNewRecord, 'Company record should be saved');
$this->assertFalse($project->company->users[0]->isNewRecord, 'Company Users records should be saved');