https://book.cakephp.org/3.0/en/appendices/orm-migration.html#no-afterfind-event-or-virtual-fields
Once defined you can access your new property using $user->full_name. Using the Modifying Results with Map/Reduce features of the ORM allow you to build aggregated data from your results, which is another use case that the afterFind callback was often used for.
Call count() on the ResultSet object
https://api.cakephp.org/3.4/class-Cake.ORM.ResultSet.html#_count
No idea why you're not using that and want to do the count manually, but go on.
Using resultFormatter()
https://book.cakephp.org/3.0/en/orm/query-builder.html#adding-calculated-fields
You behavior will have to add the formatter in your beforeFind() to the query. You can add a custom find that adds it as well. Example code (taken from the docs):
$query->formatResults(function (\Cake\Collection\CollectionInterface $results) {
return $results->map(function ($row) {
$row['age'] = $row['birth_date']->diff(new \DateTime)->y;
return $row;
});
});
Count the results there.
Using map/reduce
https://book.cakephp.org/3.0/en/orm/retrieving-data-and-resultsets.html#map-reduce
More often than not, find operations require post-processing the data that is found in the database. While entities’ getter methods can take care of most of the virtual property generation or special data formatting, sometimes you need to change the data structure in a more fundamental way.
You behavior will have to add the mapper/reducer in your beforeFind() to the query. You can add a custom find that adds it as well. Example code (taken from the docs):
$articlesByStatus = $articles->find()
->where(['author_id' => 1])
->mapReduce($mapper, $reducer);
Check the above link for a detailed explanation including examples of how to create a mapper and reducer.