From 89b2bd1da2e746a0dff073187a1b9caf0bc9a2ba Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 28 Apr 2023 13:33:48 +0530 Subject: [PATCH 1/3] Add info about finder changes to the migration guide. Refs cakephp/cakephp#17108. --- en/appendices/5-0-migration-guide.rst | 49 +++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/en/appendices/5-0-migration-guide.rst b/en/appendices/5-0-migration-guide.rst index b0ba9b6ecf..2f347db9e3 100644 --- a/en/appendices/5-0-migration-guide.rst +++ b/en/appendices/5-0-migration-guide.rst @@ -165,8 +165,8 @@ ORM - ``allowMultipleNulls`` option for ``isUnique`` rule now default to true matching the original 3.x behavior. - ``Table::query()`` has been removed in favor of query-type specific functions. -- ``Table::updateQuery()``, ``Table::selectQuery()``, ``Table::insertQuery()``, and ` - `Table::deleteQuery()``) were added and return the new type-specific query objects below. +- ``Table::updateQuery()``, ``Table::selectQuery()``, ``Table::insertQuery()``, and + ``Table::deleteQuery()``) were added and return the new type-specific query objects below. - ``SelectQuery``, ``InsertQuery``, ``UpdateQuery`` and ``DeleteQuery`` were added which represent only a single type of query and do not allow switching between query types nor calling functions unrelated to the specific query type. @@ -227,6 +227,14 @@ Database ``Connection`` methods are no longer proxied. This aligns the function name with the SQL statement. +ORM +--- + +- Calling ``Table::find()`` with options array is deprecated. Use `named arguments `__ + instead. For e.g. instead of ``find('all', ['conditions' => $array])`` use + ``find('all', conditions: $array)``. Similarly for custom finder options, instead + of ``find('list', ['valueField' => 'name'])`` use ``find('list', valueField: 'name')`` + or multiple named arguments like ``find(type: 'list', valueField: 'name', conditions: $array)``. New Features ============ @@ -256,6 +264,42 @@ Database - Supported drivers now automatically add auto-increment only to integer primary keys named "id" instead of all integer primary keys. Setting 'autoIncrement' to false always disables on all supported drivers. +ORM +--- + +- Table finders can now have typed arguments as required instead of an options array. + For e.g. a finder for fetching posts by category or user:: + + public function findByCategoryOrUser(SelectQuery $query, array $options) + { + if (isset($options['categoryId'])) { + $query->where('category_id' => $options['categoryId']); + } + if (isset($options['userId'])) { + $query->where('user_id' => $options['userId']); + } + + return $query; + } + + should now be written as:: + + public function findByCategoryOrUser(SelectQuery $query, ?int $categoryId = null, ?int $userId = null) + { + if ($categoryId) { + $query->where('category_id' => $categoryId); + } + if ($userId) { + $query->where('user_id' => $userId); + } + + return $query; + } + + The finder can then be called as ``find('byCategoryOrUser', userId: $somevar)``. + You can even include the special named arguments for setting query clauses. + ``find('byCategoryOrUser', userId: $somevar, conditions: ['enabled' => true])``. + Http ---- @@ -267,4 +311,3 @@ TestSuite --------- - ``IntegrationTestTrait::requestAsJson()`` has been added to set JSON headers for the next request. - From ea5f8775a171fd5e6c4597e1e71c4db897c3f9b5 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 28 Apr 2023 14:07:56 +0530 Subject: [PATCH 2/3] Update finder examples to use named arguments. --- en/controllers.rst | 8 +- en/controllers/components/authentication.rst | 2 +- en/controllers/pagination.rst | 12 ++- en/development/testing.rst | 8 +- en/orm/associations.rst | 4 +- en/orm/behaviors.rst | 6 +- en/orm/behaviors/translate.rst | 8 +- en/orm/behaviors/tree.rst | 32 ++++---- en/orm/retrieving-data-and-resultsets.rst | 81 +++++++------------ .../cms/tags-and-users.rst | 14 ++-- en/views/cells.rst | 2 +- 11 files changed, 71 insertions(+), 106 deletions(-) diff --git a/en/controllers.rst b/en/controllers.rst index 5be397b983..d9191c5b6c 100644 --- a/en/controllers.rst +++ b/en/controllers.rst @@ -414,10 +414,10 @@ The ``fetchTable()`` function comes handy when you need to use a table that is n the controller's default one:: // In a controller method. - $recentArticles = $this->fetchTable('Articles')->find('all', [ - 'limit' => 5, - 'order' => 'Articles.created DESC' - ]) + $recentArticles = $this->fetchTable('Articles')->find('all', + limit: 5, + order: 'Articles.created DESC' + ) ->all(); Paginating a Model diff --git a/en/controllers/components/authentication.rst b/en/controllers/components/authentication.rst index df6446765d..16973a64f4 100644 --- a/en/controllers/components/authentication.rst +++ b/en/controllers/components/authentication.rst @@ -199,7 +199,7 @@ In the example shown below the query is modified to fetch only required fields and add a condition. You must ensure that you select the fields you need to authenticate a user, such as ``username`` and ``password``:: - public function findAuth(\Cake\ORM\Query\SelectQuery $query, array $options) + public function findAuth(\Cake\ORM\Query\SelectQuery $query) { $query ->select(['id', 'username', 'password']) diff --git a/en/controllers/pagination.rst b/en/controllers/pagination.rst index 63ad748d6b..fd24fb865f 100644 --- a/en/controllers/pagination.rst +++ b/en/controllers/pagination.rst @@ -85,7 +85,7 @@ as values for the finder:: // Our custom finder is called findTagged inside ArticlesTable.php // which is why we're using `tagged` as the key. // Our finder should look like: - // public function findTagged(Query $query, array $options) { + // public function findTagged(Query $query, array $tagged = []) $settings = [ 'finder' => [ 'tagged' => $customFinderOptions @@ -189,9 +189,8 @@ need to define an alias for the model.:: ]; $publishedArticles = $this->paginate( - $this->Articles->find('all', [ - 'scope' => 'published_articles' - ])->where(['published' => true]) + $this->Articles->find('all', scope: 'published_articles') + ->where(['published' => true]) ); // Load an additional table object to allow differentiating in the paginator @@ -202,9 +201,8 @@ need to define an alias for the model.:: ]); $unpublishedArticles = $this->paginate( - $unpublishedArticlesTable->find('all', [ - 'scope' => 'unpublished_articles' - ])->where(['published' => false]) + $unpublishedArticlesTable->find('all', scope: 'unpublished_articles') + ->where(['published' => false]) ); .. _control-which-fields-used-for-ordering: diff --git a/en/development/testing.rst b/en/development/testing.rst index 58ed86bb73..a8a30f21f9 100644 --- a/en/development/testing.rst +++ b/en/development/testing.rst @@ -834,11 +834,12 @@ Let's say we already have our Articles Table class defined in class ArticlesTable extends Table { - public function findPublished(SelectQuery $query, array $options): SelectQuery + public function findPublished(SelectQuery $query): SelectQuery { $query->where([ $this->alias() . '.published' => 1 ]); + return $query; } } @@ -997,10 +998,7 @@ controller code looks like:: } } if (!empty($short)) { - $result = $this->Articles->find('all', [ - 'fields' => ['id', 'title'] - ]) - ->all(); + $result = $this->Articles->find('all', fields: ['id', 'title'])->all(); } else { $result = $this->Articles->find()->all(); } diff --git a/en/orm/associations.rst b/en/orm/associations.rst index bbd4f2319f..760bed09ad 100644 --- a/en/orm/associations.rst +++ b/en/orm/associations.rst @@ -709,8 +709,8 @@ such as a where condition by designating the through table name before the field you are filtering on:: $query = $this->find( - 'list', - ['valueField' => 'studentFirstName', 'order' => 'students.id'] + 'list', + valueField: 'studentFirstName', order: 'students.id' ) ->contain(['Courses']) ->matching('Courses') diff --git a/en/orm/behaviors.rst b/en/orm/behaviors.rst index 1c285d1fb8..afe238d62f 100644 --- a/en/orm/behaviors.rst +++ b/en/orm/behaviors.rst @@ -204,14 +204,14 @@ a finder method so we can fetch articles by their slug. Behavior finder methods, use the same conventions as :ref:`custom-find-methods` do. Our ``find('slug')`` method would look like:: - public function findSlug(SelectQuery $query, array $options) + public function findSlug(SelectQuery $query, string $slug): SelectQuery { - return $query->where(['slug' => $options['slug']]); + return $query->where(['slug' => $slug]); } Once our behavior has the above method we can call it:: - $article = $articles->find('slug', ['slug' => $value])->first(); + $article = $articles->find('slug', slug: $value)->first(); Limiting or Renaming Exposed Finder Methods ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/en/orm/behaviors/translate.rst b/en/orm/behaviors/translate.rst index 097dd86d70..547cd04492 100644 --- a/en/orm/behaviors/translate.rst +++ b/en/orm/behaviors/translate.rst @@ -239,7 +239,7 @@ use TranslateBehavior with ``find('list')``:: [1 => 'Mi primer artículo', 2 => 'El segundo artículo', 15 => 'Otro articulo' ...] // Change the locale to french for a single find call - $data = $this->Articles->find('list', ['locale' => 'fr'])->toArray(); + $data = $this->Articles->find('list', locale: 'fr')->toArray(); Retrieve All Translations For An Entity --------------------------------------- @@ -290,9 +290,7 @@ Limiting the Translations to be Retrieved You can limit the languages that are fetched from the database for a particular set of records:: - $results = $this->Articles->find('translations', [ - 'locales' => ['en', 'es'] - ]); + $results = $this->Articles->find('translations', locales: ['en', 'es']); $article = $results->first(); $spanishTranslation = $article->translation('es'); $englishTranslation = $article->translation('en'); @@ -366,7 +364,7 @@ to call the method on each table, for example:: $this->Articles->setLocale('es'); $this->Articles->Categories->setLocale('es'); - $data = $this->Articles->find('all', ['contain' => ['Categories']]); + $data = $this->Articles->find('all', contain: ['Categories']); This example also assumes that ``Categories`` has the TranslateBehavior attached to it. diff --git a/en/orm/behaviors/tree.rst b/en/orm/behaviors/tree.rst index 5c0bf0530c..c8f321301c 100644 --- a/en/orm/behaviors/tree.rst +++ b/en/orm/behaviors/tree.rst @@ -68,7 +68,7 @@ Getting direct descendents Getting a flat list of the descendants for a node can be done with:: - $descendants = $categories->find('children', ['for' => 1]); + $descendants = $categories->find('children', for: 1); foreach ($descendants as $category) { echo $category->name . "\n"; @@ -77,7 +77,7 @@ Getting a flat list of the descendants for a node can be done with:: If you need to pass conditions you do so as per normal:: $descendants = $categories - ->find('children', ['for' => 1]) + ->find('children', for: 1) ->where(['name LIKE' => '%Foo%']) ->all(); @@ -89,7 +89,7 @@ If you instead need a threaded list, where children for each node are nested in a hierarchy, you can stack the 'threaded' finder:: $children = $categories - ->find('children', ['for' => 1]) + ->find('children', for: 1) ->find('threaded') ->toArray(); @@ -97,7 +97,7 @@ in a hierarchy, you can stack the 'threaded' finder:: echo "{$child->name} has " . count($child->children) . " direct children"; } -While, if you’re using custom ``parent_id`` you need to pass it in the +While, if you're using custom ``parent_id`` you need to pass it in the 'threaded' finder option (i.e. ``parentField``) . .. note:: @@ -143,21 +143,19 @@ The ``treeList`` finder takes a number of options: An example of all options in use is:: - $query = $categories->find('treeList', [ - 'keyPath' => 'url', - 'valuePath' => 'id', - 'spacer' => ' ' - ]); + $query = $categories->find('treeList', + keyPath: 'url', + valuePath: 'id', + spacer: ' ' + ); An example using closure:: - $query = $categories->find('treeList', [ - 'keyPath' => 'url', - 'valuePath' => function($entity){ + $query = $categories->find('treeList', + valuePath: function($entity){ return $entity->url . ' ' . $entity->id - }, - 'spacer' => ' ' - ]); + } + ); Finding a path or branch in the tree ------------------------------------ @@ -167,7 +165,7 @@ of the tree. This is useful, for example, for adding the breadcrumbs list for a menu structure:: $nodeId = 5; - $crumbs = $categories->find('path', ['for' => $nodeId])->all(); + $crumbs = $categories->find('path', for: $nodeId)->all(); foreach ($crumbs as $crumb) { echo $crumb->name . ' > '; @@ -322,7 +320,7 @@ The deletion of a node is based off of the ``lft`` and ``rght`` values of the en is important to note when looping through the various children of a node for conditional deletes:: - $descendants = $teams->find('children', ['for' => 1])->all(); + $descendants = $teams->find('children', for: 1)->all(); foreach ($descendants as $descendant) { $team = $teams->get($descendant->id); // search for the up-to-date entity object diff --git a/en/orm/retrieving-data-and-resultsets.rst b/en/orm/retrieving-data-and-resultsets.rst index ced8ab7a50..88d36fe6db 100644 --- a/en/orm/retrieving-data-and-resultsets.rst +++ b/en/orm/retrieving-data-and-resultsets.rst @@ -99,7 +99,7 @@ The list of options supported by get() are: Using Finders to Load Data ========================== -.. php:method:: find($type, $options = []) +.. php:method:: find($type, mixed ...$args) Before you can work with entities, you'll need to load them. The easiest way to do this is using the ``find()`` method. The find method provides a short and @@ -150,13 +150,13 @@ You can also provide many commonly used options to ``find()``. This can help with testing as there are fewer methods to mock:: // In a controller or table method. - $query = $articles->find('all', [ - 'conditions' => ['Articles.created >' => new DateTime('-10 days')], - 'contain' => ['Authors', 'Comments'], - 'limit' => 10 - ]); + $query = $articles->find('all', + conditions: ['Articles.created >' => new DateTime('-10 days')], + contain: ['Authors', 'Comments'], + limit: 10 + ); -The list of options supported by find() are: +The list of named arguments supported by find() by default are: - ``conditions`` provide conditions for the WHERE clause of your query. - ``limit`` Set the number of rows you want. @@ -195,9 +195,7 @@ The ``first()`` method allows you to fetch only the first row from a query. If the query has not been executed, a ``LIMIT 1`` clause will be applied:: // In a controller or table method. - $query = $articles->find('all', [ - 'order' => ['Articles.created' => 'DESC'] - ]); + $query = $articles->find('all', order: ['Articles.created' => 'DESC']); $row = $query->first(); This approach replaces ``find('first')`` in previous versions of CakePHP. You @@ -215,9 +213,7 @@ Once you have created a query object, you can use the ``count()`` method to get a result count of that query:: // In a controller or table method. - $query = $articles->find('all', [ - 'conditions' => ['Articles.title LIKE' => '%Ovens%'] - ]); + $query = $articles->find('all', conditions: ['Articles.title LIKE' => '%Ovens%']); $number = $query->count(); See :ref:`query-count` for additional usage of the ``count()`` method. @@ -259,10 +255,7 @@ When calling ``list`` you can configure the fields used for the key and value with the ``keyField`` and ``valueField`` options respectively:: // In a controller or table method. - $query = $articles->find('list', [ - 'keyField' => 'slug', - 'valueField' => 'label' - ]); + $query = $articles->find('list', keyField: 'slug', valueField: 'label'); $data = $query->toArray(); // Data now looks like @@ -275,11 +268,7 @@ Results can be grouped into nested sets. This is useful when you want bucketed sets, or want to build ```` elements with ``FormHelper``:: // In a controller or table method. - $query = $articles->find('list', [ - 'keyField' => 'slug', - 'valueField' => 'label', - 'groupField' => 'author_id' - ]); + $query = $articles->find('list', keyField: 'slug', valueField: 'label', groupField: 'author_id'); $data = $query->toArray(); // Data now looks like @@ -295,10 +284,8 @@ bucketed sets, or want to build ```` elements with ``FormHelper``:: You can also create list data from associations that can be reached with joins:: - $query = $articles->find('list', [ - 'keyField' => 'id', - 'valueField' => 'author.name' - ])->contain(['Authors']); + $query = $articles->find('list', keyField: 'id', valueField: 'author.name') + ->contain(['Authors']); The ``keyField``, ``valueField``, and ``groupField`` expression will operate on entity attribute paths not the database columns. This means that you can use @@ -321,12 +308,12 @@ This example shows using the ``_getLabel()`` accessor method from the Author entity. :: // In your finders/controller: - $query = $articles->find('list', [ - 'keyField' => 'id', - 'valueField' => function ($article) { + $query = $articles->find('list', + keyField: 'id', + valueField: function ($article) { return $article->author->get('label'); } - ]) + ) ->contain('Authors'); You can also fetch the label in the list directly using. :: @@ -351,10 +338,10 @@ attribute:: $query = $comments->find('threaded'); // Expanded default values - $query = $comments->find('threaded', [ - 'keyField' => $comments->primaryKey(), - 'parentField' => 'parent_id' - ]); + $query = $comments->find('threaded', + keyField: $comments->primaryKey(), + parentField: 'parent_id' + ); $results = $query->toArray(); echo count($results[0]->children); @@ -381,19 +368,19 @@ where ``Foo`` is the name of the finder you want to create. For example if we wanted to add a finder to our articles table for finding articles written by a given user, we would do the following:: + use App\Model\Entity\User; use Cake\ORM\Query\SelectQuery; use Cake\ORM\Table; class ArticlesTable extends Table { - public function findOwnedBy(SelectQuery $query, array $options) + public function findOwnedBy(SelectQuery $query, User $user) { - $user = $options['user']; return $query->where(['author_id' => $user->id]); } } - $query = $articles->find('ownedBy', ['user' => $userEntity]); + $query = $articles->find('ownedBy', user: $userEntity); Finder methods can modify the query as required, or use the ``$options`` to customize the finder operation with relevant application logic. You can also @@ -409,14 +396,6 @@ If you need to modify the results after they have been fetched you should use a :ref:`map-reduce` function to modify the results. The map reduce features replace the 'afterFind' callback found in previous versions of CakePHP. -.. note:: - - Passing arguments exposed in the **config** array, - ``$products->find('sizes', ['large', 'medium'])`` - can give unexpected results when chaining - custom finders. Always pass options as an associative array, - ``$products->find('sizes', ['values' => ['large', 'medium']])`` - .. _dynamic-finders: Dynamic Finders @@ -449,9 +428,7 @@ with custom finders:: The above would translate into the following:: - $users->find('trolls', [ - 'conditions' => ['username' => 'bro'] - ]); + $users->find('trolls', conditions: ['username' => 'bro']); Once you have a query object from a dynamic finder, you'll need to call ``first()`` if you want the first result. @@ -504,7 +481,7 @@ you state which associations should be eager loaded using the 'contain' method:: // In a controller or table method. // As an option to find() - $query = $articles->find('all', ['contain' => ['Authors', 'Comments']]); + $query = $articles->find('all', contain: ['Authors', 'Comments']); // As a method on the query object $query = $articles->find('all'); @@ -1259,17 +1236,17 @@ even after adding a map-reduce routine:: This is particularly useful for building custom finder methods as described in the :ref:`custom-find-methods` section:: - public function findPublished(SelectQuery $query, array $options) + public function findPublished(SelectQuery $query) { return $query->where(['published' => true]); } - public function findRecent(SelectQuery $query, array $options) + public function findRecent(SelectQuery $query) { return $query->where(['created >=' => new DateTime('1 day ago')]); } - public function findCommonWords(SelectQuery $query, array $options) + public function findCommonWords(SelectQuery $query) { // Same as in the common words example in the previous section $mapper = ...; diff --git a/en/tutorials-and-examples/cms/tags-and-users.rst b/en/tutorials-and-examples/cms/tags-and-users.rst index 33cd80bca8..f69adb2322 100644 --- a/en/tutorials-and-examples/cms/tags-and-users.rst +++ b/en/tutorials-and-examples/cms/tags-and-users.rst @@ -191,9 +191,7 @@ add the following:: $tags = $this->request->getParam('pass'); // Use the ArticlesTable to find tagged articles. - $articles = $this->Articles->find('tagged', [ - 'tags' => $tags - ]) + $articles = $this->Articles->find('tagged', tags: $tags) ->all(); // Pass variables into the view template context. @@ -212,9 +210,7 @@ action using PHP's variadic argument:: public function tags(...$tags) { // Use the ArticlesTable to find tagged articles. - $articles = $this->Articles->find('tagged', [ - 'tags' => $tags - ]) + $articles = $this->Articles->find('tagged', tags: $tags) ->all(); // Pass variables into the view template context. @@ -240,7 +236,7 @@ method has not been implemented yet, so let's do that. In // The $query argument is a query builder instance. // The $options array will contain the 'tags' option we passed // to find('tagged') in our controller action. - public function findTagged(SelectQuery $query, array $options): SelectQuery + public function findTagged(SelectQuery $query, array $tags = []): SelectQuery { $columns = [ 'Articles.id', 'Articles.user_id', 'Articles.title', @@ -252,14 +248,14 @@ method has not been implemented yet, so let's do that. In ->select($columns) ->distinct($columns); - if (empty($options['tags'])) { + if (empty($tags)) { // If there are no tags provided, find articles that have no tags. $query->leftJoinWith('Tags') ->where(['Tags.title IS' => null]); } else { // Find articles that have one or more of the provided tags. $query->innerJoinWith('Tags') - ->where(['Tags.title IN' => $options['tags']]); + ->where(['Tags.title IN' => $tags]); } return $query->groupBy(['Articles.id']); diff --git a/en/views/cells.rst b/en/views/cells.rst index fb59997952..01cc13cf70 100644 --- a/en/views/cells.rst +++ b/en/views/cells.rst @@ -263,7 +263,7 @@ creating a cell object:: public function display($userId) { - $result = $this->fetchTable('Users')->find('friends', ['for' => $userId])->all(); + $result = $this->fetchTable('Users')->find('friends', for: $userId)->all(); $this->set('favorites', $result); } } From f3dd13536c893e5c46c679cbfe60e6473812a066 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 28 Apr 2023 23:47:30 -0400 Subject: [PATCH 3/3] Use simpler formatting. The orm is important enough to not be indented. --- en/appendices/5-0-migration-guide.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/en/appendices/5-0-migration-guide.rst b/en/appendices/5-0-migration-guide.rst index 2f347db9e3..8b1037ee24 100644 --- a/en/appendices/5-0-migration-guide.rst +++ b/en/appendices/5-0-migration-guide.rst @@ -267,8 +267,8 @@ Database ORM --- -- Table finders can now have typed arguments as required instead of an options array. - For e.g. a finder for fetching posts by category or user:: +Table finders can now have typed arguments as required instead of an options array. +For e.g. a finder for fetching posts by category or user:: public function findByCategoryOrUser(SelectQuery $query, array $options) { @@ -282,7 +282,7 @@ ORM return $query; } - should now be written as:: +should now be written as:: public function findByCategoryOrUser(SelectQuery $query, ?int $categoryId = null, ?int $userId = null) { @@ -296,9 +296,9 @@ ORM return $query; } - The finder can then be called as ``find('byCategoryOrUser', userId: $somevar)``. - You can even include the special named arguments for setting query clauses. - ``find('byCategoryOrUser', userId: $somevar, conditions: ['enabled' => true])``. +The finder can then be called as ``find('byCategoryOrUser', userId: $somevar)``. +You can even include the special named arguments for setting query clauses. +``find('byCategoryOrUser', userId: $somevar, conditions: ['enabled' => true])``. Http ----