SlideShare a Scribd company logo
Advanced Querying with
CakePHP 3
1 / 28
Agenda
1. A short story
2. The philosophy behind the new ORM
3. The ORM goals
4. Simple Querying
5. Using SQL Functions
6. Subqueries
7. Working with associations
8. Associations Strategies
9. Filtering by Associations
10. Raw expressions and Deep Associations
11. Formatting your results
12. Intelligent Counters
2 / 28
A Short Story
Once upon a time there was a framework that worked like...
$this->Post->recursive = 3;
$this->Post->find('all');
And there was much rejoice, and then much sadness.
3 / 28
The Philosophy Behind the
New ORM
Understanding the ideas that brought us here will help you use effectively the ORM,
and enjoy using databases again.
4 / 28
The R in ORM
In 2010 I wrote a CakePHP datasource plugin for using MongoDB.
I thought it was cool.
b0ss: Can you please get last workshop attendees' emails?
me: Wait a minute, I need to write a custom Javascript program to get that data
It took much longer than a minute.
Relational databases won't go away anytime soon
They are extremely efficient
SQL is declarative language, even non programmers can get good at it
Allow you to query your data in any angle
5 / 28
No intentions of being
more than an ORM
This means:
Not going to connect to stuff that is not a relational database
We can focus on getting the most of of relational features
No incomplete abstractions
Does not mean:
That CakePHP 3 can't use NoSQL storage.
That you will spend hours scratching your head trying to wrap an API around a
fixed Interface.
6 / 28
Goals we had in mind
7 / 28
Clean layers
Each part of the subsystem should be usable on its own.
// OMG I'm not even using the ORM
$connection->newQuery()->select('*')->from('users');
// Ok, now I am
$table = TableRegistry::get('users');
$table->find();
8 / 28
Extensible column types
system
Do you really need ENUM? Go for it!
Type::map('enum', 'EnumType');
$users->schema()->columnType('role', 'enum');
9 / 28
Lazy (almost) all the things.
No database connections unless necessary.
No Queries executed unless needed.
// Look ma', no database connection have been made yet!
$users->find('all')->where(['username' => 'jose_zap']);
// Query was executed, nothing in memory yet
$users->find('all')->where(['username' => 'jose_zap'])->all();
// Only keep one row in memory at a time
$users->find('all')->where(['username' => 'jose_zap'])->bufferResults(false);
10 / 28
Should be fun to work with
Everything can be an expression
$query = $users->find()->select(['id'])->where(['is_active' => true]);
$anotherQuery->from(['stuff' => $query]);
$anotherQuery->innerJoin(['stuff' => $query]);
$anotherQuery->where(['id IN' => $query]);
Queries can be composed
$premium = $users->find('active')->find('premium')->each(function($user) {
echo $user->name;
});
$subscribers = $users->find('active')->find('subscribedToNewsletter');
$recipients = $premium->append($free)->extract('email');
11 / 28
The Setup
class CountriesTable extends Table {
public function initialize(array $config) {
$this->table('countries');
$this->belongsTo('Capitals', [
'foreignKey' => 'capital_id',
]);
$this->hasMany('Cities', [
'foreignKey' => 'country_id',
]);
$this->hasMany('Languages', [
'foreignKey' => 'country_id',
]);
}
12 / 28
Simple Querying
Monarchies with the largest population
public function findBiggestMonarchies(Query $query) {
return $query
->where(['government_form LIKE' => '%Monarchy%'])
->order(['population' => 'DESC']);
}
{
"name": "Japan",
"population": 126714000
},
{
"name": "Thailand",
"population": 61399000
},
{
"name": "United Kingdom",
"population": 59623400
},
13 / 28
Simple Querying
Republics in the world
public function findRepublics(Query $query) {
return $query
->where(['government_form' => 'Republic'])
->orWhere(['government_form' => 'Federal Republic']);
}
14 / 28
SQL Functions
Average life expectancy
public function findAverageLifeExpectancy(Query $query) {
return $query->select(['average_exp' => $query->func()->avg('life_expectancy')]);
}
{
"average_exp": 66.48604
}
15 / 28
Subqueries
public function findWithHighLifeExp(Query $query) {
$average = $this->find('findAverageLifeExpectancy');
return $query
->where(['life_expectancy >' => $average])
->order(['life_expectancy' => 'DESC']);
}
$countries->find('republics')->find('withHighLifeExp');
Republics with high life expectancy:
{
"name": "San Marino",
"life_expectancy": 81.1
},
{
"name": "Singapore",
"life_expectancy": 80.1
},
{
"name": "Iceland",
"life_expectancy": 79.4
}
16 / 28
Working with associations
$this->hasOne('OfficialLanguages', [
'className' => LanguagesTable::class,
'foreignKey' => 'country_id',
'conditions' => ['OfficialLanguages.is_official' => 'T']
]);
Official Languages
public function findWithOfficialLanguage(Query $query) {
return $query
->contain('OfficialLanguages');
}
17 / 28
Association strategies
public function findWithSpokenLanguages(Query $query, $options = []) {
if (!empty($options['languageStrategy'])) {
$this->Languages->strategy($options['languageStrategy']);
}
return $query
->contain('Languages');
}
Change the strategy:
$countries->find('withSpokenLanguages', ['languageStrategy' => 'subquery'])
And expect this SQL to be used:
SELECT * FROM languages AS Languages
WHERE country_id IN (SELECT id FROM countries AS Countries)
18 / 28
Filtering by associations
Cities with a population larger than
Denmark
public function findWithCitiesBiggerThanDenmark(Query $query) {
$denmarkPopulation = $this->find()
->select(['population'])
->where(['id' => 'DNK']);
return $query
->distinct(['Countries.id'])
->matching('Cities', function($q) use ($denmarkPopulation) {
return $q->where(['Cities.population >' => $denmarkPopulation]);
});
}
19 / 28
Raw SQL and Deep Assocs
I want to learn a new language, so I need to go to a city where that language is
spoken by at least 25% of the people who live there:
public function findCityProbability(Query $query) {
return $query
->matching('Countries.Cities', function($q) {
$prob = $q->newExpr(
'(Languages.percentage / 100) *' .
'(Cities.population / Countries.population)'
);
return $q
->select(['probability' => $prob, 'Cities.name'])
->where(function($exp) use ($prob) {
return $exp->gte($prob, 0.25);
});
});
}
20 / 28
Post processing
Things to keep in mind
Custom finders are required to return a Query object
Returning an array or a single value is not a Query
Therefore, you cannot return arrays or any other value
The Solution
Use formatResults()
Use mapReduce()
Use any of the Collection class methods after calling find()
21 / 28
Grouping by a Property
public function findInContinentGroups(Query $query) {
$query->formatResults(function($results) {
return $results->groupBy('continent');
});
return $query;
}
"Africa": [
{
"name": "Angola"
},
{
"name": "Burundi"
},
{
"name": "Benin"
},
{
"name": "Burkina Faso"
}
"America": [...
22 / 28
Getting Key - Value Lists
public function findOfficialLanguageList(Query $query) {
$query->formatResults(function($results) {
return $results->combine('name', 'official_language.language');
});
return $query->find('withOfficialLanguage');
}
{
"Aruba": "Dutch",
"Afghanistan": "Pashto",
"Albania": "Albaniana",
"Andorra": "Catalan",
"Netherlands Antilles": "Papiamento",
"United Arab Emirates": "Arabic",
"Argentina": "Spanish",
"Armenia": "Armenian",
...
23 / 28
Multiple Formatters
public function findInRegionalGroups(Query $query) {
$query
->formatResults(function($results) {
return $results->groupBy('continent');
})
->formatResults(function($results) {
return $results->map(function($continent) {
return collection($continent)->groupBy('region');
});
});
return $query;
}
"North America": {
"Caribbean": [
{
"name": "Aruba"
},
{
"name": "Anguilla"
},
{
"name": "Netherlands Antilles"
}
...
24 / 28
Intelligent Counts
$countries->find()
->select(function($query) {
return [
'average_life_expectancy' => $query->func()->avg('life_expectancy'),
'continent'
});
->group(['continent'])
->count(); // 7
Produces the following SQL:
SELECT COUNT(*) AS `count`
FROM (
SELECT (AVG(life_expectancy)), Countries.continent
FROM countries AS Countries GROUP BY continent
)
AS count_source
Pagination: piece of cake!
25 / 28
I have 99 problems...
Custom counting ain't one
Don't care about actual results counting in a pagination query?
Prefer using estimates or a different logic?
Use custom counters!
$query = $youtubeVideos->find('superComplexStuff')->counter(function() {
return Cache::read('estimated_results');
});
$query->count(); // 10000000
26 / 28
There's Plenty More!
But unfortunately, little time...
Result streaming
Query caching
Finder callbacks
Composite Primary Key searches
Methods for finding in Tree structures
27 / 28
Thanks for your time
Questions?
https://github.com/lorenzo/cakephp3-examples
28 / 28

More Related Content

What's hot (14)

PDF
왕초보를 위한 도커 사용법
GeunCheolYeom
 
PPTX
Mule memory leak issue
JeeHyunLim
 
PDF
Osic tech talk presentation on ironic inspector
Annie Lezil
 
PPTX
Automatisation des tests - objectifs et concepts - partie 2
Christophe Rochefolle
 
PPT
Page object with selenide
COMAQA.BY
 
PDF
Ansible
Raul Leite
 
PDF
Test Automation Tool comparison – HP UFT/QTP vs. Selenium
Aspire Systems
 
PDF
Postman Webinar: Postman 101
Nikita Sharma
 
PDF
Alphorm.com Formation F5 BIG-IP LTM : Local Traffic Manager
Alphorm
 
PPTX
Introduction to KubeSphere and its open source ecosystem
KubeSphere
 
PPTX
Workshop Spring - Session 4 - Spring Batch
Antoine Rey
 
PDF
CloudStack and cloud-init
MarcusS13
 
PDF
Vault
dawnlua
 
PDF
OpenStack DevStack Install - 2부 (Multi-nodes)
Ian Choi
 
왕초보를 위한 도커 사용법
GeunCheolYeom
 
Mule memory leak issue
JeeHyunLim
 
Osic tech talk presentation on ironic inspector
Annie Lezil
 
Automatisation des tests - objectifs et concepts - partie 2
Christophe Rochefolle
 
Page object with selenide
COMAQA.BY
 
Ansible
Raul Leite
 
Test Automation Tool comparison – HP UFT/QTP vs. Selenium
Aspire Systems
 
Postman Webinar: Postman 101
Nikita Sharma
 
Alphorm.com Formation F5 BIG-IP LTM : Local Traffic Manager
Alphorm
 
Introduction to KubeSphere and its open source ecosystem
KubeSphere
 
Workshop Spring - Session 4 - Spring Batch
Antoine Rey
 
CloudStack and cloud-init
MarcusS13
 
Vault
dawnlua
 
OpenStack DevStack Install - 2부 (Multi-nodes)
Ian Choi
 

Similar to Advanced Querying with CakePHP 3 (20)

PPTX
Laravel
Sayed Ahmed
 
PDF
Agile database access with CakePHP 3
José Lorenzo Rodríguez Urdaneta
 
PDF
Five Database Mistakes and how to fix them -- Confoo Vancouver
Dave Stokes
 
PDF
CakeFest 2013 keynote
José Lorenzo Rodríguez Urdaneta
 
PPTX
Why Your Database Queries Stink -SeaGl.org November 11th, 2016
Dave Stokes
 
PDF
New in cakephp3
markstory
 
PDF
Finding Love with MongoDB
MongoDB
 
ODP
Codebits 2012 - Fast relational web site construction.
Nelson Gomes
 
PPTX
Introduction to laravel framework
Ahmad Fatoni
 
KEY
Can't Miss Features of PHP 5.3 and 5.4
Jeff Carouth
 
PPT
Dal deck
Caroline_Rose
 
PDF
Relational Database Design Bootcamp
Mark Niebergall
 
PDF
PNWPHP -- What are Databases so &#%-ing Difficult
Dave Stokes
 
PPTX
CakePHP
Robert Blomdalen
 
PDF
ORM Pink Unicorns
Ortus Solutions, Corp
 
PPTX
PHP Database Programming Basics -- Northeast PHP
Dave Stokes
 
ODP
Beyond php - it's not (just) about the code
Wim Godden
 
PPTX
[Mas 500] Data Basics
rahulbot
 
PDF
Object Oriented Programming with Laravel - Session 6
Shahrzad Peyman
 
PDF
Killing Shark-Riding Dinosaurs with ORM
Ortus Solutions, Corp
 
Laravel
Sayed Ahmed
 
Agile database access with CakePHP 3
José Lorenzo Rodríguez Urdaneta
 
Five Database Mistakes and how to fix them -- Confoo Vancouver
Dave Stokes
 
CakeFest 2013 keynote
José Lorenzo Rodríguez Urdaneta
 
Why Your Database Queries Stink -SeaGl.org November 11th, 2016
Dave Stokes
 
New in cakephp3
markstory
 
Finding Love with MongoDB
MongoDB
 
Codebits 2012 - Fast relational web site construction.
Nelson Gomes
 
Introduction to laravel framework
Ahmad Fatoni
 
Can't Miss Features of PHP 5.3 and 5.4
Jeff Carouth
 
Dal deck
Caroline_Rose
 
Relational Database Design Bootcamp
Mark Niebergall
 
PNWPHP -- What are Databases so &#%-ing Difficult
Dave Stokes
 
ORM Pink Unicorns
Ortus Solutions, Corp
 
PHP Database Programming Basics -- Northeast PHP
Dave Stokes
 
Beyond php - it's not (just) about the code
Wim Godden
 
[Mas 500] Data Basics
rahulbot
 
Object Oriented Programming with Laravel - Session 6
Shahrzad Peyman
 
Killing Shark-Riding Dinosaurs with ORM
Ortus Solutions, Corp
 
Ad

Recently uploaded (20)

PDF
Why aren't you using FME Flow's CPU Time?
Safe Software
 
PDF
5 Things to Consider When Deploying AI in Your Enterprise
Safe Software
 
PDF
Understanding AI Optimization AIO, LLMO, and GEO
CoDigital
 
PPTX
Reimaginando la Ciberdefensa: De Copilots a Redes de Agentes
Cristian Garcia G.
 
PDF
Hello I'm "AI" Your New _________________
Dr. Tathagat Varma
 
PDF
🚀 Let’s Build Our First Slack Workflow! 🔧.pdf
SanjeetMishra29
 
PDF
My Journey from CAD to BIM: A True Underdog Story
Safe Software
 
PDF
Bridging CAD, IBM TRIRIGA & GIS with FME: The Portland Public Schools Case
Safe Software
 
PDF
Darley - FIRST Copenhagen Lightning Talk (2025-06-26) Epochalypse 2038 - Time...
treyka
 
PDF
ICONIQ State of AI Report 2025 - The Builder's Playbook
Razin Mustafiz
 
PDF
“A Re-imagination of Embedded Vision System Design,” a Presentation from Imag...
Edge AI and Vision Alliance
 
PDF
Understanding The True Cost of DynamoDB Webinar
ScyllaDB
 
PDF
How to Visualize the ​Spatio-Temporal Data Using CesiumJS​
SANGHEE SHIN
 
PPTX
Wondershare Filmora Crack Free Download 2025
josanj305
 
PPTX
Enabling the Digital Artisan – keynote at ICOCI 2025
Alan Dix
 
PDF
Hyderabad MuleSoft In-Person Meetup (June 21, 2025) Slides
Ravi Tamada
 
PDF
GDG Cloud Southlake #44: Eyal Bukchin: Tightening the Kubernetes Feedback Loo...
James Anderson
 
PDF
DoS Attack vs DDoS Attack_ The Silent Wars of the Internet.pdf
CyberPro Magazine
 
PPTX
MARTSIA: A Tool for Confidential Data Exchange via Public Blockchain - Poster...
Michele Kryston
 
PDF
Kubernetes - Architecture & Components.pdf
geethak285
 
Why aren't you using FME Flow's CPU Time?
Safe Software
 
5 Things to Consider When Deploying AI in Your Enterprise
Safe Software
 
Understanding AI Optimization AIO, LLMO, and GEO
CoDigital
 
Reimaginando la Ciberdefensa: De Copilots a Redes de Agentes
Cristian Garcia G.
 
Hello I'm "AI" Your New _________________
Dr. Tathagat Varma
 
🚀 Let’s Build Our First Slack Workflow! 🔧.pdf
SanjeetMishra29
 
My Journey from CAD to BIM: A True Underdog Story
Safe Software
 
Bridging CAD, IBM TRIRIGA & GIS with FME: The Portland Public Schools Case
Safe Software
 
Darley - FIRST Copenhagen Lightning Talk (2025-06-26) Epochalypse 2038 - Time...
treyka
 
ICONIQ State of AI Report 2025 - The Builder's Playbook
Razin Mustafiz
 
“A Re-imagination of Embedded Vision System Design,” a Presentation from Imag...
Edge AI and Vision Alliance
 
Understanding The True Cost of DynamoDB Webinar
ScyllaDB
 
How to Visualize the ​Spatio-Temporal Data Using CesiumJS​
SANGHEE SHIN
 
Wondershare Filmora Crack Free Download 2025
josanj305
 
Enabling the Digital Artisan – keynote at ICOCI 2025
Alan Dix
 
Hyderabad MuleSoft In-Person Meetup (June 21, 2025) Slides
Ravi Tamada
 
GDG Cloud Southlake #44: Eyal Bukchin: Tightening the Kubernetes Feedback Loo...
James Anderson
 
DoS Attack vs DDoS Attack_ The Silent Wars of the Internet.pdf
CyberPro Magazine
 
MARTSIA: A Tool for Confidential Data Exchange via Public Blockchain - Poster...
Michele Kryston
 
Kubernetes - Architecture & Components.pdf
geethak285
 
Ad

Advanced Querying with CakePHP 3

  • 2. Agenda 1. A short story 2. The philosophy behind the new ORM 3. The ORM goals 4. Simple Querying 5. Using SQL Functions 6. Subqueries 7. Working with associations 8. Associations Strategies 9. Filtering by Associations 10. Raw expressions and Deep Associations 11. Formatting your results 12. Intelligent Counters 2 / 28
  • 3. A Short Story Once upon a time there was a framework that worked like... $this->Post->recursive = 3; $this->Post->find('all'); And there was much rejoice, and then much sadness. 3 / 28
  • 4. The Philosophy Behind the New ORM Understanding the ideas that brought us here will help you use effectively the ORM, and enjoy using databases again. 4 / 28
  • 5. The R in ORM In 2010 I wrote a CakePHP datasource plugin for using MongoDB. I thought it was cool. b0ss: Can you please get last workshop attendees' emails? me: Wait a minute, I need to write a custom Javascript program to get that data It took much longer than a minute. Relational databases won't go away anytime soon They are extremely efficient SQL is declarative language, even non programmers can get good at it Allow you to query your data in any angle 5 / 28
  • 6. No intentions of being more than an ORM This means: Not going to connect to stuff that is not a relational database We can focus on getting the most of of relational features No incomplete abstractions Does not mean: That CakePHP 3 can't use NoSQL storage. That you will spend hours scratching your head trying to wrap an API around a fixed Interface. 6 / 28
  • 7. Goals we had in mind 7 / 28
  • 8. Clean layers Each part of the subsystem should be usable on its own. // OMG I'm not even using the ORM $connection->newQuery()->select('*')->from('users'); // Ok, now I am $table = TableRegistry::get('users'); $table->find(); 8 / 28
  • 9. Extensible column types system Do you really need ENUM? Go for it! Type::map('enum', 'EnumType'); $users->schema()->columnType('role', 'enum'); 9 / 28
  • 10. Lazy (almost) all the things. No database connections unless necessary. No Queries executed unless needed. // Look ma', no database connection have been made yet! $users->find('all')->where(['username' => 'jose_zap']); // Query was executed, nothing in memory yet $users->find('all')->where(['username' => 'jose_zap'])->all(); // Only keep one row in memory at a time $users->find('all')->where(['username' => 'jose_zap'])->bufferResults(false); 10 / 28
  • 11. Should be fun to work with Everything can be an expression $query = $users->find()->select(['id'])->where(['is_active' => true]); $anotherQuery->from(['stuff' => $query]); $anotherQuery->innerJoin(['stuff' => $query]); $anotherQuery->where(['id IN' => $query]); Queries can be composed $premium = $users->find('active')->find('premium')->each(function($user) { echo $user->name; }); $subscribers = $users->find('active')->find('subscribedToNewsletter'); $recipients = $premium->append($free)->extract('email'); 11 / 28
  • 12. The Setup class CountriesTable extends Table { public function initialize(array $config) { $this->table('countries'); $this->belongsTo('Capitals', [ 'foreignKey' => 'capital_id', ]); $this->hasMany('Cities', [ 'foreignKey' => 'country_id', ]); $this->hasMany('Languages', [ 'foreignKey' => 'country_id', ]); } 12 / 28
  • 13. Simple Querying Monarchies with the largest population public function findBiggestMonarchies(Query $query) { return $query ->where(['government_form LIKE' => '%Monarchy%']) ->order(['population' => 'DESC']); } { "name": "Japan", "population": 126714000 }, { "name": "Thailand", "population": 61399000 }, { "name": "United Kingdom", "population": 59623400 }, 13 / 28
  • 14. Simple Querying Republics in the world public function findRepublics(Query $query) { return $query ->where(['government_form' => 'Republic']) ->orWhere(['government_form' => 'Federal Republic']); } 14 / 28
  • 15. SQL Functions Average life expectancy public function findAverageLifeExpectancy(Query $query) { return $query->select(['average_exp' => $query->func()->avg('life_expectancy')]); } { "average_exp": 66.48604 } 15 / 28
  • 16. Subqueries public function findWithHighLifeExp(Query $query) { $average = $this->find('findAverageLifeExpectancy'); return $query ->where(['life_expectancy >' => $average]) ->order(['life_expectancy' => 'DESC']); } $countries->find('republics')->find('withHighLifeExp'); Republics with high life expectancy: { "name": "San Marino", "life_expectancy": 81.1 }, { "name": "Singapore", "life_expectancy": 80.1 }, { "name": "Iceland", "life_expectancy": 79.4 } 16 / 28
  • 17. Working with associations $this->hasOne('OfficialLanguages', [ 'className' => LanguagesTable::class, 'foreignKey' => 'country_id', 'conditions' => ['OfficialLanguages.is_official' => 'T'] ]); Official Languages public function findWithOfficialLanguage(Query $query) { return $query ->contain('OfficialLanguages'); } 17 / 28
  • 18. Association strategies public function findWithSpokenLanguages(Query $query, $options = []) { if (!empty($options['languageStrategy'])) { $this->Languages->strategy($options['languageStrategy']); } return $query ->contain('Languages'); } Change the strategy: $countries->find('withSpokenLanguages', ['languageStrategy' => 'subquery']) And expect this SQL to be used: SELECT * FROM languages AS Languages WHERE country_id IN (SELECT id FROM countries AS Countries) 18 / 28
  • 19. Filtering by associations Cities with a population larger than Denmark public function findWithCitiesBiggerThanDenmark(Query $query) { $denmarkPopulation = $this->find() ->select(['population']) ->where(['id' => 'DNK']); return $query ->distinct(['Countries.id']) ->matching('Cities', function($q) use ($denmarkPopulation) { return $q->where(['Cities.population >' => $denmarkPopulation]); }); } 19 / 28
  • 20. Raw SQL and Deep Assocs I want to learn a new language, so I need to go to a city where that language is spoken by at least 25% of the people who live there: public function findCityProbability(Query $query) { return $query ->matching('Countries.Cities', function($q) { $prob = $q->newExpr( '(Languages.percentage / 100) *' . '(Cities.population / Countries.population)' ); return $q ->select(['probability' => $prob, 'Cities.name']) ->where(function($exp) use ($prob) { return $exp->gte($prob, 0.25); }); }); } 20 / 28
  • 21. Post processing Things to keep in mind Custom finders are required to return a Query object Returning an array or a single value is not a Query Therefore, you cannot return arrays or any other value The Solution Use formatResults() Use mapReduce() Use any of the Collection class methods after calling find() 21 / 28
  • 22. Grouping by a Property public function findInContinentGroups(Query $query) { $query->formatResults(function($results) { return $results->groupBy('continent'); }); return $query; } "Africa": [ { "name": "Angola" }, { "name": "Burundi" }, { "name": "Benin" }, { "name": "Burkina Faso" } "America": [... 22 / 28
  • 23. Getting Key - Value Lists public function findOfficialLanguageList(Query $query) { $query->formatResults(function($results) { return $results->combine('name', 'official_language.language'); }); return $query->find('withOfficialLanguage'); } { "Aruba": "Dutch", "Afghanistan": "Pashto", "Albania": "Albaniana", "Andorra": "Catalan", "Netherlands Antilles": "Papiamento", "United Arab Emirates": "Arabic", "Argentina": "Spanish", "Armenia": "Armenian", ... 23 / 28
  • 24. Multiple Formatters public function findInRegionalGroups(Query $query) { $query ->formatResults(function($results) { return $results->groupBy('continent'); }) ->formatResults(function($results) { return $results->map(function($continent) { return collection($continent)->groupBy('region'); }); }); return $query; } "North America": { "Caribbean": [ { "name": "Aruba" }, { "name": "Anguilla" }, { "name": "Netherlands Antilles" } ... 24 / 28
  • 25. Intelligent Counts $countries->find() ->select(function($query) { return [ 'average_life_expectancy' => $query->func()->avg('life_expectancy'), 'continent' }); ->group(['continent']) ->count(); // 7 Produces the following SQL: SELECT COUNT(*) AS `count` FROM ( SELECT (AVG(life_expectancy)), Countries.continent FROM countries AS Countries GROUP BY continent ) AS count_source Pagination: piece of cake! 25 / 28
  • 26. I have 99 problems... Custom counting ain't one Don't care about actual results counting in a pagination query? Prefer using estimates or a different logic? Use custom counters! $query = $youtubeVideos->find('superComplexStuff')->counter(function() { return Cache::read('estimated_results'); }); $query->count(); // 10000000 26 / 28
  • 27. There's Plenty More! But unfortunately, little time... Result streaming Query caching Finder callbacks Composite Primary Key searches Methods for finding in Tree structures 27 / 28
  • 28. Thanks for your time Questions? https://github.com/lorenzo/cakephp3-examples 28 / 28