Cakephp 3: Countercache with conditions on the target model

430 views Asked by At

I am trying to count the number of Spots associated with a Plan, but limited to Spots downloaded after the plans renewal date. Hope that makes sense. I would image something like this, but it doesn't work:

class SpotsTable extends Table
{
    public function initialize(array $config)
    {
        $this->addBehavior('CounterCache', [
            'Plan' => [
                'creditsUsed' => [
                    'conditions' => [
                        'downloaded >' => 'Plan.renewed'
                    ]
                ]
            ]
        ]);
        ...
    }
...
}

Basically right now it acts as though Plan.renewed means NULL. Is this possible, or am I on the wrong track?

1

There are 1 answers

1
ndm On BEST ANSWER

Two problems

1. Identifiers cannot be passed as string values

When using the key => value format, the value side will always be subject to binding/escaping/casting unless it's an expression object, so since the downloaded column is probably a date/time type, you'll end up with Plan.renewed being bound as a string, thus the final SQL will be something like:

downloaded > 'Plan.renewed'

which probably always results in false. Long story short, use for example an identifier expression:

'Spots.downloaded >' => new \Cake\Database\Expression\IdentifierExpression('Plan.renewed')

2. The counter query doesn't have access to the associated table

Plan.renewed will not be accessible in the query generated by the counter cache behavior, it will not automatically contain/join associations, it will create a simple query with a condition based on the foreign key value in the currently processed Spot entity.

So you have to use a custom/modified query, for example using a custom finder, something like this:

'creditsUsed' => [
    'finder' => 'downloadedAfterPlanRenewal'
]
// in SpotsTable

public function findDownloadedAfterPlanRenewal(\Cake\ORM\Query $query, array $options)
{
    return $query
        ->innerJoinWith('Plan')
        ->where([
            'Spots.downloaded >' => $query->identifier('Plan.renewed')
        ]);
}

This will properly join in the association, so that you can compare with a field from Plan. The original primary key conditions generated by the behavior will already be applied on the given query object.

See also