Skip to content

Instantly share code, notes, and snippets.

@hmic
Last active April 19, 2022 00:06
Show Gist options
  • Save hmic/f38b409bb483de8d5b85bec1e0334abe to your computer and use it in GitHub Desktop.
Save hmic/f38b409bb483de8d5b85bec1e0334abe to your computer and use it in GitHub Desktop.
Cake3 update counter caches for association
<?php
namespace App\Model\Table\Behavior;
trait UpdateCounterCacheTrait {
public function cleanupBelongsToMany($return_count = null) {
if($return_count) {
return $count;
}
return $this;
}
/**
* @param $association null|string|array
* null - update all CounterCaches
* string - update only the CounterCache for this association
* array - update CounterCaches for the listed associations,
* update only the fields listed like ['Tags' => ['count']]
* if no $cacheFields given, to update all set the key to true
* @param $cacheField null|string|array
* null - update all fields for the CounterCache(s)
* string - update only this field for the CounterCache(s)
* array - update the given fields in the CounterCache(s),
* overwriting possible set fields from the $association array
* @param $reset true|false
* reset the values to 0, if no matching entry could be found
* @throws RuntimeException when the CounterCacheBehavior is not attached
* @return int
* if $verbose_return == false, the total number of updated fields
*/
public function updateCounterCache($association = null, $cacheField = null, $reset = true) {
$counterCache = $this->behaviors()->get('CounterCache');
if(!$counterCache) {
throw new \RuntimeException('CounterCacheBehavior is not attached.');
}
if(is_string($association)) {
$association = [$association => true];
}
if(is_string($cacheField)) {
$cacheField = [$cacheField];
}
$associations = $counterCache->config();
if($association) {
$associations = array_intersect_key($associations, $association);
}
$total_count = 0;
foreach($associations as $assocName => $config) {
$assoc = $this->{$assocName};
$foreign_key = $assoc->foreignKey();
$target = $assoc->target();
$conds = $assoc->conditions();
if($cacheField) {
$config = array_intersect_key($config, array_flip($cacheField));
} elseif(is_array($association[$assocName])) {
$config = array_intersect_key($config, array_flip($association[$assocName]));
}
foreach($config as $field => $options) {
if(is_numeric($field)) {
$field = $options;
$options = [];
}
if($reset) {
$target->query()
->update()
->set($field, 0)
->where($conds)
->execute()
;
}
if(!isset($options['conditions'])) {
$options['conditions'] = [];
}
$result = $this->query()
->select([$foreign_key, 'count' => $this->query()->newExpr('COUNT(*)')])
->where($options['conditions'])
->group($foreign_key)
->toArray()
;
$totalrows = count($result);
$rowcount = 0;
$count = 0;
foreach($result as $row) {
if($rowcount++ > $totalrows / 100) {
$rowcount = 0;
}
$target->query()
->update()
->set($field, $row['count'])
->where([$target->primaryKey() => $row[$foreign_key]] + $conds)
->execute()
;
$count++;
}
$total_count += $count;
}
}
return $total_count;
}
}
@chopstik
Copy link

Instead of $result->toArray() have kept as an object to avoid PHP out of memory errors when working with larger data sets. Take a look here: https://gist.github.com/chopstik/2c370a42080b4c97ea4c7c95f449433a

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment