Sven B Sven B - 4 months ago 14
PHP Question

Indirect modification of overloaded property App\Category::$thesizes has no effect

For a webshop im trying to generate a table that looks like:

Tablename: category 1
productname S M L X total
name1 1 0 1 3 5
name2 0 1 0 2 3


Tablename: category 2
productname S L X total
name5 1 1 3 5
name8 0 0 2 2


There is a table for each category, each category has his own sizes (table 2 has no size M for example).
The tables show the amount of ordered products per size per product in each category.

In the application there is a model OrderProducts which are ordered products in each Order.

An OrderProduct has a ProductSize which is a junction table of the product sizes

A ProductSize has a Size (which contains the name of the size)

The first step im tryin to do is get all sizes/products for each category like:

$order = Order::findOrFail($id);
$products = OrderProduct::where('orders_id',$id)->get();

$categories = Category::all();
//get sizes and products per category
foreach($categories as $cat)
{
$cat->thesizes= array();
$cat->theprodcts= array();
foreach($products as $product)
{
if($product->productSize->product->category_id == $cat->id)
{
array_push($cat->thesizes,$product->productSize);
array_push($cat->theprodcts,$product);
}

}
//make sure all values are unique (no dubbele sizes).
$cat->theSizes = array_unique($cat->theSizes);
$cat->theProducts = array_unique($cat->theProducts);
}


When I run my code I get the following error:


Indirect modification of overloaded property App\Category::$thesizes
has no effect


Why do I get this error and how should I solve it?

Answer

This is because your Category class has the __get() and __set() magic methods implemented.

So line 7 ($cat->thesizes= array();) invokes Category::__set() and line 12 (array_push($cat->thesizes,$product->productSize);) invokes Category::__get() but not Category::__set(). So while you impelemented this with the intention of pushing values onto an array that you set on the Category, it won't work since array_push() is working on a return value and not the actual array stored in the Category.

There are a few ways to fix this. The most shortcut way is to change Category::__get() to return values by reference, which is done by using a sort-of type-hint on the function's return declaration

class Category
{
    public function &__get($key) {
        // body of function
    }
}

But this is probably not recommended for reasons I can go into if you're curious.

The more sensible approach, without significantly modifying your code at least, is to build the arrays within the scope of the loop and then add them to your Category objects

foreach ($categories as $cat) {
    // Scope local arrays here first
    $thesizes = array();
    $theproducts = array();

    foreach ($products as $product) {
        if ($product->productSize->product->category_id == $cat->id) {
            // Push to those local arrays
            array_push($thesizes, $product->productSize);
            array_push($theprodcts, $product);
        }
    }

    // Now assign them to the category object
    $cat->theSizes = array_unique($thesizes);
    $cat->theProducts = array_unique($theproducts);
}

If you want to go for bonus points, you can use something like Doctrine Collections to up the sophistication of your implementation

use Doctrine\Common\Collections\ArrayCollection;

// Instantiate arrays as collections
$categories = new ArrayCollection($categories);
$products = new ArrayCollection($products);

$categories = $categories->map(function(Category $cat) {
    $cat->theProducts = $products->filter(function(Product $product) use ($cat) {
        return $product->productSize->product->category_id == $cat->id;
    });
    $cat->theSizes = $cat->theProducts->map(function(Product $product) {
        return $product->productSize();
    });
});