Advanced Querying with CakePHP 3

28

description

CakePHP 3 comes with a new ORM. This talk starts with a short history of how this library came to be and then walk through a series of examples from beginner to advanced for using the Querying features of the ORM.

Transcript of Advanced Querying with CakePHP 3

Page 1: Advanced Querying with CakePHP 3

Advanced Querying withCakePHP 3

1 / 28

Page 2: Advanced Querying with CakePHP 3

Agenda1. A short story2. The philosophy behind the new ORM3. The ORM goals4. Simple Querying5. Using SQL Functions6. Subqueries7. Working with associations8. Associations Strategies9. Filtering by Associations

10. Raw expressions and Deep Associations11. Formatting your results12. Intelligent Counters

2 / 28

Page 3: Advanced Querying with CakePHP 3

A Short StoryOnce 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

Page 4: Advanced Querying with CakePHP 3

The Philosophy Behind theNew ORM

Understanding the ideas that brought us here will help you use effectively the ORM,and enjoy using databases again.

4 / 28

Page 5: Advanced Querying with CakePHP 3

The R in ORMIn 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 soonThey are extremely efficientSQL is declarative language, even non programmers can get good at itAllow you to query your data in any angle

5 / 28

Page 6: Advanced Querying with CakePHP 3

No intentions of beingmore than an ORMThis means:

Not going to connect to stuff that is not a relational databaseWe can focus on getting the most of of relational featuresNo 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 afixed Interface.

6 / 28

Page 7: Advanced Querying with CakePHP 3

Goals we had in mind

7 / 28

Page 8: Advanced Querying with CakePHP 3

Clean layersEach 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

Page 9: Advanced Querying with CakePHP 3

Extensible column typessystemDo you really need ENUM? Go for it!

Type::map('enum', 'EnumType');$users->schema()->columnType('role', 'enum');

9 / 28

Page 10: Advanced Querying with CakePHP 3

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

Page 11: Advanced Querying with CakePHP 3

Should be fun to work withEverything 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

Page 12: Advanced Querying with CakePHP 3

The Setupclass 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

Page 13: Advanced Querying with CakePHP 3

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

Page 14: Advanced Querying with CakePHP 3

Simple Querying

Republics in the world

public function findRepublics(Query $query) { return $query ->where(['government_form' => 'Republic']) ->orWhere(['government_form' => 'Federal Republic']); }

14 / 28

Page 15: Advanced Querying with CakePHP 3

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

Page 16: Advanced Querying with CakePHP 3

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

Page 17: Advanced Querying with CakePHP 3

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

Page 18: Advanced Querying with CakePHP 3

Association strategiespublic 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

Page 19: Advanced Querying with CakePHP 3

Filtering by associations

Cities with a population larger thanDenmark

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

Page 20: Advanced Querying with CakePHP 3

Raw SQL and Deep AssocsI want to learn a new language, so I need to go to a city where that language isspoken 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

Page 21: Advanced Querying with CakePHP 3

Post processing

Things to keep in mind

Custom finders are required to return a Query objectReturning an array or a single value is not a QueryTherefore, 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

Page 22: Advanced Querying with CakePHP 3

Grouping by a Propertypublic 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

Page 23: Advanced Querying with CakePHP 3

Getting Key - Value Listspublic 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

Page 24: Advanced Querying with CakePHP 3

Multiple Formatterspublic 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

Page 25: Advanced Querying with CakePHP 3

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

Page 26: Advanced Querying with CakePHP 3

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

Page 27: Advanced Querying with CakePHP 3

There's Plenty More!But unfortunately, little time...

Result streamingQuery cachingFinder callbacksComposite Primary Key searchesMethods for finding in Tree structures

27 / 28

Page 28: Advanced Querying with CakePHP 3

Thanks for your time

Questions?

https://github.com/lorenzo/cakephp3-examples

28 / 28