jimmy jimmy - 8 months ago 115
PHP Question

Yii2 activerecord custom attribute

I'm trying to use a custom attribute in a model class that extends


I've tried declaring
public $categories = []
and then either assigning values to it directly via
$model->categories = [1,2,3]
or using a setter method in my model class
public function setCategories($ids) {...
and then again assigning via
$model->categories = [1,2,3]

I've also tried updating the attribute with
$model->setAttribute('categories', [1,2,3])

In all cases
is not populated.

My end goal is to assign categories to a model and then update the db relation/tables using

Can this be done or should I extend my model class from
? If I do, what functionality will I lose?

I might have misstated my problem.

I've got a form where a user can select categories for a specific model (ie "Product"). All categories are pre-existing and products are assigned to categories via a junction table product_category('product_id', 'category_id') with a one-to-many relationship (one product has many categories).

Now in the controller that's handling the view, I'm receiving a list of category id's and I want to assign them to an attribute so that I can process them i.e. delete or add (via
) entries in the product_category table for the specific product.

Answer Source

It looks to me like you are manually trying to do relations between tables? Why not use the built in functionality and have it taken care of for you?

For this (many-to-many) you need a link activerecord or table that indicates which models link to which categories (I'm assuming here that you have an ActiveRecord that is called ModelCategory and has a model_id and a category_id:

public function getProductCategories() 
   return $this->hasMany(ProductCategory::className(), ['product_id' => 'id']);

public function getCategories()
    return $this->hasMany(Category::className(), ['id' => 'category_id'])

(You can also use viaTable() instead of via() and avoid an extra method, that is your choice.)

This means that you can access them like so:


(always use the magic functionality for relations, it's the __get() function that actually does the database query).

For assigning relations there is no automatic method. Yii has some assisting functions for this though:

$category = new Category();
// Assign attributes

$product = new Product();
// Assign attributes

$product->link('categories', $category);

Check out the link-function for more details. Obviously there are other ways as well, but it depends on your needs.

As per your extra information:

public function actionAssignCategories($product, $categories) 
   $product = Product::findOne($product);

   $existingCategories = \yii\base\ArrayHelper::getColumn($product->categories, 'category_id');
   $removeCategories = array_diff($existingCategories, $categories); 
   $addCategories = array_diff($categories, $existingCategories);             

   foreach ($addCategories as $category) { 
      $category = Category::findOne($category);
      $product->link('categories', $category);

   foreach ($removeCategories as $category) { 
      $category = Category::findOne($category);
      $product->unlink('categories', $category);

Untested, but that should give you an idea on how to tackle this.