jimmy jimmy - 12 days ago 7
PHP Question

Yii2 activerecord custom attribute

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

db\activerecord
.

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
$model->categories
is not populated.

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


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

Edit
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
link()
) entries in the product_category table for the specific product.

Answer

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'])
        ->via('productCategories');
}

(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:

$product->categories  

(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
$category->save();

$product = new Product();
// Assign attributes
$product->save();

$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.