Defining your counter caches - ml-archive/nodes-php-counter-cache GitHub Wiki

Before we can define a counter cache, we need to have a relation.

Continuing on the case explained on the previous page, this means we need to implement a user relation in our Post model.

use Nodes\CounterCache\CounterCacheable;

class Post implements CounterCacheable
{
    /**
     * Post belongs to user
     *
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function user()
    {
        return $this->belongsTo(User::class, 'user_id', 'id');
    }

    /**
     * Retrieve array of counter caches.
     *
     * @return array
     */
    public function counterCaches()
    {
        return [];
    }
}

By having a user relation, we're now able to create a counter cache between the User model and the Post model.

The way you define your relation, is to add it to the array inside the counterCaches method. The key of the array should be the name of the field, you wish to update on the related model.

use Nodes\CounterCache\CounterCacheable;

class Post implements CounterCacheable
{
    /**
     * Post belongs to user
     *
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function user()
    {
        return $this->belongsTo(User::class, 'user_id', 'id');
    }

    /**
     * Retrieve array of counter caches.
     *
     * @return array
     */
    public function counterCaches()
    {
        return [
            'posts_count' => 'user'
        ];
    }
}

In our case we want to update the field posts_count on the User model, which we have created a relation to through the user method on our Post model.

To learn how and when to execute the counter caches, jump to the next page in this Wiki.

← Implementing counter caching on your models | Executing your counter caches →

Advanced counter caches

It's not always sufficient to just use the plain relation. Sometimes you might need add WHERE conditions or you might even to update the same named fields in two different relations. No worries, this is how you do it.

Updating the "same" field on two different models

Let's use the same example as we have throughout the Wiki. We have a User model and a Post model. On the User model we have a field posts_count which we want to update every time a Post has been created.

But what if we also have a Category model, which also contains a posts_count field, which we also want to update every time a Post has been created? Don't worry, there is support for that.

You simply convert the value of your counter cache into an array so it contains a group of relations.

use Nodes\CounterCache\CounterCacheable;

class Post implements CounterCacheable
{
    /**
     * Post belongs to user
     *
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function user()
    {
        return $this->belongsTo(User::class, 'user_id', 'id');
    }

    /**
     * Post has one category
     *
     * @return \Illuminate\Database\Eloquent\Relations\HasOne
     */
    public function category()
    {
        return $this->hasOne(Category::class, 'post_id', 'id');
    }

    /**
     * Retrieve array of counter caches.
     *
     * @return array
     */
    public function counterCaches()
    {
        return [
            'posts_count' => ['user', 'category']
        ];
    }
}

Adding conditions to your counter caches

You're also able to add additional conditions to your counter caches. You might only want to update the counter caches for User who is confirmed.

Adding conditions is a little bit more complex. To avoid having to update the package every time Laravel make changes to Eloquent or add new smart features, the package is using the Builder which is available on the relation, meaning that all the genius methods like where, orWhere, whereNull etc. is available.

To add conditions to your counter caches, you need to convert the value of your simple counter to an array, where the key of that array is the name of your relation and the value is another array.

use Nodes\CounterCache\CounterCacheable;

class Post implements CounterCacheable
{
    /**
     * Post belongs to user
     *
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function user()
    {
        return $this->belongsTo(User::class, 'user_id', 'id');
    }

    /**
     * Retrieve array of counter caches.
     *
     * @return array
     */
    public function counterCaches()
    {
        return [
            'posts_count' => [
                'user' => []
            ]
        ];
    }
}

The array of the relation user is where you add your conditions. The key should be the name of your condition, where the value should be an array of the parameters, that the condition method takes.

use Nodes\CounterCache\CounterCacheable;

class Post implements CounterCacheable
{
    /**
     * Post belongs to user
     *
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function user()
    {
        return $this->belongsTo(User::class, 'user_id', 'id');
    }

    /**
     * Retrieve array of counter caches.
     *
     * @return array
     */
    public function counterCaches()
    {
        return [
            'posts_count' => [
                'user' => [
                    'where' => [
                       ['is_confirmed', '=', 1]
                    ]
                ]
            ]
        ];
    }
}

The reason to for the nested array inside the array of parameters is because the package is utilizing PHP's call_user_func_array method.

Let's use Laravel's where method as an example. The method looks like this:

public function where($column, $operator = null, $value = null, $boolean = 'and')

In counter cache terms this translates to:

'where' => [
    [$column, $operator, $value, $boolean]
]

Another example is Laravel's whereNull. The method looks like this:

public function whereNull($column, $boolean = 'and', $not = false)

In counter cache terms this translates to:

'whereNull' => [
    [$column, $boolean, $not]
]

Hopefully you get the picture now 🙂

Using conditions with multiple relations on the same named field

Just to clarify, you of course also add additional conditions, when you have counter caches with the same named field on multiple relations:

use Nodes\CounterCache\CounterCacheable;

class Post implements CounterCacheable
{
    /**
     * Post belongs to user
     *
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function user()
    {
        return $this->belongsTo(User::class, 'user_id', 'id');
    }

    /**
     * Post has one category
     *
     * @return \Illuminate\Database\Eloquent\Relations\HasOne
     */
    public function category()
    {
        return $this->hasOne(Category::class, 'post_id', 'id');
    }

    /**
     * Retrieve array of counter caches.
     *
     * @return array
     */
    public function counterCaches()
    {
        return [
            'posts_count' => [
                'user' => [
                    'where' => [
                        ['is_confirmed', '=', 1]
                    ]
                ],
                'category' => [
                    'whereNull' => [
                        ['is_deleted']
                    ]
                ]
        ];
    }
}