ndru
ndru

Reputation: 93

CakePHP 3 CounterCache - don't start counting with 0

I'm trying to migrate a IP-based voting system to logged-in-user-based voting system and would like to keep the old votes/likes. So i copied the old systems like_count into a column in my quotes table.

quotes table

enter image description here

votes table

votes

I implemented countercache behaviour for like_count in quotes table based on the votes in votes table. But if for example I'd vote the quote with id 145 up now the like_count would jump to 1 instead of 3616, because there's only 1 vote for quote id 145 in votes table.

So is there a way for the CounterCache behaviour to take into account the like_count column and start from there instead from 0?

Upvotes: 1

Views: 118

Answers (1)

ndm
ndm

Reputation: 60463

You have to account for that on your own, there's no out of the box functionality for such a case.

I'd suggest to store the old votes in a separate column, say legacy_like_count, and then either calculate the sum of the old and new votes/likes when you read the data, being it manually, or for example using a virtual property, like:

protected $_virtual = ['total_like_count'];

public function _getTotalLikeCount() {
    return $this->_properties['like_count'] + $this->_properties['legacy_like_count'];
}

or if you want the total count stored in the database, use the callback functionality supported by the counter cache behavior, where you can build a custom query that counts the new votes, and add the old number of likes, for example:

[
    'Quotes' => [
        'like_count' => function (
            \Cake\Event\Event $event,
            \Cake\Datasource\EntityInterface $entity,
            \Cake\ORM\Table $table
        ) {
            // $event = Model.afterSave or Model.afterDelete (VotesTable)
            // $entity = Vote 
            // $table = VotesTable

            $votes = $table
                ->find()
                ->where([
                    'Votes.quote_id' => $entity->get('quote_id')
                ])
                ->count();

            $quote = $table->Quotes->get($entity->get('quote_id'));

            return $votes + $quote->get('legacy_like_count');
         }
    ]
]

You could probably also do the calculation on SQL level, something along the lines of this:

$query = $table->Quotes->find();
return $query
    ->select([
        'totalVotes' => $query
            ->newExpr()
            ->add([
                'Quotes.legacy_like_count',
                $query->func()->count('Votes.id')
            ])
            ->setConjunction('+') // use tieWith() in CakePHP < 3.4
    ])
    ->leftJoinWith('Votes')
    ->where([
        'Quotes.id' => $entity->get('quote_id')
    ])
    ->groupBy('Quotes.id');

which would produce SQL similar to the following, which would (when returned as in the example) then be used as a subquery in the updating process of the like_count column:

SELECT
    (Quotes.legacy_like_count + COUNT(Votes.id)) AS totalVotes
FROM
    quotes Quotes
LEFT JOIN
    votes Votes ON Quotes.id = Votes.quote_id
WHERE
    Quotes.id = :c0
GROUP BY
    Quotes.id

Please note that this is all untested example code!

See also

Upvotes: 1

Related Questions