📜 ⬆️ ⬇️

Yii connection many to many

many-to-many

Introduction


Hi, Habr! Many probably faced the need to implement many-to-many communication functionality in Yii. It would seem that there is nothing complicated here, and most likely the developers of the framework have already tried to implement the necessary communication functionality for us, and we just need to prescribe the necessary connections in the model, and using the usual methods, save the data.

But Yii doesn’t provide such functionality, it seems that such an opportunity will appear in later versions or will be expanded only with additional extensions. As a result, we have to implement the connection ourselves.
Of course, everyone wriggles out as they can, someone uses extensions that have shown themselves well in battle. For example: with-related-behavior project repository
Someone writes a link directly in the method when saving or updating data. But this approach has a big minus. When there are a lot of connections between entities in a project, it is necessary to duplicate the code in the methods, which will further affect the fall in readability and its support.
But what if there are a lot of entities and you need to bind a lot of data?
We will try to write a simple implementation, which, if necessary, can be easily rewritten and later supported in your project.

Some theory


With a many-to-many relationship type, a record from one table is associated with several records of another table, and a record from the second table is associated with several records in the first table.
This type of relationship requires the creation of a third table, called a pivot table. The pivot table contains the primary keys of the first two tables as foreign keys.
')

Implementation


Take as an example the task in which a product can be made from several materials:

many-to-many
Here is the migration of the ProductMaterial summary entity, to which foreign keys on the Product and Material entities are added:

#protected/migrations/m140314_091505_addProductMaterialTable.php class m140314_091505_addProductMaterialTable extends CDbMigration { public function safeUp() { $prefix = $this->getDbConnection()->tablePrefix; $this->createTable('{{productMaterial}}', array( 'id' => 'int(10) unsigned NOT NULL AUTO_INCREMENT', 'productId' => 'int(10) unsigned NOT NULL', 'materialId' => 'int(10) unsigned NOT NULL', 'PRIMARY KEY (`id`)', ), 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT=\'style (to which productMaterial should belong)\';'); $this->addForeignKey($prefix.'productMaterial_product_fk_constraint', '{{productMaterial}}', 'productId', '{{product}}', 'id', 'RESTRICT', 'CASCADE'); $this->addForeignKey($prefix.'productMaterial_material_fk_constraint', '{{productMaterial}}', 'materialId', '{{material}}', 'id', 'RESTRICT', 'CASCADE'); } } 

Now we describe the behavior that will preserve, and add, and change our connections:

 #protected/extensions/ManyToManyRelationBehavior.php class ManyToManyRelationBehavior extends CBehavior{ /** * @var string name model Relation */ public $modelNameRelation; /** * @var string field name for the relationship with the current models */ public $fieldNameModelCurrent = null; /** * @var string field name for the relationship with the external models */ public $fieldNameModelRelation = null; /** * @var array list of values ​​of the current model */ public $relationList = array(); public function events() { return array_merge(parent::events(), array( 'onAfterSave' => 'afterSave', )); } public function afterSave($event){ if (is_array($this->relationList)){ $model = $this->modelNameRelation; $delete = $model::model()->deleteAll("{$this->fieldNameModelCurrent} =:param", array( ":param" => $this->owner->id, )); foreach ($this->relationList as $value){ $model = new $this->modelNameRelation; $model->{$this->fieldNameModelRelation} = intval($value); $model->{$this->fieldNameModelCurrent} = $this->owner->id; if (!$model->save()){ Yii::log('Unable to save relation: '.serialize($model->getErrors()), 'warning'); return false; } } } return true; } } 

An example of using behavior when creating a new product:

 public function actionCreate() { $model=new Product; $materialList = $_POST['Product']['materialId']; $model->attachBehavior('ManyToManyRelationBehavior', array( 'class' => 'ext.ManyToManyRelationBehavior', 'modelNameRelation' => 'ProductMaterial', //     'fieldNameModelCurrent' => 'productId',//      Product 'fieldNameModelRelation' => 'materialId', //      Material 'relationList' => $materialList, // array id material )); 

To quickly receive materials of goods through the model, add the necessary Relation to the Product model, linking them through the through parameter.

  #protected/models/Product.php public function relations() { return $this->moderationRelations(array( 'materialAll' => array(self::HAS_MANY, 'ProductMaterial', 'productId'), 'materialRelation' => array(self::HAS_MANY, 'Material', 'materialId', 'through' => 'materialAll'), )); } 

Now the materials of the goods can be obtained simply:

 $model = Material::model()->findByPk($pk); $model->materialAll; 

Conclusion


It is clear that the developers of Yii can not include in the framework all the functionality that may be required. But they gave us a great opportunity to easily write extensions with transparent access to them that can be transferred from one project to another. It is also not always convenient to take existing extensions. You can spend a lot of time studying them and finalizing the functional. During this time, you can write your own implementation, which in the future is easy to maintain and refine.

Behavior repository

Source: https://habr.com/ru/post/216605/


All Articles