Duplicaterecordbehavior

Hey guys,

playing around in Yii2 and trying to port an existing app to Yii2. Everything is going pretty good as Yii2 has such a clean workflow and lot’s of little tweaks our company made to Yii1 are build in. Since the app is handling a lot of data and we don’t like to clutter the database with redundant data pretty much all reusable data has it’s own identity (PK).

For example: Someone registers and provides us with an address. Instead of writing street, house number, etc. directly into his user record we create an address record with street, house number, zip code ID, city ID, etc. and write the PK of Address to a UserAddress pivot table.

To make sure we don’t save the same address twice we created this behavior called DuplicateRecordBehavior which checks some attributes (of the model to be saved) against the database and when the combination exists it will set the owner to the existing model, if not it will save the new model.

In Yii1 we could use setPrimaryKey, getIsNewRecord(false) and then mass assign those attributes to a new, empty model and give that back to $this->owner (inside the behavior).

In Yii2 the attributes have to be safe in order to assign them and the setPrimaryKey method doesn’t exist anymore. I’m hoping i don’t have to rewrite all validation rules to add the PK attributes as safe so does anyone have some thoughts how to achieve this behavior in Yii2?

If the example or any other part of this thread is a bit fuzzy, don’t hesitate to ask!

IMO all the features you mentioned can be easily ported to Yii2. With regards to Primary Key, the major difference is Yii2 supports composite primary keys compared to Yii1 (so you may consider this an enhancement to write your code).

There are a whole lot of other enhancements in using ActiveRecord, ActiveQuery and others using the PHP 5 traits etc… which you can apply.

However, you may probably need to do some changes. Best approach is to start reading the Yii 2.0 guide and API docs. Try first out with a fresh sample code in Yii2 and then do a small pilot in changing one sample codeset from Yii1.x to Yii2. Reason is there maybe a design improvement possible using Yii2 features which may change the way you want to do this upgrade.

Thanks for your reply. You mentioned ‘features’ but this is just one feature. All other features are ported (and if possible rewritten due to design changes) just fine. For this DuplicateRecordBehavior the Yii1 way of doing it doesn’t work (i think) so i was wondering if anyone has some ideas how to do this in Yii2. I’ve read the guide and API docs so i maybe i could use instantiateRow and populateModels or something?

I think my OT is a bit fuzzy after all so here is an example of how it’s done. In the controller we do:




$network = new Network;

$network->setAttributes(array $userInput);

$network->saveWithChecks();


$contact->network_id = $network->primaryKey;



Those inputs are validated ofcourse.

Yii1:




public function saveWithChecks()

{

	$this->getOwner()->setAttributes($this->attributes);


	$newModel = $this->resolve();


	if (!$newModel->hasErrors()) {

		$this->getOwner()->setPrimaryKey($newModel->primaryKey);

		$this->getOwner()->setIsNewRecord(false);

		$this->getOwner()->refresh();

	} else

		$this->getOwner()->addErrors($newModel->getErrors());

	return !$newModel->hasErrors();

}


public function resolve()

{

	$ownerName = get_class($this->getOwner());


	// search if a similar model exists

	$existingModel = $this->getOwner()->findByAttributes($this->attributes));

	if (is_null($existingModel)) {

		$existingModel = $ownerName::newModel();

		$existingModel->attributes = $this->getOwner()->attributes;

		$existingModel->scenario = $this->getOwner()->scenario;

		$existingModel->save();

	}


	return $existingModel;

}



And here is an example of what i have in Yii2:




public function saveWithChecks() {

	/** @var ActiveRecord $model */

	$model = $this->owner;


	$condition = [];

	foreach($this->attributes as $attribute) {

		$condition[$attribute] = $model->$attribute;

	}


	if(($this->owner = $model::findOne($condition)) === null) {

		if(!$model->save())

			throw new Exception(sprintf('Could not save %s', get_class($model)));

		$this->owner = $model;

	}


	return !$this->owner->hasErrors();

}



The problem with the code from Yii2 is that when i dump $this->owner inside the method it returns the correct model, when i dump it outside (e.g. in the controller) it returns the old model. It’s like $this->owner doesn’t get updated or something.




// inside controller

$network = new Network;

$network->value = 'test'; // This record already exists with PK = 1

$network->saveWithChecks();


var_dump($network->primaryKey); // This returns null 







// inside method

// This extends the Yii2 code part from above but instead of returning im dumping $this->owner


// return !$this->owner->hasErrors();

var_dump($this->owner->primaryKey); // This returns 1



I hope this makes my problem a bit more clear and if there are better ways of achieving this, do tell!