From 4a8bcebdaed04fe357e5c62fbd4e36901247907f Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 16 Nov 2014 17:49:02 +0100 Subject: [PATCH 0001/2059] Starting to move data validation to the marshaller --- Marshaller.php | 10 ++++++++++ Table.php | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/Marshaller.php b/Marshaller.php index af5b719d..0ab43a71 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -95,6 +95,7 @@ protected function _buildPropertyMap($options) { * @see \Cake\ORM\Table::newEntity() */ public function one(array $data, array $options = []) { + $options += ['validate' => true]; $propertyMap = $this->_buildPropertyMap($options); $schema = $this->_table->schema(); @@ -113,6 +114,13 @@ public function one(array $data, array $options = []) { } } + $errors = []; + if ($options['validate']) { + $type = is_string($options['validate']) ? $options['validate'] : 'default'; + $validator = $this->_table->validator($type); + $errors = $validator->errors($data, $entity->isNew()); + } + $primaryKey = $schema->primaryKey(); $properties = []; foreach ($data as $key => $value) { @@ -132,6 +140,7 @@ public function one(array $data, array $options = []) { if (!isset($options['fieldList'])) { $entity->set($properties); + $entity->errors($errors); return $entity; } @@ -141,6 +150,7 @@ public function one(array $data, array $options = []) { } } + $entity->errors($errors); return $entity; } diff --git a/Table.php b/Table.php index 2f83a2de..3fdedc09 100644 --- a/Table.php +++ b/Table.php @@ -1182,6 +1182,10 @@ public function save(EntityInterface $entity, $options = []) { 'associated' => true ]); + if ($entity->errors()) { + return false; + } + if ($entity->isNew() === false && !$entity->dirty()) { return $entity; } From 1e51108a5cc144f82f3076a574d1fa7dffaf2983 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 16 Nov 2014 18:04:19 +0100 Subject: [PATCH 0002/2059] Skiping marshalling of properties that do not pass validation --- Marshaller.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Marshaller.php b/Marshaller.php index 0ab43a71..3df1a58f 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -124,6 +124,9 @@ public function one(array $data, array $options = []) { $primaryKey = $schema->primaryKey(); $properties = []; foreach ($data as $key => $value) { + if (!empty($errors[$key])) { + continue; + } $columnType = $schema->columnType($key); if (isset($propertyMap[$key])) { $assoc = $propertyMap[$key]['association']; From 8e39bf51a7fce3f5531a42730dc9b98ed8b4a849 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 16 Nov 2014 21:19:00 +0100 Subject: [PATCH 0003/2059] Removing some validation logic out of the Table object --- Table.php | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Table.php b/Table.php index 3fdedc09..8c8409fc 100644 --- a/Table.php +++ b/Table.php @@ -1178,7 +1178,6 @@ public function exists($conditions) { public function save(EntityInterface $entity, $options = []) { $options = new \ArrayObject($options + [ 'atomic' => true, - 'validate' => true, 'associated' => true ]); @@ -1223,11 +1222,6 @@ protected function _processSave($entity, $options) { } $options['associated'] = $this->_associations->normalizeKeys($options['associated']); - $validate = $options['validate']; - - if ($validate && !$this->validate($entity, $options)) { - return false; - } $event = $this->dispatchEvent('Model.beforeSave', compact('entity', 'options')); if ($event->isStopped()) { @@ -1238,7 +1232,7 @@ protected function _processSave($entity, $options) { $this, $entity, $options['associated'], - ['validate' => false] + $options->getArrayCopy() + $options->getArrayCopy() ); if (!$saved && $options['atomic']) { @@ -1259,7 +1253,7 @@ protected function _processSave($entity, $options) { $this, $entity, $options['associated'], - ['validate' => (bool)$validate] + $options->getArrayCopy() + $options->getArrayCopy() ); if ($success || !$options['atomic']) { $entity->clean(); From 20b7ecfdd0e8998c9e3473338f783b70e01de488 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Mon, 17 Nov 2014 01:23:00 +0100 Subject: [PATCH 0004/2059] Making sure beforeFind is only triggered once per query --- Query.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Query.php b/Query.php index f125da30..bc7147f9 100644 --- a/Query.php +++ b/Query.php @@ -644,6 +644,11 @@ public function sql(ValueBinder $binder = null) { * @return \Cake\ORM\ResultSet */ protected function _execute() { + $this->triggerBeforeFind(); + if ($this->_results) { + $decorator = $this->_decoratorClass(); + return new $decorator($this->_results); + } $statement = $this->eagerLoader()->loadExternal($this, $this->execute()); return new ResultSet($this, $statement); } From 3864d68acf756f69d90077a6180a3db97afd8bc7 Mon Sep 17 00:00:00 2001 From: Bryan Crowe Date: Sun, 16 Nov 2014 20:03:39 -0500 Subject: [PATCH 0005/2059] Removed unused variables --- Association/SelectableAssociationTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/SelectableAssociationTrait.php b/Association/SelectableAssociationTrait.php index cbd031a3..e1fa2c6e 100644 --- a/Association/SelectableAssociationTrait.php +++ b/Association/SelectableAssociationTrait.php @@ -134,7 +134,7 @@ protected function _buildQuery($options) { * @return \Cake\ORM\Query */ public function _addFilteringJoin($query, $key, $subquery) { - $filter = $fields = []; + $filter = []; $aliasedTable = $subquery->repository()->alias(); foreach ($subquery->clause('select') as $aliasedField => $field) { From d7f0aba98cefeca3b6fe21fcc590c7b2930955b6 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 16 Nov 2014 15:53:16 -0500 Subject: [PATCH 0006/2059] Finish another block of link tag updates. --- Exception/MissingBehaviorException.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Exception/MissingBehaviorException.php b/Exception/MissingBehaviorException.php index 31cf9e33..e0626dbf 100644 --- a/Exception/MissingBehaviorException.php +++ b/Exception/MissingBehaviorException.php @@ -7,7 +7,6 @@ * Redistributions of files must retain the above copyright notice. * * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) - * @link http://book.cakephp.org/2.0/en/development/testing.html * @since 3.0.0 * @license http://www.opensource.org/licenses/mit-license.php MIT License */ From 12436bf025f7bd5149200c3ebf4be00ed25993a1 Mon Sep 17 00:00:00 2001 From: Berry Goudswaard Date: Wed, 19 Nov 2014 22:41:43 +0100 Subject: [PATCH 0007/2059] Add pluginSplit to TableRegistry::config() --- TableRegistry.php | 1 + 1 file changed, 1 insertion(+) diff --git a/TableRegistry.php b/TableRegistry.php index d4e5af3b..ecbec6c6 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -99,6 +99,7 @@ class TableRegistry { * @throws RuntimeException When you attempt to configure an existing table instance. */ public static function config($alias = null, $options = null) { + list(, $alias) = pluginSplit($alias); if ($alias === null) { return static::$_config; } From 19fe08381557282d144649855a0f3ce388e7135b Mon Sep 17 00:00:00 2001 From: Berry Goudswaard Date: Wed, 19 Nov 2014 22:43:15 +0100 Subject: [PATCH 0008/2059] Add pluginSplit to TableRegistry::set() --- TableRegistry.php | 1 + 1 file changed, 1 insertion(+) diff --git a/TableRegistry.php b/TableRegistry.php index ecbec6c6..1003814a 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -215,6 +215,7 @@ public static function exists($alias) { * @return \Cake\ORM\Table */ public static function set($alias, Table $object) { + list(, $alias) = pluginSplit($alias); return static::$_instances[$alias] = $object; } From 1f8e80bc3b6794973e37ce7f2acc04da5197de03 Mon Sep 17 00:00:00 2001 From: euromark Date: Fri, 21 Nov 2014 22:08:13 +0100 Subject: [PATCH 0009/2059] More defaults and doc block corrections. --- Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Query.php b/Query.php index bc7147f9..8f0eb79f 100644 --- a/Query.php +++ b/Query.php @@ -399,7 +399,7 @@ public function aliasField($field, $alias = null) { * the result under a single array. * * @param array $fields The fields to alias - * @param string $defaultAlias The default alias + * @param string|null $defaultAlias The default alias * @return array */ public function aliasFields($fields, $defaultAlias = null) { From 2779e8e30db9a43798427d92157876f6a9f6756d Mon Sep 17 00:00:00 2001 From: euromark Date: Sat, 22 Nov 2014 02:56:13 +0100 Subject: [PATCH 0010/2059] More corrections of doc blocks. --- Association.php | 20 ++++++++++---------- Association/BelongsTo.php | 4 ++-- Association/BelongsToMany.php | 8 ++++---- Association/ExternalAssociationTrait.php | 2 +- Association/HasOne.php | 4 ++-- EagerLoader.php | 2 +- Exception/RecordNotFoundException.php | 2 +- Query.php | 6 +++--- Table.php | 18 +++++++++--------- TableRegistry.php | 4 ++-- 10 files changed, 35 insertions(+), 35 deletions(-) diff --git a/Association.php b/Association.php index b261cb58..b410fe8f 100644 --- a/Association.php +++ b/Association.php @@ -209,7 +209,7 @@ public function __construct($name, array $options = []) { * Sets the name for this association. If no argument is passed then the current * configured name will be returned * - * @param string $name Name to be assigned + * @param string|null $name Name to be assigned * @return string */ public function name($name = null) { @@ -223,7 +223,7 @@ public function name($name = null) { * Sets whether or not cascaded deletes should also fire callbacks. If no * arguments are passed, the current configured value is returned * - * @param bool $cascadeCallbacks cascade callbacks switch value + * @param bool|null $cascadeCallbacks cascade callbacks switch value * @return bool */ public function cascadeCallbacks($cascadeCallbacks = null) { @@ -237,7 +237,7 @@ public function cascadeCallbacks($cascadeCallbacks = null) { * Sets the table instance for the source side of the association. If no arguments * are passed, the current configured table instance is returned * - * @param \Cake\ORM\Table $table the instance to be assigned as source side + * @param \Cake\ORM\Table|null $table the instance to be assigned as source side * @return \Cake\ORM\Table */ public function source(Table $table = null) { @@ -251,7 +251,7 @@ public function source(Table $table = null) { * Sets the table instance for the target side of the association. If no arguments * are passed, the current configured table instance is returned * - * @param \Cake\ORM\Table $table the instance to be assigned as target side + * @param \Cake\ORM\Table|null $table the instance to be assigned as target side * @return \Cake\ORM\Table */ public function target(Table $table = null) { @@ -277,7 +277,7 @@ public function target(Table $table = null) { * Sets a list of conditions to be always included when fetching records from * the target association. If no parameters are passed the current list is returned * - * @param array $conditions list of conditions to be used + * @param array|null $conditions list of conditions to be used * @see \Cake\Database\Query::where() for examples on the format of the array * @return array */ @@ -292,7 +292,7 @@ public function conditions($conditions = null) { * Sets the name of the field representing the foreign key to the target table. * If no parameters are passed the current field is returned * - * @param string $key the key to be used to link both tables together + * @param string|null $key the key to be used to link both tables together * @return string|array */ public function foreignKey($key = null) { @@ -310,7 +310,7 @@ public function foreignKey($key = null) { * * If no parameters are passed the current setting is returned. * - * @param bool $dependent Set the dependent mode. Use null to read the current state. + * @param bool|null $dependent Set the dependent mode. Use null to read the current state. * @return bool */ public function dependent($dependent = null) { @@ -350,7 +350,7 @@ public function joinType($type = null) { * in the source table record. * If no arguments are passed, the currently configured type is returned. * - * @param string $name The name of the association property. Use null to read the current value. + * @param string|null $name The name of the association property. Use null to read the current value. * @return string */ public function property($name = null) { @@ -370,7 +370,7 @@ public function property($name = null) { * rendering any changes to this setting void. * If no arguments are passed, the currently configured strategy is returned. * - * @param string $name The strategy type. Use null to read the current value. + * @param string|null $name The strategy type. Use null to read the current value. * @return string * @throws \InvalidArgumentException When an invalid strategy is provided. */ @@ -392,7 +392,7 @@ public function strategy($name = null) { * If no parameters are passed, it will return the currently configured * finder name. * - * @param string $finder the finder name to use + * @param string|null $finder the finder name to use * @return string */ public function finder($finder = null) { diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index f4e62664..38b27475 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -35,7 +35,7 @@ class BelongsTo extends Association { * Sets the name of the field representing the foreign key to the target table. * If no parameters are passed current field is returned * - * @param string $key the key to be used to link both tables together + * @param string|null $key the key to be used to link both tables together * @return string */ public function foreignKey($key = null) { @@ -66,7 +66,7 @@ public function cascadeDelete(EntityInterface $entity, array $options = []) { * in the source table record. * If no arguments are passed, currently configured type is returned. * - * @param string $name The property name, use null to read the current property. + * @param string|null $name The property name, use null to read the current property. * @return string */ public function property($name = null) { diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 39c4e478..58158257 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -117,7 +117,7 @@ class BelongsToMany extends Association { * Sets the name of the field representing the foreign key to the target table. * If no parameters are passed current field is returned * - * @param string $key the key to be used to link both tables together + * @param string|null $key the key to be used to link both tables together * @return string */ public function targetForeignKey($key = null) { @@ -134,7 +134,7 @@ public function targetForeignKey($key = null) { * Sets the table instance for the junction relation. If no arguments * are passed, the current configured table instance is returned * - * @param string|\Cake\ORM\Table $table Name or instance for the join table + * @param string|\Cake\ORM\Table|null $table Name or instance for the join table * @return \Cake\ORM\Table */ public function junction($table = null) { @@ -345,7 +345,7 @@ public function isOwningSide(Table $side) { * Sets the strategy that should be used for saving. If called with no * arguments, it will return the currently configured strategy * - * @param string $strategy the strategy name to be used + * @param string|null $strategy the strategy name to be used * @throws \InvalidArgumentException if an invalid strategy name is passed * @return string the strategy to be used for saving */ @@ -929,7 +929,7 @@ protected function _junctionAssociationName() { * If no arguments are passed the current configured name is returned. A default * name based of the associated tables will be generated if none found. * - * @param string $name The name of the junction table. + * @param string|null $name The name of the junction table. * @return string */ protected function _junctionTableName($name = null) { diff --git a/Association/ExternalAssociationTrait.php b/Association/ExternalAssociationTrait.php index 362a353e..1613aa1f 100644 --- a/Association/ExternalAssociationTrait.php +++ b/Association/ExternalAssociationTrait.php @@ -48,7 +48,7 @@ public function canBeJoined(array $options = []) { * Sets the name of the field representing the foreign key to the source table. * If no parameters are passed current field is returned * - * @param string $key the key to be used to link both tables together + * @param string|null $key the key to be used to link both tables together * @return string */ public function foreignKey($key = null) { diff --git a/Association/HasOne.php b/Association/HasOne.php index 13c15f09..b2cbc397 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -36,7 +36,7 @@ class HasOne extends Association { * Sets the name of the field representing the foreign key to the target table. * If no parameters are passed current field is returned * - * @param string $key the key to be used to link both tables together + * @param string|null $key the key to be used to link both tables together * @return string */ public function foreignKey($key = null) { @@ -54,7 +54,7 @@ public function foreignKey($key = null) { * in the source table record. * If no arguments are passed, currently configured type is returned. * - * @param string $name The name of the property. Pass null to read the current value. + * @param string|null $name The name of the property. Pass null to read the current value. * @return string */ public function property($name = null) { diff --git a/EagerLoader.php b/EagerLoader.php index b892dd51..8390dc97 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -122,7 +122,7 @@ public function contain($associations = []) { * `matching` option. * * @param string $assoc A single association or a dot separated path of associations. - * @param callable $builder the callback function to be used for setting extra + * @param callable|null $builder the callback function to be used for setting extra * options to the filtering query * @return array The resulting containments array */ diff --git a/Exception/RecordNotFoundException.php b/Exception/RecordNotFoundException.php index 06e0f4e0..9f20ecff 100644 --- a/Exception/RecordNotFoundException.php +++ b/Exception/RecordNotFoundException.php @@ -27,7 +27,7 @@ class RecordNotFoundException extends RuntimeException { * * @param string $message The error message * @param int $code The code of the error, is also the HTTP status code for the error. - * @param \Exception $previous the previous exception. + * @param \Exception|null $previous the previous exception. */ public function __construct($message, $code = 404, $previous = null) { parent::__construct($message, $code, $previous); diff --git a/Query.php b/Query.php index 8f0eb79f..4f37659f 100644 --- a/Query.php +++ b/Query.php @@ -735,7 +735,7 @@ protected function _dirty() { * This changes the query type to be 'update'. * Can be combined with set() and where() methods to create update queries. * - * @param string $table Unused parameter. + * @param string|null $table Unused parameter. * @return $this */ public function update($table = null) { @@ -749,7 +749,7 @@ public function update($table = null) { * This changes the query type to be 'delete'. * Can be combined with the where() method to create delete queries. * - * @param string $table Unused parameter. + * @param string|null $table Unused parameter. * @return $this */ public function delete($table = null) { @@ -824,7 +824,7 @@ public function jsonSerialize() { * By default calling select() will disable auto-fields. You can re-enable * auto-fields with this method. * - * @param bool $value The value to set or null to read the current value. + * @param bool|null $value The value to set or null to read the current value. * @return bool|$this Either the current value or the query object. */ public function autoFields($value = null) { diff --git a/Table.php b/Table.php index 2f83a2de..aa734302 100644 --- a/Table.php +++ b/Table.php @@ -269,7 +269,7 @@ public function initialize(array $config) { /** * Returns the database table name or sets a new one * - * @param string $table the new table name + * @param string|null $table the new table name * @return string */ public function table($table = null) { @@ -290,7 +290,7 @@ public function table($table = null) { /** * Returns the table alias or sets a new one * - * @param string $alias the new table alias + * @param string|null $alias the new table alias * @return string */ public function alias($alias = null) { @@ -308,7 +308,7 @@ public function alias($alias = null) { /** * Returns the connection instance or sets a new one * - * @param \Cake\Database\Connection $conn the new connection instance + * @param \Cake\Database\Connection|null $conn The new connection instance * @return \Cake\Database\Connection */ public function connection($conn = null) { @@ -327,7 +327,7 @@ public function connection($conn = null) { * If an array is passed, a new \Cake\Database\Schema\Table will be constructed * out of it and used as the schema for this table. * - * @param array|\Cake\Database\Schema\Table $schema new schema to be used for this table + * @param array|\Cake\Database\Schema\Table|null $schema New schema to be used for this table * @return \Cake\Database\Schema\Table */ public function schema($schema = null) { @@ -401,7 +401,7 @@ public function hasField($field) { /** * Returns the primary key field name or sets a new one * - * @param string|array $key sets a new name to be used as primary key + * @param string|array|null $key sets a new name to be used as primary key * @return string|array */ public function primaryKey($key = null) { @@ -421,7 +421,7 @@ public function primaryKey($key = null) { /** * Returns the display field or sets a new one * - * @param string $key sets a new name to be used as display field + * @param string|null $key sets a new name to be used as display field * @return string */ public function displayField($key = null) { @@ -446,7 +446,7 @@ public function displayField($key = null) { * Returns the class used to hydrate rows for this table or sets * a new one * - * @param string $name the name of the class to use + * @param string|null $name the name of the class to use * @throws \Cake\ORM\Exception\MissingEntityException when the entity class cannot be found * @return string */ @@ -963,7 +963,7 @@ public function get($primaryKey, $options = []) { * the $defaults. When a new entity is created, it will be saved. * * @param array $search The criteria to find existing records by. - * @param callable $callback A callback that will be invoked for newly + * @param callable|null $callback A callback that will be invoked for newly * created entities. This callback will be called *before* the entity * is persisted. * @return \Cake\Datasource\EntityInterface An entity. @@ -1040,7 +1040,7 @@ public function updateAll($fields, $conditions) { * set is specified. * * @param string $name the name of the validation set to return - * @param \Cake\Validation\Validator $validator The validator instance to store, + * @param \Cake\Validation\Validator|null $validator The validator instance to store, * use null to get a validator. * @return \Cake\Validation\Validator */ diff --git a/TableRegistry.php b/TableRegistry.php index 1003814a..168eb6f5 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -93,8 +93,8 @@ class TableRegistry { * If no arguments are passed it will return the full configuration array for * all aliases * - * @param string $alias Name of the alias - * @param null|array $options list of options for the alias + * @param string|null $alias Name of the alias + * @param array|null $options list of options for the alias * @return array The config data. * @throws RuntimeException When you attempt to configure an existing table instance. */ From 1a7449953caeb40345c7dd0ec0c8600f1984d922 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 22 Nov 2014 22:42:53 -0500 Subject: [PATCH 0011/2059] Accept -1 or 1 as # of rows on insert. While we generally want 1 row, SQLserver will return -1 as we are using OUTPUT in insert queries now. Refs #5104 --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index aa734302..a7a32dea 100644 --- a/Table.php +++ b/Table.php @@ -1323,7 +1323,7 @@ protected function _insert($entity, $data) { ->values($data) ->execute(); - if ($statement->rowCount() > 0) { + if ($statement->rowCount() !== 0) { $success = $entity; $entity->set($filteredKeys, ['guard' => false]); foreach ($primary as $key => $v) { From 2941df97fbb77a087b9184b2d4d60b5e275a175e Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Thu, 20 Nov 2014 23:11:54 +0100 Subject: [PATCH 0012/2059] Added option to pass a validation object to the marshaller --- Marshaller.php | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 3df1a58f..1415a0c2 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -114,13 +114,7 @@ public function one(array $data, array $options = []) { } } - $errors = []; - if ($options['validate']) { - $type = is_string($options['validate']) ? $options['validate'] : 'default'; - $validator = $this->_table->validator($type); - $errors = $validator->errors($data, $entity->isNew()); - } - + $errors = $this->_validate($data, $options, true); $primaryKey = $schema->primaryKey(); $properties = []; foreach ($data as $key => $value) { @@ -157,6 +151,30 @@ public function one(array $data, array $options = []) { return $entity; } +/** + * Returns the validation errors for a data set based on the passed options + * + * @param array $data The data to validate. + * @param array $options The options passed to this marshaller. + * @param bool $isNew Whether it is a new entity or one to be updated. + * @return array The list of validation errors. + */ + protected function _validate($data, $options, $isNew) { + if (!$options['validate']) { + return []; + } + + if ($options['validate'] === true) { + $options['validate'] = $this->_table->validator('default'); + } + + if (is_string($options['validate'])) { + $options['validate'] = $this->_table->validator($options['validate']); + } + + return $options['validate']->errors($data, $isNew); + } + /** * Create a new sub-marshaller and marshal the associated data. * From dae78a4bb3fda2914bde1b97ea88f1d04c59a592 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Fri, 21 Nov 2014 19:31:11 +0100 Subject: [PATCH 0013/2059] Added validation to Marshaller::merge() --- Marshaller.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Marshaller.php b/Marshaller.php index 1415a0c2..6ed3f0d7 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -307,6 +307,7 @@ protected function _loadBelongsToMany($assoc, $ids) { * @return \Cake\Datasource\EntityInterface */ public function merge(EntityInterface $entity, array $data, array $options = []) { + $options += ['validate' => true]; $propertyMap = $this->_buildPropertyMap($options); $tableName = $this->_table->alias(); @@ -314,9 +315,14 @@ public function merge(EntityInterface $entity, array $data, array $options = []) $data = $data[$tableName]; } + $errors = $this->_validate($data, $options, false); $schema = $this->_table->schema(); $properties = []; foreach ($data as $key => $value) { + if (!empty($errors[$key])) { + continue; + } + $columnType = $schema->columnType($key); $original = $entity->get($key); @@ -340,6 +346,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) if (!isset($options['fieldList'])) { $entity->set($properties); + $entity->errors($errors); return $entity; } @@ -349,6 +356,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) } } + $entity->errors($errors); return $entity; } From 5036d2bf728da1b3b62c1e5b54dd1461454db178 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sat, 22 Nov 2014 11:03:04 +0100 Subject: [PATCH 0014/2059] Removing all validation methods from Table --- EntityValidator.php | 2 +- Table.php | 114 -------------------------------------------- 2 files changed, 1 insertion(+), 115 deletions(-) diff --git a/EntityValidator.php b/EntityValidator.php index 561162ff..4da5f76d 100644 --- a/EntityValidator.php +++ b/EntityValidator.php @@ -99,7 +99,7 @@ public function one(Entity $entity, $options = []) { continue; } - $validator = $association->target()->entityValidator(); + $validator = new self($association->target()); if ($isOne) { $valid = $validator->one($value, $assoc['options']) && $valid; } else { diff --git a/Table.php b/Table.php index 8c8409fc..516c0a8d 100644 --- a/Table.php +++ b/Table.php @@ -1635,18 +1635,6 @@ public function marshaller() { return new Marshaller($this); } -/** - * Returns a new instance of an EntityValidator that is configured to be used - * for entities generated by this table. An EntityValidator can be used to - * process validation rules on a single or multiple entities and any of its - * associated values. - * - * @return EntityValidator - */ - public function entityValidator() { - return new EntityValidator($this); - } - /** * {@inheritDoc} * @@ -1781,108 +1769,6 @@ public function patchEntities($entities, array $data, array $options = []) { return $marshaller->mergeMany($entities, $data, $options); } -/** - * Validates a single entity based on the passed options and validates - * any nested entity for this table associations as requested in the - * options array. - * - * Calling this function directly is mostly useful when you need to get - * validation errors for an entity and associated nested entities before - * they are saved. - * - * {{{ - * $articles->validate($article); - * }}} - * - * You can specify which validation set to use using the options array: - * - * {{{ - * $users->validate($user, ['validate' => 'forSignup']); - * }}} - * - * By default all the associations on this table will be validated if they can - * be found in the passed entity. You can limit which associations are built, - * or include deeper associations using the options parameter - * - * {{{ - * $articles->validate($article, [ - * 'associated' => [ - * 'Tags', - * 'Comments' => [ - * 'validate' => 'myCustomSet', - * 'associated' => ['Users'] - * ] - * ] - * ]); - * }}} - * - * @param \Cake\Datasource\EntityInterface $entity The entity to be validated - * @param array|\ArrayObject $options A list of options to use while validating, the following - * keys are accepted: - * - validate: The name of the validation set to use - * - associated: map of association names to validate as well - * @return bool true if the passed entity and its associations are valid - */ - public function validate($entity, $options = []) { - if (!isset($options['associated'])) { - $options['associated'] = $this->_associations->keys(); - } - - $entityValidator = $this->entityValidator(); - return $entityValidator->one($entity, $options); - } - -/** - * Validates a list of entities based on the passed options and validates - * any nested entity for this table associations as requested in the - * options array. - * - * Calling this function directly is mostly useful when you need to get - * validation errors for a list of entities and associations before they are - * saved. - * - * {{{ - * $articles->validateMany([$article1, $article2]); - * }}} - * - * You can specify which validation set to use using the options array: - * - * {{{ - * $users->validateMany([$user1, $user2], ['validate' => 'forSignup']); - * }}} - * - * By default all the associations on this table will be validated if they can - * be found in the passed entities. You can limit which associations are built, - * or include deeper associations using the options parameter - * - * {{{ - * $articles->validateMany([$article1, $article2], [ - * 'associated' => [ - * 'Tags', - * 'Comments' => [ - * 'validate' => 'myCustomSet', - * 'associated' => ['Users'] - * ] - * ] - * ]); - * }}} - * - * @param array|\ArrayAccess $entities The entities to be validated - * @param array $options A list of options to use while validating, the following - * keys are accepted: - * - validate: The name of the validation set to use - * - associated: map of association names to validate as well - * @return bool true if the passed entities and their associations are valid - */ - public function validateMany($entities, array $options = []) { - if (!isset($options['associated'])) { - $options['associated'] = $this->_associations->keys(); - } - - $entityValidator = $this->entityValidator(); - return $entityValidator->many($entities, $options); - } - /** * Validator method used to check the uniqueness of a value for a column. * This is meant to be used with the validation API and not to be called From 75669979985b1fba432b9a70bc1c38a2416ef46f Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 23 Nov 2014 19:22:43 +0100 Subject: [PATCH 0015/2059] Added a skelleton DomainChecker class --- DomainChecker.php | 56 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 DomainChecker.php diff --git a/DomainChecker.php b/DomainChecker.php new file mode 100644 index 00000000..35d2b8e4 --- /dev/null +++ b/DomainChecker.php @@ -0,0 +1,56 @@ +_rules, $this->_createRules) as $rule) { + $success = $success && $rule($entity); + } + return $succcess; + } + + public function checkUpdate(EntityInterface $entity) { + $success = true; + foreach (array_merge($this->_rules, $this->_updateRules) as $rule) { + $success = $success && $rule($entity); + } + return $succcess; + } + +} From 32ac9ab4a8ff34d156d54eee43e2411d832edbac Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 23 Nov 2014 20:03:35 +0100 Subject: [PATCH 0016/2059] Implementing a few more details of DomainChecker --- DomainChecker.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/DomainChecker.php b/DomainChecker.php index 35d2b8e4..2919c23e 100644 --- a/DomainChecker.php +++ b/DomainChecker.php @@ -28,19 +28,22 @@ class DomainChecker { protected $_updateRules = []; - public function addAlways(callable $rule) { + public function add(callable $rule) { + $this->_rules[] = $rule; } public function addCreate(callable $rule) { + $this->_createRules[] = $rule; } public function addUpdate(callable $rule) { + $this->_updateRules[] = $rule; } public function checkCreate(EntityInterface $entity) { $success = true; foreach (array_merge($this->_rules, $this->_createRules) as $rule) { - $success = $success && $rule($entity); + $success = $rule($entity) && $success; } return $succcess; } @@ -48,7 +51,7 @@ public function checkCreate(EntityInterface $entity) { public function checkUpdate(EntityInterface $entity) { $success = true; foreach (array_merge($this->_rules, $this->_updateRules) as $rule) { - $success = $success && $rule($entity); + $success = $rule($entity) && $success; } return $succcess; } From 757e9a4b4e8fbce0d31108f8220489cc955810d5 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 23 Nov 2014 20:04:07 +0100 Subject: [PATCH 0017/2059] Starting to use the DomainChecker in the saving process --- Table.php | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 516c0a8d..9021c0a0 100644 --- a/Table.php +++ b/Table.php @@ -1178,7 +1178,8 @@ public function exists($conditions) { public function save(EntityInterface $entity, $options = []) { $options = new \ArrayObject($options + [ 'atomic' => true, - 'associated' => true + 'associated' => true, + 'domainCheck' => true ]); if ($entity->errors()) { @@ -1221,6 +1222,10 @@ protected function _processSave($entity, $options) { $entity->isNew(!$this->exists($conditions)); } + if ($options['checkDomain'] && !$this->checkDomainRules($entity)) { + return false; + } + $options['associated'] = $this->_associations->normalizeKeys($options['associated']); $event = $this->dispatchEvent('Model.beforeSave', compact('entity', 'options')); @@ -1828,6 +1833,20 @@ public function validateUnique($value, array $options, array $context = []) { return !$this->exists($conditions); } + public function checkDomainRules($entity) { + $rules = $this->domainRules(); + + if ($entity->isNew()) { + return $rules->checkCreate($entity); + } + + return $rules->checkUpdate($entity); + } + + public function domainRules() { + return new DomainChecker; + } + /** * Get the Model callbacks this table is interested in. * From e3cd84f88d1222ee72812b5d452f6a3c4e95ec29 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 23 Nov 2014 21:10:08 +0100 Subject: [PATCH 0018/2059] Improving domain checking process and adding tests --- DomainChecker.php | 7 +++++-- Table.php | 20 ++++++++++++++++++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/DomainChecker.php b/DomainChecker.php index 2919c23e..61c91f3b 100644 --- a/DomainChecker.php +++ b/DomainChecker.php @@ -30,14 +30,17 @@ class DomainChecker { public function add(callable $rule) { $this->_rules[] = $rule; + return $this; } public function addCreate(callable $rule) { $this->_createRules[] = $rule; + return $this; } public function addUpdate(callable $rule) { $this->_updateRules[] = $rule; + return $this; } public function checkCreate(EntityInterface $entity) { @@ -45,7 +48,7 @@ public function checkCreate(EntityInterface $entity) { foreach (array_merge($this->_rules, $this->_createRules) as $rule) { $success = $rule($entity) && $success; } - return $succcess; + return $success; } public function checkUpdate(EntityInterface $entity) { @@ -53,7 +56,7 @@ public function checkUpdate(EntityInterface $entity) { foreach (array_merge($this->_rules, $this->_updateRules) as $rule) { $success = $rule($entity) && $success; } - return $succcess; + return $success; } } diff --git a/Table.php b/Table.php index 9021c0a0..7d379037 100644 --- a/Table.php +++ b/Table.php @@ -29,6 +29,7 @@ use Cake\ORM\Association\HasMany; use Cake\ORM\Association\HasOne; use Cake\ORM\BehaviorRegistry; +use Cake\ORM\DomainChecker; use Cake\ORM\Exception\MissingEntityException; use Cake\ORM\Exception\RecordNotFoundException; use Cake\ORM\Marshaller; @@ -180,6 +181,14 @@ class Table implements RepositoryInterface, EventListenerInterface { */ protected $_validators = []; + +/** + * The domain rules to be applied to entities saved by this table + * + * @var \Cake\ORM\DomainChecker + */ + protected $_domainChecker; + /** * Initializes a new instance * @@ -1222,7 +1231,7 @@ protected function _processSave($entity, $options) { $entity->isNew(!$this->exists($conditions)); } - if ($options['checkDomain'] && !$this->checkDomainRules($entity)) { + if ($options['domainCheck'] && !$this->checkDomainRules($entity)) { return false; } @@ -1844,7 +1853,14 @@ public function checkDomainRules($entity) { } public function domainRules() { - return new DomainChecker; + if ($this->_domainChecker !== null) { + return $this->_domainChecker; + } + return $this->_domainChecker = $this->buildDomainRules(new DomainChecker); + } + + public function buildDomainRules(DomainChecker $rules) { + return $rules; } /** From 06cdec86220106cc67a7d3ff110b86ab31fcaf74 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 23 Nov 2014 21:03:28 -0500 Subject: [PATCH 0019/2059] Clarify what will be in the $config parameter. Refs #5246 --- Behavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior.php b/Behavior.php index f70b797a..6d75decd 100644 --- a/Behavior.php +++ b/Behavior.php @@ -162,7 +162,7 @@ public function __construct(Table $table, array $config = []) { * Implement this method to avoid having to overwrite * the constructor and call parent. * - * @param array $config The configuration array this behavior is using. + * @param array $config The configuration settings provided to this behavior. * @return void */ public function initialize(array $config) { From 154d5f2583550a2458ce061f34be7a2815f1abbd Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 25 Nov 2014 21:31:37 -0500 Subject: [PATCH 0020/2059] Define initial array value. Refs #5257 --- EntityValidator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EntityValidator.php b/EntityValidator.php index 561162ff..e92a7630 100644 --- a/EntityValidator.php +++ b/EntityValidator.php @@ -51,6 +51,7 @@ protected function _buildPropertyMap($include) { return []; } + $map = []; foreach ($include['associated'] as $key => $options) { if (is_int($key) && is_scalar($options)) { $key = $options; @@ -66,7 +67,6 @@ protected function _buildPropertyMap($include) { ]; } } - return $map; } From a70fd43ed46c6aa23c896872cfb13002a4f09a40 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 25 Nov 2014 22:55:49 -0500 Subject: [PATCH 0021/2059] Fix warnings and fatal errors when validating entities. When validating entities that contain association properties that have not been marshalled, there should not be any warnings or fatal errors emitted. We can assume that any association property that was not correctly marshalled is an error and cause validation to fail. Refs #5257 --- EntityValidator.php | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/EntityValidator.php b/EntityValidator.php index e92a7630..32b27982 100644 --- a/EntityValidator.php +++ b/EntityValidator.php @@ -15,9 +15,16 @@ namespace Cake\ORM; use ArrayObject; +use Cake\Datasource\EntityInterface; +use Cake\ORM\Association; +use Cake\ORM\Table; /** - * Contains logic for validating entities and their associations + * Contains logic for validating entities and their associations. + * + * This class is generally used by the internals of the ORM. It + * provides methods for traversing a set of entities and their associated + * properties. * * @see \Cake\ORM\Table::validate() * @see \Cake\ORM\Table::validateMany() @@ -75,12 +82,13 @@ protected function _buildPropertyMap($include) { * the table and traverses associations passed in $options to validate them * as well. * - * @param \Cake\ORM\Entity $entity The entity to be validated + * @param \Cake\Datasource\EntityInterface $entity The entity to be validated * @param array|\ArrayObject $options options for validation, including an optional key of - * associations to also be validated. + * associations to also be validated. This argument should use the same format as the $options + * argument to \Cake\ORM\Table::save(). * @return bool true if all validations passed, false otherwise */ - public function one(Entity $entity, $options = []) { + public function one(EntityInterface $entity, $options = []) { $valid = true; $types = [Association::ONE_TO_ONE, Association::MANY_TO_ONE]; $propertyMap = $this->_buildPropertyMap($options); @@ -94,7 +102,7 @@ public function one(Entity $entity, $options = []) { continue; } $isOne = in_array($association->type(), $types); - if ($isOne && !($value instanceof Entity)) { + if ($isOne && !($value instanceof EntityInterface)) { $valid = false; continue; } @@ -117,14 +125,21 @@ public function one(Entity $entity, $options = []) { * Validates a list of entities by getting the correct validator for the related * table and traverses associations passed in $include to validate them as well. * + * If any of the entities in `$entities` does not implement `Cake\Datasource\EntityInterface`, + * it will be treated as an invalid result. + * * @param array $entities List of entities to be validated * @param array|\ArrayObject $options options for validation, including an optional key of - * associations to also be validated. + * associations to also be validated. This argument should use the same format as the $options + * argument to \Cake\ORM\Table::save(). * @return bool true if all validations passed, false otherwise */ public function many(array $entities, $options = []) { $valid = true; foreach ($entities as $entity) { + if (!($entity instanceof EntityInterface)) { + return false; + } $valid = $this->one($entity, $options) && $valid; } return $valid; @@ -156,7 +171,6 @@ protected function _processValidation($entity, $options) { $success = $entity->validate($validator); $event = $this->_table->dispatchEvent('Model.afterValidate', $pass); - if ($event->isStopped()) { $success = (bool)$event->result; } From 385656b3f9d9a824ba65dc491588986983956ea9 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 27 Nov 2014 00:35:18 +0530 Subject: [PATCH 0022/2059] ObjectRegistry::loaded() now only returns lists of all loaded objects. --- BehaviorRegistry.php | 8 ++++---- Table.php | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 461e9885..cf76726e 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -134,7 +134,7 @@ protected function _getMethods(Behavior $instance, $class, $alias) { $methods = array_change_key_case($instance->implementedMethods()); foreach ($finders as $finder => $methodName) { - if (isset($this->_finderMap[$finder]) && $this->loaded($this->_finderMap[$finder][0])) { + if (isset($this->_finderMap[$finder]) && $this->has($this->_finderMap[$finder][0])) { $duplicate = $this->_finderMap[$finder]; $error = sprintf( '%s contains duplicate finder "%s" which is already provided by "%s"', @@ -148,7 +148,7 @@ protected function _getMethods(Behavior $instance, $class, $alias) { } foreach ($methods as $method => $methodName) { - if (isset($this->_methodMap[$method]) && $this->loaded($this->_methodMap[$method][0])) { + if (isset($this->_methodMap[$method]) && $this->has($this->_methodMap[$method][0])) { $duplicate = $this->_methodMap[$method]; $error = sprintf( '%s contains duplicate method "%s" which is already provided by "%s"', @@ -202,7 +202,7 @@ public function hasFinder($method) { */ public function call($method, array $args = []) { $method = strtolower($method); - if ($this->hasMethod($method) && $this->loaded($this->_methodMap[$method][0])) { + if ($this->hasMethod($method) && $this->has($this->_methodMap[$method][0])) { list($behavior, $callMethod) = $this->_methodMap[$method]; return call_user_func_array([$this->_loaded[$behavior], $callMethod], $args); } @@ -223,7 +223,7 @@ public function call($method, array $args = []) { public function callFinder($type, array $args = []) { $type = strtolower($type); - if ($this->hasFinder($type) && $this->loaded($this->_finderMap[$type][0])) { + if ($this->hasFinder($type) && $this->has($this->_finderMap[$type][0])) { list($behavior, $callMethod) = $this->_finderMap[$type]; return call_user_func_array([$this->_loaded[$behavior], $callMethod], $args); } diff --git a/Table.php b/Table.php index a7a32dea..d20b13b9 100644 --- a/Table.php +++ b/Table.php @@ -542,7 +542,7 @@ public function behaviors() { * @return array */ public function hasBehavior($name) { - return $this->_behaviors->loaded($name); + return $this->_behaviors->has($name); } /** From b8b3bb6a0ff63139bf063c0f53d7226c3475908b Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 30 Nov 2014 17:03:40 +0100 Subject: [PATCH 0023/2059] Added the table as an argument for the domain checker callables --- DomainChecker.php | 10 ++++++++-- Table.php | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/DomainChecker.php b/DomainChecker.php index 61c91f3b..b9e230be 100644 --- a/DomainChecker.php +++ b/DomainChecker.php @@ -28,6 +28,12 @@ class DomainChecker { protected $_updateRules = []; + protected $_options = []; + + public function __construct(array $options) { + $this->_options = $options; + } + public function add(callable $rule) { $this->_rules[] = $rule; return $this; @@ -46,7 +52,7 @@ public function addUpdate(callable $rule) { public function checkCreate(EntityInterface $entity) { $success = true; foreach (array_merge($this->_rules, $this->_createRules) as $rule) { - $success = $rule($entity) && $success; + $success = $rule($entity, $this->_options) && $success; } return $success; } @@ -54,7 +60,7 @@ public function checkCreate(EntityInterface $entity) { public function checkUpdate(EntityInterface $entity) { $success = true; foreach (array_merge($this->_rules, $this->_updateRules) as $rule) { - $success = $rule($entity) && $success; + $success = $rule($entity, $this->_options) && $success; } return $success; } diff --git a/Table.php b/Table.php index 7d379037..05fd1661 100644 --- a/Table.php +++ b/Table.php @@ -1856,7 +1856,7 @@ public function domainRules() { if ($this->_domainChecker !== null) { return $this->_domainChecker; } - return $this->_domainChecker = $this->buildDomainRules(new DomainChecker); + return $this->_domainChecker = $this->buildDomainRules(new DomainChecker(['scope' => $this])); } public function buildDomainRules(DomainChecker $rules) { From dd0efb9d2fe3c26ddbecbf12d1f8883a1a36d9d0 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 30 Nov 2014 17:10:00 +0100 Subject: [PATCH 0024/2059] Renaming scope to repository --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 05fd1661..4e0f059a 100644 --- a/Table.php +++ b/Table.php @@ -1856,7 +1856,7 @@ public function domainRules() { if ($this->_domainChecker !== null) { return $this->_domainChecker; } - return $this->_domainChecker = $this->buildDomainRules(new DomainChecker(['scope' => $this])); + return $this->_domainChecker = $this->buildDomainRules(new DomainChecker(['repository' => $this])); } public function buildDomainRules(DomainChecker $rules) { From ca811d0ca866c30af4d264f9993e976cf3194b1a Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 30 Nov 2014 17:43:06 +0100 Subject: [PATCH 0025/2059] Adding IsUnique reusable domain rule --- DomainChecker.php | 5 +++++ Rule/IsUnique.php | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 Rule/IsUnique.php diff --git a/DomainChecker.php b/DomainChecker.php index b9e230be..2823d904 100644 --- a/DomainChecker.php +++ b/DomainChecker.php @@ -15,6 +15,7 @@ namespace Cake\ORM; use Cake\Datasource\EntityInterface; +use Cake\ORM\Rule\IsUnique; /** * Contains logic for storing and checking domain rules on entities @@ -65,4 +66,8 @@ public function checkUpdate(EntityInterface $entity) { return $success; } + public function isUnique(array $fields) { + return new IsUnique($fields); + } + } diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php new file mode 100644 index 00000000..48625ccf --- /dev/null +++ b/Rule/IsUnique.php @@ -0,0 +1,39 @@ +_fields = $fields; + } + + public function __invoke(EntityInterface $entity, array $options) { + $conditions = $entity->extract($this->_fields); + if ($entity->isNew() === false) { + $keys = (array)$options['repository']->primaryKey(); + $conditions['NOT'] = $entity->extract($keys); + } + + return !$options['repository']->exists($conditions); + } + +} From cae8dceb3b833f2d0613297969416665ede7be55 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 30 Nov 2014 19:36:42 +0100 Subject: [PATCH 0026/2059] Adding ability to set errors on the entity for domain rules --- DomainChecker.php | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/DomainChecker.php b/DomainChecker.php index 2823d904..eacea9f2 100644 --- a/DomainChecker.php +++ b/DomainChecker.php @@ -35,18 +35,18 @@ public function __construct(array $options) { $this->_options = $options; } - public function add(callable $rule) { - $this->_rules[] = $rule; + public function add(callable $rule, array $options = []) { + $this->_rules[] = $this->_addError($rule, $options); return $this; } - public function addCreate(callable $rule) { - $this->_createRules[] = $rule; + public function addCreate(callable $rule, array $options = []) { + $this->_createRules[] = $this->_addError($rule, $options); return $this; } - public function addUpdate(callable $rule) { - $this->_updateRules[] = $rule; + public function addUpdate(callable $rule, array $options = []) { + $this->_updateRules[] = $this->_addError($rule, $options); return $this; } @@ -66,8 +66,23 @@ public function checkUpdate(EntityInterface $entity) { return $success; } - public function isUnique(array $fields) { - return new IsUnique($fields); + public function isUnique(array $fields, $message = 'This value is already in use') { + $errorField = current($fields); + return $this->_addError(new IsUnique($fields), compact('errorField', 'message')); + } + + protected function _addError($rule, $options) { + return function ($entity, $scope) use ($rule, $options) { + $pass = $rule($entity, $options + $scope); + + if ($pass || empty($options['errorField'])) { + return $pass; + } + + $message = isset($options['message']) ? $options['message'] : 'invalid'; + $entity->errors($options['errorField'], (array)$message); + return $pass; + }; } } From 1e3e3e3a1d25fb5a162010aac0524b039f2f28f4 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 30 Nov 2014 20:16:47 +0100 Subject: [PATCH 0027/2059] Added the ExistsIn domain rule --- DomainChecker.php | 6 +++++ Rule/ExistsIn.php | 56 +++++++++++++++++++++++++++++++++++++++++++++++ Rule/IsUnique.php | 22 +++++++++++++++++-- 3 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 Rule/ExistsIn.php diff --git a/DomainChecker.php b/DomainChecker.php index eacea9f2..85daab97 100644 --- a/DomainChecker.php +++ b/DomainChecker.php @@ -16,6 +16,7 @@ use Cake\Datasource\EntityInterface; use Cake\ORM\Rule\IsUnique; +use Cake\ORM\Rule\ExistsIn; /** * Contains logic for storing and checking domain rules on entities @@ -71,6 +72,11 @@ public function isUnique(array $fields, $message = 'This value is already in use return $this->_addError(new IsUnique($fields), compact('errorField', 'message')); } + public function existsIn($field, $table, $message = 'This value does not exist') { + $errorField = $field; + return $this->_addError(new ExistsIn($field, $table), compact('errorField', 'message')); + } + protected function _addError($rule, $options) { return function ($entity, $scope) use ($rule, $options) { $pass = $rule($entity, $options + $scope); diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php new file mode 100644 index 00000000..b6210085 --- /dev/null +++ b/Rule/ExistsIn.php @@ -0,0 +1,56 @@ +_field = $field; + $this->_repository = $repository; + } + +/** + * Performs the existance check + * + * @param \Cake\Datasource\EntityInterface $entity The entity form where to extract the fields + * @param array $options Options passed to the check, + * where the `repository` key is required. + */ + public function __invoke(EntityInterface $entity, array $options) { + if (is_string($this->_repository)) { + $this->_repository = $options['repository']->association($this->_repository); + } + + $conditions = array_combine( + (array)$this->_repository->primaryKey(), + $entity->extract((array)$this->_field) + ); + return $this->_repository->exists($conditions); + } + +} diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index 48625ccf..758bad96 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -17,15 +17,33 @@ use Cake\Datasource\EntityInterface; /** - * - * + * Checks that a list of fields from an entity are unique in the table */ class IsUnique { +/** + * The list of fields to check + * + * @var array + */ + protected $_fields; + +/** + * Constructor. + * + * @param array $fields The list of fields to check uniqueness for + */ public function __construct(array $fields) { $this->_fields = $fields; } +/** + * Performs the uniqueness check + * + * @param \Cake\Datasource\EntityInterface $entity The entity form where to extract the fields + * @param array $options Options passed to the check, + * where the `repository` key is required. + */ public function __invoke(EntityInterface $entity, array $options) { $conditions = $entity->extract($this->_fields); if ($entity->isNew() === false) { From 011889a50cb4e9ba1c345e43ebb821dd1a8c650a Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 30 Nov 2014 21:35:00 -0500 Subject: [PATCH 0028/2059] Fix notice error in ResultSet. When the originating table has no fields selected, a notice error should not be triggered. Refs #5267 --- ResultSet.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ResultSet.php b/ResultSet.php index b8d7f873..bdb84ff4 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -373,11 +373,13 @@ protected function _groupResult($row) { $results[$table][$field] = $value; } + if (isset($presentAliases[$defaultAlias])) { + $results[$defaultAlias] = $this->_castValues( + $this->_defaultTable, + $results[$defaultAlias] + ); + } unset($presentAliases[$defaultAlias]); - $results[$defaultAlias] = $this->_castValues( - $this->_defaultTable, - $results[$defaultAlias] - ); $options = [ 'useSetters' => false, @@ -390,7 +392,6 @@ protected function _groupResult($row) { $alias = $assoc['nestKey']; $instance = $assoc['instance']; - // Doing this before we're sure the root assoc has data is the problem. if (!isset($results[$alias])) { $results = $instance->defaultRowValue($results, $assoc['canBeJoined']); continue; From 6eaac43fce44d2fa6690d6c2c24d1c80f5eb3ee0 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 1 Dec 2014 22:40:33 -0500 Subject: [PATCH 0029/2059] Add Query::firstOrFail() This method lets you use get() like behavior on any finder. This is intended to replace #5157. --- Query.php | 22 ++++++++++++++++++++++ Table.php | 13 +------------ 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/Query.php b/Query.php index 4f37659f..afd96ade 100644 --- a/Query.php +++ b/Query.php @@ -20,6 +20,7 @@ use Cake\ORM\EagerLoader; use Cake\ORM\ResultSet; use Cake\ORM\Table; +use Cake\ORM\Exception\RecordNotFoundException; use JsonSerializable; /** @@ -835,6 +836,27 @@ public function autoFields($value = null) { return $this; } +/** + * Get the first result from the executing query or raist an exception. + * + * @throws \Cake\ORM\RecordNotFoundException When there is no first record. + * @return mixed The first result from the ResultSet. + */ + public function firstOrFail() { + $entity = $this->first(); + if ($entity) { + return $entity; + } + $binder = new ValueBinder(); + $conditions = $this->clause('where'); + + throw new RecordNotFoundException(sprintf( + 'Record not found in table "%s" for conditions "%s"', + $this->repository()->table(), + $conditions->sql($binder) + )); + } + /** * Decorates the results iterator with MapReduce routines and formatters * diff --git a/Table.php b/Table.php index d20b13b9..9ee188bc 100644 --- a/Table.php +++ b/Table.php @@ -30,7 +30,6 @@ use Cake\ORM\Association\HasOne; use Cake\ORM\BehaviorRegistry; use Cake\ORM\Exception\MissingEntityException; -use Cake\ORM\Exception\RecordNotFoundException; use Cake\ORM\Marshaller; use Cake\Utility\Inflector; use Cake\Validation\Validator; @@ -941,17 +940,7 @@ public function get($primaryKey, $options = []) { } $query->cache($cacheKey, $cacheConfig); } - - $entity = $query->first(); - - if ($entity) { - return $entity; - } - throw new RecordNotFoundException(sprintf( - 'Record "%s" not found in table "%s"', - implode(',', (array)$primaryKey), - $this->table() - )); + return $query->firstOrFail(); } /** From 00a70df360a4ed1b1f4c3ecfb1549d1a044d0cd8 Mon Sep 17 00:00:00 2001 From: mark_story Date: Tue, 2 Dec 2014 12:54:04 -0500 Subject: [PATCH 0030/2059] Simplify error message and test. --- Query.php | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Query.php b/Query.php index afd96ade..e29e03be 100644 --- a/Query.php +++ b/Query.php @@ -837,7 +837,7 @@ public function autoFields($value = null) { } /** - * Get the first result from the executing query or raist an exception. + * Get the first result from the executing query or raise an exception. * * @throws \Cake\ORM\RecordNotFoundException When there is no first record. * @return mixed The first result from the ResultSet. @@ -847,13 +847,9 @@ public function firstOrFail() { if ($entity) { return $entity; } - $binder = new ValueBinder(); - $conditions = $this->clause('where'); - throw new RecordNotFoundException(sprintf( - 'Record not found in table "%s" for conditions "%s"', - $this->repository()->table(), - $conditions->sql($binder) + 'Record not found in table "%s"', + $this->repository()->table() )); } From 3e242090a501d6308ace7bd305b9367ea80045b6 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Tue, 2 Dec 2014 19:28:09 +0100 Subject: [PATCH 0031/2059] Renaming some classes and methods --- DomainChecker.php => RulesChecker.php | 2 +- Table.php | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) rename DomainChecker.php => RulesChecker.php (99%) diff --git a/DomainChecker.php b/RulesChecker.php similarity index 99% rename from DomainChecker.php rename to RulesChecker.php index 85daab97..6f62a095 100644 --- a/DomainChecker.php +++ b/RulesChecker.php @@ -22,7 +22,7 @@ * Contains logic for storing and checking domain rules on entities * */ -class DomainChecker { +class RulesChecker { protected $_rules = []; diff --git a/Table.php b/Table.php index 4e0f059a..a5c6273a 100644 --- a/Table.php +++ b/Table.php @@ -29,10 +29,10 @@ use Cake\ORM\Association\HasMany; use Cake\ORM\Association\HasOne; use Cake\ORM\BehaviorRegistry; -use Cake\ORM\DomainChecker; use Cake\ORM\Exception\MissingEntityException; use Cake\ORM\Exception\RecordNotFoundException; use Cake\ORM\Marshaller; +use Cake\ORM\RulesChecker; use Cake\Utility\Inflector; use Cake\Validation\Validator; use RuntimeException; @@ -185,9 +185,9 @@ class Table implements RepositoryInterface, EventListenerInterface { /** * The domain rules to be applied to entities saved by this table * - * @var \Cake\ORM\DomainChecker + * @var \Cake\ORM\RulesChecker */ - protected $_domainChecker; + protected $_rulesChecker; /** * Initializes a new instance @@ -1853,13 +1853,13 @@ public function checkDomainRules($entity) { } public function domainRules() { - if ($this->_domainChecker !== null) { - return $this->_domainChecker; + if ($this->_rulesChecker !== null) { + return $this->_rulesChecker; } - return $this->_domainChecker = $this->buildDomainRules(new DomainChecker(['repository' => $this])); + return $this->_rulesChecker = $this->buildRules(new RulesChecker(['repository' => $this])); } - public function buildDomainRules(DomainChecker $rules) { + public function buildRules(RulesChecker $rules) { return $rules; } From 690343b1d668b8c82325b8758f367376fae17e5e Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Tue, 2 Dec 2014 19:31:38 +0100 Subject: [PATCH 0032/2059] Renaming option meant to avoid rule checking during the save process --- Table.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index a5c6273a..2da7a255 100644 --- a/Table.php +++ b/Table.php @@ -1188,7 +1188,7 @@ public function save(EntityInterface $entity, $options = []) { $options = new \ArrayObject($options + [ 'atomic' => true, 'associated' => true, - 'domainCheck' => true + 'checkRules' => true ]); if ($entity->errors()) { @@ -1231,7 +1231,7 @@ protected function _processSave($entity, $options) { $entity->isNew(!$this->exists($conditions)); } - if ($options['domainCheck'] && !$this->checkDomainRules($entity)) { + if ($options['checkRules'] && !$this->checkDomainRules($entity)) { return false; } From 4ea93e663f6a0d75ada0501c135638fdb33e6d38 Mon Sep 17 00:00:00 2001 From: mark_story Date: Tue, 2 Dec 2014 13:05:54 -0500 Subject: [PATCH 0033/2059] Move RecordNotFoundException to Datasource. Also move findOrFail() into QueryTrait so it can be used in other queryable implementations. --- Exception/RecordNotFoundException.php | 36 --------------------------- Query.php | 17 ------------- 2 files changed, 53 deletions(-) delete mode 100644 Exception/RecordNotFoundException.php diff --git a/Exception/RecordNotFoundException.php b/Exception/RecordNotFoundException.php deleted file mode 100644 index 9f20ecff..00000000 --- a/Exception/RecordNotFoundException.php +++ /dev/null @@ -1,36 +0,0 @@ -first(); - if ($entity) { - return $entity; - } - throw new RecordNotFoundException(sprintf( - 'Record not found in table "%s"', - $this->repository()->table() - )); - } - /** * Decorates the results iterator with MapReduce routines and formatters * From 96354a1d3d759a6402f0395e60f1b34c87c678a7 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Tue, 2 Dec 2014 19:37:08 +0100 Subject: [PATCH 0034/2059] Adding missing property and fixing typos --- Rule/ExistsIn.php | 17 ++++++++++++----- Rule/IsUnique.php | 2 +- RulesChecker.php | 2 +- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index b6210085..b1f2c37d 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -22,22 +22,29 @@ */ class ExistsIn { +/** + * The list of fields to check + * + * @var array + */ + protected $_fields; + /** * Constructor. * - * @param string $field The field to check existance for. + * @param string|array $fields The field or fields to check existance as primary key. * @param object|string $repository The repository where the field will be looked for, * or the association name for the repository. */ - public function __construct($field, $repository) { - $this->_field = $field; + public function __construct($fields, $repository) { + $this->_field = (array)$fields; $this->_repository = $repository; } /** * Performs the existance check * - * @param \Cake\Datasource\EntityInterface $entity The entity form where to extract the fields + * @param \Cake\Datasource\EntityInterface $entity The entity from where to extract the fields * @param array $options Options passed to the check, * where the `repository` key is required. */ @@ -48,7 +55,7 @@ public function __invoke(EntityInterface $entity, array $options) { $conditions = array_combine( (array)$this->_repository->primaryKey(), - $entity->extract((array)$this->_field) + $entity->extract($this->_fields) ); return $this->_repository->exists($conditions); } diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index 758bad96..bcb5fe0c 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -40,7 +40,7 @@ public function __construct(array $fields) { /** * Performs the uniqueness check * - * @param \Cake\Datasource\EntityInterface $entity The entity form where to extract the fields + * @param \Cake\Datasource\EntityInterface $entity The entity from where to extract the fields * @param array $options Options passed to the check, * where the `repository` key is required. */ diff --git a/RulesChecker.php b/RulesChecker.php index 6f62a095..34d1a2ce 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -19,7 +19,7 @@ use Cake\ORM\Rule\ExistsIn; /** - * Contains logic for storing and checking domain rules on entities + * Contains logic for storing and checking rules on entities * */ class RulesChecker { From f8525a101beb7f324c16fd35420d6a198405b1a7 Mon Sep 17 00:00:00 2001 From: mark_story Date: Tue, 2 Dec 2014 14:10:16 -0500 Subject: [PATCH 0035/2059] Fix PHPCS errors. --- Query.php | 1 - Table.php | 2 -- 2 files changed, 3 deletions(-) diff --git a/Query.php b/Query.php index 71e4afa4..4f37659f 100644 --- a/Query.php +++ b/Query.php @@ -20,7 +20,6 @@ use Cake\ORM\EagerLoader; use Cake\ORM\ResultSet; use Cake\ORM\Table; -use Cake\ORM\Exception\RecordNotFoundException; use JsonSerializable; /** diff --git a/Table.php b/Table.php index 9ee188bc..81e084a0 100644 --- a/Table.php +++ b/Table.php @@ -905,8 +905,6 @@ protected function _setFieldMatchers($options, $keys) { /** * {@inheritDoc} * - * @throws \Cake\ORM\Exception\RecordNotFoundException if no record can be found given - * a primary key value. * @throws \InvalidArgumentException When $primaryKey has an incorrect number of elements. */ public function get($primaryKey, $options = []) { From ac2f99c4a3b0725c0f2e40269a291afabbff109d Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Tue, 2 Dec 2014 20:46:25 +0100 Subject: [PATCH 0036/2059] Renaming more methods and implemeting events for the rules checking --- Rule/ExistsIn.php | 2 +- Table.php | 29 +++++++++++++++++++++-------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index b1f2c37d..283ed097 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -37,7 +37,7 @@ class ExistsIn { * or the association name for the repository. */ public function __construct($fields, $repository) { - $this->_field = (array)$fields; + $this->_fields = (array)$fields; $this->_repository = $repository; } diff --git a/Table.php b/Table.php index 2da7a255..66fc8cf9 100644 --- a/Table.php +++ b/Table.php @@ -1231,7 +1231,7 @@ protected function _processSave($entity, $options) { $entity->isNew(!$this->exists($conditions)); } - if ($options['checkRules'] && !$this->checkDomainRules($entity)) { + if ($options['checkRules'] && !$this->checkRules($entity)) { return false; } @@ -1842,17 +1842,30 @@ public function validateUnique($value, array $options, array $context = []) { return !$this->exists($conditions); } - public function checkDomainRules($entity) { - $rules = $this->domainRules(); + public function checkRules($entity) { + $rules = $this->rulesChecker(); + $event = $this->dispatchEvent('Model.beforeRules', compact('entity', 'rules')); + if ($event->isStopped()) { + return $event->result; + } + if ($entity->isNew()) { - return $rules->checkCreate($entity); + $result = $rules->checkCreate($entity); + } else { + $result = $rules->checkUpdate($entity); + } + + $event = $this->dispatchEvent('Model.afterRules', compact('entity', 'rules', 'result')); + + if ($event->isStopped()) { + return $event->result; } - return $rules->checkUpdate($entity); + return $result; } - public function domainRules() { + public function rulesChecker() { if ($this->_rulesChecker !== null) { return $this->_rulesChecker; } @@ -1881,8 +1894,8 @@ public function implementedEvents() { 'Model.afterSave' => 'afterSave', 'Model.beforeDelete' => 'beforeDelete', 'Model.afterDelete' => 'afterDelete', - 'Model.beforeValidate' => 'beforeValidate', - 'Model.afterValidate' => 'afterValidate', + 'Model.beforeRules' => 'beforeRules', + 'Model.afterRules' => 'afterRules', ]; $events = []; From 7ec6b93f3333d0cb26b276d179e825ba7e42fa6f Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 3 Dec 2014 01:54:45 +0530 Subject: [PATCH 0037/2059] Add Table::addAssociations() It allows setting multiple associations using a single function call using an array argument and facilitates DRYer app code. --- Table.php | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/Table.php b/Table.php index d20b13b9..3d5079e6 100644 --- a/Table.php +++ b/Table.php @@ -564,6 +564,47 @@ public function associations() { return $this->_associations; } +/** + * Setup associations. + * + * It takes an array containing set of table names indexed by association type + * as argument: + * + * {{{ + * $this->Comment->associations([ + * 'belongsTo' => [ + * 'Comments', + * 'Users' => ['className' => 'App\Model\Table\UsersTable'] + * ], + * 'belongsToMany' => [ + * 'Tags' + * ] + * ]); + * }}} + * + * @param array $params Set of associations to bind (indexed by association type) + * @return void + * @see \Cake\ORM\Table::belongsToMany() + * @see \Cake\ORM\Table::hasOne() + * @see \Cake\ORM\Table::hasMany() + * @see \Cake\ORM\Table::belongsToMany() + */ + public function addAssociations(array $params) { + if ($params === null) { + return $this->_associations; + } + + foreach ($params as $assocType => $tables) { + foreach ($tables as $associated => $options) { + if (is_numeric($associated)) { + $associated = $options; + $options = []; + } + $this->{$assocType}($associated, $options); + } + } + } + /** * Creates a new BelongsTo association between this table and a target * table. A "belongs to" association is a N-1 relationship where this table From 0c6120338654f5216649dc6a34407a6227ed358e Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 3 Dec 2014 01:57:31 +0530 Subject: [PATCH 0038/2059] Docbloc fix. --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 3d5079e6..2dd66ca8 100644 --- a/Table.php +++ b/Table.php @@ -584,7 +584,7 @@ public function associations() { * * @param array $params Set of associations to bind (indexed by association type) * @return void - * @see \Cake\ORM\Table::belongsToMany() + * @see \Cake\ORM\Table::belongsTo() * @see \Cake\ORM\Table::hasOne() * @see \Cake\ORM\Table::hasMany() * @see \Cake\ORM\Table::belongsToMany() From 9ded2efc46c37b0bd451776fea5c5f4b059d4056 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 3 Dec 2014 02:00:29 +0530 Subject: [PATCH 0039/2059] Improve docblock example. --- Table.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Table.php b/Table.php index 2dd66ca8..36376bf5 100644 --- a/Table.php +++ b/Table.php @@ -571,14 +571,12 @@ public function associations() { * as argument: * * {{{ - * $this->Comment->associations([ + * $this->Posts->associations([ * 'belongsTo' => [ - * 'Comments', * 'Users' => ['className' => 'App\Model\Table\UsersTable'] * ], - * 'belongsToMany' => [ - * 'Tags' - * ] + * 'hasMany' => ['Comments'], + * 'belongsToMany' => ['Tags'] * ]); * }}} * From 78986f140535a5bdaa6c4122f3943693622ec9a4 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 3 Dec 2014 02:03:28 +0530 Subject: [PATCH 0040/2059] Use FQN in docblocks. --- AssociationCollection.php | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/AssociationCollection.php b/AssociationCollection.php index 3c4c2a50..0d208c1d 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -43,8 +43,8 @@ class AssociationCollection { * This makes using plugins simpler as the Plugin.Class syntax is frequently used. * * @param string $alias The association alias - * @param Association $association The association to add. - * @return Association The association object being added. + * @param \Cake\ORM\Association $association The association to add. + * @return \Cake\ORM\Association The association object being added. */ public function add($alias, Association $association) { list(, $alias) = pluginSplit($alias); @@ -55,7 +55,7 @@ public function add($alias, Association $association) { * Fetch an attached association by name. * * @param string $alias The association alias to get. - * @return Association|null Either the association or null. + * @return \Cake\ORM\Association|null Either the association or null. */ public function get($alias) { $alias = strtolower($alias); @@ -69,7 +69,7 @@ public function get($alias) { * Fetch an association by property name. * * @param string $prop The property to find an association by. - * @return Association|null Either the association or null. + * @return \Cake\ORM\Association|null Either the association or null. */ public function getByProperty($prop) { foreach ($this->_items as $assoc) { @@ -144,8 +144,8 @@ public function removeAll() { * Parent associations include any association where the given table * is the owning side. * - * @param Table $table The table entity is for. - * @param Entity $entity The entity to save associated data for. + * @param \Cake\ORM\Table $table The table entity is for. + * @param \Cake\ORM\Entity $entity The entity to save associated data for. * @param array $associations The list of associations to save parents from. * associations not in this list will not be saved. * @param array $options The options for the save operation. @@ -164,8 +164,8 @@ public function saveParents(Table $table, Entity $entity, $associations, array $ * Child associations include any association where the given table * is not the owning side. * - * @param Table $table The table entity is for. - * @param Entity $entity The entity to save associated data for. + * @param \Cake\ORM\Table $table The table entity is for. + * @param \Cake\ORM\Entity $entity The entity to save associated data for. * @param array $associations The list of associations to save children from. * associations not in this list will not be saved. * @param array $options The options for the save operation. @@ -181,8 +181,8 @@ public function saveChildren(Table $table, Entity $entity, array $associations, /** * Helper method for saving an association's data. * - * @param Table $table The table the save is currently operating on - * @param Entity $entity The entity to save + * @param \Cake\ORM\Table $table The table the save is currently operating on + * @param \Cake\ORM\Entity $entity The entity to save * @param array $associations Array of associations to save. * @param array $options Original options * @param bool $owningSide Compared with association classes' @@ -219,8 +219,8 @@ protected function _saveAssociations($table, $entity, $associations, $options, $ /** * Helper method for saving an association's data. * - * @param Association $association The association object to save with. - * @param Entity $entity The entity to save + * @param \Cake\ORM\Association $association The association object to save with. + * @param \Cake\ORM\Entity $entity The entity to save * @param array $nested Options for deeper associations * @param array $options Original options * @return bool Success @@ -238,7 +238,7 @@ protected function _save($association, $entity, $nested, $options) { /** * Cascade a delete across the various associations. * - * @param Entity $entity The entity to delete associations for. + * @param \Cake\ORM\Entity $entity The entity to delete associations for. * @param array $options The options used in the delete operation. * @return void */ From 3608f1433840554e6213bad7ec799a2e59c5e120 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 3 Dec 2014 02:09:03 +0530 Subject: [PATCH 0041/2059] Removed unneeded null argument check. --- Table.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Table.php b/Table.php index 36376bf5..e166a4b9 100644 --- a/Table.php +++ b/Table.php @@ -588,10 +588,6 @@ public function associations() { * @see \Cake\ORM\Table::belongsToMany() */ public function addAssociations(array $params) { - if ($params === null) { - return $this->_associations; - } - foreach ($params as $assocType => $tables) { foreach ($tables as $associated => $options) { if (is_numeric($associated)) { From ad462071bfe01b5dd86717d700e8c092b4a0ba1e Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Tue, 2 Dec 2014 23:19:24 +0100 Subject: [PATCH 0042/2059] Documenting some of the methods --- RulesChecker.php | 102 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/RulesChecker.php b/RulesChecker.php index 34d1a2ce..a492f234 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -24,33 +24,119 @@ */ class RulesChecker { +/** + * The list of rules to be checked on every case + * + * @var array + */ protected $_rules = []; +/** + * The list of rules to check during create operations + * + * @var array + */ protected $_createRules = []; +/** + * The list of rules to check during update operations + * + * @var array + */ protected $_updateRules = []; +/** + * List of options to pass to every callable rule + * + * @var array + */ protected $_options = []; +/** + * Constructor. Takes the options to be passed to all rules. + * + * @param array $options The options to pass to every rule + */ public function __construct(array $options) { $this->_options = $options; } +/** + * Adds a rule that will be applied to the entity both on create and update + * operations. + * + * ### Options + * + * The options array accept the following special keys: + * + * - `errorField`: The name of the entity field that will be marked as invalid + * if the rule does not pass. + * - `message`: The error message to set to `errorField` if the rule does not pass. + * + * @param callable $rule A callable function or object that will return whether + * the entity is valid or not. + * @param array $options List of extra options to pass to the rule callable as + * second argument. + * @return $this + */ public function add(callable $rule, array $options = []) { $this->_rules[] = $this->_addError($rule, $options); return $this; } +/** + * Adds a rule that will be applied to the entity both on create + * operations. + * + * ### Options + * + * The options array accept the following special keys: + * + * - `errorField`: The name of the entity field that will be marked as invalid + * if the rule does not pass. + * - `message`: The error message to set to `errorField` if the rule does not pass. + * + * @param callable $rule A callable function or object that will return whether + * the entity is valid or not. + * @param array $options List of extra options to pass to the rule callable as + * second argument. + * @return $this + */ public function addCreate(callable $rule, array $options = []) { $this->_createRules[] = $this->_addError($rule, $options); return $this; } +/** + * Adds a rule that will be applied to the entity both on update + * operations. + * + * ### Options + * + * The options array accept the following special keys: + * + * - `errorField`: The name of the entity field that will be marked as invalid + * if the rule does not pass. + * - `message`: The error message to set to `errorField` if the rule does not pass. + * + * @param callable $rule A callable function or object that will return whether + * the entity is valid or not. + * @param array $options List of extra options to pass to the rule callable as + * second argument. + * @return $this + */ public function addUpdate(callable $rule, array $options = []) { $this->_updateRules[] = $this->_addError($rule, $options); return $this; } +/** + * Runs each of the rules by passing the provided entity and returns true if all + * of them pass. The rules selected will be only those specified to be run on 'create' + * + * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. + * @return bool + */ public function checkCreate(EntityInterface $entity) { $success = true; foreach (array_merge($this->_rules, $this->_createRules) as $rule) { @@ -59,6 +145,13 @@ public function checkCreate(EntityInterface $entity) { return $success; } +/** + * Runs each of the rules by passing the provided entity and returns true if all + * of them pass. The rules selected will be only those specified to be run on 'update' + * + * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. + * @return bool + */ public function checkUpdate(EntityInterface $entity) { $success = true; foreach (array_merge($this->_rules, $this->_updateRules) as $rule) { @@ -67,11 +160,20 @@ public function checkUpdate(EntityInterface $entity) { return $success; } +/** + * Returns a callable that can be used as a rule for checking the uniqueness of a value + * in the table. + * + * @param array $fields The list of fields to check for uniqueness. + * @param string $message The error message to show in case the rule does not pass. + * @return callable + */ public function isUnique(array $fields, $message = 'This value is already in use') { $errorField = current($fields); return $this->_addError(new IsUnique($fields), compact('errorField', 'message')); } + public function existsIn($field, $table, $message = 'This value does not exist') { $errorField = $field; return $this->_addError(new ExistsIn($field, $table), compact('errorField', 'message')); From 0dec68b7d5ba3b7fc53a979d744eed23d4252a87 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 2 Dec 2014 22:07:48 -0500 Subject: [PATCH 0043/2059] Update doc blocks for addAssociations() --- Table.php | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Table.php b/Table.php index e166a4b9..0a2794d0 100644 --- a/Table.php +++ b/Table.php @@ -565,21 +565,25 @@ public function associations() { } /** - * Setup associations. + * Setup multiple associations. * * It takes an array containing set of table names indexed by association type * as argument: * * {{{ * $this->Posts->associations([ - * 'belongsTo' => [ - * 'Users' => ['className' => 'App\Model\Table\UsersTable'] - * ], - * 'hasMany' => ['Comments'], - * 'belongsToMany' => ['Tags'] + * 'belongsTo' => [ + * 'Users' => ['className' => 'App\Model\Table\UsersTable'] + * ], + * 'hasMany' => ['Comments'], + * 'belongsToMany' => ['Tags'] * ]); * }}} * + * Each association type accepts multiple associations where the keys + * are the aliases, and the values are association config data. If numeric + * keys are used the values will be treated as association aliases. + * * @param array $params Set of associations to bind (indexed by association type) * @return void * @see \Cake\ORM\Table::belongsTo() From a4c4b6a2d026bae52c35b541dfe6aa4346f03a39 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 2 Dec 2014 23:12:09 -0500 Subject: [PATCH 0044/2059] Raise an exception on invalid belongs to many data. If an association uses an incorrect find type (like list), the results cannot be joined together so we should raise an exception. Refs #5282 --- Association/BelongsToMany.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 58158257..7fad0e18 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -277,6 +277,7 @@ protected function _joinCondition($options) { * @param \Cake\ORM\Query $fetchQuery The query to get results from * @param array $options The options passed to the eager loader * @return array + * @throws \RuntimeException when the association property is not part of the results set. */ protected function _buildResultMap($fetchQuery, $options) { $resultMap = []; @@ -285,6 +286,12 @@ protected function _buildResultMap($fetchQuery, $options) { $hydrated = $fetchQuery->hydrate(); foreach ($fetchQuery->all() as $result) { + if (!isset($result[$property])) { + throw new \RuntimeException(sprintf( + '"%s" is missing from the belongsToMany results. Results cannot be created.', + $property + )); + } $result[$this->_junctionProperty] = $result[$property]; unset($result[$property]); From 2434c670a1c42a56cc6f3143e42a69e239367e91 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 3 Dec 2014 12:31:07 +0530 Subject: [PATCH 0045/2059] Fix method name in docblock --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 0a2794d0..d76d215c 100644 --- a/Table.php +++ b/Table.php @@ -571,7 +571,7 @@ public function associations() { * as argument: * * {{{ - * $this->Posts->associations([ + * $this->Posts->addAssociations([ * 'belongsTo' => [ * 'Users' => ['className' => 'App\Model\Table\UsersTable'] * ], From 084e896b5eaaeab3a6f68057c956defec319dc8f Mon Sep 17 00:00:00 2001 From: Walther Lalk Date: Wed, 3 Dec 2014 10:18:41 +0200 Subject: [PATCH 0046/2059] Add a hasFinder method to table object --- Table.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Table.php b/Table.php index d20b13b9..4ed5033e 100644 --- a/Table.php +++ b/Table.php @@ -1481,6 +1481,19 @@ protected function _processDelete($entity, $options) { return $success; } +/** + * Returns true if the finder exists for the table + * + * @param string $type name of finder to check + * + * @return bool + */ + public function hasFinder($type) { + $finder = 'find' . $type; + + return method_exists($this, $finder) || ($this->_behaviors && $this->_behaviors->hasFinder($type)); + } + /** * Calls a finder method directly and applies it to the passed query, * if no query is passed a new one will be created and returned From d384eb1f1c45e4d0eea58a6c4788520796b3e00d Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Wed, 3 Dec 2014 19:44:38 +0100 Subject: [PATCH 0047/2059] Adding more doc blocks --- RulesChecker.php | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/RulesChecker.php b/RulesChecker.php index a492f234..c539480d 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -164,6 +164,12 @@ public function checkUpdate(EntityInterface $entity) { * Returns a callable that can be used as a rule for checking the uniqueness of a value * in the table. * + * ### Example: + * + * {{{ + * $rules->add($rules->isUnique('email', 'The email should be unique')); + * }}} + * * @param array $fields The list of fields to check for uniqueness. * @param string $message The error message to show in case the rule does not pass. * @return callable @@ -173,12 +179,38 @@ public function isUnique(array $fields, $message = 'This value is already in use return $this->_addError(new IsUnique($fields), compact('errorField', 'message')); } - +/** + * Returns a callable that can be used as a rule for checking that the values + * extracted from the entity to check exist as the primary key in another table. + * + * This is useful for enforcing foreign key integrity checks. + * + * ### Example: + * + * {{{ + * $rules->add($rules->existsIn('author_id', 'Authors', 'Invalid Author')); + * + * $rules->add($rules->existsIn('site_id', new SitesTable(), 'Invalid Site')); + * }}} + * + * @param string|array $fields The field or list of fields to check for existance by + * primary key lookup in the other table. + * @param string $message The error message to show in case the rule does not pass. + * @return callable + */ public function existsIn($field, $table, $message = 'This value does not exist') { $errorField = $field; return $this->_addError(new ExistsIn($field, $table), compact('errorField', 'message')); } +/** + * Utility method for decorating any callable so that if it returns false, the correct + * property in the entity is marked as invalid. + * + * @param callable $rule The rule to decorate + * @param array $options The options containing the error message and field + * @return callable + */ protected function _addError($rule, $options) { return function ($entity, $scope) use ($rule, $options) { $pass = $rule($entity, $options + $scope); From d40f09bc30ff4fef46f1f3e18af3e54d74e227ba Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Wed, 3 Dec 2014 19:55:54 +0100 Subject: [PATCH 0048/2059] Updating some doc blocks --- Table.php | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/Table.php b/Table.php index 66fc8cf9..d408ca72 100644 --- a/Table.php +++ b/Table.php @@ -83,12 +83,13 @@ * $primary parameter indicates whether or not this is the root query, * or an associated query. * - * - `beforeValidate(Event $event, Entity $entity, ArrayObject $options, Validator $validator)` - * Fired before an entity is validated. By stopping this event, you can abort - * the validate + save operations. + * - `beforeRules(Event $event, Entity $entity, RulesChecker $rules)` + * Fired before an entity is validated using the rules checker. By stopping this event, + * you can return the final value of the rules checking operation. * - * - `afterValidate(Event $event, Entity $entity, ArrayObject $options, Validator $validator)` - * Fired after an entity is validated. + * - `afterRules(Event $event, Entity $entity,RulesChecker $rules, bool $result)` + * Fired after the rules have been checked on the entity.By stopping this event, + * you can return the final value of the rules checking operation. * * - `beforeSave(Event $event, Entity $entity, ArrayObject $options)` * Fired before each entity is saved. Stopping this event will abort the save @@ -1113,10 +1114,8 @@ public function exists($conditions) { * * - atomic: Whether to execute the save and callbacks inside a database * transaction (default: true) - * - validate: Whether or not validate the entity before saving, if validation - * fails, it will abort the save operation. If this key is set to a string value, - * the validator object registered in this table under the provided name will be - * used instead of the default one. (default:true) + * - checkRules: Whether or not to check the rules on entity before saving, if the checking + * fails, it will abort the save operation. (default:true) * - associated: If true it will save all associated entities as they are found * in the passed `$entity` whenever the property defined for the association * is marked as dirty. Associated records are saved recursively unless told @@ -1129,15 +1128,15 @@ public function exists($conditions) { * * When saving, this method will trigger four events: * - * - Model.beforeValidate: Will be triggered right before any validation is done - * for the passed entity if the validate key in $options is not set to false. - * Listeners will receive as arguments the entity, the options array and the - * validation object to be used for validating the entity. If the event is - * stopped the validation result will be set to the result of the event itself. - * - Model.afterValidate: Will be triggered right after the `validate()` method is - * called in the entity. Listeners will receive as arguments the entity, the - * options array and the validation object to be used for validating the entity. - * If the event is stopped the validation result will be set to the result of + * - Model.beforeRules: Will be triggered right before any rule checking is done + * for the passed entity if the `checkRules` key in $options is not set to false. + * Listeners will receive as arguments the entity and the + * RulesChecker object to be used for validating the entity. If the event is + * stopped the checking result will be set to the result of the event itself. + * - Model.afterRules: Will be triggered right after the `checkRules()` method is + * called for the entity. Listeners will receive as arguments the entity, the + * RulesChecker object that was used and the result of checking the rules. + * If the event is stopped the checking result will be set to the result of * the event itself. * - Model.beforeSave: Will be triggered just before the list of fields to be * persisted is calculated. It receives both the entity and the options as @@ -1169,12 +1168,12 @@ public function exists($conditions) { * $articles->save($entity, ['associated' => ['Comments']); * * // Save the company, the employees and related addresses for each of them. - * // For employees use the 'special' validation group + * // For employees do not check the entity rules * $companies->save($entity, [ * 'associated' => [ * 'Employees' => [ * 'associated' => ['Addresses'], - * 'validate' => 'special' + * 'checkRules' => false * ] * ] * ]); From de2cc7eb326e8edf96998e5d481d1f7947254ee7 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Wed, 3 Dec 2014 20:20:22 +0100 Subject: [PATCH 0049/2059] Adding the missing doc blocks --- Table.php | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/Table.php b/Table.php index d408ca72..6fbaf03f 100644 --- a/Table.php +++ b/Table.php @@ -1841,7 +1841,14 @@ public function validateUnique($value, array $options, array $context = []) { return !$this->exists($conditions); } - public function checkRules($entity) { +/** + * Returns whether or not the passed entity complies with all the rules stored in + * the rules checker. + * + * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. + * @return bool + */ + public function checkRules(EntityInterface $entity) { $rules = $this->rulesChecker(); $event = $this->dispatchEvent('Model.beforeRules', compact('entity', 'rules')); @@ -1849,12 +1856,7 @@ public function checkRules($entity) { return $event->result; } - if ($entity->isNew()) { - $result = $rules->checkCreate($entity); - } else { - $result = $rules->checkUpdate($entity); - } - + $result = $entity->isNew() ? $rules->checkCreate($entity) : $rules->checkUpdate($entity); $event = $this->dispatchEvent('Model.afterRules', compact('entity', 'rules', 'result')); if ($event->isStopped()) { @@ -1864,6 +1866,14 @@ public function checkRules($entity) { return $result; } +/** + * Returns the rule checker for this table. A rules checker object is used to + * test an entity for validity on rules that may involve complex logic or data that + * needs to be fetched from the database or other sources. + * + * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. + * @return \Cake\ORM\RulesChecker + */ public function rulesChecker() { if ($this->_rulesChecker !== null) { return $this->_rulesChecker; @@ -1871,6 +1881,14 @@ public function rulesChecker() { return $this->_rulesChecker = $this->buildRules(new RulesChecker(['repository' => $this])); } +/** + * Returns rules chaker object after modifying the one that was passed. Subclasses + * can override this method in order to initialize the rules to be applied to + * entities saved by this table. + * + * @param \Cake\ORM\RulesChecker $rules The rules object to be modified. + * @return \Cake\ORM\RulesChecker + */ public function buildRules(RulesChecker $rules) { return $rules; } From 0c11cb5d3eeae8f0213cbc8c1acafa18047952f2 Mon Sep 17 00:00:00 2001 From: antograssiot Date: Thu, 4 Dec 2014 21:04:49 +0100 Subject: [PATCH 0050/2059] correct typos --- Rule/ExistsIn.php | 4 ++-- RulesChecker.php | 8 +++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 283ed097..c2e9a272 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -32,7 +32,7 @@ class ExistsIn { /** * Constructor. * - * @param string|array $fields The field or fields to check existance as primary key. + * @param string|array $fields The field or fields to check existence as primary key. * @param object|string $repository The repository where the field will be looked for, * or the association name for the repository. */ @@ -42,7 +42,7 @@ public function __construct($fields, $repository) { } /** - * Performs the existance check + * Performs the existence check * * @param \Cake\Datasource\EntityInterface $entity The entity from where to extract the fields * @param array $options Options passed to the check, diff --git a/RulesChecker.php b/RulesChecker.php index c539480d..6e5c84e7 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -85,8 +85,7 @@ public function add(callable $rule, array $options = []) { } /** - * Adds a rule that will be applied to the entity both on create - * operations. + * Adds a rule that will be applied to the entity on create operations. * * ### Options * @@ -108,8 +107,7 @@ public function addCreate(callable $rule, array $options = []) { } /** - * Adds a rule that will be applied to the entity both on update - * operations. + * Adds a rule that will be applied to the entity on update operations. * * ### Options * @@ -193,7 +191,7 @@ public function isUnique(array $fields, $message = 'This value is already in use * $rules->add($rules->existsIn('site_id', new SitesTable(), 'Invalid Site')); * }}} * - * @param string|array $fields The field or list of fields to check for existance by + * @param string|array $fields The field or list of fields to check for existence by * primary key lookup in the other table. * @param string $message The error message to show in case the rule does not pass. * @return callable From d1db4d21a3bda3fba3bd408bfbc4ad224bd64a68 Mon Sep 17 00:00:00 2001 From: antograssiot Date: Thu, 4 Dec 2014 21:23:16 +0100 Subject: [PATCH 0051/2059] update php cs --- Rule/ExistsIn.php | 8 ++++++++ Rule/IsUnique.php | 1 + RulesChecker.php | 7 ++++--- Table.php | 4 +--- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index c2e9a272..cbe2dd11 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -29,6 +29,13 @@ class ExistsIn { */ protected $_fields; +/** + * The repository where the field will be looked for + * + * @var array + */ + protected $_repository; + /** * Constructor. * @@ -47,6 +54,7 @@ public function __construct($fields, $repository) { * @param \Cake\Datasource\EntityInterface $entity The entity from where to extract the fields * @param array $options Options passed to the check, * where the `repository` key is required. + * @return bool */ public function __invoke(EntityInterface $entity, array $options) { if (is_string($this->_repository)) { diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index bcb5fe0c..1fc0add8 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -43,6 +43,7 @@ public function __construct(array $fields) { * @param \Cake\Datasource\EntityInterface $entity The entity from where to extract the fields * @param array $options Options passed to the check, * where the `repository` key is required. + * @return bool */ public function __invoke(EntityInterface $entity, array $options) { $conditions = $entity->extract($this->_fields); diff --git a/RulesChecker.php b/RulesChecker.php index 6e5c84e7..56d215b9 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -15,8 +15,8 @@ namespace Cake\ORM; use Cake\Datasource\EntityInterface; -use Cake\ORM\Rule\IsUnique; use Cake\ORM\Rule\ExistsIn; +use Cake\ORM\Rule\IsUnique; /** * Contains logic for storing and checking rules on entities @@ -191,8 +191,9 @@ public function isUnique(array $fields, $message = 'This value is already in use * $rules->add($rules->existsIn('site_id', new SitesTable(), 'Invalid Site')); * }}} * - * @param string|array $fields The field or list of fields to check for existence by + * @param string|array $field The field or list of fields to check for existence by * primary key lookup in the other table. + * @param object|string $table The table name where the fields existence will be checked. * @param string $message The error message to show in case the rule does not pass. * @return callable */ @@ -216,7 +217,7 @@ protected function _addError($rule, $options) { if ($pass || empty($options['errorField'])) { return $pass; } - + $message = isset($options['message']) ? $options['message'] : 'invalid'; $entity->errors($options['errorField'], (array)$message); return $pass; diff --git a/Table.php b/Table.php index 6fbaf03f..f70531ff 100644 --- a/Table.php +++ b/Table.php @@ -182,7 +182,6 @@ class Table implements RepositoryInterface, EventListenerInterface { */ protected $_validators = []; - /** * The domain rules to be applied to entities saved by this table * @@ -1855,7 +1854,7 @@ public function checkRules(EntityInterface $entity) { if ($event->isStopped()) { return $event->result; } - + $result = $entity->isNew() ? $rules->checkCreate($entity) : $rules->checkUpdate($entity); $event = $this->dispatchEvent('Model.afterRules', compact('entity', 'rules', 'result')); @@ -1871,7 +1870,6 @@ public function checkRules(EntityInterface $entity) { * test an entity for validity on rules that may involve complex logic or data that * needs to be fetched from the database or other sources. * - * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. * @return \Cake\ORM\RulesChecker */ public function rulesChecker() { From 04b8b806cfdd59e94fa9a1b6acf04d6bda826eb7 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Fri, 5 Dec 2014 13:52:02 +0100 Subject: [PATCH 0052/2059] Added a Model.buildRules event This will help listeners such as behaviors to inject rules only in a table lifetime. --- Table.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Table.php b/Table.php index f70531ff..d92ab0bc 100644 --- a/Table.php +++ b/Table.php @@ -83,6 +83,9 @@ * $primary parameter indicates whether or not this is the root query, * or an associated query. * + * - `buildRules(Event $event, RulesChecker $rules)` + * Allows listeners to modify the rules checker by adding more rules. + * * - `beforeRules(Event $event, Entity $entity, RulesChecker $rules)` * Fired before an entity is validated using the rules checker. By stopping this event, * you can return the final value of the rules checking operation. @@ -1876,7 +1879,9 @@ public function rulesChecker() { if ($this->_rulesChecker !== null) { return $this->_rulesChecker; } - return $this->_rulesChecker = $this->buildRules(new RulesChecker(['repository' => $this])); + $this->_rulesChecker = $this->buildRules(new RulesChecker(['repository' => $this])); + $this->dispatchEvent('Model.buildRules', ['rules' => $this->_rulesChecker]); + return $this->_rulesChecker; } /** From cd96a2c11397f2e84723d618ef0a4301a3f1aa65 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Fri, 5 Dec 2014 14:11:56 +0100 Subject: [PATCH 0053/2059] Making behaviors listeners of rules events --- Behavior.php | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/Behavior.php b/Behavior.php index f70b797a..b5abbb4b 100644 --- a/Behavior.php +++ b/Behavior.php @@ -53,12 +53,16 @@ * $primary parameter indicates whether or not this is the root query, * or an associated query. * - * - `beforeValidate(Event $event, Entity $entity, ArrayObject $options, Validator $validator)` - * Fired before an entity is validated. By stopping this event, you can abort - * the validate + save operations. + * - `buildRules(Event $event, RulesChecker $rules)` + * Allows listeners to modify the rules checker by adding more rules. * - * - `afterValidate(Event $event, Entity $entity, ArrayObject $options, Validator $validator)` - * Fired after an entity is validated. + * - `beforeRules(Event $event, Entity $entity, RulesChecker $rules)` + * Fired before an entity is validated using the rules checker. By stopping this event, + * you can return the final value of the rules checking operation. + * + * - `afterRules(Event $event, Entity $entity,RulesChecker $rules, bool $result)` + * Fired after the rules have been checked on the entity.By stopping this event, + * you can return the final value of the rules checking operation. * * - `beforeSave(Event $event, Entity $entity, ArrayObject $options)` * Fired before each entity is saved. Stopping this event will abort the save @@ -239,8 +243,9 @@ public function implementedEvents() { 'Model.afterSave' => 'afterSave', 'Model.beforeDelete' => 'beforeDelete', 'Model.afterDelete' => 'afterDelete', - 'Model.beforeValidate' => 'beforeValidate', - 'Model.afterValidate' => 'afterValidate', + 'Model.buildRules' => 'buildRules', + 'Model.beforeRules' => 'beforeRules', + 'Model.afterRules' => 'afterRules', ]; $config = $this->config(); $priority = isset($config['priority']) ? $config['priority'] : null; From bf2842761d82afcf3b86f3d9ed8794b405c3f75a Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Fri, 5 Dec 2014 15:01:51 +0100 Subject: [PATCH 0054/2059] Added the Model.buildValidation event to tje tables This covers most of the cases for which beforeValidate was being used. --- Table.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Table.php b/Table.php index d92ab0bc..eb1086e5 100644 --- a/Table.php +++ b/Table.php @@ -83,7 +83,10 @@ * $primary parameter indicates whether or not this is the root query, * or an associated query. * - * - `buildRules(Event $event, RulesChecker $rules)` + * - `buildValidator(Event $event, Validator $validator, string $name)` + * Allows listeners to modify validation rules for the provided named validator. + * + * - `buildRules(Event $event, RulesChecker $rules)` * Allows listeners to modify the rules checker by adding more rules. * * - `beforeRules(Event $event, Entity $entity, RulesChecker $rules)` @@ -1064,6 +1067,7 @@ public function validator($name = 'default', Validator $validator = null) { if ($validator === null) { $validator = new Validator(); $validator = $this->{'validation' . ucfirst($name)}($validator); + $this->dispatchEvent('Model.buildValidator', compact('validator', 'name')); } $validator->provider('table', $this); From 555ce208a128c232284590425dfabfa769fb608e Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Fri, 5 Dec 2014 15:58:25 +0100 Subject: [PATCH 0055/2059] Added the buildValidation event to behaviors --- Behavior.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Behavior.php b/Behavior.php index b5abbb4b..34639ff8 100644 --- a/Behavior.php +++ b/Behavior.php @@ -53,8 +53,13 @@ * $primary parameter indicates whether or not this is the root query, * or an associated query. * + * - `buildValidator(Event $event, Validator $validator, string $name)` + * Fired when the validator object identified by $name is being built. Yiu can use this + * callback to add validation rules or add validation providers. + * * - `buildRules(Event $event, RulesChecker $rules)` - * Allows listeners to modify the rules checker by adding more rules. + * Fired when the rules checking object for the table is being built. You can use this + * callback to add more rules to the set. * * - `beforeRules(Event $event, Entity $entity, RulesChecker $rules)` * Fired before an entity is validated using the rules checker. By stopping this event, @@ -243,6 +248,7 @@ public function implementedEvents() { 'Model.afterSave' => 'afterSave', 'Model.beforeDelete' => 'beforeDelete', 'Model.afterDelete' => 'afterDelete', + 'Model.buildValidator' => 'buildValidator', 'Model.buildRules' => 'buildRules', 'Model.beforeRules' => 'beforeRules', 'Model.afterRules' => 'afterRules', From 66b39fda3e802ca5bc8cffd4997b098643d28607 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 5 Dec 2014 23:01:27 -0500 Subject: [PATCH 0056/2059] Update API docs. --- Behavior.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Behavior.php b/Behavior.php index 57f77ad9..3acc9302 100644 --- a/Behavior.php +++ b/Behavior.php @@ -54,7 +54,7 @@ * or an associated query. * * - `buildValidator(Event $event, Validator $validator, string $name)` - * Fired when the validator object identified by $name is being built. Yiu can use this + * Fired when the validator object identified by $name is being built. You can use this * callback to add validation rules or add validation providers. * * - `buildRules(Event $event, RulesChecker $rules)` @@ -62,11 +62,11 @@ * callback to add more rules to the set. * * - `beforeRules(Event $event, Entity $entity, RulesChecker $rules)` - * Fired before an entity is validated using the rules checker. By stopping this event, + * Fired before an entity is validated using by a rules checker. By stopping this event, * you can return the final value of the rules checking operation. * - * - `afterRules(Event $event, Entity $entity,RulesChecker $rules, bool $result)` - * Fired after the rules have been checked on the entity.By stopping this event, + * - `afterRules(Event $event, Entity $entity, RulesChecker $rules, bool $result)` + * Fired after the rules have been checked on the entity. By stopping this event, * you can return the final value of the rules checking operation. * * - `beforeSave(Event $event, Entity $entity, ArrayObject $options)` From d50dcbcb01f9bb11963509e994364de5659f6967 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 5 Dec 2014 23:10:13 -0500 Subject: [PATCH 0057/2059] Catch invalid validator types. --- Marshaller.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 6ed3f0d7..ac9503f7 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -21,6 +21,7 @@ use Cake\ORM\Association; use Cake\ORM\AssociationsNormalizerTrait; use Cake\ORM\Table; +use \RuntimeException; /** * Contains logic to convert array data into entities. @@ -158,19 +159,23 @@ public function one(array $data, array $options = []) { * @param array $options The options passed to this marshaller. * @param bool $isNew Whether it is a new entity or one to be updated. * @return array The list of validation errors. + * @throws \RuntimeException If no validator can be created. */ protected function _validate($data, $options, $isNew) { if (!$options['validate']) { return []; } - if ($options['validate'] === true) { $options['validate'] = $this->_table->validator('default'); } - if (is_string($options['validate'])) { $options['validate'] = $this->_table->validator($options['validate']); } + if (!is_object($options['validate'])) { + throw new RuntimeException( + sprintf('validate must be a boolean, a string or an object. Got %s.', gettype($options['validate'])) + ); + } return $options['validate']->errors($data, $isNew); } @@ -297,6 +302,8 @@ protected function _loadBelongsToMany($assoc, $ids) { * ### Options: * * * associated: Associations listed here will be marshalled as well. + * * validate: Whether or not to validate data before hydrating the entities. Can + * also be set to a string to use a specific validator. Defaults to true/default. * * fieldList: A whitelist of fields to be assigned to the entity. If not present * the accessible fields list in the entity will be used. * From 1a1e78b9159b12a18d61c8987744ce2d7c46e78d Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 5 Dec 2014 23:18:34 -0500 Subject: [PATCH 0058/2059] Expand API docs for RulesChecker. Add a bit of meat to the API docs. --- RulesChecker.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/RulesChecker.php b/RulesChecker.php index 56d215b9..79b42984 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -21,6 +21,22 @@ /** * Contains logic for storing and checking rules on entities * + * RulesCheckers are used by Table classes to ensure that the + * current entity state satifies the application logic and business rules. + * + * RulesCheckers afford different rules to be applied in the create and update + * scenario. + * + * ### Adding rules + * + * Rules must be callable objects that return true/false depending on whether or + * not the rule has been satisified. You can use RulesChecker::add(), RulesChecker::addCreate() + * and RulesChecker::addUpdate() to add rules to a checker. + * + * ### Running checks + * + * Generally a Table object will invoke the rules objects, but you can manually + * invoke the checks by calling RulesChecker::checkCreate() or RulesChecker::checkUpdate(). */ class RulesChecker { From 51b5735b0d34235c82b20b3275427e07eb2fb48e Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 5 Dec 2014 23:23:23 -0500 Subject: [PATCH 0059/2059] More doc block tweaks. --- Table.php | 59 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/Table.php b/Table.php index d500a5cc..f1b2cfec 100644 --- a/Table.php +++ b/Table.php @@ -93,7 +93,7 @@ * you can return the final value of the rules checking operation. * * - `afterRules(Event $event, Entity $entity,RulesChecker $rules, bool $result)` - * Fired after the rules have been checked on the entity.By stopping this event, + * Fired after the rules have been checked on the entity. By stopping this event, * you can return the final value of the rules checking operation. * * - `beforeSave(Event $event, Entity $entity, ArrayObject $options)` @@ -1145,48 +1145,49 @@ public function exists($conditions) { * The options array can receive the following keys: * * - atomic: Whether to execute the save and callbacks inside a database - * transaction (default: true) + * transaction (default: true) * - checkRules: Whether or not to check the rules on entity before saving, if the checking - * fails, it will abort the save operation. (default:true) + * fails, it will abort the save operation. (default:true) * - associated: If true it will save all associated entities as they are found - * in the passed `$entity` whenever the property defined for the association - * is marked as dirty. Associated records are saved recursively unless told - * otherwise. If an array, it will be interpreted as the list of associations - * to be saved. It is possible to provide different options for saving on associated - * table objects using this key by making the custom options the array value. - * If false no associated records will be saved. (default: true) + * in the passed `$entity` whenever the property defined for the association + * is marked as dirty. Associated records are saved recursively unless told + * otherwise. If an array, it will be interpreted as the list of associations + * to be saved. It is possible to provide different options for saving on associated + * table objects using this key by making the custom options the array value. + * If false no associated records will be saved. (default: true) * * ### Events * * When saving, this method will trigger four events: * * - Model.beforeRules: Will be triggered right before any rule checking is done - * for the passed entity if the `checkRules` key in $options is not set to false. - * Listeners will receive as arguments the entity and the - * RulesChecker object to be used for validating the entity. If the event is - * stopped the checking result will be set to the result of the event itself. + * for the passed entity if the `checkRules` key in $options is not set to false. + * Listeners will receive as arguments the entity and the + * RulesChecker object to be used for validating the entity. If the event is + * stopped the checking result will be set to the result of the event itself. * - Model.afterRules: Will be triggered right after the `checkRules()` method is - * called for the entity. Listeners will receive as arguments the entity, the - * RulesChecker object that was used and the result of checking the rules. - * If the event is stopped the checking result will be set to the result of - * the event itself. + * called for the entity. Listeners will receive as arguments the entity, the + * RulesChecker object that was used and the result of checking the rules. + * If the event is stopped the checking result will be set to the result of + * the event itself. * - Model.beforeSave: Will be triggered just before the list of fields to be - * persisted is calculated. It receives both the entity and the options as - * arguments. The options array is passed as an ArrayObject, so any changes in - * it will be reflected in every listener and remembered at the end of the event - * so it can be used for the rest of the save operation. Returning false in any - * of the listeners will abort the saving process. If the event is stopped - * using the event API, the event object's `result` property will be returned. - * This can be useful when having your own saving strategy implemented inside a - * listener. + * persisted is calculated. It receives both the entity and the options as + * arguments. The options array is passed as an ArrayObject, so any changes in + * it will be reflected in every listener and remembered at the end of the event + * so it can be used for the rest of the save operation. Returning false in any + * of the listeners will abort the saving process. If the event is stopped + * using the event API, the event object's `result` property will be returned. + * This can be useful when having your own saving strategy implemented inside a + * listener. * - Model.afterSave: Will be triggered after a successful insert or save, - * listeners will receive the entity and the options array as arguments. The type - * of operation performed (insert or update) can be determined by checking the - * entity's method `isNew`, true meaning an insert and false an update. + * listeners will receive the entity and the options array as arguments. The type + * of operation performed (insert or update) can be determined by checking the + * entity's method `isNew`, true meaning an insert and false an update. * * This method will determine whether the passed entity needs to be * inserted or updated in the database. It does that by checking the `isNew` - * method on the entity. + * method on the entity. If the entity to be saved returns a non-empty value from + * its `errors()` method, it will not be saved. * * ### Saving on associated tables * From 8dfaf704e08a3d6d48a72f9c0caf0885b4126076 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 5 Dec 2014 23:31:21 -0500 Subject: [PATCH 0060/2059] Correct more docs and add an assertion. --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index f1b2cfec..87c0f62c 100644 --- a/Table.php +++ b/Table.php @@ -1929,7 +1929,7 @@ public function rulesChecker() { } /** - * Returns rules chaker object after modifying the one that was passed. Subclasses + * Returns rules checker object after modifying the one that was passed. Subclasses * can override this method in order to initialize the rules to be applied to * entities saved by this table. * From ed5cf40ed6b79b18cb58adf3c4df47870159663a Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sat, 6 Dec 2014 16:11:32 +0100 Subject: [PATCH 0061/2059] Fixing isUnique and extistsIn rules when updating entities --- Rule/ExistsIn.php | 4 ++++ Rule/IsUnique.php | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index cbe2dd11..5e7fa25c 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -61,6 +61,10 @@ public function __invoke(EntityInterface $entity, array $options) { $this->_repository = $options['repository']->association($this->_repository); } + if (!$entity->extract($this->_fields, true)) { + return true; + } + $conditions = array_combine( (array)$this->_repository->primaryKey(), $entity->extract($this->_fields) diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index 1fc0add8..2b814143 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -46,6 +46,10 @@ public function __construct(array $fields) { * @return bool */ public function __invoke(EntityInterface $entity, array $options) { + if (!$entity->extract($this->_fields, true)) { + return true; + } + $conditions = $entity->extract($this->_fields); if ($entity->isNew() === false) { $keys = (array)$options['repository']->primaryKey(); From 20e961ac3d3b737ddbd3fe5faf9fa5dbdaeb19b4 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 7 Dec 2014 00:41:51 +0100 Subject: [PATCH 0062/2059] Adding support to the RulesChecker to check entities before deleting them --- RulesChecker.php | 100 +++++++++++++++++++++++++++++++++++++++++++++-- Table.php | 42 +++++++++++++------- 2 files changed, 124 insertions(+), 18 deletions(-) diff --git a/RulesChecker.php b/RulesChecker.php index 79b42984..760f73fd 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -17,6 +17,7 @@ use Cake\Datasource\EntityInterface; use Cake\ORM\Rule\ExistsIn; use Cake\ORM\Rule\IsUnique; +use InvalidArgumentException; /** * Contains logic for storing and checking rules on entities @@ -30,18 +31,40 @@ * ### Adding rules * * Rules must be callable objects that return true/false depending on whether or - * not the rule has been satisified. You can use RulesChecker::add(), RulesChecker::addCreate() - * and RulesChecker::addUpdate() to add rules to a checker. + * not the rule has been satisified. You can use RulesChecker::add(), RulesChecker::addCreate(), + * RulesChecker::addUpdate() and RulesChecker::addDelete to add rules to a checker. * * ### Running checks * * Generally a Table object will invoke the rules objects, but you can manually - * invoke the checks by calling RulesChecker::checkCreate() or RulesChecker::checkUpdate(). + * invoke the checks by calling RulesChecker::checkCreate(), RulesChecker::checkUpdate() or + * RulesChecker::checkDelete(). */ class RulesChecker { /** - * The list of rules to be checked on every case + * Indicates that the checking rules to apply are those used for creating entities + * + * @var string + */ + const CREATE = 'create'; + +/** + * Indicates that the checking rules to apply are those used for updating entities + * + * @var string + */ + const UPDATE = 'update'; + +/** + * Indicates that the checking rules to apply are those used for deleting entities + * + * @var string + */ + const DELETE = 'delete'; + +/** + * The list of rules to be checked on both create and update operations * * @var array */ @@ -61,6 +84,13 @@ class RulesChecker { */ protected $_updateRules = []; +/** + * The list of rules to check during delete operations + * + * @var array + */ + protected $_deleteRules = []; + /** * List of options to pass to every callable rule * @@ -144,6 +174,53 @@ public function addUpdate(callable $rule, array $options = []) { return $this; } +/** + * Adds a rule that will be applied to the entity on delete operations. + * + * ### Options + * + * The options array accept the following special keys: + * + * - `errorField`: The name of the entity field that will be marked as invalid + * if the rule does not pass. + * - `message`: The error message to set to `errorField` if the rule does not pass. + * + * @param callable $rule A callable function or object that will return whether + * the entity is valid or not. + * @param array $options List of extra options to pass to the rule callable as + * second argument. + * @return $this + */ + public function addDelete(callable $rule, array $options = []) { + $this->_deleteRules[] = $this->_addError($rule, $options); + return $this; + } + +/** + * Runs each of the rules by passing the provided entity and returns true if all + * of them pass. The rules to be applied are depended on the $mode parameter which + * can only be RulesChecker::CREATE, RulesChecker::UPDATE or RulesChecker::DELETE + * + * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. + * @return bool + * @throws \InvalidArgumentException if an invalid mode is passed. + */ + public function check(EntityInterface $entity, $mode) { + if ($mode === self::CREATE) { + return $this->checkCreate($entity); + } + + if ($mode === self::UPDATE) { + return $this->checkUpdate($entity); + } + + if ($mode === self::DELETE) { + return $this->checkDelete($entity); + } + + throw new InvalidArgumentException('Wrong checking mode: ' . $mode); + } + /** * Runs each of the rules by passing the provided entity and returns true if all * of them pass. The rules selected will be only those specified to be run on 'create' @@ -174,6 +251,21 @@ public function checkUpdate(EntityInterface $entity) { return $success; } +/** + * Runs each of the rules by passing the provided entity and returns true if all + * of them pass. The rules selected will be only those specified to be run on 'delete' + * + * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. + * @return bool + */ + public function checkDelete(EntityInterface $entity) { + $success = true; + foreach ($this->_deleteRules as $rule) { + $success = $rule($entity, $this->_options) && $success; + } + return $success; + } + /** * Returns a callable that can be used as a rule for checking the uniqueness of a value * in the table. diff --git a/Table.php b/Table.php index 87c0f62c..e1c56487 100644 --- a/Table.php +++ b/Table.php @@ -1263,7 +1263,8 @@ protected function _processSave($entity, $options) { $entity->isNew(!$this->exists($conditions)); } - if ($options['checkRules'] && !$this->checkRules($entity)) { + $mode = $entity->isNew() ? RulesChecker::CREATE : RulesChecker::UPDATE; + if ($options['checkRules'] && !$this->checkRules($entity, $mode)) { return false; } @@ -1448,6 +1449,7 @@ protected function _update($entity, $data) { * ### Options * * - `atomic` Defaults to true. When true the deletion happens within a transaction. + * - `checkRules` Defaults to true. Check deletion rules before deleting the record. * * ### Events * @@ -1462,7 +1464,7 @@ protected function _update($entity, $data) { * */ public function delete(EntityInterface $entity, $options = []) { - $options = new \ArrayObject($options + ['atomic' => true]); + $options = new \ArrayObject($options + ['atomic' => true, 'checkRules' => true]); $process = function () use ($entity, $options) { return $this->_processDelete($entity, $options); @@ -1487,14 +1489,6 @@ public function delete(EntityInterface $entity, $options = []) { * @return bool success */ protected function _processDelete($entity, $options) { - $event = $this->dispatchEvent('Model.beforeDelete', [ - 'entity' => $entity, - 'options' => $options - ]); - if ($event->isStopped()) { - return $event->result; - } - if ($entity->isNew()) { return false; } @@ -1504,6 +1498,20 @@ protected function _processDelete($entity, $options) { $msg = 'Deleting requires all primary key values.'; throw new \InvalidArgumentException($msg); } + + if ($options['checkRules'] && !$this->checkRules($entity, RulesChecker::DELETE)) { + return false; + } + + $event = $this->dispatchEvent('Model.beforeDelete', [ + 'entity' => $entity, + 'options' => $options + ]); + + if ($event->isStopped()) { + return $event->result; + } + $this->_associations->cascadeDelete($entity, $options->getArrayCopy()); $query = $this->query(); @@ -1894,16 +1902,22 @@ public function validateUnique($value, array $options, array $context = []) { * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. * @return bool */ - public function checkRules(EntityInterface $entity) { + public function checkRules(EntityInterface $entity, $operation = RulesChecker::CREATE) { $rules = $this->rulesChecker(); - $event = $this->dispatchEvent('Model.beforeRules', compact('entity', 'rules')); + $event = $this->dispatchEvent( + 'Model.beforeRules', + compact('entity', 'rules', 'operation') + ); if ($event->isStopped()) { return $event->result; } - $result = $entity->isNew() ? $rules->checkCreate($entity) : $rules->checkUpdate($entity); - $event = $this->dispatchEvent('Model.afterRules', compact('entity', 'rules', 'result')); + $result = $rules->check($entity, $operation); + $event = $this->dispatchEvent( + 'Model.afterRules', + compact('entity', 'rules', 'result', 'operation') + ); if ($event->isStopped()) { return $event->result; From e380f737e49b0926c04cd75ed092952d65a12b2b Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Mon, 8 Dec 2014 23:41:57 +0100 Subject: [PATCH 0063/2059] Hadling ResultSet::first() trhough collection functions In the past we had to handle the first() function differently so we could close the statement. Doe to improvemets in how the statement is closed in te query and the connection, this is no loger required. --- ResultSet.php | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/ResultSet.php b/ResultSet.php index bdb84ff4..490f7889 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -247,33 +247,6 @@ public function unserialize($serialized) { $this->_results = unserialize($serialized); } -/** - * Returns the first result in this set and blocks the set so that no other - * results can be fetched. - * - * When using serialized results, the index will be incremented past the - * end of the results simulating the behavior when the result set is backed - * by a statement. - * - * @return array|object|null - */ - public function first() { - if (isset($this->_results[0])) { - return $this->_results[0]; - } - - if ($this->valid()) { - if ($this->_statement) { - $this->_statement->closeCursor(); - } - if (!$this->_statement && $this->_results) { - $this->_index = count($this->_results); - } - return $this->_current; - } - return null; - } - /** * Gives the number of rows in the result set. * From db0f93d2e3799a3daf847db48d737f39b790085e Mon Sep 17 00:00:00 2001 From: euromark Date: Wed, 10 Dec 2014 15:28:28 +0100 Subject: [PATCH 0064/2059] Make atomic updateAll and deleteAll return the affected rows instead of bool. --- Table.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Table.php b/Table.php index ffed264c..c14874a8 100644 --- a/Table.php +++ b/Table.php @@ -1024,9 +1024,8 @@ public function updateAll($fields, $conditions) { ->set($fields) ->where($conditions); $statement = $query->execute(); - $success = $statement->rowCount() > 0; $statement->closeCursor(); - return $success; + return $statement->rowCount(); } /** @@ -1104,9 +1103,8 @@ public function deleteAll($conditions) { ->delete() ->where($conditions); $statement = $query->execute(); - $success = $statement->rowCount() > 0; $statement->closeCursor(); - return $success; + return $statement->rowCount(); } /** From fd61e72274c9be661d5072efe72ebaf0004f973c Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Tue, 9 Dec 2014 23:22:11 +0100 Subject: [PATCH 0065/2059] Dirty hacking as a proof of concept of separating matching and contain --- EagerLoader.php | 27 ++++++++++++++++++++------- ResultSet.php | 4 +++- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index 8390dc97..7bcea150 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -77,6 +77,8 @@ class EagerLoader { */ protected $_aliasList = []; + public $_matching = null; + /** * Sets the list of associations that should be eagerly loaded along for a * specific table using when a query is provided. The list of associated tables @@ -127,6 +129,10 @@ public function contain($associations = []) { * @return array The resulting containments array */ public function matching($assoc, callable $builder = null) { + if ($this->_matching === null) { + $this->_matching = new self(); + } + $assocs = explode('.', $assoc); $last = array_pop($assocs); $containments = []; @@ -138,7 +144,7 @@ public function matching($assoc, callable $builder = null) { } $pointer[$last] = ['queryBuilder' => $builder, 'matching' => true]; - return $this->contain($containments); + return $this->_matching->contain($containments); } /** @@ -242,7 +248,7 @@ protected function _reformatContain($associations, $original) { * @return void */ public function attachAssociations(Query $query, Table $repository, $includeFields) { - if (empty($this->_containments)) { + if (empty($this->_containments) && $this->_matching === null) { return; } @@ -270,7 +276,8 @@ public function attachAssociations(Query $query, Table $repository, $includeFiel */ public function attachableAssociations(Table $repository) { $contain = $this->normalized($repository); - return $this->_resolveJoins($contain); + $matching = $this->_matching ? $this->_matching->normalized($repository) : []; + return $this->_resolveJoins($contain, $matching); } /** @@ -290,7 +297,7 @@ public function externalAssociations(Table $repository) { } $contain = $this->normalized($repository); - $this->_resolveJoins($contain); + $this->_resolveJoins($contain, []); return $this->_loadExternal; } @@ -409,13 +416,19 @@ protected function _correctStrategy(&$config, $alias) { * @param array $associations list of associations for $source * @return array */ - protected function _resolveJoins($associations) { + protected function _resolveJoins($associations, $matching = []) { $result = []; + foreach ($matching as $table => $options) { + $result[$table] = $options; + $result += $this->_resolveJoins($options['associations'], []); + } foreach ($associations as $table => $options) { - if ($options['canBeJoined']) { + $inMatching = isset($matching[$table]); + if (!$inMatching && $options['canBeJoined']) { $result[$table] = $options; - $result += $this->_resolveJoins($options['associations']); + $result += $this->_resolveJoins($options['associations'], $inMatching ? $mathching[$table] : []); } else { + $options['canBeJoined'] = false; $this->_loadExternal[] = $options; } } diff --git a/ResultSet.php b/ResultSet.php index bdb84ff4..879275ab 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -299,8 +299,9 @@ public function count() { */ protected function _calculateAssociationMap() { $contain = $this->_query->eagerLoader()->normalized($this->_defaultTable); + $contain = $contain ?: []; if (!$contain) { - return; + //return; } $map = []; @@ -319,6 +320,7 @@ protected function _calculateAssociationMap() { } }; $visitor($contain, []); + $this->_query->eagerLoader()->_matching ? $visitor($this->_query->eagerLoader()->_matching->normalized($this->_defaultTable), []) : []; $this->_associationMap = $map; } From 629a8920a87063cb630cad79c386fac5b3acc3cd Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Thu, 11 Dec 2014 18:11:47 +0100 Subject: [PATCH 0066/2059] Fixing a failing tests and marking another as icomplete --- Association.php | 14 ++++++++++---- EagerLoader.php | 6 +++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/Association.php b/Association.php index b410fe8f..42df423f 100644 --- a/Association.php +++ b/Association.php @@ -665,19 +665,25 @@ protected function _formatAssociationResults($query, $surrogate, $options) { */ protected function _bindNewAssociations($query, $surrogate, $options) { $contain = $surrogate->contain(); + $matching = $surrogate->eagerLoader()->matching(); $target = $this->_targetTable; - if (!$contain) { + if (!$contain && !$matching) { return; } $loader = $surrogate->eagerLoader(); $loader->attachAssociations($query, $target, $options['includeFields']); - $newBinds = []; + $newContain = []; foreach ($contain as $alias => $value) { - $newBinds[$options['aliasPath'] . '.' . $alias] = $value; + $newContain[$options['aliasPath'] . '.' . $alias] = $value; + } + + $query->contain($newContain); + + foreach ($matching as $alias => $value) { + $query->matching($options['aliasPath'] . '.' . $alias, $value['queryBuilder']); } - $query->contain($newBinds); } /** diff --git a/EagerLoader.php b/EagerLoader.php index 7bcea150..ea6e3b9c 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -128,11 +128,15 @@ public function contain($associations = []) { * options to the filtering query * @return array The resulting containments array */ - public function matching($assoc, callable $builder = null) { + public function matching($assoc = null, callable $builder = null) { if ($this->_matching === null) { $this->_matching = new self(); } + if ($assoc === null) { + return $this->_matching->contain(); + } + $assocs = explode('.', $assoc); $last = array_pop($assocs); $containments = []; From 503a8b7c149141bbae5f049bf8e488dc23633cfa Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Thu, 11 Dec 2014 18:16:46 +0100 Subject: [PATCH 0067/2059] Fixing __debugInfo for the query --- Association.php | 6 +++--- Query.php | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Association.php b/Association.php index 42df423f..e4e938f0 100644 --- a/Association.php +++ b/Association.php @@ -664,15 +664,15 @@ protected function _formatAssociationResults($query, $surrogate, $options) { * @return void */ protected function _bindNewAssociations($query, $surrogate, $options) { - $contain = $surrogate->contain(); - $matching = $surrogate->eagerLoader()->matching(); + $loader = $surrogate->eagerLoader(); + $contain = $loader->contain(); + $matching = $loader->matching(); $target = $this->_targetTable; if (!$contain && !$matching) { return; } - $loader = $surrogate->eagerLoader(); $loader->attachAssociations($query, $target, $options['includeFields']); $newContain = []; foreach ($contain as $alias => $value) { diff --git a/Query.php b/Query.php index 4f37659f..5fe7f72d 100644 --- a/Query.php +++ b/Query.php @@ -796,12 +796,14 @@ public function __call($method, $arguments) { * {@inheritDoc} */ public function __debugInfo() { + $eagerLoader = $this->eagerLoader(); return parent::__debugInfo() + [ 'hydrate' => $this->_hydrate, 'buffered' => $this->_useBufferedResults, 'formatters' => count($this->_formatters), 'mapReducers' => count($this->_mapReduce), - 'contain' => $this->contain(), + 'contain' => $eagerLoader->contain(), + 'matching' => $eagerLoader->matching(), 'extraOptions' => $this->_options, 'repository' => $this->_repository ]; From 23a8bab6d6c589ec14b48b647e29472ef83df5fb Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Fri, 12 Dec 2014 00:42:27 +0100 Subject: [PATCH 0068/2059] Moving code from ResultSet to EagerLoader as it needed to access too much internal info --- EagerLoader.php | 40 ++++++++++++++++++++++++++++++++++++++++ ResultSet.php | 25 +------------------------ 2 files changed, 41 insertions(+), 24 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index ea6e3b9c..acd2a0a4 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -479,6 +479,46 @@ public function loadExternal($query, $statement) { return $statement; } +/** + * Returns an array having as keys a dotted path of associations that participate + * in this eager loader. The values of the array will contain the following keys + * + * - alias: The association alias + * - instance: The association instance + * - canBeJoined: Whether or not the association will be loaded using a JOIN + * - entityClass: The entity that should eb used for hydrating the results + * - nestKey: A dottet path that can be used to inserting the data in the correct nesting. + * + * @param \Cake\ORM\Table $repository The table containing the association that + * will be normalized + * @return array + */ + public function associationsMap($table) { + $map = []; + + if (!$this->matching() && !$this->contain()) { + return $map; + } + + $visitor = function ($level) use (&$visitor, &$map) { + foreach ($level as $assoc => $meta) { + $map[$meta['aliasPath']] = [ + 'alias' => $assoc, + 'instance' => $meta['instance'], + 'canBeJoined' => $meta['canBeJoined'], + 'entityClass' => $meta['instance']->target()->entityClass(), + 'nestKey' => $meta['canBeJoined'] ? $assoc : $meta['aliasPath'] + ]; + if ($meta['canBeJoined'] && !empty($meta['associations'])) { + $visitor($meta['associations']); + } + } + }; + $visitor($this->normalized($table), []); + $visitor($this->_matching->normalized($table), []); + return $map; + } + /** * Helper function used to return the keys from the query records that will be used * to eagerly load associations. diff --git a/ResultSet.php b/ResultSet.php index 879275ab..e7e185c4 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -298,30 +298,7 @@ public function count() { * @return void */ protected function _calculateAssociationMap() { - $contain = $this->_query->eagerLoader()->normalized($this->_defaultTable); - $contain = $contain ?: []; - if (!$contain) { - //return; - } - - $map = []; - $visitor = function ($level) use (&$visitor, &$map) { - foreach ($level as $assoc => $meta) { - $map[$meta['aliasPath']] = [ - 'alias' => $assoc, - 'instance' => $meta['instance'], - 'canBeJoined' => $meta['canBeJoined'], - 'entityClass' => $meta['instance']->target()->entityClass(), - 'nestKey' => $meta['canBeJoined'] ? $assoc : $meta['aliasPath'] - ]; - if ($meta['canBeJoined'] && !empty($meta['associations'])) { - $visitor($meta['associations']); - } - } - }; - $visitor($contain, []); - $this->_query->eagerLoader()->_matching ? $visitor($this->_query->eagerLoader()->_matching->normalized($this->_defaultTable), []) : []; - $this->_associationMap = $map; + return $this->_associationMap = $this->_query->eagerLoader()->associationsMap($this->_defaultTable); } /** From 085e25282caeaf5d3fc73dddbcff97167e0496aa Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Fri, 12 Dec 2014 00:46:26 +0100 Subject: [PATCH 0069/2059] Fixing doc blocks and setting property back to protected --- EagerLoader.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index acd2a0a4..3ca3487d 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -77,7 +77,12 @@ class EagerLoader { */ protected $_aliasList = []; - public $_matching = null; +/** + * Another EagerLoader instance that will be used for 'matching' associations. + * + * @var \Cake\ORM\EagerLoader + */ + protected $_matching; /** * Sets the list of associations that should be eagerly loaded along for a @@ -486,8 +491,8 @@ public function loadExternal($query, $statement) { * - alias: The association alias * - instance: The association instance * - canBeJoined: Whether or not the association will be loaded using a JOIN - * - entityClass: The entity that should eb used for hydrating the results - * - nestKey: A dottet path that can be used to inserting the data in the correct nesting. + * - entityClass: The entity that should be used for hydrating the results + * - nestKey: A dotted path that can be used to inserting the data in the correct nesting. * * @param \Cake\ORM\Table $repository The table containing the association that * will be normalized From c78f0b6de15543231d39aea82c917987f0ca01d1 Mon Sep 17 00:00:00 2001 From: euromark Date: Fri, 12 Dec 2014 04:03:43 +0100 Subject: [PATCH 0070/2059] More return fixes. --- EagerLoader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EagerLoader.php b/EagerLoader.php index 8390dc97..d184a743 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -378,7 +378,7 @@ protected function _fixStrategies() { * * @param array &$config The association config * @param string $alias the name of the association to evaluate - * @return void + * @return void|array * @throws \RuntimeException if a duplicate association in the same chain is detected * but is not possible to change the strategy due to conflicting settings */ From 96463db864e441dcaadb17978040cff2e4aead95 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sat, 13 Dec 2014 12:28:13 +0100 Subject: [PATCH 0071/2059] Managed to not use matching() for loading belognsToMany This will help with separating the results from matching and everything else --- Association/BelongsToMany.php | 14 +++++++++++--- EagerLoader.php | 25 +++++++++++++++++++------ ResultSet.php | 1 + 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 7fad0e18..1b2ad4b1 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -868,7 +868,9 @@ protected function _collectJointEntities($sourceEntity, $targetEntities) { /** * Auxiliary function to construct a new Query object to return all the records * in the target table that are associated to those specified in $options from - * the source table + * the source table. + * + * This is used for eager loading records on the target table based on conditions. * * @param array $options options accepted by eagerLoader() * @return \Cake\ORM\Query @@ -888,8 +890,14 @@ protected function _buildQuery($options) { ] ]; - $joins = $matching + $joins; - $query->join($joins, [], true)->matching($name); + $assoc = $this->target()->association($name); + $query + ->join($matching + $joins, [], true) + ->autoFields(empty($query->clause('select'))) + ->select($query->aliasFields((array)$assoc->foreignKey(), $name)); + + $query->eagerLoader()->addToJoinsMap($name, $assoc); + $assoc->attachTo($query); return $query; } diff --git a/EagerLoader.php b/EagerLoader.php index 3ca3487d..33c865be 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -84,6 +84,8 @@ class EagerLoader { */ protected $_matching; + protected $_joinsMap = []; + /** * Sets the list of associations that should be eagerly loaded along for a * specific table using when a query is provided. The list of associated tables @@ -501,29 +503,40 @@ public function loadExternal($query, $statement) { public function associationsMap($table) { $map = []; - if (!$this->matching() && !$this->contain()) { + if (!$this->matching() && !$this->contain() && empty($this->_joinsMap)) { return $map; } - $visitor = function ($level) use (&$visitor, &$map) { + $visitor = function ($level, $matching = false) use (&$visitor, &$map) { foreach ($level as $assoc => $meta) { $map[$meta['aliasPath']] = [ 'alias' => $assoc, 'instance' => $meta['instance'], 'canBeJoined' => $meta['canBeJoined'], 'entityClass' => $meta['instance']->target()->entityClass(), - 'nestKey' => $meta['canBeJoined'] ? $assoc : $meta['aliasPath'] + 'nestKey' => $meta['canBeJoined'] ? $assoc : $meta['aliasPath'], + 'matching' => $matching ]; if ($meta['canBeJoined'] && !empty($meta['associations'])) { - $visitor($meta['associations']); + $visitor($meta['associations'], $matching); } } }; - $visitor($this->normalized($table), []); - $visitor($this->_matching->normalized($table), []); + $visitor($this->_matching->normalized($table), true); + $visitor($this->normalized($table)); + $visitor($this->_joinsMap); return $map; } + public function addToJoinsMap($alias, $assoc) { + $this->_joinsMap[$alias] = [ + 'aliasPath' => $alias, + 'instance' => $assoc, + 'canBeJoined' => true, + 'associations' => [] + ]; + } + /** * Helper function used to return the keys from the query records that will be used * to eagerly load associations. diff --git a/ResultSet.php b/ResultSet.php index e7e185c4..8274f2cf 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -401,6 +401,7 @@ protected function _groupResult($row) { $entity->clean(); $results[$alias] = $entity; } + $results = $instance->transformRow($results, $alias, $assoc['canBeJoined']); } From 3b221d8213aa0f4034864e77fc631b000daae578 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sat, 13 Dec 2014 22:42:21 +0100 Subject: [PATCH 0072/2059] Placing data generated by matching into a _matchingData key for each row. As the data completely breaks how entities are naturally nested, it makes more sense to have it in its own place. This also permits using matching and contain in the same query --- Association/BelongsToMany.php | 6 +++--- ResultSet.php | 10 ++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 1b2ad4b1..e8e8a7e0 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -233,9 +233,9 @@ public function attachTo(Query $query, array $options = []) { unset($options['queryBuilder']); $options = ['conditions' => [$cond]] + compact('includeFields'); $options['foreignKey'] = $this->targetForeignKey(); - $this->_targetTable - ->association($junction->alias()) - ->attachTo($query, $options); + $assoc = $this->_targetTable->association($junction->alias()); + $assoc->attachTo($query, $options); + $query->eagerLoader()->addToJoinsMap($junction->alias(), $assoc); } /** diff --git a/ResultSet.php b/ResultSet.php index 8274f2cf..3e574211 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -402,6 +402,12 @@ protected function _groupResult($row) { $results[$alias] = $entity; } + if ($assoc['matching']) { + $results['_matchingData'][$alias] = $results[$alias]; + unset($results[$alias]); + continue; + } + $results = $instance->transformRow($results, $alias, $assoc['canBeJoined']); } @@ -412,6 +418,10 @@ protected function _groupResult($row) { $results[$defaultAlias][$alias] = $results[$alias]; } + if (isset($results['_matchingData'])) { + $results[$defaultAlias]['_matchingData'] = $results['_matchingData']; + } + $options['source'] = $defaultAlias; $results = $results[$defaultAlias]; if ($this->_hydrate && !($results instanceof Entity)) { From 0992cbff1427e56b5972612610d25ce9c01aa70a Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sat, 13 Dec 2014 23:26:51 +0100 Subject: [PATCH 0073/2059] Removing exception that prevented matching and contain in the same association This type of conflic is not possible anymore --- EagerLoader.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index 33c865be..3e1b9c22 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -409,13 +409,6 @@ protected function _correctStrategy(&$config, $alias) { return $config; } - if (!empty($config['config']['matching'])) { - throw new \RuntimeException(sprintf( - 'Cannot use "matching" on "%s" as there is another association with the same alias', - $alias - )); - } - $config['canBeJoined'] = false; $config['config']['strategy'] = $config['instance']::STRATEGY_SELECT; } From 242cbefb9bd2e1f0855c1bad3a1c94ccef78bd4e Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sat, 13 Dec 2014 23:32:42 +0100 Subject: [PATCH 0074/2059] Adding some doc blocks --- EagerLoader.php | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/EagerLoader.php b/EagerLoader.php index 3e1b9c22..67d267cc 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -16,6 +16,7 @@ use Cake\Database\Statement\BufferedStatement; use Cake\Database\Statement\CallbackStatement; +use Cake\ORM\Association; use Cake\ORM\Query; use Cake\ORM\Table; use Closure; @@ -84,6 +85,12 @@ class EagerLoader { */ protected $_matching; +/** + * A map of table aliases pointing to the association objects they represent + * for the query. + * + * @var array + */ protected $_joinsMap = []; /** @@ -521,7 +528,17 @@ public function associationsMap($table) { return $map; } - public function addToJoinsMap($alias, $assoc) { +/** + * Registers a table alias, typically loaded as a join in a query, as belonging to + * an association. This helps hydrators know what to do with the columns coming + * from such joined table. + * + * @param string $alias The table alias as it appears in the query. + * @param \Cake\ORM\Association $assoc The association object the alias represents. + * will be normalized + * @return void + */ + public function addToJoinsMap($alias, Association $assoc) { $this->_joinsMap[$alias] = [ 'aliasPath' => $alias, 'instance' => $assoc, From 0dfdcce7bfc582c6eac71d239319d8f99cf655c1 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sat, 13 Dec 2014 23:41:09 +0100 Subject: [PATCH 0075/2059] Fixing code for PHP 5.4 --- Association/BelongsToMany.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index e8e8a7e0..072c0bcd 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -893,7 +893,7 @@ protected function _buildQuery($options) { $assoc = $this->target()->association($name); $query ->join($matching + $joins, [], true) - ->autoFields(empty($query->clause('select'))) + ->autoFields($query->clause('select') === []) ->select($query->aliasFields((array)$assoc->foreignKey(), $name)); $query->eagerLoader()->addToJoinsMap($name, $assoc); From 7d7d03dafc3d6aad0d2d453dee342fe8b4312509 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sat, 13 Dec 2014 23:43:18 +0100 Subject: [PATCH 0076/2059] Fixing doc blocks --- EagerLoader.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index 67d267cc..6cc4f584 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -424,7 +424,8 @@ protected function _correctStrategy(&$config, $alias) { * Helper function used to compile a list of all associations that can be * joined in the query. * - * @param array $associations list of associations for $source + * @param array $associations list of associations from which to obtain joins + * @param array $matching list of associations that should be forcedly joined * @return array */ protected function _resolveJoins($associations, $matching = []) { @@ -496,7 +497,7 @@ public function loadExternal($query, $statement) { * - entityClass: The entity that should be used for hydrating the results * - nestKey: A dotted path that can be used to inserting the data in the correct nesting. * - * @param \Cake\ORM\Table $repository The table containing the association that + * @param \Cake\ORM\Table $table The table containing the association that * will be normalized * @return array */ From 8e1fe73fa01cb903976ea8243bac6fdc65e60dd6 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 13 Dec 2014 22:22:16 -0500 Subject: [PATCH 0077/2059] Documentation fixups for matching improvements. --- EagerLoader.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index 6cc4f584..8e615cbe 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -424,8 +424,8 @@ protected function _correctStrategy(&$config, $alias) { * Helper function used to compile a list of all associations that can be * joined in the query. * - * @param array $associations list of associations from which to obtain joins - * @param array $matching list of associations that should be forcedly joined + * @param array $associations list of associations from which to obtain joins. + * @param array $matching list of associations that should be forcibly joined. * @return array */ protected function _resolveJoins($associations, $matching = []) { @@ -495,7 +495,7 @@ public function loadExternal($query, $statement) { * - instance: The association instance * - canBeJoined: Whether or not the association will be loaded using a JOIN * - entityClass: The entity that should be used for hydrating the results - * - nestKey: A dotted path that can be used to inserting the data in the correct nesting. + * - nestKey: A dotted path that can be used to correctly insert the data into the results. * * @param \Cake\ORM\Table $table The table containing the association that * will be normalized @@ -535,7 +535,7 @@ public function associationsMap($table) { * from such joined table. * * @param string $alias The table alias as it appears in the query. - * @param \Cake\ORM\Association $assoc The association object the alias represents. + * @param \Cake\ORM\Association $assoc The association object the alias represents; * will be normalized * @return void */ From fb4740c35ca5c427742848d2998ee6a00345db84 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Mon, 15 Dec 2014 23:11:53 +0100 Subject: [PATCH 0078/2059] A first approach to separating hydration of associations from the main loop This will make the associations hydration more robust --- EagerLoader.php | 2 +- ResultSet.php | 49 ++++++++++++++++++++++++++++++++++--------------- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index 6cc4f584..599e036b 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -510,7 +510,7 @@ public function associationsMap($table) { $visitor = function ($level, $matching = false) use (&$visitor, &$map) { foreach ($level as $assoc => $meta) { - $map[$meta['aliasPath']] = [ + $map[] = [ 'alias' => $assoc, 'instance' => $meta['instance'], 'canBeJoined' => $meta['canBeJoined'], diff --git a/ResultSet.php b/ResultSet.php index 3e574211..a50f5bec 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -328,11 +328,43 @@ protected function _fetchResult() { protected function _groupResult($row) { $defaultAlias = $this->_defaultTable->alias(); $results = $presentAliases = []; + $options = [ + 'useSetters' => false, + 'markClean' => true, + 'markNew' => false, + 'guard' => false + ]; + + foreach (collection($this->_associationMap)->match(['matching' => true]) as $matching) { + foreach ($row as $key => $value) { + if (strpos($key, $matching['alias'] . '__') !== 0) { + continue; + } + list($table, $field) = explode('__', $key); + $results['_matchingData'][$table][$field] = $value; + unset($row[$key]); + } + if (empty($results['_matchingData'][$matching['alias']])) { + continue; + } + + $results['_matchingData'][$matching['alias']] = $this->_castValues( + $matching['instance']->target(), + $results['_matchingData'][$matching['alias']] + ); + + if ($this->_hydrate) { + $entity = new $matching['entityClass']($results['_matchingData'][$matching['alias']], $options); + $entity->clean(); + $results['_matchingData'][$matching['alias']] = $entity; + } + } + foreach ($row as $key => $value) { $table = $defaultAlias; $field = $key; - if (isset($this->_associationMap[$key])) { + if (!is_scalar($value)) { $results[$key] = $value; continue; } @@ -360,14 +392,7 @@ protected function _groupResult($row) { } unset($presentAliases[$defaultAlias]); - $options = [ - 'useSetters' => false, - 'markClean' => true, - 'markNew' => false, - 'guard' => false - ]; - - foreach (array_reverse($this->_associationMap) as $assoc) { + foreach (collection(array_reverse($this->_associationMap))->match(['matching' => false]) as $assoc) { $alias = $assoc['nestKey']; $instance = $assoc['instance']; @@ -402,12 +427,6 @@ protected function _groupResult($row) { $results[$alias] = $entity; } - if ($assoc['matching']) { - $results['_matchingData'][$alias] = $results[$alias]; - unset($results[$alias]); - continue; - } - $results = $instance->transformRow($results, $alias, $assoc['canBeJoined']); } From 5879d22e0f673d47fc8e9adf93810c2a768bdfe4 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Mon, 15 Dec 2014 23:24:40 +0100 Subject: [PATCH 0079/2059] Restructuring how the junction table results are returned for matching, there is no good reason for nesting them into another alias. --- Association/BelongsToMany.php | 2 +- EagerLoader.php | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 072c0bcd..de07ca1f 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -235,7 +235,7 @@ public function attachTo(Query $query, array $options = []) { $options['foreignKey'] = $this->targetForeignKey(); $assoc = $this->_targetTable->association($junction->alias()); $assoc->attachTo($query, $options); - $query->eagerLoader()->addToJoinsMap($junction->alias(), $assoc); + $query->eagerLoader()->addToJoinsMap($junction->alias(), $assoc, true); } /** diff --git a/EagerLoader.php b/EagerLoader.php index 599e036b..6edb758c 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -516,7 +516,7 @@ public function associationsMap($table) { 'canBeJoined' => $meta['canBeJoined'], 'entityClass' => $meta['instance']->target()->entityClass(), 'nestKey' => $meta['canBeJoined'] ? $assoc : $meta['aliasPath'], - 'matching' => $matching + 'matching' => isset($meta['matching']) ? $meta['matching'] : $matching ]; if ($meta['canBeJoined'] && !empty($meta['associations'])) { $visitor($meta['associations'], $matching); @@ -537,13 +537,16 @@ public function associationsMap($table) { * @param string $alias The table alias as it appears in the query. * @param \Cake\ORM\Association $assoc The association object the alias represents. * will be normalized + * @param bool $asMatching Whether or not this join results should be treated as a + * 'matching' association. * @return void */ - public function addToJoinsMap($alias, Association $assoc) { + public function addToJoinsMap($alias, Association $assoc, $asMatching = false) { $this->_joinsMap[$alias] = [ 'aliasPath' => $alias, 'instance' => $assoc, 'canBeJoined' => true, + 'matching' => $asMatching, 'associations' => [] ]; } From ebdf5a57f9c0ecd12d05f13a2eddc8fd8515201a Mon Sep 17 00:00:00 2001 From: AD7six Date: Mon, 15 Dec 2014 09:43:33 +0000 Subject: [PATCH 0080/2059] Always throw a 404 Account for the way that (array)null results in [] and not [null] --- Table.php | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Table.php b/Table.php index c14874a8..172586a9 100644 --- a/Table.php +++ b/Table.php @@ -19,6 +19,7 @@ use Cake\Database\Schema\Table as Schema; use Cake\Database\Type; use Cake\Datasource\EntityInterface; +use Cake\Datasource\Exception\RecordNotFoundException; use Cake\Datasource\RepositoryInterface; use Cake\Event\EventListenerInterface; use Cake\Event\EventManager; @@ -944,7 +945,8 @@ protected function _setFieldMatchers($options, $keys) { /** * {@inheritDoc} * - * @throws \InvalidArgumentException When $primaryKey has an incorrect number of elements. + * @throws Cake\Datasource\Exception\RecordNotFoundException When $primaryKey has an + * incorrect number of elements. */ public function get($primaryKey, $options = []) { $key = (array)$this->primaryKey(); @@ -954,10 +956,15 @@ public function get($primaryKey, $options = []) { } $primaryKey = (array)$primaryKey; if (count($key) !== count($primaryKey)) { - throw new \InvalidArgumentException(sprintf( - "Incorrect number of primary key values. Expected %d got %d.", - count($key), - count($primaryKey) + $primaryKey = $primaryKey ?: [null]; + $primaryKey = array_map(function($key) { + return var_export($key, true); + }, $primaryKey); + + throw new RecordNotFoundException(sprintf( + 'Invalid primary key, record not found in table "%s" with primary key [%s]', + $this->table(), + implode($primaryKey, ', ') )); } $conditions = array_combine($key, $primaryKey); From da094a3434b6c0d9550c3c6df017ce2836d5ffeb Mon Sep 17 00:00:00 2001 From: AD7six Date: Mon, 15 Dec 2014 09:57:36 +0000 Subject: [PATCH 0081/2059] phpcs fix --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 172586a9..68ddb579 100644 --- a/Table.php +++ b/Table.php @@ -957,7 +957,7 @@ public function get($primaryKey, $options = []) { $primaryKey = (array)$primaryKey; if (count($key) !== count($primaryKey)) { $primaryKey = $primaryKey ?: [null]; - $primaryKey = array_map(function($key) { + $primaryKey = array_map(function ($key) { return var_export($key, true); }, $primaryKey); From 623509f8375a3bd535cacb422cf6e002aea3a68a Mon Sep 17 00:00:00 2001 From: AD7six Date: Tue, 16 Dec 2014 13:11:57 +0000 Subject: [PATCH 0082/2059] Add InvalidPrimaryKeyException Used with findOrFail when passed an invalid pk --- Table.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Table.php b/Table.php index 68ddb579..da481c60 100644 --- a/Table.php +++ b/Table.php @@ -19,7 +19,7 @@ use Cake\Database\Schema\Table as Schema; use Cake\Database\Type; use Cake\Datasource\EntityInterface; -use Cake\Datasource\Exception\RecordNotFoundException; +use Cake\Datasource\Exception\InvalidPrimaryKeyException; use Cake\Datasource\RepositoryInterface; use Cake\Event\EventListenerInterface; use Cake\Event\EventManager; @@ -945,7 +945,7 @@ protected function _setFieldMatchers($options, $keys) { /** * {@inheritDoc} * - * @throws Cake\Datasource\Exception\RecordNotFoundException When $primaryKey has an + * @throws Cake\Datasource\Exception\InvalidPrimaryKeyException When $primaryKey has an * incorrect number of elements. */ public function get($primaryKey, $options = []) { @@ -961,8 +961,8 @@ public function get($primaryKey, $options = []) { return var_export($key, true); }, $primaryKey); - throw new RecordNotFoundException(sprintf( - 'Invalid primary key, record not found in table "%s" with primary key [%s]', + throw new InvalidPrimaryKeyException(sprintf( + 'Record not found in table "%s" with primary key [%s]', $this->table(), implode($primaryKey, ', ') )); From c2ac6b03d3e8c686e697e8123eabb3e1cc89fa8e Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Wed, 17 Dec 2014 19:01:13 +0100 Subject: [PATCH 0083/2059] Cleaning up some code in ResultSet --- ResultSet.php | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/ResultSet.php b/ResultSet.php index a50f5bec..b2aef2a2 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -14,6 +14,7 @@ */ namespace Cake\ORM; +use Cake\Collection\Collection; use Cake\Collection\CollectionTrait; use Cake\Database\Exception; use Cake\Database\Type; @@ -68,11 +69,20 @@ class ResultSet implements ResultSetInterface { protected $_defaultTable; /** - * List of associations that should be eager loaded + * List of associations that should be placed under the `_matchingData` + * result key. * * @var array */ - protected $_associationMap = []; + protected $_matchingMap = []; + +/** + * List of associations that should be eager loaded. + * + * @var array + */ + protected $_containMap = []; + /** * Map of fields that are fetched from the statement with @@ -298,7 +308,14 @@ public function count() { * @return void */ protected function _calculateAssociationMap() { - return $this->_associationMap = $this->_query->eagerLoader()->associationsMap($this->_defaultTable); + $map = $this->_query->eagerLoader()->associationsMap($this->_defaultTable); + $this->_matchingMap = (new Collection($map)) + ->match(['matching' => true]) + ->compile(); + + $this->_containMap = (new Collection(array_reverse($map))) + ->match(['matching' => false]) + ->compile(); } /** @@ -335,7 +352,7 @@ protected function _groupResult($row) { 'guard' => false ]; - foreach (collection($this->_associationMap)->match(['matching' => true]) as $matching) { + foreach ($this->_matchingMap as $matching) { foreach ($row as $key => $value) { if (strpos($key, $matching['alias'] . '__') !== 0) { continue; @@ -364,7 +381,7 @@ protected function _groupResult($row) { $table = $defaultAlias; $field = $key; - if (!is_scalar($value)) { + if ($value !== null && !is_scalar($value)) { $results[$key] = $value; continue; } @@ -392,7 +409,7 @@ protected function _groupResult($row) { } unset($presentAliases[$defaultAlias]); - foreach (collection(array_reverse($this->_associationMap))->match(['matching' => false]) as $assoc) { + foreach ($this->_containMap as $assoc) { $alias = $assoc['nestKey']; $instance = $assoc['instance']; From c682764d96669283c7c5d12548750cda118d6a22 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Thu, 18 Dec 2014 12:38:07 +0100 Subject: [PATCH 0084/2059] Improving doc blocks --- EagerLoader.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/EagerLoader.php b/EagerLoader.php index 7a8250a5..84cdbc28 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -137,7 +137,10 @@ public function contain($associations = []) { * parameter, this will translate in setting all those associations with the * `matching` option. * - * @param string $assoc A single association or a dot separated path of associations. + * If called with no arguments it will return the current tree of associations to + * be matched. + * + * @param string $assoc|null A single association or a dot separated path of associations. * @param callable|null $builder the callback function to be used for setting extra * options to the filtering query * @return array The resulting containments array @@ -496,6 +499,7 @@ public function loadExternal($query, $statement) { * - canBeJoined: Whether or not the association will be loaded using a JOIN * - entityClass: The entity that should be used for hydrating the results * - nestKey: A dotted path that can be used to correctly insert the data into the results. + * - mathcing: Whether or not it is an association loaded through `matching()`. * * @param \Cake\ORM\Table $table The table containing the association that * will be normalized From 4120f8b41b5982aef1fa5bf9d3491f7ad412de10 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Thu, 18 Dec 2014 13:03:33 +0100 Subject: [PATCH 0085/2059] Fixed CS errors --- EagerLoader.php | 2 +- ResultSet.php | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index 84cdbc28..6d7c4b32 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -140,7 +140,7 @@ public function contain($associations = []) { * If called with no arguments it will return the current tree of associations to * be matched. * - * @param string $assoc|null A single association or a dot separated path of associations. + * @param string|null $assoc A single association or a dot separated path of associations. * @param callable|null $builder the callback function to be used for setting extra * options to the filtering query * @return array The resulting containments array diff --git a/ResultSet.php b/ResultSet.php index b2aef2a2..7e378898 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -83,7 +83,6 @@ class ResultSet implements ResultSetInterface { */ protected $_containMap = []; - /** * Map of fields that are fetched from the statement with * their type and the table they belong to From a1804100ec6cd441f0c709b27c14f568079bb97f Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Thu, 18 Dec 2014 17:41:26 +0100 Subject: [PATCH 0086/2059] Fixing doc blocks --- RulesChecker.php | 1 + Table.php | 1 + 2 files changed, 2 insertions(+) diff --git a/RulesChecker.php b/RulesChecker.php index 760f73fd..6f2cbe47 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -203,6 +203,7 @@ public function addDelete(callable $rule, array $options = []) { * * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. * @return bool + * @param string $mode Either 'create, 'update' or 'delete'. * @throws \InvalidArgumentException if an invalid mode is passed. */ public function check(EntityInterface $entity, $mode) { diff --git a/Table.php b/Table.php index 31f48a69..71a6c026 100644 --- a/Table.php +++ b/Table.php @@ -1905,6 +1905,7 @@ public function validateUnique($value, array $options, array $context = []) { * the rules checker. * * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. + * @param string $operation Either 'create, 'update' or 'delete'. * @return bool */ public function checkRules(EntityInterface $entity, $operation = RulesChecker::CREATE) { From fdc6fd3baaa01d8f36c6310fcdcdec4f7a03cf8e Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Thu, 18 Dec 2014 17:57:33 +0100 Subject: [PATCH 0087/2059] Fixing CS errors --- RulesChecker.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RulesChecker.php b/RulesChecker.php index 6f2cbe47..4d7ddccc 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -202,8 +202,8 @@ public function addDelete(callable $rule, array $options = []) { * can only be RulesChecker::CREATE, RulesChecker::UPDATE or RulesChecker::DELETE * * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. - * @return bool * @param string $mode Either 'create, 'update' or 'delete'. + * @return bool * @throws \InvalidArgumentException if an invalid mode is passed. */ public function check(EntityInterface $entity, $mode) { From 3f6f6633fd03aae47d743c823205a74183393c78 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 21 Dec 2014 18:45:02 +0100 Subject: [PATCH 0088/2059] Moving the EntityInterface::validate() method to another interface Since we have validation hapening in the marshaller instead, moving this type of validation as an opt-in feature seems to be the best for now --- EntityValidator.php | 8 +++++++- EntityValidatorTrait.php | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 EntityValidatorTrait.php diff --git a/EntityValidator.php b/EntityValidator.php index d2a6f7df..2d4368b1 100644 --- a/EntityValidator.php +++ b/EntityValidator.php @@ -18,6 +18,7 @@ use Cake\Datasource\EntityInterface; use Cake\ORM\Association; use Cake\ORM\Table; +use Cake\Validation\ValidatableInterface; /** * Contains logic for validating entities and their associations. @@ -118,6 +119,11 @@ public function one(EntityInterface $entity, $options = []) { if (!isset($options['validate'])) { $options['validate'] = true; } + + if (!($entity instanceof ValidatableInterface)) { + return $valid; + } + return $this->_processValidation($entity, $options) && $valid; } @@ -168,7 +174,7 @@ protected function _processValidation($entity, $options) { return true; } - $success = $entity->validate($validator); + $success = empty($entity->validate($validator)); $event = $this->_table->dispatchEvent('Model.afterValidate', $pass); if ($event->isStopped()) { diff --git a/EntityValidatorTrait.php b/EntityValidatorTrait.php new file mode 100644 index 00000000..bf8afec3 --- /dev/null +++ b/EntityValidatorTrait.php @@ -0,0 +1,40 @@ +_properties; + $new = $this->isNew(); + $validator->provider('entity', $this); + $this->errors($validator->errors($data, $new === null ? true : $new)); + return $this->_errors; + } + +} From 8799b85a092191a8884b8bf470933ffb20709108 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 21 Dec 2014 19:21:44 +0100 Subject: [PATCH 0089/2059] Fixing fatal error in PHP 5.4 --- EntityValidator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EntityValidator.php b/EntityValidator.php index 2d4368b1..f12edf21 100644 --- a/EntityValidator.php +++ b/EntityValidator.php @@ -174,7 +174,7 @@ protected function _processValidation($entity, $options) { return true; } - $success = empty($entity->validate($validator)); + $success = !$entity->validate($validator); $event = $this->_table->dispatchEvent('Model.afterValidate', $pass); if ($event->isStopped()) { From c732a3b71568d5a48d34b21a0724b0b28a907f8c Mon Sep 17 00:00:00 2001 From: Berry Goudswaard Date: Mon, 22 Dec 2014 17:00:58 +0100 Subject: [PATCH 0090/2059] Implement TableRegistry::delete() --- TableRegistry.php | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/TableRegistry.php b/TableRegistry.php index 168eb6f5..37e83ab0 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -242,4 +242,29 @@ public static function genericInstances() { return static::$_fallbacked; } +/** + * Deletes an instance from the registry. + * + * Plugin name will be trimmed off of aliases as instances + * stored in the registry will be without the plugin name as well. + * + * @param string $alias The alias to delete. + * @return void + */ + public static function delete($alias) { + list(, $alias) = pluginSplit($alias); + + if (isset(static::$_instances[$alias])) { + unset(static::$_instances[$alias]); + } + + if (isset(static::$_config[$alias])) { + unset(static::$_config[$alias]); + } + + if (isset(static::$_fallbacked[$alias])) { + unset(static::$_fallbacked[$alias]); + } + } + } From 8caac6959530f0cfb098bd2d4fdb91f0dff44d78 Mon Sep 17 00:00:00 2001 From: Berry Goudswaard Date: Mon, 22 Dec 2014 17:27:04 +0100 Subject: [PATCH 0091/2059] Change delete() to remove() --- TableRegistry.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/TableRegistry.php b/TableRegistry.php index 37e83ab0..b1f09ce7 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -243,15 +243,15 @@ public static function genericInstances() { } /** - * Deletes an instance from the registry. + * Removes an instance from the registry. * * Plugin name will be trimmed off of aliases as instances * stored in the registry will be without the plugin name as well. * - * @param string $alias The alias to delete. + * @param string $alias The alias to remove. * @return void */ - public static function delete($alias) { + public static function remove($alias) { list(, $alias) = pluginSplit($alias); if (isset(static::$_instances[$alias])) { From c65322effef66e371f093af8a4f1e3e7a19a3d3b Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Tue, 23 Dec 2014 19:14:13 +0100 Subject: [PATCH 0092/2059] Adding a fix for #5463 --- ResultSet.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/ResultSet.php b/ResultSet.php index e937e410..82f31a70 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -283,11 +283,12 @@ protected function _calculateAssociationMap() { $map = $this->_query->eagerLoader()->associationsMap($this->_defaultTable); $this->_matchingMap = (new Collection($map)) ->match(['matching' => true]) - ->compile(); + ->toArray(); $this->_containMap = (new Collection(array_reverse($map))) ->match(['matching' => false]) - ->compile(); + ->indexBy('nestKey') + ->toArray(); } /** @@ -331,7 +332,10 @@ protected function _groupResult($row) { } list($table, $field) = explode('__', $key); $results['_matchingData'][$table][$field] = $value; - unset($row[$key]); + + if (!isset($this->_containMap[$table])) { + unset($row[$key]); + } } if (empty($results['_matchingData'][$matching['alias']])) { continue; @@ -343,6 +347,7 @@ protected function _groupResult($row) { ); if ($this->_hydrate) { + $options['source'] = $matching['alias']; $entity = new $matching['entityClass']($results['_matchingData'][$matching['alias']], $options); $entity->clean(); $results['_matchingData'][$matching['alias']] = $entity; From 337d20f33ac659a418892ce9b37e3ff3be7b678f Mon Sep 17 00:00:00 2001 From: Berry Goudswaard Date: Wed, 24 Dec 2014 22:51:05 +0100 Subject: [PATCH 0093/2059] Remove isset statements because they have no use --- TableRegistry.php | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/TableRegistry.php b/TableRegistry.php index b1f09ce7..b3262db3 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -254,17 +254,9 @@ public static function genericInstances() { public static function remove($alias) { list(, $alias) = pluginSplit($alias); - if (isset(static::$_instances[$alias])) { - unset(static::$_instances[$alias]); - } - - if (isset(static::$_config[$alias])) { - unset(static::$_config[$alias]); - } - - if (isset(static::$_fallbacked[$alias])) { - unset(static::$_fallbacked[$alias]); - } + unset(static::$_instances[$alias]); + unset(static::$_config[$alias]); + unset(static::$_fallbacked[$alias]); } } From 5476a479f907e3f3d92e8aaec8474349593f4e42 Mon Sep 17 00:00:00 2001 From: Berry Goudswaard Date: Wed, 24 Dec 2014 23:27:16 +0100 Subject: [PATCH 0094/2059] Combine unsets --- TableRegistry.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/TableRegistry.php b/TableRegistry.php index b3262db3..978b1ce5 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -254,9 +254,11 @@ public static function genericInstances() { public static function remove($alias) { list(, $alias) = pluginSplit($alias); - unset(static::$_instances[$alias]); - unset(static::$_config[$alias]); - unset(static::$_fallbacked[$alias]); + unset( + static::$_instances[$alias], + static::$_config[$alias], + static::$_fallbacked[$alias] + ); } } From ba83c056d0fbd0f4394593cd180210d3e741332b Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Fri, 26 Dec 2014 17:50:40 +0100 Subject: [PATCH 0095/2059] Passing save options to the rules checker. This will help passing some extra data or disabling temporarily some rules --- RulesChecker.php | 27 +++++++++++++++++---------- Table.php | 19 ++++++++++++------- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/RulesChecker.php b/RulesChecker.php index 4d7ddccc..1bf21e08 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -203,20 +203,21 @@ public function addDelete(callable $rule, array $options = []) { * * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. * @param string $mode Either 'create, 'update' or 'delete'. + * @param array $options Extra options to pass to checker functions. * @return bool * @throws \InvalidArgumentException if an invalid mode is passed. */ - public function check(EntityInterface $entity, $mode) { + public function check(EntityInterface $entity, $mode, array $options = []) { if ($mode === self::CREATE) { - return $this->checkCreate($entity); + return $this->checkCreate($entity, $options); } if ($mode === self::UPDATE) { - return $this->checkUpdate($entity); + return $this->checkUpdate($entity, $options); } if ($mode === self::DELETE) { - return $this->checkDelete($entity); + return $this->checkDelete($entity, $options); } throw new InvalidArgumentException('Wrong checking mode: ' . $mode); @@ -227,12 +228,14 @@ public function check(EntityInterface $entity, $mode) { * of them pass. The rules selected will be only those specified to be run on 'create' * * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. + * @param array $options Extra options to pass to checker functions. * @return bool */ - public function checkCreate(EntityInterface $entity) { + public function checkCreate(EntityInterface $entity, array $options = []) { $success = true; + $options = $options + $this->_options; foreach (array_merge($this->_rules, $this->_createRules) as $rule) { - $success = $rule($entity, $this->_options) && $success; + $success = $rule($entity, $options) && $success; } return $success; } @@ -242,12 +245,14 @@ public function checkCreate(EntityInterface $entity) { * of them pass. The rules selected will be only those specified to be run on 'update' * * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. + * @param array $options Extra options to pass to checker functions. * @return bool */ - public function checkUpdate(EntityInterface $entity) { + public function checkUpdate(EntityInterface $entity, array $options = []) { $success = true; + $options = $options + $this->_options; foreach (array_merge($this->_rules, $this->_updateRules) as $rule) { - $success = $rule($entity, $this->_options) && $success; + $success = $rule($entity, $options) && $success; } return $success; } @@ -257,12 +262,14 @@ public function checkUpdate(EntityInterface $entity) { * of them pass. The rules selected will be only those specified to be run on 'delete' * * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. + * @param array $options Extra options to pass to checker functions. * @return bool */ - public function checkDelete(EntityInterface $entity) { + public function checkDelete(EntityInterface $entity, array $options = []) { $success = true; + $options = $options + $this->_options; foreach ($this->_deleteRules as $rule) { - $success = $rule($entity, $this->_options) && $success; + $success = $rule($entity, $options) && $success; } return $success; } diff --git a/Table.php b/Table.php index 71a6c026..69964b91 100644 --- a/Table.php +++ b/Table.php @@ -14,6 +14,7 @@ */ namespace Cake\ORM; +use ArrayObject; use BadMethodCallException; use Cake\Core\App; use Cake\Database\Schema\Table as Schema; @@ -1222,7 +1223,7 @@ public function exists($conditions) { * */ public function save(EntityInterface $entity, $options = []) { - $options = new \ArrayObject($options + [ + $options = new ArrayObject($options + [ 'atomic' => true, 'associated' => true, 'checkRules' => true @@ -1269,7 +1270,7 @@ protected function _processSave($entity, $options) { } $mode = $entity->isNew() ? RulesChecker::CREATE : RulesChecker::UPDATE; - if ($options['checkRules'] && !$this->checkRules($entity, $mode)) { + if ($options['checkRules'] && !$this->checkRules($entity, $mode, $options)) { return false; } @@ -1469,7 +1470,7 @@ protected function _update($entity, $data) { * */ public function delete(EntityInterface $entity, $options = []) { - $options = new \ArrayObject($options + ['atomic' => true, 'checkRules' => true]); + $options = new ArrayObject($options + ['atomic' => true, 'checkRules' => true]); $process = function () use ($entity, $options) { return $this->_processDelete($entity, $options); @@ -1905,24 +1906,28 @@ public function validateUnique($value, array $options, array $context = []) { * the rules checker. * * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. + * @param \ArrayObject|array $options The options To be passed to the rules. * @param string $operation Either 'create, 'update' or 'delete'. * @return bool */ - public function checkRules(EntityInterface $entity, $operation = RulesChecker::CREATE) { + public function checkRules(EntityInterface $entity, $operation = RulesChecker::CREATE, $options = null) { $rules = $this->rulesChecker(); + $options = $options ?: new ArrayObject; + $options = is_array($options) ? new ArrayObject($options) : $options; + $event = $this->dispatchEvent( 'Model.beforeRules', - compact('entity', 'rules', 'operation') + compact('entity', 'options', 'operation', 'rules') ); if ($event->isStopped()) { return $event->result; } - $result = $rules->check($entity, $operation); + $result = $rules->check($entity, $operation, $options->getArrayCopy()); $event = $this->dispatchEvent( 'Model.afterRules', - compact('entity', 'rules', 'result', 'operation') + compact('entity', 'options', 'result', 'operation', 'rules') ); if ($event->isStopped()) { From 62c7baeb053b7ea1c442497b09d4b434d4a959d5 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Fri, 26 Dec 2014 19:16:52 +0100 Subject: [PATCH 0096/2059] Updating doc blocks --- Behavior.php | 4 ++-- Table.php | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Behavior.php b/Behavior.php index 3acc9302..84fc3c35 100644 --- a/Behavior.php +++ b/Behavior.php @@ -61,11 +61,11 @@ * Fired when the rules checking object for the table is being built. You can use this * callback to add more rules to the set. * - * - `beforeRules(Event $event, Entity $entity, RulesChecker $rules)` + * - `beforeRules(Event $event, Entity $entity, ArrayObject $options, $operation, RulesChecker $rules)` * Fired before an entity is validated using by a rules checker. By stopping this event, * you can return the final value of the rules checking operation. * - * - `afterRules(Event $event, Entity $entity, RulesChecker $rules, bool $result)` + * - `afterRules(Event $event, Entity $entity, ArrayObject $options, bool $result, $operation, RulesChecker $rules,)` * Fired after the rules have been checked on the entity. By stopping this event, * you can return the final value of the rules checking operation. * diff --git a/Table.php b/Table.php index 69964b91..6b9d20b8 100644 --- a/Table.php +++ b/Table.php @@ -1168,12 +1168,13 @@ public function exists($conditions) { * * - Model.beforeRules: Will be triggered right before any rule checking is done * for the passed entity if the `checkRules` key in $options is not set to false. - * Listeners will receive as arguments the entity and the - * RulesChecker object to be used for validating the entity. If the event is + * Listeners will receive as arguments the entity, options array, the operation type + * and the RulesChecker object to be used for validating the entity. If the event is * stopped the checking result will be set to the result of the event itself. * - Model.afterRules: Will be triggered right after the `checkRules()` method is - * called for the entity. Listeners will receive as arguments the entity, the - * RulesChecker object that was used and the result of checking the rules. + * called for the entity. Listeners will receive as arguments the entity, + * options array, the operation type, the result of checking the rules and the + * RulesChecker object that was used. * If the event is stopped the checking result will be set to the result of * the event itself. * - Model.beforeSave: Will be triggered just before the list of fields to be From 1022b3d4d67ef67eaef68aaea281f9aeb7ab0c66 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Fri, 26 Dec 2014 19:30:41 +0100 Subject: [PATCH 0097/2059] Passing custom options from delete to the rules checker Adding more tests --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 6b9d20b8..7007d444 100644 --- a/Table.php +++ b/Table.php @@ -1506,7 +1506,7 @@ protected function _processDelete($entity, $options) { throw new \InvalidArgumentException($msg); } - if ($options['checkRules'] && !$this->checkRules($entity, RulesChecker::DELETE)) { + if ($options['checkRules'] && !$this->checkRules($entity, RulesChecker::DELETE, $options)) { return false; } From a06b57009b7a97258dad862310c39f7575f079a3 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Fri, 26 Dec 2014 20:39:36 +0100 Subject: [PATCH 0098/2059] Prevented unneeded existsIn check for automatically saved hasMany --- Association/HasMany.php | 1 + Rule/ExistsIn.php | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/Association/HasMany.php b/Association/HasMany.php index f08daf3f..46be4435 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -90,6 +90,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) { ); $target = $this->target(); $original = $targetEntities; + $options['_sourceTable'] = $this->source(); foreach ($targetEntities as $k => $targetEntity) { if (!($targetEntity instanceof EntityInterface)) { diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 5e7fa25c..ef1fcc1f 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -15,6 +15,7 @@ namespace Cake\ORM\Rule; use Cake\Datasource\EntityInterface; +use Cake\ORM\Association; /** * Checks that the value provided in a field exists as the primary key of another @@ -61,6 +62,15 @@ public function __invoke(EntityInterface $entity, array $options) { $this->_repository = $options['repository']->association($this->_repository); } + if (!empty($options['_sourceTable'])) { + $source = $this->_repository instanceof Association ? + $this->_repository->target() : + $this->_repository; + if ($source === $options['_sourceTable']) { + return true; + } + } + if (!$entity->extract($this->_fields, true)) { return true; } From e4620c73e44f35634aa5f6bc81b39dbd3b377c3b Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 26 Dec 2014 23:19:58 -0500 Subject: [PATCH 0099/2059] Update doc block and fix phpcs error. --- Table.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Table.php b/Table.php index 7007d444..35d15993 100644 --- a/Table.php +++ b/Table.php @@ -1907,6 +1907,7 @@ public function validateUnique($value, array $options, array $context = []) { * the rules checker. * * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. + * @param string $operation The operation being run. Either 'create', 'update' or 'delete'. * @param \ArrayObject|array $options The options To be passed to the rules. * @param string $operation Either 'create, 'update' or 'delete'. * @return bool From 626c073034429b9a89b3b9dcacb8761616e7c341 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 26 Dec 2014 23:30:14 -0500 Subject: [PATCH 0100/2059] Remove duplicate parameter doc string. --- Table.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Table.php b/Table.php index 35d15993..4ceed5d5 100644 --- a/Table.php +++ b/Table.php @@ -1909,7 +1909,6 @@ public function validateUnique($value, array $options, array $context = []) { * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. * @param string $operation The operation being run. Either 'create', 'update' or 'delete'. * @param \ArrayObject|array $options The options To be passed to the rules. - * @param string $operation Either 'create, 'update' or 'delete'. * @return bool */ public function checkRules(EntityInterface $entity, $operation = RulesChecker::CREATE, $options = null) { From fe622d128f11dfa3f515a329a531ae39cbdf02ee Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sat, 27 Dec 2014 20:47:39 +0100 Subject: [PATCH 0101/2059] Removing the rules checker object as the last param of the events --- Behavior.php | 4 ++-- Table.php | 14 ++++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/Behavior.php b/Behavior.php index 84fc3c35..44112ef4 100644 --- a/Behavior.php +++ b/Behavior.php @@ -61,11 +61,11 @@ * Fired when the rules checking object for the table is being built. You can use this * callback to add more rules to the set. * - * - `beforeRules(Event $event, Entity $entity, ArrayObject $options, $operation, RulesChecker $rules)` + * - `beforeRules(Event $event, Entity $entity, ArrayObject $options, $operation)` * Fired before an entity is validated using by a rules checker. By stopping this event, * you can return the final value of the rules checking operation. * - * - `afterRules(Event $event, Entity $entity, ArrayObject $options, bool $result, $operation, RulesChecker $rules,)` + * - `afterRules(Event $event, Entity $entity, ArrayObject $options, bool $result, $operation)` * Fired after the rules have been checked on the entity. By stopping this event, * you can return the final value of the rules checking operation. * diff --git a/Table.php b/Table.php index 4ceed5d5..41263be0 100644 --- a/Table.php +++ b/Table.php @@ -90,7 +90,7 @@ * - `buildRules(Event $event, RulesChecker $rules)` * Allows listeners to modify the rules checker by adding more rules. * - * - `beforeRules(Event $event, Entity $entity, RulesChecker $rules)` + * - `beforeRules(Event $event, Entity $entity, ArrayObject $options, string $operation)` * Fired before an entity is validated using the rules checker. By stopping this event, * you can return the final value of the rules checking operation. * @@ -1168,13 +1168,11 @@ public function exists($conditions) { * * - Model.beforeRules: Will be triggered right before any rule checking is done * for the passed entity if the `checkRules` key in $options is not set to false. - * Listeners will receive as arguments the entity, options array, the operation type - * and the RulesChecker object to be used for validating the entity. If the event is - * stopped the checking result will be set to the result of the event itself. + * Listeners will receive as arguments the entity, options array and the operation type. + * If the event is stopped the checking result will be set to the result of the event itself. * - Model.afterRules: Will be triggered right after the `checkRules()` method is * called for the entity. Listeners will receive as arguments the entity, - * options array, the operation type, the result of checking the rules and the - * RulesChecker object that was used. + * options array, the result of checking the rules and the operation type. * If the event is stopped the checking result will be set to the result of * the event itself. * - Model.beforeSave: Will be triggered just before the list of fields to be @@ -1918,7 +1916,7 @@ public function checkRules(EntityInterface $entity, $operation = RulesChecker::C $event = $this->dispatchEvent( 'Model.beforeRules', - compact('entity', 'options', 'operation', 'rules') + compact('entity', 'options', 'operation') ); if ($event->isStopped()) { @@ -1928,7 +1926,7 @@ public function checkRules(EntityInterface $entity, $operation = RulesChecker::C $result = $rules->check($entity, $operation, $options->getArrayCopy()); $event = $this->dispatchEvent( 'Model.afterRules', - compact('entity', 'options', 'result', 'operation', 'rules') + compact('entity', 'options', 'result', 'operation') ); if ($event->isStopped()) { From b59ed24f9a11228ac29b0763835227a79302870f Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Mon, 29 Dec 2014 23:27:22 +0100 Subject: [PATCH 0102/2059] Allowing null to be passed to newEntity() This helps creting new entities without running any validation --- Table.php | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 41263be0..64190a4d 100644 --- a/Table.php +++ b/Table.php @@ -1742,8 +1742,27 @@ public function marshaller() { * ['accessibleFields' => ['protected_field' => true]] * ); * }}} + * + * By default, the data is validated before being passed to the new entity. In + * the case of invalid fields, those will not be present in the resulting object. + * The `validate` option can be used to disable validation on the passed data: + * + * {{{ + * $article = $this->Articles->newEntity( + * $this->request->data(), + * ['validate' => false] + * ); + * }}} + * + * You can also pass the name of the validator to use in the `validate` option. + * If `null` is passed to the first param of this function, no validation will + * be performed. */ - public function newEntity(array $data = [], array $options = []) { + public function newEntity($data = null, array $options = []) { + if ($data === null) { + $class = $this->entityClass(); + return new $class; + } if (!isset($options['associated'])) { $options['associated'] = $this->_associations->keys(); } @@ -1802,6 +1821,16 @@ public function newEntities(array $data, array $options = []) { * ] * ); * }}} + * + * By default, the data is validated before being passed to the entity. In + * the case of invalid fields, those will not be assigned to the entity. + * The `validate` option can be used to disable validation on the passed data: + * + * {{{ + * $article = $this->patchEntity($article, $this->request->data(),[ + * 'validate' => false + * ]); + * }}} */ public function patchEntity(EntityInterface $entity, array $data, array $options = []) { if (!isset($options['associated'])) { From 52a20166c7a12b6270e12a6936beba287607890a Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Wed, 31 Dec 2014 13:05:00 +0100 Subject: [PATCH 0103/2059] Remove forced isNew = false in Marshaller::merge() for validation The previous code made impossible to patch new entities and haver thier validation run correctly. --- Marshaller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index ac9503f7..7244ca99 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -322,7 +322,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) $data = $data[$tableName]; } - $errors = $this->_validate($data, $options, false); + $errors = $this->_validate($data, $options, $entity->isNew()); $schema = $this->_table->schema(); $properties = []; foreach ($data as $key => $value) { From 5bf72081bd3db077a88c35f5a0756bf2af1d7e68 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Wed, 31 Dec 2014 14:25:07 +0100 Subject: [PATCH 0104/2059] Re-implemented Table::validateUnique to reuse logic --- Rule/IsUnique.php | 5 ++++- Table.php | 37 ++++++++++++------------------------- 2 files changed, 16 insertions(+), 26 deletions(-) diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index 2b814143..e7f2c922 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -53,7 +53,10 @@ public function __invoke(EntityInterface $entity, array $options) { $conditions = $entity->extract($this->_fields); if ($entity->isNew() === false) { $keys = (array)$options['repository']->primaryKey(); - $conditions['NOT'] = $entity->extract($keys); + $keys = $entity->extract($keys); + if (array_filter($keys, 'strlen')) { + $conditions['NOT'] = $keys; + } } return !$options['repository']->exists($conditions); diff --git a/Table.php b/Table.php index 64190a4d..1641ed7d 100644 --- a/Table.php +++ b/Table.php @@ -34,6 +34,7 @@ use Cake\ORM\Exception\MissingEntityException; use Cake\ORM\Marshaller; use Cake\ORM\RulesChecker; +use Cake\ORM\Rule\IsUnique; use Cake\Utility\Inflector; use Cake\Validation\Validator; use RuntimeException; @@ -1900,33 +1901,19 @@ public function patchEntities($entities, array $data, array $options = []) { * * @param mixed $value The value of column to be checked for uniqueness * @param array $options The options array, optionally containing the 'scope' key - * @param array $context The validation context as provided by the validation routine * @return bool true if the value is unique */ - public function validateUnique($value, array $options, array $context = []) { - if (empty($context)) { - $context = $options; - } - - $conditions = [$context['field'] => $value]; - if (!empty($options['scope']) && isset($context['data'][$options['scope']])) { - $scope = $options['scope']; - $scopedValue = $context['data'][$scope]; - $conditions[$scope] = $scopedValue; - } - - if (!$context['newRecord']) { - $keys = (array)$this->primaryKey(); - $not = []; - foreach ($keys as $key) { - if (isset($context['data'][$key])) { - $not[$key] = $context['data'][$key]; - } - } - $conditions['NOT'] = $not; - } - - return !$this->exists($conditions); + public function validateUnique($value, array $options) { + $entity = new Entity( + $options['data'], + ['useSetters' => false, 'markNew' => $options['newRecord']] + ); + $fields = array_merge( + [$options['field']], + isset($options['scope']) ? (array)$options['scope'] : [] + ); + $rule = new IsUnique($fields); + return $rule($entity, ['repository' => $this]); } /** From ef95863acadb35d65a2213b5f3377fb75b645344 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Wed, 31 Dec 2014 14:34:07 +0100 Subject: [PATCH 0105/2059] Passing the primary key values to the validation data, as it can be useful for certain validation routines. --- Marshaller.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index 7244ca99..d32603f1 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -317,12 +317,18 @@ public function merge(EntityInterface $entity, array $data, array $options = []) $options += ['validate' => true]; $propertyMap = $this->_buildPropertyMap($options); $tableName = $this->_table->alias(); + $isNew = $entity->isNew(); + $keys = []; if (isset($data[$tableName])) { $data = $data[$tableName]; } - $errors = $this->_validate($data, $options, $entity->isNew()); + if (!$isNew) { + $keys = $entity->extract((array)$this->_table->primaryKey()); + } + + $errors = $this->_validate($data + $keys, $options, $isNew); $schema = $this->_table->schema(); $properties = []; foreach ($data as $key => $value) { From 8bc1a31804c54d2a5241e82eeebe193873771898 Mon Sep 17 00:00:00 2001 From: Bryan Crowe Date: Wed, 31 Dec 2014 12:38:56 -0500 Subject: [PATCH 0106/2059] Run phpcbf for PSR2 CS fixers --- Association.php | 1611 ++++---- Association/BelongsTo.php | 363 +- Association/BelongsToMany.php | 1916 +++++----- Association/DependentDeleteTrait.php | 58 +- Association/ExternalAssociationTrait.php | 199 +- Association/HasMany.php | 240 +- Association/HasOne.php | 267 +- Association/SelectableAssociationTrait.php | 580 +-- AssociationCollection.php | 464 +-- AssociationsNormalizerTrait.php | 77 +- Behavior.php | 524 +-- BehaviorRegistry.php | 417 ++- EagerLoader.php | 1218 +++--- Entity.php | 95 +- EntityValidator.php | 313 +- EntityValidatorTrait.php | 33 +- Exception/MissingBehaviorException.php | 6 +- Exception/MissingEntityException.php | 6 +- Exception/MissingTableClassException.php | 6 +- Marshaller.php | 1030 +++--- Query.php | 1648 +++++---- ResultSet.php | 951 ++--- Rule/ExistsIn.php | 110 +- Rule/IsUnique.php | 78 +- RulesChecker.php | 538 +-- Table.php | 3896 ++++++++++---------- TableRegistry.php | 391 +- 27 files changed, 8663 insertions(+), 8372 deletions(-) diff --git a/Association.php b/Association.php index e4e938f0..4439d0d1 100644 --- a/Association.php +++ b/Association.php @@ -28,831 +28,860 @@ * to configure and customize the way interconnected records are retrieved. * */ -abstract class Association { +abstract class Association +{ - use ConventionsTrait; + use ConventionsTrait; -/** + /** * Strategy name to use joins for fetching associated records * * @var string */ - const STRATEGY_JOIN = 'join'; + const STRATEGY_JOIN = 'join'; -/** + /** * Strategy name to use a subquery for fetching associated records * * @var string */ - const STRATEGY_SUBQUERY = 'subquery'; + const STRATEGY_SUBQUERY = 'subquery'; -/** + /** * Strategy name to use a select for fetching associated records * * @var string */ - const STRATEGY_SELECT = 'select'; + const STRATEGY_SELECT = 'select'; -/** + /** * Association type for one to one associations. * * @var string */ - const ONE_TO_ONE = 'oneToOne'; + const ONE_TO_ONE = 'oneToOne'; -/** + /** * Association type for one to many associations. * * @var string */ - const ONE_TO_MANY = 'oneToMany'; + const ONE_TO_MANY = 'oneToMany'; -/** + /** * Association type for many to many associations. * * @var string */ - const MANY_TO_MANY = 'manyToMany'; + const MANY_TO_MANY = 'manyToMany'; -/** + /** * Association type for many to one associations. * * @var string */ - const MANY_TO_ONE = 'manyToOne'; - -/** - * Name given to the association, it usually represents the alias - * assigned to the target associated table - * - * @var string - */ - protected $_name; - -/** - * The class name of the target table object - * - * @var string - */ - protected $_className; - -/** - * The name of the field representing the foreign key to the table to load - * - * @var string|array - */ - protected $_foreignKey; - -/** - * A list of conditions to be always included when fetching records from - * the target association - * - * @var array - */ - protected $_conditions = []; - -/** - * Whether the records on the target table are dependent on the source table, - * often used to indicate that records should be removed if the owning record in - * the source table is deleted. - * - * @var bool - */ - protected $_dependent = false; - -/** - * Whether or not cascaded deletes should also fire callbacks. - * - * @var string - */ - protected $_cascadeCallbacks = false; - -/** - * Source table instance - * - * @var \Cake\ORM\Table - */ - protected $_sourceTable; - -/** - * Target table instance - * - * @var \Cake\ORM\Table - */ - protected $_targetTable; - -/** - * The type of join to be used when adding the association to a query - * - * @var string - */ - protected $_joinType = 'LEFT'; - -/** - * The property name that should be filled with data from the target table - * in the source table record. - * - * @var string - */ - protected $_propertyName; - -/** - * The strategy name to be used to fetch associated records. Some association - * types might not implement but one strategy to fetch records. - * - * @var string - */ - protected $_strategy = self::STRATEGY_JOIN; - -/** - * The default finder name to use for fetching rows from the target table - * - * @var string - */ - protected $_finder = 'all'; - -/** - * Constructor. Subclasses can override _options function to get the original - * list of passed options if expecting any other special key - * - * @param string $name The name given to the association - * @param array $options A list of properties to be set on this object - */ - public function __construct($name, array $options = []) { - $defaults = [ - 'cascadeCallbacks', - 'className', - 'conditions', - 'dependent', - 'finder', - 'foreignKey', - 'joinType', - 'propertyName', - 'sourceTable', - 'targetTable' - ]; - foreach ($defaults as $property) { - if (isset($options[$property])) { - $this->{'_' . $property} = $options[$property]; - } - } - - $this->_name = $name; - $this->_options($options); - - if (!empty($options['strategy'])) { - $this->strategy($options['strategy']); - } - } - -/** - * Sets the name for this association. If no argument is passed then the current - * configured name will be returned - * - * @param string|null $name Name to be assigned - * @return string - */ - public function name($name = null) { - if ($name !== null) { - $this->_name = $name; - } - return $this->_name; - } - -/** - * Sets whether or not cascaded deletes should also fire callbacks. If no - * arguments are passed, the current configured value is returned - * - * @param bool|null $cascadeCallbacks cascade callbacks switch value - * @return bool - */ - public function cascadeCallbacks($cascadeCallbacks = null) { - if ($cascadeCallbacks !== null) { - $this->_cascadeCallbacks = $cascadeCallbacks; - } - return $this->_cascadeCallbacks; - } - -/** - * Sets the table instance for the source side of the association. If no arguments - * are passed, the current configured table instance is returned - * - * @param \Cake\ORM\Table|null $table the instance to be assigned as source side - * @return \Cake\ORM\Table - */ - public function source(Table $table = null) { - if ($table === null) { - return $this->_sourceTable; - } - return $this->_sourceTable = $table; - } - -/** - * Sets the table instance for the target side of the association. If no arguments - * are passed, the current configured table instance is returned - * - * @param \Cake\ORM\Table|null $table the instance to be assigned as target side - * @return \Cake\ORM\Table - */ - public function target(Table $table = null) { - if ($table === null && $this->_targetTable) { - return $this->_targetTable; - } - - if ($table !== null) { - return $this->_targetTable = $table; - } - - if ($table === null) { - $config = []; - if (!TableRegistry::exists($this->_name)) { - $config = ['className' => $this->_className]; - } - $this->_targetTable = TableRegistry::get($this->_name, $config); - } - return $this->_targetTable; - } - -/** - * Sets a list of conditions to be always included when fetching records from - * the target association. If no parameters are passed the current list is returned - * - * @param array|null $conditions list of conditions to be used - * @see \Cake\Database\Query::where() for examples on the format of the array - * @return array - */ - public function conditions($conditions = null) { - if ($conditions !== null) { - $this->_conditions = $conditions; - } - return $this->_conditions; - } - -/** - * Sets the name of the field representing the foreign key to the target table. - * If no parameters are passed the current field is returned - * - * @param string|null $key the key to be used to link both tables together - * @return string|array - */ - public function foreignKey($key = null) { - if ($key !== null) { - $this->_foreignKey = $key; - } - return $this->_foreignKey; - } - -/** - * Sets whether the records on the target table are dependent on the source table. - * - * This is primarily used to indicate that records should be removed if the owning record in - * the source table is deleted. - * - * If no parameters are passed the current setting is returned. - * - * @param bool|null $dependent Set the dependent mode. Use null to read the current state. - * @return bool - */ - public function dependent($dependent = null) { - if ($dependent !== null) { - $this->_dependent = $dependent; - } - return $this->_dependent; - } - -/** - * Whether this association can be expressed directly in a query join - * - * @param array $options custom options key that could alter the return value - * @return bool - */ - public function canBeJoined(array $options = []) { - $strategy = isset($options['strategy']) ? $options['strategy'] : $this->strategy(); - return $strategy == $this::STRATEGY_JOIN; - } - -/** - * Sets the type of join to be used when adding the association to a query. - * If no arguments are passed, the currently configured type is returned. - * - * @param string $type the join type to be used (e.g. INNER) - * @return string - */ - public function joinType($type = null) { - if ($type === null) { - return $this->_joinType; - } - return $this->_joinType = $type; - } - -/** - * Sets the property name that should be filled with data from the target table - * in the source table record. - * If no arguments are passed, the currently configured type is returned. - * - * @param string|null $name The name of the association property. Use null to read the current value. - * @return string - */ - public function property($name = null) { - if ($name !== null) { - $this->_propertyName = $name; - } - if ($name === null && !$this->_propertyName) { - list(, $name) = pluginSplit($this->_name); - $this->_propertyName = Inflector::underscore($name); - } - return $this->_propertyName; - } - -/** - * Sets the strategy name to be used to fetch associated records. Keep in mind - * that some association types might not implement but a default strategy, - * rendering any changes to this setting void. - * If no arguments are passed, the currently configured strategy is returned. - * - * @param string|null $name The strategy type. Use null to read the current value. - * @return string - * @throws \InvalidArgumentException When an invalid strategy is provided. - */ - public function strategy($name = null) { - if ($name !== null) { - $valid = [self::STRATEGY_JOIN, self::STRATEGY_SELECT, self::STRATEGY_SUBQUERY]; - if (!in_array($name, $valid)) { - throw new \InvalidArgumentException( - sprintf('Invalid strategy "%s" was provided', $name) - ); - } - $this->_strategy = $name; - } - return $this->_strategy; - } - -/** - * Sets the default finder to use for fetching rows from the target table. - * If no parameters are passed, it will return the currently configured - * finder name. - * - * @param string|null $finder the finder name to use - * @return string - */ - public function finder($finder = null) { - if ($finder !== null) { - $this->_finder = $finder; - } - return $this->_finder; - } - -/** - * Override this function to initialize any concrete association class, it will - * get passed the original list of options used in the constructor - * - * @param array $options List of options used for initialization - * @return void - */ - protected function _options(array $options) { - } - -/** - * Alters a Query object to include the associated target table data in the final - * result - * - * The options array accept the following keys: - * - * - includeFields: Whether to include target model fields in the result or not - * - foreignKey: The name of the field to use as foreign key, if false none - * will be used - * - conditions: array with a list of conditions to filter the join with, this - * will be merged with any conditions originally configured for this association - * - fields: a list of fields in the target table to include in the result - * - type: The type of join to be used (e.g. INNER) - * - matching: Indicates whether the query records should be filtered based on - * the records found on this association. This will force a 'INNER JOIN' - * - aliasPath: A dot separated string representing the path of association names - * followed from the passed query main table to this association. - * - propertyPath: A dot separated string representing the path of association - * properties to be followed from the passed query main entity to this - * association - * - joinType: The SQL join type to use in the query. - * - * @param Query $query the query to be altered to include the target table data - * @param array $options Any extra options or overrides to be taken in account - * @return void - * @throws \RuntimeException if the query builder passed does not return a query - * object - */ - public function attachTo(Query $query, array $options = []) { - $target = $this->target(); - $joinType = empty($options['joinType']) ? $this->joinType() : $options['joinType']; - $options += [ - 'includeFields' => true, - 'foreignKey' => $this->foreignKey(), - 'conditions' => [], - 'fields' => [], - 'type' => empty($options['matching']) ? $joinType : 'INNER', - 'table' => $target->table(), - 'finder' => $this->finder() - ]; - - if (!empty($options['foreignKey'])) { - $joinCondition = $this->_joinCondition($options); - if ($joinCondition) { - $options['conditions'][] = $joinCondition; - } - } - - list($finder, $opts) = $this->_extractFinder($options['finder']); - $dummy = $this - ->find($finder, $opts) - ->eagerLoaded(true); - if (!empty($options['queryBuilder'])) { - $dummy = $options['queryBuilder']($dummy); - if (!($dummy instanceof Query)) { - throw new \RuntimeException(sprintf( - 'Query builder for association "%s" did not return a query', - $this->name() - )); - } - } - - $dummy->where($options['conditions']); - $this->_dispatchBeforeFind($dummy); - - $joinOptions = ['table' => 1, 'conditions' => 1, 'type' => 1]; - $options['conditions'] = $dummy->clause('where'); - $query->join([$target->alias() => array_intersect_key($options, $joinOptions)]); - - $this->_appendFields($query, $dummy, $options); - $this->_formatAssociationResults($query, $dummy, $options); - $this->_bindNewAssociations($query, $dummy, $options); - } - -/** - * Correctly nests a result row associated values into the correct array keys inside the - * source results. - * - * @param array $row The row to transform - * @param string $nestKey The array key under which the results for this association - * should be found - * @param bool $joined Whether or not the row is a result of a direct join - * with this association - * @return array - */ - public function transformRow($row, $nestKey, $joined) { - $sourceAlias = $this->source()->alias(); - $nestKey = $nestKey ?: $this->_name; - if (isset($row[$sourceAlias])) { - $row[$sourceAlias][$this->property()] = $row[$nestKey]; - unset($row[$nestKey]); - } - return $row; - } - -/** - * Returns a modified row after appending a property for this association - * with the default empty value according to whether the association was - * joined or fetched externally. - * - * @param array $row The row to set a default on. - * @param bool $joined Whether or not the row is a result of a direct join - * with this association - * @return array - */ - public function defaultRowValue($row, $joined) { - $sourceAlias = $this->source()->alias(); - if (isset($row[$sourceAlias])) { - $row[$sourceAlias][$this->property()] = null; - } - return $row; - } - -/** - * Proxies the finding operation to the target table's find method - * and modifies the query accordingly based of this association - * configuration - * - * @param string|array $type the type of query to perform, if an array is passed, - * it will be interpreted as the `$options` parameter - * @param array $options The options to for the find - * @see \Cake\ORM\Table::find() - * @return \Cake\ORM\Query - */ - public function find($type = null, array $options = []) { - $type = $type ?: $this->finder(); - list($type, $opts) = $this->_extractFinder($type, $options); - return $this->target() - ->find($type, $options + $opts) - ->where($this->conditions()); - } - -/** - * Proxies the update operation to the target table's updateAll method - * - * @param array $fields A hash of field => new value. - * @param mixed $conditions Conditions to be used, accepts anything Query::where() - * can take. - * @see \Cake\ORM\Table::updateAll() - * @return bool Success Returns true if one or more rows are affected. - */ - public function updateAll($fields, $conditions) { - $target = $this->target(); - $expression = $target->query() - ->where($this->conditions()) - ->where($conditions) - ->clause('where'); - return $target->updateAll($fields, $expression); - } - -/** - * Proxies the delete operation to the target table's deleteAll method - * - * @param mixed $conditions Conditions to be used, accepts anything Query::where() - * can take. - * @return bool Success Returns true if one or more rows are affected. - * @see \Cake\ORM\Table::delteAll() - */ - public function deleteAll($conditions) { - $target = $this->target(); - $expression = $target->query() - ->where($this->conditions()) - ->where($conditions) - ->clause('where'); - return $target->deleteAll($expression); - } - -/** - * Triggers beforeFind on the target table for the query this association is - * attaching to - * - * @param \Cake\ORM\Query $query the query this association is attaching itself to - * @return void - */ - protected function _dispatchBeforeFind($query) { - $table = $this->target(); - $options = $query->getOptions(); - $table->dispatchEvent('Model.beforeFind', [$query, $options, false]); - } - -/** - * Helper function used to conditionally append fields to the select clause of - * a query from the fields found in another query object. - * - * @param \Cake\ORM\Query $query the query that will get the fields appended to - * @param \Cake\ORM\Query $surrogate the query having the fields to be copied from - * @param array $options options passed to the method `attachTo` - * @return void - */ - protected function _appendFields($query, $surrogate, $options) { - $fields = $surrogate->clause('select') ?: $options['fields']; - $target = $this->_targetTable; - $autoFields = $surrogate->autoFields(); - if (empty($fields) && !$autoFields) { - if ($options['includeFields'] && ($fields === null || $fields !== false)) { - $fields = $target->schema()->columns(); - } - } - - if ($autoFields === true) { - $fields = array_merge((array)$fields, $target->schema()->columns()); - } - - if (!empty($fields)) { - $query->select($query->aliasFields($fields, $target->alias())); - } - } - -/** - * Adds a formatter function to the passed `$query` if the `$surrogate` query - * declares any other formatter. Since the `$surrogate` query correspond to - * the associated target table, the resulting formatter will be the result of - * applying the surrogate formatters to only the property corresponding to - * such table. - * - * @param \Cake\ORM\Query $query the query that will get the formatter applied to - * @param \Cake\ORM\Query $surrogate the query having formatters for the associated - * target table. - * @param array $options options passed to the method `attachTo` - * @return void - */ - protected function _formatAssociationResults($query, $surrogate, $options) { - $formatters = $surrogate->formatResults(); - - if (!$formatters) { - return; - } - - $property = $options['propertyPath']; - $query->formatResults(function ($results) use ($formatters, $property) { - $extracted = $results->extract($property)->compile(); - foreach ($formatters as $callable) { - $extracted = new ResultSetDecorator($callable($extracted)); - } - return $results->insert($property, $extracted); - }, Query::PREPEND); - } - -/** - * Applies all attachable associations to `$query` out of the containments found - * in the `$surrogate` query. - * - * Copies all contained associations from the `$surrogate` query into the - * passed `$query`. Containments are altered so that they respect the associations - * chain from which they originated. - * - * @param \Cake\ORM\Query $query the query that will get the associations attached to - * @param \Cake\ORM\Query $surrogate the query having the containments to be attached - * @param array $options options passed to the method `attachTo` - * @return void - */ - protected function _bindNewAssociations($query, $surrogate, $options) { - $loader = $surrogate->eagerLoader(); - $contain = $loader->contain(); - $matching = $loader->matching(); - $target = $this->_targetTable; - - if (!$contain && !$matching) { - return; - } - - $loader->attachAssociations($query, $target, $options['includeFields']); - $newContain = []; - foreach ($contain as $alias => $value) { - $newContain[$options['aliasPath'] . '.' . $alias] = $value; - } - - $query->contain($newContain); - - foreach ($matching as $alias => $value) { - $query->matching($options['aliasPath'] . '.' . $alias, $value['queryBuilder']); - } - } - -/** - * Returns a single or multiple conditions to be appended to the generated join - * clause for getting the results on the target table. - * - * @param array $options list of options passed to attachTo method - * @return array - * @throws \RuntimeException if the number of columns in the foreignKey do not - * match the number of columns in the source table primaryKey - */ - protected function _joinCondition($options) { - $conditions = []; - $tAlias = $this->target()->alias(); - $sAlias = $this->source()->alias(); - $foreignKey = (array)$options['foreignKey']; - $primaryKey = (array)$this->_sourceTable->primaryKey(); - - if (count($foreignKey) !== count($primaryKey)) { - $msg = 'Cannot match provided foreignKey for "%s", got "(%s)" but expected foreign key for "(%s)"'; - throw new \RuntimeException(sprintf( - $msg, - $this->_name, - implode(', ', $foreignKey), - implode(', ', $primaryKey) - )); - } - - foreach ($foreignKey as $k => $f) { - $field = sprintf('%s.%s', $sAlias, $primaryKey[$k]); - $value = new IdentifierExpression(sprintf('%s.%s', $tAlias, $f)); - $conditions[$field] = $value; - } - - return $conditions; - } - -/** - * Helper method to infer the requested finder and its options. - * - * Returns the inferred options from the finder $type. - * - * ### Examples: - * - * The following will call the finder 'translations' with the value of the finder as its options: - * $query->contain(['Comments' => ['finder' => ['translations']]]); - * $query->contain(['Comments' => ['finder' => ['translations' => []]]]); - * $query->contain(['Comments' => ['finder' => ['translations' => ['locales' => ['en_US']]]]]); - * - * @param string|array $finderData The finder name or an array having the name as key - * and options as value. - * @return array - */ - protected function _extractFinder($finderData) { - $finderData = (array)$finderData; - - if (is_numeric(key($finderData))) { - return [current($finderData), []]; - } - - return [key($finderData), current($finderData)]; - } - -/** - * Proxies property retrieval to the target table. This is handy for getting this - * association's associations - * - * @param string $property the property name - * @return \Cake\ORM\Association - * @throws \RuntimeException if no association with such name exists - */ - public function __get($property) { - return $this->target()->{$property}; - } - -/** - * Proxies the isset call to the target table. This is handy to check if the - * target table has another association with the passed name - * - * @param string $property the property name - * @return bool true if the property exists - */ - public function __isset($property) { - return isset($this->target()->{$property}); - } - -/** - * Proxies method calls to the target table. - * - * @param string $method name of the method to be invoked - * @param array $argument List of arguments passed to the function - * @return mixed - * @throws \BadMethodCallException - */ - public function __call($method, $argument) { - return call_user_func_array([$this->target(), $method], $argument); - } - -/** - * Get the relationship type. - * - * @return string Constant of either ONE_TO_ONE, MANY_TO_ONE, ONE_TO_MANY or MANY_TO_MANY. - */ - public abstract function type(); - -/** - * Eager loads a list of records in the target table that are related to another - * set of records in the source table. Source records can specified in two ways: - * first one is by passing a Query object setup to find on the source table and - * the other way is by explicitly passing an array of primary key values from - * the source table. - * - * The required way of passing related source records is controlled by "strategy" - * By default the subquery strategy is used, which requires a query on the source - * When using the select strategy, the list of primary keys will be used. - * - * Returns a closure that should be run for each record returned in a specific - * Query. This callable will be responsible for injecting the fields that are - * related to each specific passed row. - * - * Options array accepts the following keys: - * - * - query: Query object setup to find the source table records - * - keys: List of primary key values from the source table - * - foreignKey: The name of the field used to relate both tables - * - conditions: List of conditions to be passed to the query where() method - * - sort: The direction in which the records should be returned - * - fields: List of fields to select from the target table - * - contain: List of related tables to eager load associated to the target table - * - strategy: The name of strategy to use for finding target table records - * - nestKey: The array key under which results will be found when transforming the row - * - * @param array $options The options for eager loading. - * @return \Closure - */ - public abstract function eagerLoader(array $options); - -/** - * Handles cascading a delete from an associated model. - * - * Each implementing class should handle the cascaded delete as - * required. - * - * @param \Cake\Datasource\EntityInterface $entity The entity that started the cascaded delete. - * @param array $options The options for the original delete. - * @return bool Success - */ - public abstract function cascadeDelete(EntityInterface $entity, array $options = []); - -/** - * Returns whether or not the passed table is the owning side for this - * association. This means that rows in the 'target' table would miss important - * or required information if the row in 'source' did not exist. - * - * @param \Cake\ORM\Table $side The potential Table with ownership - * @return bool - */ - public abstract function isOwningSide(Table $side); - -/** - * Extract the target's association data our from the passed entity and proxies - * the saving operation to the target table. - * - * @param \Cake\Datasource\EntityInterface $entity the data to be saved - * @param array|\ArrayObject $options The options for saving associated data. - * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns - * the saved entity - * @see Table::save() - */ - public abstract function saveAssociated(EntityInterface $entity, array $options = []); - + const MANY_TO_ONE = 'manyToOne'; + + /** + * Name given to the association, it usually represents the alias + * assigned to the target associated table + * + * @var string + */ + protected $_name; + + /** + * The class name of the target table object + * + * @var string + */ + protected $_className; + + /** + * The name of the field representing the foreign key to the table to load + * + * @var string|array + */ + protected $_foreignKey; + + /** + * A list of conditions to be always included when fetching records from + * the target association + * + * @var array + */ + protected $_conditions = []; + + /** + * Whether the records on the target table are dependent on the source table, + * often used to indicate that records should be removed if the owning record in + * the source table is deleted. + * + * @var bool + */ + protected $_dependent = false; + + /** + * Whether or not cascaded deletes should also fire callbacks. + * + * @var string + */ + protected $_cascadeCallbacks = false; + + /** + * Source table instance + * + * @var \Cake\ORM\Table + */ + protected $_sourceTable; + + /** + * Target table instance + * + * @var \Cake\ORM\Table + */ + protected $_targetTable; + + /** + * The type of join to be used when adding the association to a query + * + * @var string + */ + protected $_joinType = 'LEFT'; + + /** + * The property name that should be filled with data from the target table + * in the source table record. + * + * @var string + */ + protected $_propertyName; + + /** + * The strategy name to be used to fetch associated records. Some association + * types might not implement but one strategy to fetch records. + * + * @var string + */ + protected $_strategy = self::STRATEGY_JOIN; + + /** + * The default finder name to use for fetching rows from the target table + * + * @var string + */ + protected $_finder = 'all'; + + /** + * Constructor. Subclasses can override _options function to get the original + * list of passed options if expecting any other special key + * + * @param string $name The name given to the association + * @param array $options A list of properties to be set on this object + */ + public function __construct($name, array $options = []) + { + $defaults = [ + 'cascadeCallbacks', + 'className', + 'conditions', + 'dependent', + 'finder', + 'foreignKey', + 'joinType', + 'propertyName', + 'sourceTable', + 'targetTable' + ]; + foreach ($defaults as $property) { + if (isset($options[$property])) { + $this->{'_' . $property} = $options[$property]; + } + } + + $this->_name = $name; + $this->_options($options); + + if (!empty($options['strategy'])) { + $this->strategy($options['strategy']); + } + } + + /** + * Sets the name for this association. If no argument is passed then the current + * configured name will be returned + * + * @param string|null $name Name to be assigned + * @return string + */ + public function name($name = null) + { + if ($name !== null) { + $this->_name = $name; + } + return $this->_name; + } + + /** + * Sets whether or not cascaded deletes should also fire callbacks. If no + * arguments are passed, the current configured value is returned + * + * @param bool|null $cascadeCallbacks cascade callbacks switch value + * @return bool + */ + public function cascadeCallbacks($cascadeCallbacks = null) + { + if ($cascadeCallbacks !== null) { + $this->_cascadeCallbacks = $cascadeCallbacks; + } + return $this->_cascadeCallbacks; + } + + /** + * Sets the table instance for the source side of the association. If no arguments + * are passed, the current configured table instance is returned + * + * @param \Cake\ORM\Table|null $table the instance to be assigned as source side + * @return \Cake\ORM\Table + */ + public function source(Table $table = null) + { + if ($table === null) { + return $this->_sourceTable; + } + return $this->_sourceTable = $table; + } + + /** + * Sets the table instance for the target side of the association. If no arguments + * are passed, the current configured table instance is returned + * + * @param \Cake\ORM\Table|null $table the instance to be assigned as target side + * @return \Cake\ORM\Table + */ + public function target(Table $table = null) + { + if ($table === null && $this->_targetTable) { + return $this->_targetTable; + } + + if ($table !== null) { + return $this->_targetTable = $table; + } + + if ($table === null) { + $config = []; + if (!TableRegistry::exists($this->_name)) { + $config = ['className' => $this->_className]; + } + $this->_targetTable = TableRegistry::get($this->_name, $config); + } + return $this->_targetTable; + } + + /** + * Sets a list of conditions to be always included when fetching records from + * the target association. If no parameters are passed the current list is returned + * + * @param array|null $conditions list of conditions to be used + * @see \Cake\Database\Query::where() for examples on the format of the array + * @return array + */ + public function conditions($conditions = null) + { + if ($conditions !== null) { + $this->_conditions = $conditions; + } + return $this->_conditions; + } + + /** + * Sets the name of the field representing the foreign key to the target table. + * If no parameters are passed the current field is returned + * + * @param string|null $key the key to be used to link both tables together + * @return string|array + */ + public function foreignKey($key = null) + { + if ($key !== null) { + $this->_foreignKey = $key; + } + return $this->_foreignKey; + } + + /** + * Sets whether the records on the target table are dependent on the source table. + * + * This is primarily used to indicate that records should be removed if the owning record in + * the source table is deleted. + * + * If no parameters are passed the current setting is returned. + * + * @param bool|null $dependent Set the dependent mode. Use null to read the current state. + * @return bool + */ + public function dependent($dependent = null) + { + if ($dependent !== null) { + $this->_dependent = $dependent; + } + return $this->_dependent; + } + + /** + * Whether this association can be expressed directly in a query join + * + * @param array $options custom options key that could alter the return value + * @return bool + */ + public function canBeJoined(array $options = []) + { + $strategy = isset($options['strategy']) ? $options['strategy'] : $this->strategy(); + return $strategy == $this::STRATEGY_JOIN; + } + + /** + * Sets the type of join to be used when adding the association to a query. + * If no arguments are passed, the currently configured type is returned. + * + * @param string $type the join type to be used (e.g. INNER) + * @return string + */ + public function joinType($type = null) + { + if ($type === null) { + return $this->_joinType; + } + return $this->_joinType = $type; + } + + /** + * Sets the property name that should be filled with data from the target table + * in the source table record. + * If no arguments are passed, the currently configured type is returned. + * + * @param string|null $name The name of the association property. Use null to read the current value. + * @return string + */ + public function property($name = null) + { + if ($name !== null) { + $this->_propertyName = $name; + } + if ($name === null && !$this->_propertyName) { + list(, $name) = pluginSplit($this->_name); + $this->_propertyName = Inflector::underscore($name); + } + return $this->_propertyName; + } + + /** + * Sets the strategy name to be used to fetch associated records. Keep in mind + * that some association types might not implement but a default strategy, + * rendering any changes to this setting void. + * If no arguments are passed, the currently configured strategy is returned. + * + * @param string|null $name The strategy type. Use null to read the current value. + * @return string + * @throws \InvalidArgumentException When an invalid strategy is provided. + */ + public function strategy($name = null) + { + if ($name !== null) { + $valid = [self::STRATEGY_JOIN, self::STRATEGY_SELECT, self::STRATEGY_SUBQUERY]; + if (!in_array($name, $valid)) { + throw new \InvalidArgumentException( + sprintf('Invalid strategy "%s" was provided', $name) + ); + } + $this->_strategy = $name; + } + return $this->_strategy; + } + + /** + * Sets the default finder to use for fetching rows from the target table. + * If no parameters are passed, it will return the currently configured + * finder name. + * + * @param string|null $finder the finder name to use + * @return string + */ + public function finder($finder = null) + { + if ($finder !== null) { + $this->_finder = $finder; + } + return $this->_finder; + } + + /** + * Override this function to initialize any concrete association class, it will + * get passed the original list of options used in the constructor + * + * @param array $options List of options used for initialization + * @return void + */ + protected function _options(array $options) + { + } + + /** + * Alters a Query object to include the associated target table data in the final + * result + * + * The options array accept the following keys: + * + * - includeFields: Whether to include target model fields in the result or not + * - foreignKey: The name of the field to use as foreign key, if false none + * will be used + * - conditions: array with a list of conditions to filter the join with, this + * will be merged with any conditions originally configured for this association + * - fields: a list of fields in the target table to include in the result + * - type: The type of join to be used (e.g. INNER) + * - matching: Indicates whether the query records should be filtered based on + * the records found on this association. This will force a 'INNER JOIN' + * - aliasPath: A dot separated string representing the path of association names + * followed from the passed query main table to this association. + * - propertyPath: A dot separated string representing the path of association + * properties to be followed from the passed query main entity to this + * association + * - joinType: The SQL join type to use in the query. + * + * @param Query $query the query to be altered to include the target table data + * @param array $options Any extra options or overrides to be taken in account + * @return void + * @throws \RuntimeException if the query builder passed does not return a query + * object + */ + public function attachTo(Query $query, array $options = []) + { + $target = $this->target(); + $joinType = empty($options['joinType']) ? $this->joinType() : $options['joinType']; + $options += [ + 'includeFields' => true, + 'foreignKey' => $this->foreignKey(), + 'conditions' => [], + 'fields' => [], + 'type' => empty($options['matching']) ? $joinType : 'INNER', + 'table' => $target->table(), + 'finder' => $this->finder() + ]; + + if (!empty($options['foreignKey'])) { + $joinCondition = $this->_joinCondition($options); + if ($joinCondition) { + $options['conditions'][] = $joinCondition; + } + } + + list($finder, $opts) = $this->_extractFinder($options['finder']); + $dummy = $this + ->find($finder, $opts) + ->eagerLoaded(true); + if (!empty($options['queryBuilder'])) { + $dummy = $options['queryBuilder']($dummy); + if (!($dummy instanceof Query)) { + throw new \RuntimeException(sprintf( + 'Query builder for association "%s" did not return a query', + $this->name() + )); + } + } + + $dummy->where($options['conditions']); + $this->_dispatchBeforeFind($dummy); + + $joinOptions = ['table' => 1, 'conditions' => 1, 'type' => 1]; + $options['conditions'] = $dummy->clause('where'); + $query->join([$target->alias() => array_intersect_key($options, $joinOptions)]); + + $this->_appendFields($query, $dummy, $options); + $this->_formatAssociationResults($query, $dummy, $options); + $this->_bindNewAssociations($query, $dummy, $options); + } + + /** + * Correctly nests a result row associated values into the correct array keys inside the + * source results. + * + * @param array $row The row to transform + * @param string $nestKey The array key under which the results for this association + * should be found + * @param bool $joined Whether or not the row is a result of a direct join + * with this association + * @return array + */ + public function transformRow($row, $nestKey, $joined) + { + $sourceAlias = $this->source()->alias(); + $nestKey = $nestKey ?: $this->_name; + if (isset($row[$sourceAlias])) { + $row[$sourceAlias][$this->property()] = $row[$nestKey]; + unset($row[$nestKey]); + } + return $row; + } + + /** + * Returns a modified row after appending a property for this association + * with the default empty value according to whether the association was + * joined or fetched externally. + * + * @param array $row The row to set a default on. + * @param bool $joined Whether or not the row is a result of a direct join + * with this association + * @return array + */ + public function defaultRowValue($row, $joined) + { + $sourceAlias = $this->source()->alias(); + if (isset($row[$sourceAlias])) { + $row[$sourceAlias][$this->property()] = null; + } + return $row; + } + + /** + * Proxies the finding operation to the target table's find method + * and modifies the query accordingly based of this association + * configuration + * + * @param string|array $type the type of query to perform, if an array is passed, + * it will be interpreted as the `$options` parameter + * @param array $options The options to for the find + * @see \Cake\ORM\Table::find() + * @return \Cake\ORM\Query + */ + public function find($type = null, array $options = []) + { + $type = $type ?: $this->finder(); + list($type, $opts) = $this->_extractFinder($type, $options); + return $this->target() + ->find($type, $options + $opts) + ->where($this->conditions()); + } + + /** + * Proxies the update operation to the target table's updateAll method + * + * @param array $fields A hash of field => new value. + * @param mixed $conditions Conditions to be used, accepts anything Query::where() + * can take. + * @see \Cake\ORM\Table::updateAll() + * @return bool Success Returns true if one or more rows are affected. + */ + public function updateAll($fields, $conditions) + { + $target = $this->target(); + $expression = $target->query() + ->where($this->conditions()) + ->where($conditions) + ->clause('where'); + return $target->updateAll($fields, $expression); + } + + /** + * Proxies the delete operation to the target table's deleteAll method + * + * @param mixed $conditions Conditions to be used, accepts anything Query::where() + * can take. + * @return bool Success Returns true if one or more rows are affected. + * @see \Cake\ORM\Table::delteAll() + */ + public function deleteAll($conditions) + { + $target = $this->target(); + $expression = $target->query() + ->where($this->conditions()) + ->where($conditions) + ->clause('where'); + return $target->deleteAll($expression); + } + + /** + * Triggers beforeFind on the target table for the query this association is + * attaching to + * + * @param \Cake\ORM\Query $query the query this association is attaching itself to + * @return void + */ + protected function _dispatchBeforeFind($query) + { + $table = $this->target(); + $options = $query->getOptions(); + $table->dispatchEvent('Model.beforeFind', [$query, $options, false]); + } + + /** + * Helper function used to conditionally append fields to the select clause of + * a query from the fields found in another query object. + * + * @param \Cake\ORM\Query $query the query that will get the fields appended to + * @param \Cake\ORM\Query $surrogate the query having the fields to be copied from + * @param array $options options passed to the method `attachTo` + * @return void + */ + protected function _appendFields($query, $surrogate, $options) + { + $fields = $surrogate->clause('select') ?: $options['fields']; + $target = $this->_targetTable; + $autoFields = $surrogate->autoFields(); + if (empty($fields) && !$autoFields) { + if ($options['includeFields'] && ($fields === null || $fields !== false)) { + $fields = $target->schema()->columns(); + } + } + + if ($autoFields === true) { + $fields = array_merge((array)$fields, $target->schema()->columns()); + } + + if (!empty($fields)) { + $query->select($query->aliasFields($fields, $target->alias())); + } + } + + /** + * Adds a formatter function to the passed `$query` if the `$surrogate` query + * declares any other formatter. Since the `$surrogate` query correspond to + * the associated target table, the resulting formatter will be the result of + * applying the surrogate formatters to only the property corresponding to + * such table. + * + * @param \Cake\ORM\Query $query the query that will get the formatter applied to + * @param \Cake\ORM\Query $surrogate the query having formatters for the associated + * target table. + * @param array $options options passed to the method `attachTo` + * @return void + */ + protected function _formatAssociationResults($query, $surrogate, $options) + { + $formatters = $surrogate->formatResults(); + + if (!$formatters) { + return; + } + + $property = $options['propertyPath']; + $query->formatResults(function ($results) use ($formatters, $property) { + $extracted = $results->extract($property)->compile(); + foreach ($formatters as $callable) { + $extracted = new ResultSetDecorator($callable($extracted)); + } + return $results->insert($property, $extracted); + }, Query::PREPEND); + } + + /** + * Applies all attachable associations to `$query` out of the containments found + * in the `$surrogate` query. + * + * Copies all contained associations from the `$surrogate` query into the + * passed `$query`. Containments are altered so that they respect the associations + * chain from which they originated. + * + * @param \Cake\ORM\Query $query the query that will get the associations attached to + * @param \Cake\ORM\Query $surrogate the query having the containments to be attached + * @param array $options options passed to the method `attachTo` + * @return void + */ + protected function _bindNewAssociations($query, $surrogate, $options) + { + $loader = $surrogate->eagerLoader(); + $contain = $loader->contain(); + $matching = $loader->matching(); + $target = $this->_targetTable; + + if (!$contain && !$matching) { + return; + } + + $loader->attachAssociations($query, $target, $options['includeFields']); + $newContain = []; + foreach ($contain as $alias => $value) { + $newContain[$options['aliasPath'] . '.' . $alias] = $value; + } + + $query->contain($newContain); + + foreach ($matching as $alias => $value) { + $query->matching($options['aliasPath'] . '.' . $alias, $value['queryBuilder']); + } + } + + /** + * Returns a single or multiple conditions to be appended to the generated join + * clause for getting the results on the target table. + * + * @param array $options list of options passed to attachTo method + * @return array + * @throws \RuntimeException if the number of columns in the foreignKey do not + * match the number of columns in the source table primaryKey + */ + protected function _joinCondition($options) + { + $conditions = []; + $tAlias = $this->target()->alias(); + $sAlias = $this->source()->alias(); + $foreignKey = (array)$options['foreignKey']; + $primaryKey = (array)$this->_sourceTable->primaryKey(); + + if (count($foreignKey) !== count($primaryKey)) { + $msg = 'Cannot match provided foreignKey for "%s", got "(%s)" but expected foreign key for "(%s)"'; + throw new \RuntimeException(sprintf( + $msg, + $this->_name, + implode(', ', $foreignKey), + implode(', ', $primaryKey) + )); + } + + foreach ($foreignKey as $k => $f) { + $field = sprintf('%s.%s', $sAlias, $primaryKey[$k]); + $value = new IdentifierExpression(sprintf('%s.%s', $tAlias, $f)); + $conditions[$field] = $value; + } + + return $conditions; + } + + /** + * Helper method to infer the requested finder and its options. + * + * Returns the inferred options from the finder $type. + * + * ### Examples: + * + * The following will call the finder 'translations' with the value of the finder as its options: + * $query->contain(['Comments' => ['finder' => ['translations']]]); + * $query->contain(['Comments' => ['finder' => ['translations' => []]]]); + * $query->contain(['Comments' => ['finder' => ['translations' => ['locales' => ['en_US']]]]]); + * + * @param string|array $finderData The finder name or an array having the name as key + * and options as value. + * @return array + */ + protected function _extractFinder($finderData) + { + $finderData = (array)$finderData; + + if (is_numeric(key($finderData))) { + return [current($finderData), []]; + } + + return [key($finderData), current($finderData)]; + } + + /** + * Proxies property retrieval to the target table. This is handy for getting this + * association's associations + * + * @param string $property the property name + * @return \Cake\ORM\Association + * @throws \RuntimeException if no association with such name exists + */ + public function __get($property) + { + return $this->target()->{$property}; + } + + /** + * Proxies the isset call to the target table. This is handy to check if the + * target table has another association with the passed name + * + * @param string $property the property name + * @return bool true if the property exists + */ + public function __isset($property) + { + return isset($this->target()->{$property}); + } + + /** + * Proxies method calls to the target table. + * + * @param string $method name of the method to be invoked + * @param array $argument List of arguments passed to the function + * @return mixed + * @throws \BadMethodCallException + */ + public function __call($method, $argument) + { + return call_user_func_array([$this->target(), $method], $argument); + } + + /** + * Get the relationship type. + * + * @return string Constant of either ONE_TO_ONE, MANY_TO_ONE, ONE_TO_MANY or MANY_TO_MANY. + */ + abstract public function type(); + + /** + * Eager loads a list of records in the target table that are related to another + * set of records in the source table. Source records can specified in two ways: + * first one is by passing a Query object setup to find on the source table and + * the other way is by explicitly passing an array of primary key values from + * the source table. + * + * The required way of passing related source records is controlled by "strategy" + * By default the subquery strategy is used, which requires a query on the source + * When using the select strategy, the list of primary keys will be used. + * + * Returns a closure that should be run for each record returned in a specific + * Query. This callable will be responsible for injecting the fields that are + * related to each specific passed row. + * + * Options array accepts the following keys: + * + * - query: Query object setup to find the source table records + * - keys: List of primary key values from the source table + * - foreignKey: The name of the field used to relate both tables + * - conditions: List of conditions to be passed to the query where() method + * - sort: The direction in which the records should be returned + * - fields: List of fields to select from the target table + * - contain: List of related tables to eager load associated to the target table + * - strategy: The name of strategy to use for finding target table records + * - nestKey: The array key under which results will be found when transforming the row + * + * @param array $options The options for eager loading. + * @return \Closure + */ + abstract public function eagerLoader(array $options); + + /** + * Handles cascading a delete from an associated model. + * + * Each implementing class should handle the cascaded delete as + * required. + * + * @param \Cake\Datasource\EntityInterface $entity The entity that started the cascaded delete. + * @param array $options The options for the original delete. + * @return bool Success + */ + abstract public function cascadeDelete(EntityInterface $entity, array $options = []); + + /** + * Returns whether or not the passed table is the owning side for this + * association. This means that rows in the 'target' table would miss important + * or required information if the row in 'source' did not exist. + * + * @param \Cake\ORM\Table $side The potential Table with ownership + * @return bool + */ + abstract public function isOwningSide(Table $side); + + /** + * Extract the target's association data our from the passed entity and proxies + * the saving operation to the target table. + * + * @param \Cake\Datasource\EntityInterface $entity the data to be saved + * @param array|\ArrayObject $options The options for saving associated data. + * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns + * the saved entity + * @see Table::save() + */ + abstract public function saveAssociated(EntityInterface $entity, array $options = []); } diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 38b27475..bedcda68 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -27,181 +27,190 @@ * * An example of a BelongsTo association would be Article belongs to Author. */ -class BelongsTo extends Association { - - use SelectableAssociationTrait; - -/** - * Sets the name of the field representing the foreign key to the target table. - * If no parameters are passed current field is returned - * - * @param string|null $key the key to be used to link both tables together - * @return string - */ - public function foreignKey($key = null) { - if ($key === null) { - if ($this->_foreignKey === null) { - $this->_foreignKey = $this->_modelKey($this->target()->alias()); - } - return $this->_foreignKey; - } - return parent::foreignKey($key); - } - -/** - * Handle cascading deletes. - * - * BelongsTo associations are never cleared in a cascading delete scenario. - * - * @param \Cake\Datasource\EntityInterface $entity The entity that started the cascaded delete. - * @param array $options The options for the original delete. - * @return bool Success. - */ - public function cascadeDelete(EntityInterface $entity, array $options = []) { - return true; - } - -/** - * Sets the property name that should be filled with data from the target table - * in the source table record. - * If no arguments are passed, currently configured type is returned. - * - * @param string|null $name The property name, use null to read the current property. - * @return string - */ - public function property($name = null) { - if ($name !== null) { - return parent::property($name); - } - if ($name === null && !$this->_propertyName) { - list(, $name) = pluginSplit($this->_name); - $this->_propertyName = Inflector::underscore(Inflector::singularize($name)); - } - return $this->_propertyName; - } - -/** - * Returns whether or not the passed table is the owning side for this - * association. This means that rows in the 'target' table would miss important - * or required information if the row in 'source' did not exist. - * - * @param \Cake\ORM\Table $side The potential Table with ownership - * @return bool - */ - public function isOwningSide(Table $side) { - return $side === $this->target(); - } - -/** - * Get the relationship type. - * - * @return string - */ - public function type() { - return self::MANY_TO_ONE; - } - -/** - * Takes an entity from the source table and looks if there is a field - * matching the property name for this association. The found entity will be - * saved on the target table for this association by passing supplied - * `$options` - * - * @param \Cake\Datasource\EntityInterface $entity an entity from the source table - * @param array|\ArrayObject $options options to be passed to the save method in - * the target table - * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns - * the saved entity - * @see Table::save() - */ - public function saveAssociated(EntityInterface $entity, array $options = []) { - $targetEntity = $entity->get($this->property()); - if (empty($targetEntity) || !($targetEntity instanceof EntityInterface)) { - return $entity; - } - - $table = $this->target(); - $targetEntity = $table->save($targetEntity, $options); - if (!$targetEntity) { - return false; - } - - $properties = array_combine( - (array)$this->foreignKey(), - $targetEntity->extract((array)$table->primaryKey()) - ); - $entity->set($properties, ['guard' => false]); - return $entity; - } - -/** - * Returns a single or multiple conditions to be appended to the generated join - * clause for getting the results on the target table. - * - * @param array $options list of options passed to attachTo method - * @return array - * @throws \RuntimeException if the number of columns in the foreignKey do not - * match the number of columns in the target table primaryKey - */ - protected function _joinCondition($options) { - $conditions = []; - $tAlias = $this->target()->alias(); - $sAlias = $this->_sourceTable->alias(); - $foreignKey = (array)$options['foreignKey']; - $primaryKey = (array)$this->_targetTable->primaryKey(); - - if (count($foreignKey) !== count($primaryKey)) { - $msg = 'Cannot match provided foreignKey for "%s", got "(%s)" but expected foreign key for "(%s)"'; - throw new \RuntimeException(sprintf( - $msg, - $this->_name, - implode(', ', $foreignKey), - implode(', ', $primaryKey) - )); - } - - foreach ($foreignKey as $k => $f) { - $field = sprintf('%s.%s', $tAlias, $primaryKey[$k]); - $value = new IdentifierExpression(sprintf('%s.%s', $sAlias, $f)); - $conditions[$field] = $value; - } - - return $conditions; - } - -/** - * {@inheritDoc} - */ - protected function _linkField($options) { - $links = []; - $name = $this->alias(); - - foreach ((array)$this->target()->primaryKey() as $key) { - $links[] = sprintf('%s.%s', $name, $key); - } - - if (count($links) === 1) { - return $links[0]; - } - - return $links; - } - -/** - * {@inheritDoc} - */ - protected function _buildResultMap($fetchQuery, $options) { - $resultMap = []; - $key = (array)$this->target()->primaryKey(); - - foreach ($fetchQuery->all() as $result) { - $values = []; - foreach ($key as $k) { - $values[] = $result[$k]; - } - $resultMap[implode(';', $values)] = $result; - } - return $resultMap; - } - +class BelongsTo extends Association +{ + + use SelectableAssociationTrait; + + /** + * Sets the name of the field representing the foreign key to the target table. + * If no parameters are passed current field is returned + * + * @param string|null $key the key to be used to link both tables together + * @return string + */ + public function foreignKey($key = null) + { + if ($key === null) { + if ($this->_foreignKey === null) { + $this->_foreignKey = $this->_modelKey($this->target()->alias()); + } + return $this->_foreignKey; + } + return parent::foreignKey($key); + } + + /** + * Handle cascading deletes. + * + * BelongsTo associations are never cleared in a cascading delete scenario. + * + * @param \Cake\Datasource\EntityInterface $entity The entity that started the cascaded delete. + * @param array $options The options for the original delete. + * @return bool Success. + */ + public function cascadeDelete(EntityInterface $entity, array $options = []) + { + return true; + } + + /** + * Sets the property name that should be filled with data from the target table + * in the source table record. + * If no arguments are passed, currently configured type is returned. + * + * @param string|null $name The property name, use null to read the current property. + * @return string + */ + public function property($name = null) + { + if ($name !== null) { + return parent::property($name); + } + if ($name === null && !$this->_propertyName) { + list(, $name) = pluginSplit($this->_name); + $this->_propertyName = Inflector::underscore(Inflector::singularize($name)); + } + return $this->_propertyName; + } + + /** + * Returns whether or not the passed table is the owning side for this + * association. This means that rows in the 'target' table would miss important + * or required information if the row in 'source' did not exist. + * + * @param \Cake\ORM\Table $side The potential Table with ownership + * @return bool + */ + public function isOwningSide(Table $side) + { + return $side === $this->target(); + } + + /** + * Get the relationship type. + * + * @return string + */ + public function type() + { + return self::MANY_TO_ONE; + } + + /** + * Takes an entity from the source table and looks if there is a field + * matching the property name for this association. The found entity will be + * saved on the target table for this association by passing supplied + * `$options` + * + * @param \Cake\Datasource\EntityInterface $entity an entity from the source table + * @param array|\ArrayObject $options options to be passed to the save method in + * the target table + * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns + * the saved entity + * @see Table::save() + */ + public function saveAssociated(EntityInterface $entity, array $options = []) + { + $targetEntity = $entity->get($this->property()); + if (empty($targetEntity) || !($targetEntity instanceof EntityInterface)) { + return $entity; + } + + $table = $this->target(); + $targetEntity = $table->save($targetEntity, $options); + if (!$targetEntity) { + return false; + } + + $properties = array_combine( + (array)$this->foreignKey(), + $targetEntity->extract((array)$table->primaryKey()) + ); + $entity->set($properties, ['guard' => false]); + return $entity; + } + + /** + * Returns a single or multiple conditions to be appended to the generated join + * clause for getting the results on the target table. + * + * @param array $options list of options passed to attachTo method + * @return array + * @throws \RuntimeException if the number of columns in the foreignKey do not + * match the number of columns in the target table primaryKey + */ + protected function _joinCondition($options) + { + $conditions = []; + $tAlias = $this->target()->alias(); + $sAlias = $this->_sourceTable->alias(); + $foreignKey = (array)$options['foreignKey']; + $primaryKey = (array)$this->_targetTable->primaryKey(); + + if (count($foreignKey) !== count($primaryKey)) { + $msg = 'Cannot match provided foreignKey for "%s", got "(%s)" but expected foreign key for "(%s)"'; + throw new \RuntimeException(sprintf( + $msg, + $this->_name, + implode(', ', $foreignKey), + implode(', ', $primaryKey) + )); + } + + foreach ($foreignKey as $k => $f) { + $field = sprintf('%s.%s', $tAlias, $primaryKey[$k]); + $value = new IdentifierExpression(sprintf('%s.%s', $sAlias, $f)); + $conditions[$field] = $value; + } + + return $conditions; + } + + /** + * {@inheritDoc} + */ + protected function _linkField($options) + { + $links = []; + $name = $this->alias(); + + foreach ((array)$this->target()->primaryKey() as $key) { + $links[] = sprintf('%s.%s', $name, $key); + } + + if (count($links) === 1) { + return $links[0]; + } + + return $links; + } + + /** + * {@inheritDoc} + */ + protected function _buildResultMap($fetchQuery, $options) + { + $resultMap = []; + $key = (array)$this->target()->primaryKey(); + + foreach ($fetchQuery->all() as $result) { + $values = []; + foreach ($key as $k) { + $values[] = $result[$k]; + } + $resultMap[implode(';', $values)] = $result; + } + return $resultMap; + } } diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index de07ca1f..ada79443 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -27,961 +27,985 @@ * * An example of a BelongsToMany association would be Article belongs to many Tags. */ -class BelongsToMany extends Association { +class BelongsToMany extends Association +{ - use ExternalAssociationTrait { - _options as _externalOptions; - _buildQuery as _buildBaseQuery; - } + use ExternalAssociationTrait { + _options as _externalOptions; + _buildQuery as _buildBaseQuery; + } -/** + /** * Saving strategy that will only append to the links set * * @var string */ - const SAVE_APPEND = 'append'; + const SAVE_APPEND = 'append'; -/** + /** * Saving strategy that will replace the links with the provided set * * @var string */ - const SAVE_REPLACE = 'replace'; - -/** - * The type of join to be used when adding the association to a query - * - * @var string - */ - protected $_joinType = 'INNER'; - -/** - * The strategy name to be used to fetch associated records. - * - * @var string - */ - protected $_strategy = parent::STRATEGY_SELECT; - -/** - * Junction table instance - * - * @var \Cake\ORM\Table - */ - protected $_junctionTable; - -/** - * Junction table name - * - * @var string - */ - protected $_junctionTableName; - -/** - * The name of the hasMany association from the target table - * to the junction table - * - * @var string - */ - protected $_junctionAssociationName; - -/** - * The name of the property to be set containing data from the junction table - * once a record from the target table is hydrated - * - * @var string - */ - protected $_junctionProperty = '_joinData'; - -/** - * Saving strategy to be used by this association - * - * @var string - */ - protected $_saveStrategy = self::SAVE_REPLACE; - -/** - * The name of the field representing the foreign key to the target table - * - * @var string|array - */ - protected $_targetForeignKey; - -/** - * The table instance for the junction relation. - * - * @var string|\Cake\ORM\Table - */ - protected $_through; - -/** - * Sets the name of the field representing the foreign key to the target table. - * If no parameters are passed current field is returned - * - * @param string|null $key the key to be used to link both tables together - * @return string - */ - public function targetForeignKey($key = null) { - if ($key === null) { - if ($this->_targetForeignKey === null) { - $this->_targetForeignKey = $this->_modelKey($this->target()->alias()); - } - return $this->_targetForeignKey; - } - return $this->_targetForeignKey = $key; - } - -/** - * Sets the table instance for the junction relation. If no arguments - * are passed, the current configured table instance is returned - * - * @param string|\Cake\ORM\Table|null $table Name or instance for the join table - * @return \Cake\ORM\Table - */ - public function junction($table = null) { - $target = $this->target(); - $source = $this->source(); - $sAlias = $source->alias(); - $tAlias = $target->alias(); - - if ($table === null) { - if (!empty($this->_junctionTable)) { - return $this->_junctionTable; - } - - if (!empty($this->_through)) { - $table = $this->_through; - } else { - $tableName = $this->_junctionTableName(); - $tableAlias = Inflector::camelize($tableName); - - $config = []; - if (!TableRegistry::exists($tableAlias)) { - $config = ['table' => $tableName]; - } - $table = TableRegistry::get($tableAlias, $config); - } - } - - if (is_string($table)) { - $table = TableRegistry::get($table); - } - $junctionAlias = $table->alias(); - - if (!$table->association($sAlias)) { - $table - ->belongsTo($sAlias, ['foreignKey' => $this->foreignKey()]) - ->target($source); - } - - if (!$table->association($tAlias)) { - $table - ->belongsTo($tAlias, ['foreignKey' => $this->targetForeignKey()]) - ->target($target); - } - - if (!$target->association($junctionAlias)) { - $target->hasMany($junctionAlias, [ - 'targetTable' => $table, - 'foreignKey' => $this->targetForeignKey(), - ]); - } - - if (!$target->association($sAlias)) { - $target->belongsToMany($sAlias, [ - 'sourceTable' => $target, - 'targetTable' => $source, - 'foreignKey' => $this->targetForeignKey(), - 'targetForeignKey' => $this->foreignKey(), - 'through' => $table - ]); - } - - if (!$source->association($table->alias())) { - $source->hasMany($junctionAlias)->target($table); - } - - return $this->_junctionTable = $table; - } - -/** - * Alters a Query object to include the associated target table data in the final - * result - * - * The options array accept the following keys: - * - * - includeFields: Whether to include target model fields in the result or not - * - foreignKey: The name of the field to use as foreign key, if false none - * will be used - * - conditions: array with a list of conditions to filter the join with - * - fields: a list of fields in the target table to include in the result - * - type: The type of join to be used (e.g. INNER) - * - * @param Query $query the query to be altered to include the target table data - * @param array $options Any extra options or overrides to be taken in account - * @return void - */ - public function attachTo(Query $query, array $options = []) { - parent::attachTo($query, $options); - $junction = $this->junction(); - $belongsTo = $junction->association($this->source()->alias()); - $cond = $belongsTo->_joinCondition(['foreignKey' => $belongsTo->foreignKey()]); - - if (isset($options['includeFields'])) { - $includeFields = $options['includeFields']; - } - - unset($options['queryBuilder']); - $options = ['conditions' => [$cond]] + compact('includeFields'); - $options['foreignKey'] = $this->targetForeignKey(); - $assoc = $this->_targetTable->association($junction->alias()); - $assoc->attachTo($query, $options); - $query->eagerLoader()->addToJoinsMap($junction->alias(), $assoc, true); - } - -/** - * {@inheritDoc} - */ - public function transformRow($row, $nestKey, $joined) { - $alias = $this->junction()->alias(); - if ($joined) { - $row[$this->target()->alias()][$this->_junctionProperty] = $row[$alias]; - unset($row[$alias]); - } - - return parent::transformRow($row, $nestKey, $joined); - } - -/** - * Get the relationship type. - * - * @return string - */ - public function type() { - return self::MANY_TO_MANY; - } - -/** - * Return false as join conditions are defined in the junction table - * - * @param array $options list of options passed to attachTo method - * @return bool false - */ - protected function _joinCondition($options) { - return false; - } - -/** - * Builds an array containing the results from fetchQuery indexed by - * the foreignKey value corresponding to this association. - * - * @param \Cake\ORM\Query $fetchQuery The query to get results from - * @param array $options The options passed to the eager loader - * @return array - * @throws \RuntimeException when the association property is not part of the results set. - */ - protected function _buildResultMap($fetchQuery, $options) { - $resultMap = []; - $key = (array)$options['foreignKey']; - $property = $this->target()->association($this->junction()->alias())->property(); - $hydrated = $fetchQuery->hydrate(); - - foreach ($fetchQuery->all() as $result) { - if (!isset($result[$property])) { - throw new \RuntimeException(sprintf( - '"%s" is missing from the belongsToMany results. Results cannot be created.', - $property - )); - } - $result[$this->_junctionProperty] = $result[$property]; - unset($result[$property]); - - if ($hydrated) { - $result->dirty($this->_junctionProperty, false); - } - - $values = []; - foreach ($key as $k) { - $values[] = $result[$this->_junctionProperty][$k]; - } - $resultMap[implode(';', $values)][] = $result; - } - return $resultMap; - } - -/** - * Clear out the data in the junction table for a given entity. - * - * @param \Cake\Datasource\EntityInterface $entity The entity that started the cascading delete. - * @param array $options The options for the original delete. - * @return bool Success. - */ - public function cascadeDelete(EntityInterface $entity, array $options = []) { - $foreignKey = (array)$this->foreignKey(); - $primaryKey = (array)$this->source()->primaryKey(); - $conditions = []; - - if ($primaryKey) { - $conditions = array_combine($foreignKey, $entity->extract((array)$primaryKey)); - } - - $table = $this->junction(); - $hasMany = $this->source()->association($table->alias()); - if ($this->_cascadeCallbacks) { - foreach ($hasMany->find('all')->where($conditions) as $related) { - $table->delete($related, $options); - } - return true; - } - - $conditions = array_merge($conditions, $hasMany->conditions()); - return $table->deleteAll($conditions); - } - -/** - * Returns boolean true, as both of the tables 'own' rows in the other side - * of the association via the joint table. - * - * @param \Cake\ORM\Table $side The potential Table with ownership - * @return bool - */ - public function isOwningSide(Table $side) { - return true; - } - -/** - * Sets the strategy that should be used for saving. If called with no - * arguments, it will return the currently configured strategy - * - * @param string|null $strategy the strategy name to be used - * @throws \InvalidArgumentException if an invalid strategy name is passed - * @return string the strategy to be used for saving - */ - public function saveStrategy($strategy = null) { - if ($strategy === null) { - return $this->_saveStrategy; - } - if (!in_array($strategy, [self::SAVE_APPEND, self::SAVE_REPLACE])) { - $msg = sprintf('Invalid save strategy "%s"', $strategy); - throw new \InvalidArgumentException($msg); - } - return $this->_saveStrategy = $strategy; - } - -/** - * Takes an entity from the source table and looks if there is a field - * matching the property name for this association. The found entity will be - * saved on the target table for this association by passing supplied - * `$options` - * - * When using the 'append' strategy, this function will only create new links - * between each side of this association. It will not destroy existing ones even - * though they may not be present in the array of entities to be saved. - * - * When using the 'replace' strategy, existing links will be removed and new links - * will be created in the joint table. If there exists links in the database to some - * of the entities intended to be saved by this method, they will be updated, - * not deleted. - * - * @param \Cake\Datasource\EntityInterface $entity an entity from the source table - * @param array|\ArrayObject $options options to be passed to the save method in - * the target table - * @throws \InvalidArgumentException if the property representing the association - * in the parent entity cannot be traversed - * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns - * the saved entity - * @see Table::save() - * @see BelongsToMany::replaceLinks() - */ - public function saveAssociated(EntityInterface $entity, array $options = []) { - $targetEntity = $entity->get($this->property()); - $strategy = $this->saveStrategy(); - - if ($targetEntity === null) { - return false; - } - - if ($targetEntity === [] && $entity->isNew()) { - return $entity; - } - - if ($strategy === self::SAVE_APPEND) { - return $this->_saveTarget($entity, $targetEntity, $options); - } - - if ($this->replaceLinks($entity, $targetEntity, $options)) { - return $entity; - } - - return false; - } - -/** - * Persists each of the entities into the target table and creates links between - * the parent entity and each one of the saved target entities. - * - * @param \Cake\Datasource\EntityInterface $parentEntity the source entity containing the target - * entities to be saved. - * @param array|\Traversable $entities list of entities to persist in target table and to - * link to the parent entity - * @param array $options list of options accepted by Table::save() - * @throws \InvalidArgumentException if the property representing the association - * in the parent entity cannot be traversed - * @return \Cake\Datasource\EntityInterface|bool The parent entity after all links have been - * created if no errors happened, false otherwise - */ - protected function _saveTarget(EntityInterface $parentEntity, $entities, $options) { - $joinAssociations = false; - if (!empty($options['associated'][$this->_junctionProperty]['associated'])) { - $joinAssociations = $options['associated'][$this->_junctionProperty]['associated']; - } - unset($options['associated'][$this->_junctionProperty]); - - if (!(is_array($entities) || $entities instanceof \Traversable)) { - $name = $this->property(); - $message = sprintf('Could not save %s, it cannot be traversed', $name); - throw new \InvalidArgumentException($message); - } - - $table = $this->target(); - $original = $entities; - $persisted = []; - - foreach ($entities as $k => $entity) { - if (!($entity instanceof EntityInterface)) { - break; - } - - if (!empty($options['atomic'])) { - $entity = clone $entity; - } - - if ($table->save($entity, $options)) { - $entities[$k] = $entity; - $persisted[] = $entity; - continue; - } - - if (!empty($options['atomic'])) { - $original[$k]->errors($entity->errors()); - return false; - } - } - - $options['associated'] = $joinAssociations; - $success = $this->_saveLinks($parentEntity, $persisted, $options); - if (!$success && !empty($options['atomic'])) { - $parentEntity->set($this->property(), $original); - return false; - } - - $parentEntity->set($this->property(), $entities); - return $parentEntity; - } - -/** - * Creates links between the source entity and each of the passed target entities - * - * @param \Cake\Datasource\EntityInterface $sourceEntity the entity from source table in this - * association - * @param array $targetEntities list of entities to link to link to the source entity using the - * junction table - * @param array $options list of options accepted by Table::save() - * @return bool success - */ - protected function _saveLinks(EntityInterface $sourceEntity, $targetEntities, $options) { - $target = $this->target(); - $junction = $this->junction(); - $source = $this->source(); - $entityClass = $junction->entityClass(); - $belongsTo = $junction->association($target->alias()); - $foreignKey = (array)$this->foreignKey(); - $assocForeignKey = (array)$belongsTo->foreignKey(); - $targetPrimaryKey = (array)$target->primaryKey(); - $sourcePrimaryKey = (array)$source->primaryKey(); - $jointProperty = $this->_junctionProperty; - $junctionAlias = $junction->alias(); - - foreach ($targetEntities as $e) { - $joint = $e->get($jointProperty); - if (!$joint || !($joint instanceof EntityInterface)) { - $joint = new $entityClass([], ['markNew' => true, 'source' => $junctionAlias]); - } - - $joint->set(array_combine( - $foreignKey, - $sourceEntity->extract($sourcePrimaryKey) - ), ['guard' => false]); - $joint->set(array_combine($assocForeignKey, $e->extract($targetPrimaryKey)), ['guard' => false]); - $saved = $junction->save($joint, $options); - - if (!$saved && !empty($options['atomic'])) { - return false; - } - - $e->set($jointProperty, $joint); - $e->dirty($jointProperty, false); - } - - return true; - } - -/** - * Associates the source entity to each of the target entities provided by - * creating links in the junction table. Both the source entity and each of - * the target entities are assumed to be already persisted, if the are marked - * as new or their status is unknown, an exception will be thrown. - * - * When using this method, all entities in `$targetEntities` will be appended to - * the source entity's property corresponding to this association object. - * - * This method does not check link uniqueness. - * - * ### Example: - * - * {{{ - * $newTags = $tags->find('relevant')->execute(); - * $articles->association('tags')->link($article, $newTags); - * }}} - * - * `$article->get('tags')` will contain all tags in `$newTags` after liking - * - * @param \Cake\Datasource\EntityInterface $sourceEntity the row belonging to the `source` side - * of this association - * @param array $targetEntities list of entities belonging to the `target` side - * of this association - * @param array $options list of options to be passed to the save method - * @throws \InvalidArgumentException when any of the values in $targetEntities is - * detected to not be already persisted - * @return bool true on success, false otherwise - */ - public function link(EntityInterface $sourceEntity, array $targetEntities, array $options = []) { - $this->_checkPersistenceStatus($sourceEntity, $targetEntities); - $property = $this->property(); - $links = $sourceEntity->get($property) ?: []; - $links = array_merge($links, $targetEntities); - $sourceEntity->set($property, $links); - - return $this->junction()->connection()->transactional( - function () use ($sourceEntity, $targetEntities, $options) { - return $this->_saveLinks($sourceEntity, $targetEntities, $options); - } - ); - } - -/** - * Removes all links between the passed source entity and each of the provided - * target entities. This method assumes that all passed objects are already persisted - * in the database and that each of them contain a primary key value. - * - * By default this method will also unset each of the entity objects stored inside - * the source entity. - * - * ### Example: - * - * {{{ - * $article->tags = [$tag1, $tag2, $tag3, $tag4]; - * $tags = [$tag1, $tag2, $tag3]; - * $articles->association('tags')->unlink($article, $tags); - * }}} - * - * `$article->get('tags')` will contain only `[$tag4]` after deleting in the database - * - * @param \Cake\Datasource\EntityInterface $sourceEntity an entity persisted in the source table for - * this association - * @param array $targetEntities list of entities persisted in the target table for - * this association - * @param bool $cleanProperty whether or not to remove all the objects in $targetEntities - * that are stored in $sourceEntity - * @throws \InvalidArgumentException if non persisted entities are passed or if - * any of them is lacking a primary key value - * @return void - */ - public function unlink(EntityInterface $sourceEntity, array $targetEntities, $cleanProperty = true) { - $this->_checkPersistenceStatus($sourceEntity, $targetEntities); - $property = $this->property(); - - $this->junction()->connection()->transactional( - function () use ($sourceEntity, $targetEntities) { - $links = $this->_collectJointEntities($sourceEntity, $targetEntities); - foreach ($links as $entity) { - $this->_junctionTable->delete($entity); - } - } - ); - - $existing = $sourceEntity->get($property) ?: []; - if (!$cleanProperty || empty($existing)) { - return; - } - - $storage = new \SplObjectStorage; - foreach ($targetEntities as $e) { - $storage->attach($e); - } - - foreach ($existing as $k => $e) { - if ($storage->contains($e)) { - unset($existing[$k]); - } - } - - $sourceEntity->set($property, array_values($existing)); - $sourceEntity->dirty($property, false); - } - -/** - * Replaces existing association links between the source entity and the target - * with the ones passed. This method does a smart cleanup, links that are already - * persisted and present in `$targetEntities` will not be deleted, new links will - * be created for the passed target entities that are not already in the database - * and the rest will be removed. - * - * For example, if an article is linked to tags 'cake' and 'framework' and you pass - * to this method an array containing the entities for tags 'cake', 'php' and 'awesome', - * only the link for cake will be kept in database, the link for 'framework' will be - * deleted and the links for 'php' and 'awesome' will be created. - * - * Existing links are not deleted and created again, they are either left untouched - * or updated so that potential extra information stored in the joint row is not - * lost. Updating the link row can be done by making sure the corresponding passed - * target entity contains the joint property with its primary key and any extra - * information to be stored. - * - * On success, the passed `$sourceEntity` will contain `$targetEntities` as value - * in the corresponding property for this association. - * - * This method assumes that links between both the source entity and each of the - * target entities are unique. That is, for any given row in the source table there - * can only be one link in the junction table pointing to any other given row in - * the target table. - * - * Additional options for new links to be saved can be passed in the third argument, - * check `Table::save()` for information on the accepted options. - * - * ### Example: - * - * {{{ - * $article->tags = [$tag1, $tag2, $tag3, $tag4]; - * $articles->save($article); - * $tags = [$tag1, $tag3]; - * $articles->association('tags')->replaceLinks($article, $tags); - * }}} - * - * `$article->get('tags')` will contain only `[$tag1, $tag3]` at the end - * - * @param \Cake\Datasource\EntityInterface $sourceEntity an entity persisted in the source table for - * this association - * @param array $targetEntities list of entities from the target table to be linked - * @param array $options list of options to be passed to `save` persisting or - * updating new links - * @throws \InvalidArgumentException if non persisted entities are passed or if - * any of them is lacking a primary key value - * @return bool success - */ - public function replaceLinks(EntityInterface $sourceEntity, array $targetEntities, array $options = []) { - $primaryKey = (array)$this->source()->primaryKey(); - $primaryValue = $sourceEntity->extract($primaryKey); - - if (count(array_filter($primaryValue, 'strlen')) !== count($primaryKey)) { - $message = 'Could not find primary key value for source entity'; - throw new \InvalidArgumentException($message); - } - - return $this->junction()->connection()->transactional( - function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { - $foreignKey = (array)$this->foreignKey(); - $hasMany = $this->source()->association($this->_junctionTable->alias()); - $existing = $hasMany->find('all') - ->where(array_combine($foreignKey, $primaryValue)); - - $jointEntities = $this->_collectJointEntities($sourceEntity, $targetEntities); - $inserts = $this->_diffLinks($existing, $jointEntities, $targetEntities); - - if ($inserts && !$this->_saveTarget($sourceEntity, $inserts, $options)) { - return false; - } - - $property = $this->property(); - - if (count($inserts)) { - $inserted = array_combine( - array_keys($inserts), - (array)$sourceEntity->get($property) - ); - $targetEntities = $inserted + $targetEntities; - } - - ksort($targetEntities); - $sourceEntity->set($property, array_values($targetEntities)); - $sourceEntity->dirty($property, false); - return true; - } - ); - } - -/** - * Helper method used to delete the difference between the links passed in - * `$existing` and `$jointEntities`. This method will return the values from - * `$targetEntities` that were not deleted from calculating the difference. - * - * @param \Cake\ORM\Query $existing a query for getting existing links - * @param array $jointEntities link entities that should be persisted - * @param array $targetEntities entities in target table that are related to - * the `$jointEntitites` - * @return array - */ - protected function _diffLinks($existing, $jointEntities, $targetEntities) { - $junction = $this->junction(); - $target = $this->target(); - $belongsTo = $junction->association($target->alias()); - $foreignKey = (array)$this->foreignKey(); - $assocForeignKey = (array)$belongsTo->foreignKey(); - - $keys = array_merge($foreignKey, $assocForeignKey); - $deletes = $indexed = $present = []; - - foreach ($jointEntities as $i => $entity) { - $indexed[$i] = $entity->extract($keys); - $present[$i] = array_values($entity->extract($assocForeignKey)); - } - - foreach ($existing as $result) { - $fields = $result->extract($keys); - $found = false; - foreach ($indexed as $i => $data) { - if ($fields === $data) { - unset($indexed[$i]); - $found = true; - break; - } - } - - if (!$found) { - $deletes[] = $result; - } - } - - $primary = (array)$target->primaryKey(); - $jointProperty = $this->_junctionProperty; - foreach ($targetEntities as $k => $entity) { - $key = array_values($entity->extract($primary)); - foreach ($present as $i => $data) { - if ($key === $data && !$entity->get($jointProperty)) { - unset($targetEntities[$k], $present[$i]); - break; - } - } - } - - if ($deletes) { - foreach ($deletes as $entity) { - $junction->delete($entity); - } - } - - return $targetEntities; - } - -/** - * Throws an exception should any of the passed entities is not persisted. - * - * @param \Cake\ORM\Entity $sourceEntity the row belonging to the `source` side - * of this association - * @param array $targetEntities list of entities belonging to the `target` side - * of this association - * @return bool - * @throws \InvalidArgumentException - */ - protected function _checkPersistenceStatus($sourceEntity, array $targetEntities) { - if ($sourceEntity->isNew()) { - $error = 'Source entity needs to be persisted before proceeding'; - throw new \InvalidArgumentException($error); - } - - foreach ($targetEntities as $entity) { - if ($entity->isNew()) { - $error = 'Cannot link not persisted entities'; - throw new \InvalidArgumentException($error); - } - } - - return true; - } - -/** - * Returns the list of joint entities that exist between the source entity - * and each of the passed target entities - * - * @param \Cake\Datasource\EntityInterface $sourceEntity The row belonging to the source side - * of this association. - * @param array $targetEntities The rows belonging to the target side of this - * association. - * @throws \InvalidArgumentException if any of the entities is lacking a primary - * key value - * @return array - */ - protected function _collectJointEntities($sourceEntity, $targetEntities) { - $target = $this->target(); - $source = $this->source(); - $junction = $this->junction(); - $jointProperty = $this->_junctionProperty; - $primary = (array)$target->primaryKey(); - - $result = []; - $missing = []; - - foreach ($targetEntities as $entity) { - $joint = $entity->get($jointProperty); - - if (!$joint || !($joint instanceof EntityInterface)) { - $missing[] = $entity->extract($primary); - continue; - } - - $result[] = $joint; - } - - if (empty($missing)) { - return $result; - } - - $belongsTo = $junction->association($target->alias()); - $hasMany = $source->association($junction->alias()); - $foreignKey = (array)$this->foreignKey(); - $assocForeignKey = (array)$belongsTo->foreignKey(); - $sourceKey = $sourceEntity->extract((array)$source->primaryKey()); - - foreach ($missing as $key) { - $unions[] = $hasMany->find('all') - ->where(array_combine($foreignKey, $sourceKey)) - ->andWhere(array_combine($assocForeignKey, $key)); - } - - $query = array_shift($unions); - foreach ($unions as $q) { - $query->union($q); - } - - return array_merge($result, $query->toArray()); - } - -/** - * Auxiliary function to construct a new Query object to return all the records - * in the target table that are associated to those specified in $options from - * the source table. - * - * This is used for eager loading records on the target table based on conditions. - * - * @param array $options options accepted by eagerLoader() - * @return \Cake\ORM\Query - * @throws \InvalidArgumentException When a key is required for associations but not selected. - */ - protected function _buildQuery($options) { - $name = $this->_junctionAssociationName(); - $query = $this->_buildBaseQuery($options); - $joins = $query->join() ?: []; - $keys = $this->_linkField($options); - - $matching = [ - $name => [ - 'table' => $this->junction()->table(), - 'conditions' => $keys, - 'type' => 'INNER' - ] - ]; - - $assoc = $this->target()->association($name); - $query - ->join($matching + $joins, [], true) - ->autoFields($query->clause('select') === []) - ->select($query->aliasFields((array)$assoc->foreignKey(), $name)); - - $query->eagerLoader()->addToJoinsMap($name, $assoc); - $assoc->attachTo($query); - return $query; - } - -/** - * Generates a string used as a table field that contains the values upon - * which the filter should be applied - * - * @param array $options the options to use for getting the link field. - * @return string - */ - protected function _linkField($options) { - $links = []; - $name = $this->_junctionAssociationName(); - - foreach ((array)$options['foreignKey'] as $key) { - $links[] = sprintf('%s.%s', $name, $key); - } - - if (count($links) === 1) { - return $links[0]; - } - - return $links; - } - -/** - * Returns the name of the association from the target table to the junction table, - * this name is used to generate alias in the query and to later on retrieve the - * results. - * - * @return string - */ - protected function _junctionAssociationName() { - if (!$this->_junctionAssociationName) { - $this->_junctionAssociationName = $this->target() - ->association($this->junction()->alias()) - ->name(); - } - return $this->_junctionAssociationName; - } - -/** - * Sets the name of the junction table. - * If no arguments are passed the current configured name is returned. A default - * name based of the associated tables will be generated if none found. - * - * @param string|null $name The name of the junction table. - * @return string - */ - protected function _junctionTableName($name = null) { - if ($name === null) { - if (empty($this->_junctionTableName)) { - $aliases = array_map('\Cake\Utility\Inflector::underscore', [ - $this->source()->alias(), - $this->target()->alias() - ]); - sort($aliases); - $this->_junctionTableName = implode('_', $aliases); - } - return $this->_junctionTableName; - } - return $this->_junctionTableName = $name; - } - -/** - * Parse extra options passed in the constructor. - * - * @param array $opts original list of options passed in constructor - * @return void - */ - protected function _options(array $opts) { - $this->_externalOptions($opts); - if (!empty($opts['targetForeignKey'])) { - $this->targetForeignKey($opts['targetForeignKey']); - } - if (!empty($opts['joinTable'])) { - $this->_junctionTableName($opts['joinTable']); - } - if (!empty($opts['through'])) { - $this->_through = $opts['through']; - } - if (!empty($opts['saveStrategy'])) { - $this->saveStrategy($opts['saveStrategy']); - } - } - + const SAVE_REPLACE = 'replace'; + + /** + * The type of join to be used when adding the association to a query + * + * @var string + */ + protected $_joinType = 'INNER'; + + /** + * The strategy name to be used to fetch associated records. + * + * @var string + */ + protected $_strategy = parent::STRATEGY_SELECT; + + /** + * Junction table instance + * + * @var \Cake\ORM\Table + */ + protected $_junctionTable; + + /** + * Junction table name + * + * @var string + */ + protected $_junctionTableName; + + /** + * The name of the hasMany association from the target table + * to the junction table + * + * @var string + */ + protected $_junctionAssociationName; + + /** + * The name of the property to be set containing data from the junction table + * once a record from the target table is hydrated + * + * @var string + */ + protected $_junctionProperty = '_joinData'; + + /** + * Saving strategy to be used by this association + * + * @var string + */ + protected $_saveStrategy = self::SAVE_REPLACE; + + /** + * The name of the field representing the foreign key to the target table + * + * @var string|array + */ + protected $_targetForeignKey; + + /** + * The table instance for the junction relation. + * + * @var string|\Cake\ORM\Table + */ + protected $_through; + + /** + * Sets the name of the field representing the foreign key to the target table. + * If no parameters are passed current field is returned + * + * @param string|null $key the key to be used to link both tables together + * @return string + */ + public function targetForeignKey($key = null) + { + if ($key === null) { + if ($this->_targetForeignKey === null) { + $this->_targetForeignKey = $this->_modelKey($this->target()->alias()); + } + return $this->_targetForeignKey; + } + return $this->_targetForeignKey = $key; + } + + /** + * Sets the table instance for the junction relation. If no arguments + * are passed, the current configured table instance is returned + * + * @param string|\Cake\ORM\Table|null $table Name or instance for the join table + * @return \Cake\ORM\Table + */ + public function junction($table = null) + { + $target = $this->target(); + $source = $this->source(); + $sAlias = $source->alias(); + $tAlias = $target->alias(); + + if ($table === null) { + if (!empty($this->_junctionTable)) { + return $this->_junctionTable; + } + + if (!empty($this->_through)) { + $table = $this->_through; + } else { + $tableName = $this->_junctionTableName(); + $tableAlias = Inflector::camelize($tableName); + + $config = []; + if (!TableRegistry::exists($tableAlias)) { + $config = ['table' => $tableName]; + } + $table = TableRegistry::get($tableAlias, $config); + } + } + + if (is_string($table)) { + $table = TableRegistry::get($table); + } + $junctionAlias = $table->alias(); + + if (!$table->association($sAlias)) { + $table + ->belongsTo($sAlias, ['foreignKey' => $this->foreignKey()]) + ->target($source); + } + + if (!$table->association($tAlias)) { + $table + ->belongsTo($tAlias, ['foreignKey' => $this->targetForeignKey()]) + ->target($target); + } + + if (!$target->association($junctionAlias)) { + $target->hasMany($junctionAlias, [ + 'targetTable' => $table, + 'foreignKey' => $this->targetForeignKey(), + ]); + } + + if (!$target->association($sAlias)) { + $target->belongsToMany($sAlias, [ + 'sourceTable' => $target, + 'targetTable' => $source, + 'foreignKey' => $this->targetForeignKey(), + 'targetForeignKey' => $this->foreignKey(), + 'through' => $table + ]); + } + + if (!$source->association($table->alias())) { + $source->hasMany($junctionAlias)->target($table); + } + + return $this->_junctionTable = $table; + } + + /** + * Alters a Query object to include the associated target table data in the final + * result + * + * The options array accept the following keys: + * + * - includeFields: Whether to include target model fields in the result or not + * - foreignKey: The name of the field to use as foreign key, if false none + * will be used + * - conditions: array with a list of conditions to filter the join with + * - fields: a list of fields in the target table to include in the result + * - type: The type of join to be used (e.g. INNER) + * + * @param Query $query the query to be altered to include the target table data + * @param array $options Any extra options or overrides to be taken in account + * @return void + */ + public function attachTo(Query $query, array $options = []) + { + parent::attachTo($query, $options); + $junction = $this->junction(); + $belongsTo = $junction->association($this->source()->alias()); + $cond = $belongsTo->_joinCondition(['foreignKey' => $belongsTo->foreignKey()]); + + if (isset($options['includeFields'])) { + $includeFields = $options['includeFields']; + } + + unset($options['queryBuilder']); + $options = ['conditions' => [$cond]] + compact('includeFields'); + $options['foreignKey'] = $this->targetForeignKey(); + $assoc = $this->_targetTable->association($junction->alias()); + $assoc->attachTo($query, $options); + $query->eagerLoader()->addToJoinsMap($junction->alias(), $assoc, true); + } + + /** + * {@inheritDoc} + */ + public function transformRow($row, $nestKey, $joined) + { + $alias = $this->junction()->alias(); + if ($joined) { + $row[$this->target()->alias()][$this->_junctionProperty] = $row[$alias]; + unset($row[$alias]); + } + + return parent::transformRow($row, $nestKey, $joined); + } + + /** + * Get the relationship type. + * + * @return string + */ + public function type() + { + return self::MANY_TO_MANY; + } + + /** + * Return false as join conditions are defined in the junction table + * + * @param array $options list of options passed to attachTo method + * @return bool false + */ + protected function _joinCondition($options) + { + return false; + } + + /** + * Builds an array containing the results from fetchQuery indexed by + * the foreignKey value corresponding to this association. + * + * @param \Cake\ORM\Query $fetchQuery The query to get results from + * @param array $options The options passed to the eager loader + * @return array + * @throws \RuntimeException when the association property is not part of the results set. + */ + protected function _buildResultMap($fetchQuery, $options) + { + $resultMap = []; + $key = (array)$options['foreignKey']; + $property = $this->target()->association($this->junction()->alias())->property(); + $hydrated = $fetchQuery->hydrate(); + + foreach ($fetchQuery->all() as $result) { + if (!isset($result[$property])) { + throw new \RuntimeException(sprintf( + '"%s" is missing from the belongsToMany results. Results cannot be created.', + $property + )); + } + $result[$this->_junctionProperty] = $result[$property]; + unset($result[$property]); + + if ($hydrated) { + $result->dirty($this->_junctionProperty, false); + } + + $values = []; + foreach ($key as $k) { + $values[] = $result[$this->_junctionProperty][$k]; + } + $resultMap[implode(';', $values)][] = $result; + } + return $resultMap; + } + + /** + * Clear out the data in the junction table for a given entity. + * + * @param \Cake\Datasource\EntityInterface $entity The entity that started the cascading delete. + * @param array $options The options for the original delete. + * @return bool Success. + */ + public function cascadeDelete(EntityInterface $entity, array $options = []) + { + $foreignKey = (array)$this->foreignKey(); + $primaryKey = (array)$this->source()->primaryKey(); + $conditions = []; + + if ($primaryKey) { + $conditions = array_combine($foreignKey, $entity->extract((array)$primaryKey)); + } + + $table = $this->junction(); + $hasMany = $this->source()->association($table->alias()); + if ($this->_cascadeCallbacks) { + foreach ($hasMany->find('all')->where($conditions) as $related) { + $table->delete($related, $options); + } + return true; + } + + $conditions = array_merge($conditions, $hasMany->conditions()); + return $table->deleteAll($conditions); + } + + /** + * Returns boolean true, as both of the tables 'own' rows in the other side + * of the association via the joint table. + * + * @param \Cake\ORM\Table $side The potential Table with ownership + * @return bool + */ + public function isOwningSide(Table $side) + { + return true; + } + + /** + * Sets the strategy that should be used for saving. If called with no + * arguments, it will return the currently configured strategy + * + * @param string|null $strategy the strategy name to be used + * @throws \InvalidArgumentException if an invalid strategy name is passed + * @return string the strategy to be used for saving + */ + public function saveStrategy($strategy = null) + { + if ($strategy === null) { + return $this->_saveStrategy; + } + if (!in_array($strategy, [self::SAVE_APPEND, self::SAVE_REPLACE])) { + $msg = sprintf('Invalid save strategy "%s"', $strategy); + throw new \InvalidArgumentException($msg); + } + return $this->_saveStrategy = $strategy; + } + + /** + * Takes an entity from the source table and looks if there is a field + * matching the property name for this association. The found entity will be + * saved on the target table for this association by passing supplied + * `$options` + * + * When using the 'append' strategy, this function will only create new links + * between each side of this association. It will not destroy existing ones even + * though they may not be present in the array of entities to be saved. + * + * When using the 'replace' strategy, existing links will be removed and new links + * will be created in the joint table. If there exists links in the database to some + * of the entities intended to be saved by this method, they will be updated, + * not deleted. + * + * @param \Cake\Datasource\EntityInterface $entity an entity from the source table + * @param array|\ArrayObject $options options to be passed to the save method in + * the target table + * @throws \InvalidArgumentException if the property representing the association + * in the parent entity cannot be traversed + * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns + * the saved entity + * @see Table::save() + * @see BelongsToMany::replaceLinks() + */ + public function saveAssociated(EntityInterface $entity, array $options = []) + { + $targetEntity = $entity->get($this->property()); + $strategy = $this->saveStrategy(); + + if ($targetEntity === null) { + return false; + } + + if ($targetEntity === [] && $entity->isNew()) { + return $entity; + } + + if ($strategy === self::SAVE_APPEND) { + return $this->_saveTarget($entity, $targetEntity, $options); + } + + if ($this->replaceLinks($entity, $targetEntity, $options)) { + return $entity; + } + + return false; + } + + /** + * Persists each of the entities into the target table and creates links between + * the parent entity and each one of the saved target entities. + * + * @param \Cake\Datasource\EntityInterface $parentEntity the source entity containing the target + * entities to be saved. + * @param array|\Traversable $entities list of entities to persist in target table and to + * link to the parent entity + * @param array $options list of options accepted by Table::save() + * @throws \InvalidArgumentException if the property representing the association + * in the parent entity cannot be traversed + * @return \Cake\Datasource\EntityInterface|bool The parent entity after all links have been + * created if no errors happened, false otherwise + */ + protected function _saveTarget(EntityInterface $parentEntity, $entities, $options) + { + $joinAssociations = false; + if (!empty($options['associated'][$this->_junctionProperty]['associated'])) { + $joinAssociations = $options['associated'][$this->_junctionProperty]['associated']; + } + unset($options['associated'][$this->_junctionProperty]); + + if (!(is_array($entities) || $entities instanceof \Traversable)) { + $name = $this->property(); + $message = sprintf('Could not save %s, it cannot be traversed', $name); + throw new \InvalidArgumentException($message); + } + + $table = $this->target(); + $original = $entities; + $persisted = []; + + foreach ($entities as $k => $entity) { + if (!($entity instanceof EntityInterface)) { + break; + } + + if (!empty($options['atomic'])) { + $entity = clone $entity; + } + + if ($table->save($entity, $options)) { + $entities[$k] = $entity; + $persisted[] = $entity; + continue; + } + + if (!empty($options['atomic'])) { + $original[$k]->errors($entity->errors()); + return false; + } + } + + $options['associated'] = $joinAssociations; + $success = $this->_saveLinks($parentEntity, $persisted, $options); + if (!$success && !empty($options['atomic'])) { + $parentEntity->set($this->property(), $original); + return false; + } + + $parentEntity->set($this->property(), $entities); + return $parentEntity; + } + + /** + * Creates links between the source entity and each of the passed target entities + * + * @param \Cake\Datasource\EntityInterface $sourceEntity the entity from source table in this + * association + * @param array $targetEntities list of entities to link to link to the source entity using the + * junction table + * @param array $options list of options accepted by Table::save() + * @return bool success + */ + protected function _saveLinks(EntityInterface $sourceEntity, $targetEntities, $options) + { + $target = $this->target(); + $junction = $this->junction(); + $source = $this->source(); + $entityClass = $junction->entityClass(); + $belongsTo = $junction->association($target->alias()); + $foreignKey = (array)$this->foreignKey(); + $assocForeignKey = (array)$belongsTo->foreignKey(); + $targetPrimaryKey = (array)$target->primaryKey(); + $sourcePrimaryKey = (array)$source->primaryKey(); + $jointProperty = $this->_junctionProperty; + $junctionAlias = $junction->alias(); + + foreach ($targetEntities as $e) { + $joint = $e->get($jointProperty); + if (!$joint || !($joint instanceof EntityInterface)) { + $joint = new $entityClass([], ['markNew' => true, 'source' => $junctionAlias]); + } + + $joint->set(array_combine( + $foreignKey, + $sourceEntity->extract($sourcePrimaryKey) + ), ['guard' => false]); + $joint->set(array_combine($assocForeignKey, $e->extract($targetPrimaryKey)), ['guard' => false]); + $saved = $junction->save($joint, $options); + + if (!$saved && !empty($options['atomic'])) { + return false; + } + + $e->set($jointProperty, $joint); + $e->dirty($jointProperty, false); + } + + return true; + } + + /** + * Associates the source entity to each of the target entities provided by + * creating links in the junction table. Both the source entity and each of + * the target entities are assumed to be already persisted, if the are marked + * as new or their status is unknown, an exception will be thrown. + * + * When using this method, all entities in `$targetEntities` will be appended to + * the source entity's property corresponding to this association object. + * + * This method does not check link uniqueness. + * + * ### Example: + * + * {{{ + * $newTags = $tags->find('relevant')->execute(); + * $articles->association('tags')->link($article, $newTags); + * }}} + * + * `$article->get('tags')` will contain all tags in `$newTags` after liking + * + * @param \Cake\Datasource\EntityInterface $sourceEntity the row belonging to the `source` side + * of this association + * @param array $targetEntities list of entities belonging to the `target` side + * of this association + * @param array $options list of options to be passed to the save method + * @throws \InvalidArgumentException when any of the values in $targetEntities is + * detected to not be already persisted + * @return bool true on success, false otherwise + */ + public function link(EntityInterface $sourceEntity, array $targetEntities, array $options = []) + { + $this->_checkPersistenceStatus($sourceEntity, $targetEntities); + $property = $this->property(); + $links = $sourceEntity->get($property) ?: []; + $links = array_merge($links, $targetEntities); + $sourceEntity->set($property, $links); + + return $this->junction()->connection()->transactional( + function () use ($sourceEntity, $targetEntities, $options) { + return $this->_saveLinks($sourceEntity, $targetEntities, $options); + } + ); + } + + /** + * Removes all links between the passed source entity and each of the provided + * target entities. This method assumes that all passed objects are already persisted + * in the database and that each of them contain a primary key value. + * + * By default this method will also unset each of the entity objects stored inside + * the source entity. + * + * ### Example: + * + * {{{ + * $article->tags = [$tag1, $tag2, $tag3, $tag4]; + * $tags = [$tag1, $tag2, $tag3]; + * $articles->association('tags')->unlink($article, $tags); + * }}} + * + * `$article->get('tags')` will contain only `[$tag4]` after deleting in the database + * + * @param \Cake\Datasource\EntityInterface $sourceEntity an entity persisted in the source table for + * this association + * @param array $targetEntities list of entities persisted in the target table for + * this association + * @param bool $cleanProperty whether or not to remove all the objects in $targetEntities + * that are stored in $sourceEntity + * @throws \InvalidArgumentException if non persisted entities are passed or if + * any of them is lacking a primary key value + * @return void + */ + public function unlink(EntityInterface $sourceEntity, array $targetEntities, $cleanProperty = true) + { + $this->_checkPersistenceStatus($sourceEntity, $targetEntities); + $property = $this->property(); + + $this->junction()->connection()->transactional( + function () use ($sourceEntity, $targetEntities) { + $links = $this->_collectJointEntities($sourceEntity, $targetEntities); + foreach ($links as $entity) { + $this->_junctionTable->delete($entity); + } + } + ); + + $existing = $sourceEntity->get($property) ?: []; + if (!$cleanProperty || empty($existing)) { + return; + } + + $storage = new \SplObjectStorage; + foreach ($targetEntities as $e) { + $storage->attach($e); + } + + foreach ($existing as $k => $e) { + if ($storage->contains($e)) { + unset($existing[$k]); + } + } + + $sourceEntity->set($property, array_values($existing)); + $sourceEntity->dirty($property, false); + } + + /** + * Replaces existing association links between the source entity and the target + * with the ones passed. This method does a smart cleanup, links that are already + * persisted and present in `$targetEntities` will not be deleted, new links will + * be created for the passed target entities that are not already in the database + * and the rest will be removed. + * + * For example, if an article is linked to tags 'cake' and 'framework' and you pass + * to this method an array containing the entities for tags 'cake', 'php' and 'awesome', + * only the link for cake will be kept in database, the link for 'framework' will be + * deleted and the links for 'php' and 'awesome' will be created. + * + * Existing links are not deleted and created again, they are either left untouched + * or updated so that potential extra information stored in the joint row is not + * lost. Updating the link row can be done by making sure the corresponding passed + * target entity contains the joint property with its primary key and any extra + * information to be stored. + * + * On success, the passed `$sourceEntity` will contain `$targetEntities` as value + * in the corresponding property for this association. + * + * This method assumes that links between both the source entity and each of the + * target entities are unique. That is, for any given row in the source table there + * can only be one link in the junction table pointing to any other given row in + * the target table. + * + * Additional options for new links to be saved can be passed in the third argument, + * check `Table::save()` for information on the accepted options. + * + * ### Example: + * + * {{{ + * $article->tags = [$tag1, $tag2, $tag3, $tag4]; + * $articles->save($article); + * $tags = [$tag1, $tag3]; + * $articles->association('tags')->replaceLinks($article, $tags); + * }}} + * + * `$article->get('tags')` will contain only `[$tag1, $tag3]` at the end + * + * @param \Cake\Datasource\EntityInterface $sourceEntity an entity persisted in the source table for + * this association + * @param array $targetEntities list of entities from the target table to be linked + * @param array $options list of options to be passed to `save` persisting or + * updating new links + * @throws \InvalidArgumentException if non persisted entities are passed or if + * any of them is lacking a primary key value + * @return bool success + */ + public function replaceLinks(EntityInterface $sourceEntity, array $targetEntities, array $options = []) + { + $primaryKey = (array)$this->source()->primaryKey(); + $primaryValue = $sourceEntity->extract($primaryKey); + + if (count(array_filter($primaryValue, 'strlen')) !== count($primaryKey)) { + $message = 'Could not find primary key value for source entity'; + throw new \InvalidArgumentException($message); + } + + return $this->junction()->connection()->transactional( + function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { + $foreignKey = (array)$this->foreignKey(); + $hasMany = $this->source()->association($this->_junctionTable->alias()); + $existing = $hasMany->find('all') + ->where(array_combine($foreignKey, $primaryValue)); + + $jointEntities = $this->_collectJointEntities($sourceEntity, $targetEntities); + $inserts = $this->_diffLinks($existing, $jointEntities, $targetEntities); + + if ($inserts && !$this->_saveTarget($sourceEntity, $inserts, $options)) { + return false; + } + + $property = $this->property(); + + if (count($inserts)) { + $inserted = array_combine( + array_keys($inserts), + (array)$sourceEntity->get($property) + ); + $targetEntities = $inserted + $targetEntities; + } + + ksort($targetEntities); + $sourceEntity->set($property, array_values($targetEntities)); + $sourceEntity->dirty($property, false); + return true; + } + ); + } + + /** + * Helper method used to delete the difference between the links passed in + * `$existing` and `$jointEntities`. This method will return the values from + * `$targetEntities` that were not deleted from calculating the difference. + * + * @param \Cake\ORM\Query $existing a query for getting existing links + * @param array $jointEntities link entities that should be persisted + * @param array $targetEntities entities in target table that are related to + * the `$jointEntitites` + * @return array + */ + protected function _diffLinks($existing, $jointEntities, $targetEntities) + { + $junction = $this->junction(); + $target = $this->target(); + $belongsTo = $junction->association($target->alias()); + $foreignKey = (array)$this->foreignKey(); + $assocForeignKey = (array)$belongsTo->foreignKey(); + + $keys = array_merge($foreignKey, $assocForeignKey); + $deletes = $indexed = $present = []; + + foreach ($jointEntities as $i => $entity) { + $indexed[$i] = $entity->extract($keys); + $present[$i] = array_values($entity->extract($assocForeignKey)); + } + + foreach ($existing as $result) { + $fields = $result->extract($keys); + $found = false; + foreach ($indexed as $i => $data) { + if ($fields === $data) { + unset($indexed[$i]); + $found = true; + break; + } + } + + if (!$found) { + $deletes[] = $result; + } + } + + $primary = (array)$target->primaryKey(); + $jointProperty = $this->_junctionProperty; + foreach ($targetEntities as $k => $entity) { + $key = array_values($entity->extract($primary)); + foreach ($present as $i => $data) { + if ($key === $data && !$entity->get($jointProperty)) { + unset($targetEntities[$k], $present[$i]); + break; + } + } + } + + if ($deletes) { + foreach ($deletes as $entity) { + $junction->delete($entity); + } + } + + return $targetEntities; + } + + /** + * Throws an exception should any of the passed entities is not persisted. + * + * @param \Cake\ORM\Entity $sourceEntity the row belonging to the `source` side + * of this association + * @param array $targetEntities list of entities belonging to the `target` side + * of this association + * @return bool + * @throws \InvalidArgumentException + */ + protected function _checkPersistenceStatus($sourceEntity, array $targetEntities) + { + if ($sourceEntity->isNew()) { + $error = 'Source entity needs to be persisted before proceeding'; + throw new \InvalidArgumentException($error); + } + + foreach ($targetEntities as $entity) { + if ($entity->isNew()) { + $error = 'Cannot link not persisted entities'; + throw new \InvalidArgumentException($error); + } + } + + return true; + } + + /** + * Returns the list of joint entities that exist between the source entity + * and each of the passed target entities + * + * @param \Cake\Datasource\EntityInterface $sourceEntity The row belonging to the source side + * of this association. + * @param array $targetEntities The rows belonging to the target side of this + * association. + * @throws \InvalidArgumentException if any of the entities is lacking a primary + * key value + * @return array + */ + protected function _collectJointEntities($sourceEntity, $targetEntities) + { + $target = $this->target(); + $source = $this->source(); + $junction = $this->junction(); + $jointProperty = $this->_junctionProperty; + $primary = (array)$target->primaryKey(); + + $result = []; + $missing = []; + + foreach ($targetEntities as $entity) { + $joint = $entity->get($jointProperty); + + if (!$joint || !($joint instanceof EntityInterface)) { + $missing[] = $entity->extract($primary); + continue; + } + + $result[] = $joint; + } + + if (empty($missing)) { + return $result; + } + + $belongsTo = $junction->association($target->alias()); + $hasMany = $source->association($junction->alias()); + $foreignKey = (array)$this->foreignKey(); + $assocForeignKey = (array)$belongsTo->foreignKey(); + $sourceKey = $sourceEntity->extract((array)$source->primaryKey()); + + foreach ($missing as $key) { + $unions[] = $hasMany->find('all') + ->where(array_combine($foreignKey, $sourceKey)) + ->andWhere(array_combine($assocForeignKey, $key)); + } + + $query = array_shift($unions); + foreach ($unions as $q) { + $query->union($q); + } + + return array_merge($result, $query->toArray()); + } + + /** + * Auxiliary function to construct a new Query object to return all the records + * in the target table that are associated to those specified in $options from + * the source table. + * + * This is used for eager loading records on the target table based on conditions. + * + * @param array $options options accepted by eagerLoader() + * @return \Cake\ORM\Query + * @throws \InvalidArgumentException When a key is required for associations but not selected. + */ + protected function _buildQuery($options) + { + $name = $this->_junctionAssociationName(); + $query = $this->_buildBaseQuery($options); + $joins = $query->join() ?: []; + $keys = $this->_linkField($options); + + $matching = [ + $name => [ + 'table' => $this->junction()->table(), + 'conditions' => $keys, + 'type' => 'INNER' + ] + ]; + + $assoc = $this->target()->association($name); + $query + ->join($matching + $joins, [], true) + ->autoFields($query->clause('select') === []) + ->select($query->aliasFields((array)$assoc->foreignKey(), $name)); + + $query->eagerLoader()->addToJoinsMap($name, $assoc); + $assoc->attachTo($query); + return $query; + } + + /** + * Generates a string used as a table field that contains the values upon + * which the filter should be applied + * + * @param array $options the options to use for getting the link field. + * @return string + */ + protected function _linkField($options) + { + $links = []; + $name = $this->_junctionAssociationName(); + + foreach ((array)$options['foreignKey'] as $key) { + $links[] = sprintf('%s.%s', $name, $key); + } + + if (count($links) === 1) { + return $links[0]; + } + + return $links; + } + + /** + * Returns the name of the association from the target table to the junction table, + * this name is used to generate alias in the query and to later on retrieve the + * results. + * + * @return string + */ + protected function _junctionAssociationName() + { + if (!$this->_junctionAssociationName) { + $this->_junctionAssociationName = $this->target() + ->association($this->junction()->alias()) + ->name(); + } + return $this->_junctionAssociationName; + } + + /** + * Sets the name of the junction table. + * If no arguments are passed the current configured name is returned. A default + * name based of the associated tables will be generated if none found. + * + * @param string|null $name The name of the junction table. + * @return string + */ + protected function _junctionTableName($name = null) + { + if ($name === null) { + if (empty($this->_junctionTableName)) { + $aliases = array_map('\Cake\Utility\Inflector::underscore', [ + $this->source()->alias(), + $this->target()->alias() + ]); + sort($aliases); + $this->_junctionTableName = implode('_', $aliases); + } + return $this->_junctionTableName; + } + return $this->_junctionTableName = $name; + } + + /** + * Parse extra options passed in the constructor. + * + * @param array $opts original list of options passed in constructor + * @return void + */ + protected function _options(array $opts) + { + $this->_externalOptions($opts); + if (!empty($opts['targetForeignKey'])) { + $this->targetForeignKey($opts['targetForeignKey']); + } + if (!empty($opts['joinTable'])) { + $this->_junctionTableName($opts['joinTable']); + } + if (!empty($opts['through'])) { + $this->_through = $opts['through']; + } + if (!empty($opts['saveStrategy'])) { + $this->saveStrategy($opts['saveStrategy']); + } + } } diff --git a/Association/DependentDeleteTrait.php b/Association/DependentDeleteTrait.php index 417ceb8b..bd3027de 100644 --- a/Association/DependentDeleteTrait.php +++ b/Association/DependentDeleteTrait.php @@ -21,35 +21,37 @@ * * Included by HasOne and HasMany association classes. */ -trait DependentDeleteTrait { +trait DependentDeleteTrait +{ -/** - * Cascade a delete to remove dependent records. - * - * This method does nothing if the association is not dependent. - * - * @param \Cake\Datasource\EntityInterface $entity The entity that started the cascaded delete. - * @param array $options The options for the original delete. - * @return bool Success. - */ - public function cascadeDelete(EntityInterface $entity, array $options = []) { - if (!$this->dependent()) { - return true; - } - $table = $this->target(); - $foreignKey = (array)$this->foreignKey(); - $primaryKey = (array)$this->source()->primaryKey(); - $conditions = array_combine($foreignKey, $entity->extract($primaryKey)); + /** + * Cascade a delete to remove dependent records. + * + * This method does nothing if the association is not dependent. + * + * @param \Cake\Datasource\EntityInterface $entity The entity that started the cascaded delete. + * @param array $options The options for the original delete. + * @return bool Success. + */ + public function cascadeDelete(EntityInterface $entity, array $options = []) + { + if (!$this->dependent()) { + return true; + } + $table = $this->target(); + $foreignKey = (array)$this->foreignKey(); + $primaryKey = (array)$this->source()->primaryKey(); + $conditions = array_combine($foreignKey, $entity->extract($primaryKey)); - if ($this->_cascadeCallbacks) { - $query = $this->find('all')->where($conditions)->bufferResults(false); - foreach ($query as $related) { - $table->delete($related, $options); - } - return true; - } + if ($this->_cascadeCallbacks) { + $query = $this->find('all')->where($conditions)->bufferResults(false); + foreach ($query as $related) { + $table->delete($related, $options); + } + return true; + } - $conditions = array_merge($conditions, $this->conditions()); - return $table->deleteAll($conditions); - } + $conditions = array_merge($conditions, $this->conditions()); + return $table->deleteAll($conditions); + } } diff --git a/Association/ExternalAssociationTrait.php b/Association/ExternalAssociationTrait.php index 1613aa1f..466b20cc 100644 --- a/Association/ExternalAssociationTrait.php +++ b/Association/ExternalAssociationTrait.php @@ -20,110 +20,117 @@ * Represents a type of association that that needs to be recovered by performing * an extra query. */ -trait ExternalAssociationTrait { +trait ExternalAssociationTrait +{ - use SelectableAssociationTrait { - _defaultOptions as private _selectableOptions; - } + use SelectableAssociationTrait { + _defaultOptions as private _selectableOptions; + } -/** - * Order in which target records should be returned - * - * @var mixed - */ - protected $_sort; + /** + * Order in which target records should be returned + * + * @var mixed + */ + protected $_sort; -/** - * Whether this association can be expressed directly in a query join - * - * @param array $options custom options key that could alter the return value - * @return bool if the 'matching' key in $option is true then this function - * will return true, false otherwise - */ - public function canBeJoined(array $options = []) { - return !empty($options['matching']); - } + /** + * Whether this association can be expressed directly in a query join + * + * @param array $options custom options key that could alter the return value + * @return bool if the 'matching' key in $option is true then this function + * will return true, false otherwise + */ + public function canBeJoined(array $options = []) + { + return !empty($options['matching']); + } -/** - * Sets the name of the field representing the foreign key to the source table. - * If no parameters are passed current field is returned - * - * @param string|null $key the key to be used to link both tables together - * @return string - */ - public function foreignKey($key = null) { - if ($key === null) { - if ($this->_foreignKey === null) { - $this->_foreignKey = $this->_modelKey($this->source()->alias()); - } - return $this->_foreignKey; - } - return parent::foreignKey($key); - } + /** + * Sets the name of the field representing the foreign key to the source table. + * If no parameters are passed current field is returned + * + * @param string|null $key the key to be used to link both tables together + * @return string + */ + public function foreignKey($key = null) + { + if ($key === null) { + if ($this->_foreignKey === null) { + $this->_foreignKey = $this->_modelKey($this->source()->alias()); + } + return $this->_foreignKey; + } + return parent::foreignKey($key); + } -/** - * Sets the sort order in which target records should be returned. - * If no arguments are passed the currently configured value is returned - * - * @param mixed $sort A find() compatible order clause - * @return mixed - */ - public function sort($sort = null) { - if ($sort !== null) { - $this->_sort = $sort; - } - return $this->_sort; - } + /** + * Sets the sort order in which target records should be returned. + * If no arguments are passed the currently configured value is returned + * + * @param mixed $sort A find() compatible order clause + * @return mixed + */ + public function sort($sort = null) + { + if ($sort !== null) { + $this->_sort = $sort; + } + return $this->_sort; + } -/** - * {@inheritDoc} - */ - public function defaultRowValue($row, $joined) { - $sourceAlias = $this->source()->alias(); - if (isset($row[$sourceAlias])) { - $row[$sourceAlias][$this->property()] = $joined ? null : []; - } - return $row; - } + /** + * {@inheritDoc} + */ + public function defaultRowValue($row, $joined) + { + $sourceAlias = $this->source()->alias(); + if (isset($row[$sourceAlias])) { + $row[$sourceAlias][$this->property()] = $joined ? null : []; + } + return $row; + } -/** - * Returns the default options to use for the eagerLoader - * - * @return array - */ - protected function _defaultOptions() { - return $this->_selectableOptions() + [ - 'sort' => $this->sort() - ]; - } + /** + * Returns the default options to use for the eagerLoader + * + * @return array + */ + protected function _defaultOptions() + { + return $this->_selectableOptions() + [ + 'sort' => $this->sort() + ]; + } -/** - * {@inheritDoc} - */ - protected function _buildResultMap($fetchQuery, $options) { - $resultMap = []; - $key = (array)$options['foreignKey']; - - foreach ($fetchQuery->all() as $result) { - $values = []; - foreach ($key as $k) { - $values[] = $result[$k]; - } - $resultMap[implode(';', $values)][] = $result; - } - return $resultMap; - } + /** + * {@inheritDoc} + */ + protected function _buildResultMap($fetchQuery, $options) + { + $resultMap = []; + $key = (array)$options['foreignKey']; -/** - * Parse extra options passed in the constructor. - * - * @param array $opts original list of options passed in constructor - * @return void - */ - protected function _options(array $opts) { - if (isset($opts['sort'])) { - $this->sort($opts['sort']); - } - } + foreach ($fetchQuery->all() as $result) { + $values = []; + foreach ($key as $k) { + $values[] = $result[$k]; + } + $resultMap[implode(';', $values)][] = $result; + } + return $resultMap; + } + /** + * Parse extra options passed in the constructor. + * + * @param array $opts original list of options passed in constructor + * @return void + */ + protected function _options(array $opts) + { + if (isset($opts['sort'])) { + $this->sort($opts['sort']); + } + } } diff --git a/Association/HasMany.php b/Association/HasMany.php index 46be4435..57540e3c 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -27,122 +27,126 @@ * * An example of a HasMany association would be Author has many Articles. */ -class HasMany extends Association { - - use DependentDeleteTrait; - use ExternalAssociationTrait; - -/** - * The type of join to be used when adding the association to a query - * - * @var string - */ - protected $_joinType = 'INNER'; - -/** - * The strategy name to be used to fetch associated records. - * - * @var string - */ - protected $_strategy = parent::STRATEGY_SELECT; - -/** - * Returns whether or not the passed table is the owning side for this - * association. This means that rows in the 'target' table would miss important - * or required information if the row in 'source' did not exist. - * - * @param \Cake\ORM\Table $side The potential Table with ownership - * @return bool - */ - public function isOwningSide(Table $side) { - return $side === $this->source(); - } - -/** - * Takes an entity from the source table and looks if there is a field - * matching the property name for this association. The found entity will be - * saved on the target table for this association by passing supplied - * `$options` - * - * @param \Cake\Datasource\EntityInterface $entity an entity from the source table - * @param array|\ArrayObject $options options to be passed to the save method in - * the target table - * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns - * the saved entity - * @see Table::save() - * @throws \InvalidArgumentException when the association data cannot be traversed. - */ - public function saveAssociated(EntityInterface $entity, array $options = []) { - $targetEntities = $entity->get($this->property()); - if (empty($targetEntities)) { - return $entity; - } - - if (!is_array($targetEntities) && !($targetEntities instanceof \Traversable)) { - $name = $this->property(); - $message = sprintf('Could not save %s, it cannot be traversed', $name); - throw new \InvalidArgumentException($message); - } - - $properties = array_combine( - (array)$this->foreignKey(), - $entity->extract((array)$this->source()->primaryKey()) - ); - $target = $this->target(); - $original = $targetEntities; - $options['_sourceTable'] = $this->source(); - - foreach ($targetEntities as $k => $targetEntity) { - if (!($targetEntity instanceof EntityInterface)) { - break; - } - - if (!empty($options['atomic'])) { - $targetEntity = clone $targetEntity; - } - - $targetEntity->set($properties, ['guard' => false]); - if ($target->save($targetEntity, $options)) { - $targetEntities[$k] = $targetEntity; - continue; - } - - if (!empty($options['atomic'])) { - $original[$k]->errors($targetEntity->errors()); - $entity->set($this->property(), $original); - return false; - } - } - - $entity->set($this->property(), $targetEntities); - return $entity; - } - -/** - * {@inheritDoc} - */ - protected function _linkField($options) { - $links = []; - $name = $this->alias(); - - foreach ((array)$options['foreignKey'] as $key) { - $links[] = sprintf('%s.%s', $name, $key); - } - - if (count($links) === 1) { - return $links[0]; - } - - return $links; - } - -/** - * Get the relationship type. - * - * @return string - */ - public function type() { - return self::ONE_TO_MANY; - } - +class HasMany extends Association +{ + + use DependentDeleteTrait; + use ExternalAssociationTrait; + + /** + * The type of join to be used when adding the association to a query + * + * @var string + */ + protected $_joinType = 'INNER'; + + /** + * The strategy name to be used to fetch associated records. + * + * @var string + */ + protected $_strategy = parent::STRATEGY_SELECT; + + /** + * Returns whether or not the passed table is the owning side for this + * association. This means that rows in the 'target' table would miss important + * or required information if the row in 'source' did not exist. + * + * @param \Cake\ORM\Table $side The potential Table with ownership + * @return bool + */ + public function isOwningSide(Table $side) + { + return $side === $this->source(); + } + + /** + * Takes an entity from the source table and looks if there is a field + * matching the property name for this association. The found entity will be + * saved on the target table for this association by passing supplied + * `$options` + * + * @param \Cake\Datasource\EntityInterface $entity an entity from the source table + * @param array|\ArrayObject $options options to be passed to the save method in + * the target table + * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns + * the saved entity + * @see Table::save() + * @throws \InvalidArgumentException when the association data cannot be traversed. + */ + public function saveAssociated(EntityInterface $entity, array $options = []) + { + $targetEntities = $entity->get($this->property()); + if (empty($targetEntities)) { + return $entity; + } + + if (!is_array($targetEntities) && !($targetEntities instanceof \Traversable)) { + $name = $this->property(); + $message = sprintf('Could not save %s, it cannot be traversed', $name); + throw new \InvalidArgumentException($message); + } + + $properties = array_combine( + (array)$this->foreignKey(), + $entity->extract((array)$this->source()->primaryKey()) + ); + $target = $this->target(); + $original = $targetEntities; + $options['_sourceTable'] = $this->source(); + + foreach ($targetEntities as $k => $targetEntity) { + if (!($targetEntity instanceof EntityInterface)) { + break; + } + + if (!empty($options['atomic'])) { + $targetEntity = clone $targetEntity; + } + + $targetEntity->set($properties, ['guard' => false]); + if ($target->save($targetEntity, $options)) { + $targetEntities[$k] = $targetEntity; + continue; + } + + if (!empty($options['atomic'])) { + $original[$k]->errors($targetEntity->errors()); + $entity->set($this->property(), $original); + return false; + } + } + + $entity->set($this->property(), $targetEntities); + return $entity; + } + + /** + * {@inheritDoc} + */ + protected function _linkField($options) + { + $links = []; + $name = $this->alias(); + + foreach ((array)$options['foreignKey'] as $key) { + $links[] = sprintf('%s.%s', $name, $key); + } + + if (count($links) === 1) { + return $links[0]; + } + + return $links; + } + + /** + * Get the relationship type. + * + * @return string + */ + public function type() + { + return self::ONE_TO_MANY; + } } diff --git a/Association/HasOne.php b/Association/HasOne.php index b2cbc397..5eef0ff4 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -27,134 +27,141 @@ * * An example of a HasOne association would be User has one Profile. */ -class HasOne extends Association { - - use DependentDeleteTrait; - use SelectableAssociationTrait; - -/** - * Sets the name of the field representing the foreign key to the target table. - * If no parameters are passed current field is returned - * - * @param string|null $key the key to be used to link both tables together - * @return string - */ - public function foreignKey($key = null) { - if ($key === null) { - if ($this->_foreignKey === null) { - $this->_foreignKey = $this->_modelKey($this->source()->alias()); - } - return $this->_foreignKey; - } - return parent::foreignKey($key); - } - -/** - * Sets the property name that should be filled with data from the target table - * in the source table record. - * If no arguments are passed, currently configured type is returned. - * - * @param string|null $name The name of the property. Pass null to read the current value. - * @return string - */ - public function property($name = null) { - if ($name !== null) { - return parent::property($name); - } - if ($name === null && !$this->_propertyName) { - list(, $name) = pluginSplit($this->_name); - $this->_propertyName = Inflector::underscore(Inflector::singularize($name)); - } - return $this->_propertyName; - } - -/** - * Returns whether or not the passed table is the owning side for this - * association. This means that rows in the 'target' table would miss important - * or required information if the row in 'source' did not exist. - * - * @param \Cake\ORM\Table $side The potential Table with ownership - * @return bool - */ - public function isOwningSide(Table $side) { - return $side === $this->source(); - } - -/** - * Get the relationship type. - * - * @return string - */ - public function type() { - return self::ONE_TO_ONE; - } - -/** - * Takes an entity from the source table and looks if there is a field - * matching the property name for this association. The found entity will be - * saved on the target table for this association by passing supplied - * `$options` - * - * @param \Cake\Datasource\EntityInterface $entity an entity from the source table - * @param array|\ArrayObject $options options to be passed to the save method in - * the target table - * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns - * the saved entity - * @see Table::save() - */ - public function saveAssociated(EntityInterface $entity, array $options = []) { - $targetEntity = $entity->get($this->property()); - if (empty($targetEntity) || !($targetEntity instanceof EntityInterface)) { - return $entity; - } - - $properties = array_combine( - (array)$this->foreignKey(), - $entity->extract((array)$this->source()->primaryKey()) - ); - $targetEntity->set($properties, ['guard' => false]); - - if (!$this->target()->save($targetEntity, $options)) { - $targetEntity->unsetProperty(array_keys($properties)); - return false; - } - - return $entity; - } - -/** - * {@inheritDoc} - */ - protected function _linkField($options) { - $links = []; - $name = $this->alias(); - - foreach ((array)$options['foreignKey'] as $key) { - $links[] = sprintf('%s.%s', $name, $key); - } - - if (count($links) === 1) { - return $links[0]; - } - - return $links; - } - -/** - * {@inheritDoc} - */ - protected function _buildResultMap($fetchQuery, $options) { - $resultMap = []; - $key = (array)$options['foreignKey']; - - foreach ($fetchQuery->all() as $result) { - $values = []; - foreach ($key as $k) { - $values[] = $result[$k]; - } - $resultMap[implode(';', $values)] = $result; - } - return $resultMap; - } - +class HasOne extends Association +{ + + use DependentDeleteTrait; + use SelectableAssociationTrait; + + /** + * Sets the name of the field representing the foreign key to the target table. + * If no parameters are passed current field is returned + * + * @param string|null $key the key to be used to link both tables together + * @return string + */ + public function foreignKey($key = null) + { + if ($key === null) { + if ($this->_foreignKey === null) { + $this->_foreignKey = $this->_modelKey($this->source()->alias()); + } + return $this->_foreignKey; + } + return parent::foreignKey($key); + } + + /** + * Sets the property name that should be filled with data from the target table + * in the source table record. + * If no arguments are passed, currently configured type is returned. + * + * @param string|null $name The name of the property. Pass null to read the current value. + * @return string + */ + public function property($name = null) + { + if ($name !== null) { + return parent::property($name); + } + if ($name === null && !$this->_propertyName) { + list(, $name) = pluginSplit($this->_name); + $this->_propertyName = Inflector::underscore(Inflector::singularize($name)); + } + return $this->_propertyName; + } + + /** + * Returns whether or not the passed table is the owning side for this + * association. This means that rows in the 'target' table would miss important + * or required information if the row in 'source' did not exist. + * + * @param \Cake\ORM\Table $side The potential Table with ownership + * @return bool + */ + public function isOwningSide(Table $side) + { + return $side === $this->source(); + } + + /** + * Get the relationship type. + * + * @return string + */ + public function type() + { + return self::ONE_TO_ONE; + } + + /** + * Takes an entity from the source table and looks if there is a field + * matching the property name for this association. The found entity will be + * saved on the target table for this association by passing supplied + * `$options` + * + * @param \Cake\Datasource\EntityInterface $entity an entity from the source table + * @param array|\ArrayObject $options options to be passed to the save method in + * the target table + * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns + * the saved entity + * @see Table::save() + */ + public function saveAssociated(EntityInterface $entity, array $options = []) + { + $targetEntity = $entity->get($this->property()); + if (empty($targetEntity) || !($targetEntity instanceof EntityInterface)) { + return $entity; + } + + $properties = array_combine( + (array)$this->foreignKey(), + $entity->extract((array)$this->source()->primaryKey()) + ); + $targetEntity->set($properties, ['guard' => false]); + + if (!$this->target()->save($targetEntity, $options)) { + $targetEntity->unsetProperty(array_keys($properties)); + return false; + } + + return $entity; + } + + /** + * {@inheritDoc} + */ + protected function _linkField($options) + { + $links = []; + $name = $this->alias(); + + foreach ((array)$options['foreignKey'] as $key) { + $links[] = sprintf('%s.%s', $name, $key); + } + + if (count($links) === 1) { + return $links[0]; + } + + return $links; + } + + /** + * {@inheritDoc} + */ + protected function _buildResultMap($fetchQuery, $options) + { + $resultMap = []; + $key = (array)$options['foreignKey']; + + foreach ($fetchQuery->all() as $result) { + $values = []; + foreach ($key as $k) { + $values[] = $result[$k]; + } + $resultMap[implode(';', $values)] = $result; + } + return $resultMap; + } } diff --git a/Association/SelectableAssociationTrait.php b/Association/SelectableAssociationTrait.php index e1fa2c6e..d3703a77 100644 --- a/Association/SelectableAssociationTrait.php +++ b/Association/SelectableAssociationTrait.php @@ -20,289 +20,299 @@ /** * Represents a type of association that that can be fetched using another query */ -trait SelectableAssociationTrait { - -/** - * Returns true if the eager loading process will require a set of parent table's - * primary keys in order to use them as a filter in the finder query. - * - * @param array $options The options containing the strategy to be used. - * @return bool true if a list of keys will be required - */ - public function requiresKeys(array $options = []) { - $strategy = isset($options['strategy']) ? $options['strategy'] : $this->strategy(); - return $strategy === $this::STRATEGY_SELECT; - } - -/** - * {@inheritDoc} - */ - public function eagerLoader(array $options) { - $options += $this->_defaultOptions(); - $queryBuilder = false; - if (!empty($options['queryBuilder'])) { - $queryBuilder = $options['queryBuilder']; - unset($options['queryBuilder']); - } - - $fetchQuery = $this->_buildQuery($options); - if ($queryBuilder) { - $fetchQuery = $queryBuilder($fetchQuery); - } - $resultMap = $this->_buildResultMap($fetchQuery, $options); - return $this->_resultInjector($fetchQuery, $resultMap, $options); - } - -/** - * Returns the default options to use for the eagerLoader - * - * @return array - */ - protected function _defaultOptions() { - return [ - 'foreignKey' => $this->foreignKey(), - 'conditions' => [], - 'strategy' => $this->strategy(), - 'nestKey' => $this->_name - ]; - } - -/** - * Auxiliary function to construct a new Query object to return all the records - * in the target table that are associated to those specified in $options from - * the source table - * - * @param array $options options accepted by eagerLoader() - * @return \Cake\ORM\Query - * @throws \InvalidArgumentException When a key is required for associations but not selected. - */ - protected function _buildQuery($options) { - $target = $this->target(); - $alias = $target->alias(); - $key = $this->_linkField($options); - $filter = $options['keys']; - $useSubquery = $options['strategy'] === $this::STRATEGY_SUBQUERY; - - $finder = isset($options['finder']) ? $options['finder'] : $this->finder(); - list($finder, $opts) = $this->_extractFinder($finder); - $fetchQuery = $this - ->find($finder, $opts) - ->where($options['conditions']) - ->eagerLoaded(true) - ->hydrate($options['query']->hydrate()); - - if ($useSubquery) { - $filter = $this->_buildSubquery($options['query']); - $fetchQuery = $this->_addFilteringJoin($fetchQuery, $key, $filter); - } else { - $fetchQuery = $this->_addFilteringCondition($fetchQuery, $key, $filter); - } - - if (!empty($options['fields'])) { - $fields = $fetchQuery->aliasFields($options['fields'], $alias); - if (!in_array($key, $fields)) { - throw new \InvalidArgumentException( - sprintf('You are required to select the "%s" field', $key) - ); - } - $fetchQuery->select($fields); - } - - if (!empty($options['sort'])) { - $fetchQuery->order($options['sort']); - } - - if (!empty($options['contain'])) { - $fetchQuery->contain($options['contain']); - } - - if (!empty($options['queryBuilder'])) { - $options['queryBuilder']($fetchQuery); - } - - return $fetchQuery; - } - -/** - * Appends any conditions required to load the relevant set of records in the - * target table query given a filter key and some filtering values when the - * filtering needs to be done using a subquery. - * - * @param \Cake\ORM\Query $query Target table's query - * @param string $key the fields that should be used for filtering - * @param \Cake\ORM\Query $subquery The Subquery to use for filtering - * @return \Cake\ORM\Query - */ - public function _addFilteringJoin($query, $key, $subquery) { - $filter = []; - $aliasedTable = $subquery->repository()->alias(); - - foreach ($subquery->clause('select') as $aliasedField => $field) { - $filter[] = new IdentifierExpression($field); - } - $subquery->select($filter, true); - - if (is_array($key)) { - $conditions = $this->_createTupleCondition($query, $key, $filter, '='); - } else { - $filter = current($filter); - } - - $conditions = isset($conditions) ? $conditions : $query->newExpr([$key => $filter]); - return $query->innerJoin( - [$aliasedTable => $subquery], - $conditions - ); - } - -/** - * Appends any conditions required to load the relevant set of records in the - * target table query given a filter key and some filtering values. - * - * @param \Cake\ORM\Query $query Target table's query - * @param string|array $key the fields that should be used for filtering - * @param mixed $filter the value that should be used to match for $key - * @return \Cake\ORM\Query - */ - protected function _addFilteringCondition($query, $key, $filter) { - if (is_array($key)) { - $conditions = $this->_createTupleCondition($query, $key, $filter, 'IN'); - } - - $conditions = isset($conditions) ? $conditions : [$key . ' IN' => $filter]; - return $query->andWhere($conditions); - } - -/** - * Returns a TupleComparison object that can be used for matching all the fields - * from $keys with the tuple values in $filter using the provided operator. - * - * @param \Cake\ORM\Query $query Target table's query - * @param array $keys the fields that should be used for filtering - * @param mixed $filter the value that should be used to match for $key - * @param string $operator The operator for comparing the tuples - * @return \Cake\Database\Expression\TupleComparison - */ - protected function _createTupleCondition($query, $keys, $filter, $operator) { - $types = []; - $defaults = $query->defaultTypes(); - foreach ($keys as $k) { - if (isset($defaults[$k])) { - $types[] = $defaults[$k]; - } - } - return new TupleComparison($keys, $filter, $types, $operator); - } - -/** - * Generates a string used as a table field that contains the values upon - * which the filter should be applied - * - * @param array $options The options for getting the link field. - * @return string|array - */ - protected abstract function _linkField($options); - -/** - * Builds a query to be used as a condition for filtering records in the - * target table, it is constructed by cloning the original query that was used - * to load records in the source table. - * - * @param \Cake\ORM\Query $query the original query used to load source records - * @return \Cake\ORM\Query - */ - protected function _buildSubquery($query) { - $filterQuery = clone $query; - $filterQuery->autoFields(false); - $filterQuery->mapReduce(null, null, true); - $filterQuery->formatResults(null, true); - $filterQuery->contain([], true); - - if (!$filterQuery->clause('limit')) { - $filterQuery->limit(null); - $filterQuery->order([], true); - $filterQuery->offset(null); - } - - $keys = (array)$query->repository()->primaryKey(); - - if ($this->type() === $this::MANY_TO_ONE) { - $keys = (array)$this->foreignKey(); - } - - $fields = $query->aliasFields($keys); - return $filterQuery->select($fields, true); - } - -/** - * Builds an array containing the results from fetchQuery indexed by - * the foreignKey value corresponding to this association. - * - * @param \Cake\ORM\Query $fetchQuery The query to get results from - * @param array $options The options passed to the eager loader - * @return array - */ - protected abstract function _buildResultMap($fetchQuery, $options); - -/** - * Returns a callable to be used for each row in a query result set - * for injecting the eager loaded rows - * - * @param \Cake\ORM\Query $fetchQuery the Query used to fetch results - * @param array $resultMap an array with the foreignKey as keys and - * the corresponding target table results as value. - * @param array $options The options passed to the eagerLoader method - * @return \Closure - */ - protected function _resultInjector($fetchQuery, $resultMap, $options) { - $source = $this->source(); - $sAlias = $source->alias(); - $keys = $this->type() === $this::MANY_TO_ONE ? - $this->foreignKey() : - $source->primaryKey(); - - $sourceKeys = []; - foreach ((array)$keys as $key) { - $sourceKeys[] = key($fetchQuery->aliasField($key, $sAlias)); - } - - $nestKey = $options['nestKey']; - if (count($sourceKeys) > 1) { - return $this->_multiKeysInjector($resultMap, $sourceKeys, $nestKey); - } - - $sourceKey = $sourceKeys[0]; - return function ($row) use ($resultMap, $sourceKey, $nestKey) { - if (isset($resultMap[$row[$sourceKey]])) { - $row[$nestKey] = $resultMap[$row[$sourceKey]]; - } - return $row; - }; - } - -/** - * Returns a callable to be used for each row in a query result set - * for injecting the eager loaded rows when the matching needs to - * be done with multiple foreign keys - * - * @param array $resultMap A keyed arrays containing the target table - * @param array $sourceKeys An array with aliased keys to match - * @param string $nestKey The key under which results should be nested - * @return \Closure - */ - protected function _multiKeysInjector($resultMap, $sourceKeys, $nestKey) { - return function ($row) use ($resultMap, $sourceKeys, $nestKey) { - $values = []; - foreach ($sourceKeys as $key) { - $values[] = $row[$key]; - } - - $key = implode(';', $values); - if (isset($resultMap[$key])) { - $row[$nestKey] = $resultMap[$key]; - } - return $row; - }; - } - +trait SelectableAssociationTrait +{ + + /** + * Returns true if the eager loading process will require a set of parent table's + * primary keys in order to use them as a filter in the finder query. + * + * @param array $options The options containing the strategy to be used. + * @return bool true if a list of keys will be required + */ + public function requiresKeys(array $options = []) + { + $strategy = isset($options['strategy']) ? $options['strategy'] : $this->strategy(); + return $strategy === $this::STRATEGY_SELECT; + } + + /** + * {@inheritDoc} + */ + public function eagerLoader(array $options) + { + $options += $this->_defaultOptions(); + $queryBuilder = false; + if (!empty($options['queryBuilder'])) { + $queryBuilder = $options['queryBuilder']; + unset($options['queryBuilder']); + } + + $fetchQuery = $this->_buildQuery($options); + if ($queryBuilder) { + $fetchQuery = $queryBuilder($fetchQuery); + } + $resultMap = $this->_buildResultMap($fetchQuery, $options); + return $this->_resultInjector($fetchQuery, $resultMap, $options); + } + + /** + * Returns the default options to use for the eagerLoader + * + * @return array + */ + protected function _defaultOptions() + { + return [ + 'foreignKey' => $this->foreignKey(), + 'conditions' => [], + 'strategy' => $this->strategy(), + 'nestKey' => $this->_name + ]; + } + + /** + * Auxiliary function to construct a new Query object to return all the records + * in the target table that are associated to those specified in $options from + * the source table + * + * @param array $options options accepted by eagerLoader() + * @return \Cake\ORM\Query + * @throws \InvalidArgumentException When a key is required for associations but not selected. + */ + protected function _buildQuery($options) + { + $target = $this->target(); + $alias = $target->alias(); + $key = $this->_linkField($options); + $filter = $options['keys']; + $useSubquery = $options['strategy'] === $this::STRATEGY_SUBQUERY; + + $finder = isset($options['finder']) ? $options['finder'] : $this->finder(); + list($finder, $opts) = $this->_extractFinder($finder); + $fetchQuery = $this + ->find($finder, $opts) + ->where($options['conditions']) + ->eagerLoaded(true) + ->hydrate($options['query']->hydrate()); + + if ($useSubquery) { + $filter = $this->_buildSubquery($options['query']); + $fetchQuery = $this->_addFilteringJoin($fetchQuery, $key, $filter); + } else { + $fetchQuery = $this->_addFilteringCondition($fetchQuery, $key, $filter); + } + + if (!empty($options['fields'])) { + $fields = $fetchQuery->aliasFields($options['fields'], $alias); + if (!in_array($key, $fields)) { + throw new \InvalidArgumentException( + sprintf('You are required to select the "%s" field', $key) + ); + } + $fetchQuery->select($fields); + } + + if (!empty($options['sort'])) { + $fetchQuery->order($options['sort']); + } + + if (!empty($options['contain'])) { + $fetchQuery->contain($options['contain']); + } + + if (!empty($options['queryBuilder'])) { + $options['queryBuilder']($fetchQuery); + } + + return $fetchQuery; + } + + /** + * Appends any conditions required to load the relevant set of records in the + * target table query given a filter key and some filtering values when the + * filtering needs to be done using a subquery. + * + * @param \Cake\ORM\Query $query Target table's query + * @param string $key the fields that should be used for filtering + * @param \Cake\ORM\Query $subquery The Subquery to use for filtering + * @return \Cake\ORM\Query + */ + public function _addFilteringJoin($query, $key, $subquery) + { + $filter = []; + $aliasedTable = $subquery->repository()->alias(); + + foreach ($subquery->clause('select') as $aliasedField => $field) { + $filter[] = new IdentifierExpression($field); + } + $subquery->select($filter, true); + + if (is_array($key)) { + $conditions = $this->_createTupleCondition($query, $key, $filter, '='); + } else { + $filter = current($filter); + } + + $conditions = isset($conditions) ? $conditions : $query->newExpr([$key => $filter]); + return $query->innerJoin( + [$aliasedTable => $subquery], + $conditions + ); + } + + /** + * Appends any conditions required to load the relevant set of records in the + * target table query given a filter key and some filtering values. + * + * @param \Cake\ORM\Query $query Target table's query + * @param string|array $key the fields that should be used for filtering + * @param mixed $filter the value that should be used to match for $key + * @return \Cake\ORM\Query + */ + protected function _addFilteringCondition($query, $key, $filter) + { + if (is_array($key)) { + $conditions = $this->_createTupleCondition($query, $key, $filter, 'IN'); + } + + $conditions = isset($conditions) ? $conditions : [$key . ' IN' => $filter]; + return $query->andWhere($conditions); + } + + /** + * Returns a TupleComparison object that can be used for matching all the fields + * from $keys with the tuple values in $filter using the provided operator. + * + * @param \Cake\ORM\Query $query Target table's query + * @param array $keys the fields that should be used for filtering + * @param mixed $filter the value that should be used to match for $key + * @param string $operator The operator for comparing the tuples + * @return \Cake\Database\Expression\TupleComparison + */ + protected function _createTupleCondition($query, $keys, $filter, $operator) + { + $types = []; + $defaults = $query->defaultTypes(); + foreach ($keys as $k) { + if (isset($defaults[$k])) { + $types[] = $defaults[$k]; + } + } + return new TupleComparison($keys, $filter, $types, $operator); + } + + /** + * Generates a string used as a table field that contains the values upon + * which the filter should be applied + * + * @param array $options The options for getting the link field. + * @return string|array + */ + protected abstract function _linkField($options); + + /** + * Builds a query to be used as a condition for filtering records in the + * target table, it is constructed by cloning the original query that was used + * to load records in the source table. + * + * @param \Cake\ORM\Query $query the original query used to load source records + * @return \Cake\ORM\Query + */ + protected function _buildSubquery($query) + { + $filterQuery = clone $query; + $filterQuery->autoFields(false); + $filterQuery->mapReduce(null, null, true); + $filterQuery->formatResults(null, true); + $filterQuery->contain([], true); + + if (!$filterQuery->clause('limit')) { + $filterQuery->limit(null); + $filterQuery->order([], true); + $filterQuery->offset(null); + } + + $keys = (array)$query->repository()->primaryKey(); + + if ($this->type() === $this::MANY_TO_ONE) { + $keys = (array)$this->foreignKey(); + } + + $fields = $query->aliasFields($keys); + return $filterQuery->select($fields, true); + } + + /** + * Builds an array containing the results from fetchQuery indexed by + * the foreignKey value corresponding to this association. + * + * @param \Cake\ORM\Query $fetchQuery The query to get results from + * @param array $options The options passed to the eager loader + * @return array + */ + protected abstract function _buildResultMap($fetchQuery, $options); + + /** + * Returns a callable to be used for each row in a query result set + * for injecting the eager loaded rows + * + * @param \Cake\ORM\Query $fetchQuery the Query used to fetch results + * @param array $resultMap an array with the foreignKey as keys and + * the corresponding target table results as value. + * @param array $options The options passed to the eagerLoader method + * @return \Closure + */ + protected function _resultInjector($fetchQuery, $resultMap, $options) + { + $source = $this->source(); + $sAlias = $source->alias(); + $keys = $this->type() === $this::MANY_TO_ONE ? + $this->foreignKey() : + $source->primaryKey(); + + $sourceKeys = []; + foreach ((array)$keys as $key) { + $sourceKeys[] = key($fetchQuery->aliasField($key, $sAlias)); + } + + $nestKey = $options['nestKey']; + if (count($sourceKeys) > 1) { + return $this->_multiKeysInjector($resultMap, $sourceKeys, $nestKey); + } + + $sourceKey = $sourceKeys[0]; + return function ($row) use ($resultMap, $sourceKey, $nestKey) { + if (isset($resultMap[$row[$sourceKey]])) { + $row[$nestKey] = $resultMap[$row[$sourceKey]]; + } + return $row; + }; + } + + /** + * Returns a callable to be used for each row in a query result set + * for injecting the eager loaded rows when the matching needs to + * be done with multiple foreign keys + * + * @param array $resultMap A keyed arrays containing the target table + * @param array $sourceKeys An array with aliased keys to match + * @param string $nestKey The key under which results should be nested + * @return \Closure + */ + protected function _multiKeysInjector($resultMap, $sourceKeys, $nestKey) + { + return function ($row) use ($resultMap, $sourceKeys, $nestKey) { + $values = []; + foreach ($sourceKeys as $key) { + $values[] = $row[$key]; + } + + $key = implode(';', $values); + if (isset($resultMap[$key])) { + $row[$nestKey] = $resultMap[$key]; + } + return $row; + }; + } } diff --git a/AssociationCollection.php b/AssociationCollection.php index 0d208c1d..56f5d42b 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -25,247 +25,261 @@ * Contains methods for managing associations, and * ordering operations around saving and deleting. */ -class AssociationCollection { +class AssociationCollection +{ - use AssociationsNormalizerTrait; + use AssociationsNormalizerTrait; -/** - * Stored associations - * - * @var array - */ - protected $_items = []; + /** + * Stored associations + * + * @var array + */ + protected $_items = []; -/** - * Add an association to the collection - * - * If the alias added contains a `.` the part preceding the `.` will be dropped. - * This makes using plugins simpler as the Plugin.Class syntax is frequently used. - * - * @param string $alias The association alias - * @param \Cake\ORM\Association $association The association to add. - * @return \Cake\ORM\Association The association object being added. - */ - public function add($alias, Association $association) { - list(, $alias) = pluginSplit($alias); - return $this->_items[strtolower($alias)] = $association; - } + /** + * Add an association to the collection + * + * If the alias added contains a `.` the part preceding the `.` will be dropped. + * This makes using plugins simpler as the Plugin.Class syntax is frequently used. + * + * @param string $alias The association alias + * @param \Cake\ORM\Association $association The association to add. + * @return \Cake\ORM\Association The association object being added. + */ + public function add($alias, Association $association) + { + list(, $alias) = pluginSplit($alias); + return $this->_items[strtolower($alias)] = $association; + } -/** - * Fetch an attached association by name. - * - * @param string $alias The association alias to get. - * @return \Cake\ORM\Association|null Either the association or null. - */ - public function get($alias) { - $alias = strtolower($alias); - if (isset($this->_items[$alias])) { - return $this->_items[$alias]; - } - return null; - } + /** + * Fetch an attached association by name. + * + * @param string $alias The association alias to get. + * @return \Cake\ORM\Association|null Either the association or null. + */ + public function get($alias) + { + $alias = strtolower($alias); + if (isset($this->_items[$alias])) { + return $this->_items[$alias]; + } + return null; + } -/** - * Fetch an association by property name. - * - * @param string $prop The property to find an association by. - * @return \Cake\ORM\Association|null Either the association or null. - */ - public function getByProperty($prop) { - foreach ($this->_items as $assoc) { - if ($assoc->property() === $prop) { - return $assoc; - } - } - return null; - } + /** + * Fetch an association by property name. + * + * @param string $prop The property to find an association by. + * @return \Cake\ORM\Association|null Either the association or null. + */ + public function getByProperty($prop) + { + foreach ($this->_items as $assoc) { + if ($assoc->property() === $prop) { + return $assoc; + } + } + return null; + } -/** - * Check for an attached association by name. - * - * @param string $alias The association alias to get. - * @return bool Whether or not the association exists. - */ - public function has($alias) { - return isset($this->_items[strtolower($alias)]); - } + /** + * Check for an attached association by name. + * + * @param string $alias The association alias to get. + * @return bool Whether or not the association exists. + */ + public function has($alias) + { + return isset($this->_items[strtolower($alias)]); + } -/** - * Get the names of all the associations in the collection. - * - * @return array - */ - public function keys() { - return array_keys($this->_items); - } + /** + * Get the names of all the associations in the collection. + * + * @return array + */ + public function keys() + { + return array_keys($this->_items); + } -/** - * Get an array of associations matching a specific type. - * - * @param string $class The type of associations you want. For example 'BelongsTo' - * @return array An array of Association objects. - */ - public function type($class) { - $out = array_filter($this->_items, function ($assoc) use ($class) { - list(, $name) = namespaceSplit(get_class($assoc)); - return $class === $name; - }); - return array_values($out); - } + /** + * Get an array of associations matching a specific type. + * + * @param string $class The type of associations you want. For example 'BelongsTo' + * @return array An array of Association objects. + */ + public function type($class) + { + $out = array_filter($this->_items, function ($assoc) use ($class) { + list(, $name) = namespaceSplit(get_class($assoc)); + return $class === $name; + }); + return array_values($out); + } -/** - * Drop/remove an association. - * - * Once removed the association will not longer be reachable - * - * @param string $alias The alias name. - * @return void - */ - public function remove($alias) { - unset($this->_items[strtolower($alias)]); - } + /** + * Drop/remove an association. + * + * Once removed the association will not longer be reachable + * + * @param string $alias The alias name. + * @return void + */ + public function remove($alias) + { + unset($this->_items[strtolower($alias)]); + } -/** - * Remove all registered associations. - * - * Once removed associations will not longer be reachable - * - * @return void - */ - public function removeAll() { - foreach ($this->_items as $alias => $object) { - $this->remove($alias); - } - } + /** + * Remove all registered associations. + * + * Once removed associations will not longer be reachable + * + * @return void + */ + public function removeAll() + { + foreach ($this->_items as $alias => $object) { + $this->remove($alias); + } + } -/** - * Save all the associations that are parents of the given entity. - * - * Parent associations include any association where the given table - * is the owning side. - * - * @param \Cake\ORM\Table $table The table entity is for. - * @param \Cake\ORM\Entity $entity The entity to save associated data for. - * @param array $associations The list of associations to save parents from. - * associations not in this list will not be saved. - * @param array $options The options for the save operation. - * @return bool Success - */ - public function saveParents(Table $table, Entity $entity, $associations, array $options = []) { - if (empty($associations)) { - return true; - } - return $this->_saveAssociations($table, $entity, $associations, $options, false); - } + /** + * Save all the associations that are parents of the given entity. + * + * Parent associations include any association where the given table + * is the owning side. + * + * @param \Cake\ORM\Table $table The table entity is for. + * @param \Cake\ORM\Entity $entity The entity to save associated data for. + * @param array $associations The list of associations to save parents from. + * associations not in this list will not be saved. + * @param array $options The options for the save operation. + * @return bool Success + */ + public function saveParents(Table $table, Entity $entity, $associations, array $options = []) + { + if (empty($associations)) { + return true; + } + return $this->_saveAssociations($table, $entity, $associations, $options, false); + } -/** - * Save all the associations that are children of the given entity. - * - * Child associations include any association where the given table - * is not the owning side. - * - * @param \Cake\ORM\Table $table The table entity is for. - * @param \Cake\ORM\Entity $entity The entity to save associated data for. - * @param array $associations The list of associations to save children from. - * associations not in this list will not be saved. - * @param array $options The options for the save operation. - * @return bool Success - */ - public function saveChildren(Table $table, Entity $entity, array $associations, array $options) { - if (empty($associations)) { - return true; - } - return $this->_saveAssociations($table, $entity, $associations, $options, true); - } + /** + * Save all the associations that are children of the given entity. + * + * Child associations include any association where the given table + * is not the owning side. + * + * @param \Cake\ORM\Table $table The table entity is for. + * @param \Cake\ORM\Entity $entity The entity to save associated data for. + * @param array $associations The list of associations to save children from. + * associations not in this list will not be saved. + * @param array $options The options for the save operation. + * @return bool Success + */ + public function saveChildren(Table $table, Entity $entity, array $associations, array $options) + { + if (empty($associations)) { + return true; + } + return $this->_saveAssociations($table, $entity, $associations, $options, true); + } -/** - * Helper method for saving an association's data. - * - * @param \Cake\ORM\Table $table The table the save is currently operating on - * @param \Cake\ORM\Entity $entity The entity to save - * @param array $associations Array of associations to save. - * @param array $options Original options - * @param bool $owningSide Compared with association classes' - * isOwningSide method. - * @return bool Success - * @throws \InvalidArgumentException When an unknown alias is used. - */ - protected function _saveAssociations($table, $entity, $associations, $options, $owningSide) { - unset($options['associated']); - foreach ($associations as $alias => $nested) { - if (is_int($alias)) { - $alias = $nested; - $nested = []; - } - $relation = $this->get($alias); - if (!$relation) { - $msg = sprintf( - 'Cannot save %s, it is not associated to %s', - $alias, - $table->alias() - ); - throw new \InvalidArgumentException($msg); - } - if ($relation->isOwningSide($table) !== $owningSide) { - continue; - } - if (!$this->_save($relation, $entity, $nested, $options)) { - return false; - } - } - return true; - } - -/** - * Helper method for saving an association's data. - * - * @param \Cake\ORM\Association $association The association object to save with. - * @param \Cake\ORM\Entity $entity The entity to save - * @param array $nested Options for deeper associations - * @param array $options Original options - * @return bool Success - */ - protected function _save($association, $entity, $nested, $options) { - if (!$entity->dirty($association->property())) { - return true; - } - if (!empty($nested)) { - $options = (array)$nested + $options; - } - return (bool)$association->saveAssociated($entity, $options); - } + /** + * Helper method for saving an association's data. + * + * @param \Cake\ORM\Table $table The table the save is currently operating on + * @param \Cake\ORM\Entity $entity The entity to save + * @param array $associations Array of associations to save. + * @param array $options Original options + * @param bool $owningSide Compared with association classes' + * isOwningSide method. + * @return bool Success + * @throws \InvalidArgumentException When an unknown alias is used. + */ + protected function _saveAssociations($table, $entity, $associations, $options, $owningSide) + { + unset($options['associated']); + foreach ($associations as $alias => $nested) { + if (is_int($alias)) { + $alias = $nested; + $nested = []; + } + $relation = $this->get($alias); + if (!$relation) { + $msg = sprintf( + 'Cannot save %s, it is not associated to %s', + $alias, + $table->alias() + ); + throw new \InvalidArgumentException($msg); + } + if ($relation->isOwningSide($table) !== $owningSide) { + continue; + } + if (!$this->_save($relation, $entity, $nested, $options)) { + return false; + } + } + return true; + } -/** - * Cascade a delete across the various associations. - * - * @param \Cake\ORM\Entity $entity The entity to delete associations for. - * @param array $options The options used in the delete operation. - * @return void - */ - public function cascadeDelete(Entity $entity, array $options) { - foreach ($this->_items as $assoc) { - $assoc->cascadeDelete($entity, $options); - } - } + /** + * Helper method for saving an association's data. + * + * @param \Cake\ORM\Association $association The association object to save with. + * @param \Cake\ORM\Entity $entity The entity to save + * @param array $nested Options for deeper associations + * @param array $options Original options + * @return bool Success + */ + protected function _save($association, $entity, $nested, $options) + { + if (!$entity->dirty($association->property())) { + return true; + } + if (!empty($nested)) { + $options = (array)$nested + $options; + } + return (bool)$association->saveAssociated($entity, $options); + } -/** - * Returns an associative array of association names out a mixed - * array. If true is passed, then it returns all association names - * in this collection. - * - * @param bool|array $keys the list of association names to normalize - * @return array - */ - public function normalizeKeys($keys) { - if ($keys === true) { - $keys = $this->keys(); - } + /** + * Cascade a delete across the various associations. + * + * @param \Cake\ORM\Entity $entity The entity to delete associations for. + * @param array $options The options used in the delete operation. + * @return void + */ + public function cascadeDelete(Entity $entity, array $options) + { + foreach ($this->_items as $assoc) { + $assoc->cascadeDelete($entity, $options); + } + } - if (empty($keys)) { - return []; - } + /** + * Returns an associative array of association names out a mixed + * array. If true is passed, then it returns all association names + * in this collection. + * + * @param bool|array $keys the list of association names to normalize + * @return array + */ + public function normalizeKeys($keys) + { + if ($keys === true) { + $keys = $this->keys(); + } - return $this->_normalizeAssociations($keys); - } + if (empty($keys)) { + return []; + } + return $this->_normalizeAssociations($keys); + } } diff --git a/AssociationsNormalizerTrait.php b/AssociationsNormalizerTrait.php index 5eaa040b..a8e1c7e4 100644 --- a/AssociationsNormalizerTrait.php +++ b/AssociationsNormalizerTrait.php @@ -18,49 +18,50 @@ * Contains methods for parsing the associated tables array that is typically * passed to a save operation */ -trait AssociationsNormalizerTrait { +trait AssociationsNormalizerTrait +{ -/** - * Returns an array out of the original passed associations list where dot notation - * is transformed into nested arrays so that they can be parsed by other routines - * - * @param array $associations The array of included associations. - * @return array An array having dot notation transformed into nested arrays - */ - protected function _normalizeAssociations($associations) { - $result = []; - foreach ($associations as $table => $options) { - $pointer =& $result; - - if (is_int($table)) { - $table = $options; - $options = []; - } + /** + * Returns an array out of the original passed associations list where dot notation + * is transformed into nested arrays so that they can be parsed by other routines + * + * @param array $associations The array of included associations. + * @return array An array having dot notation transformed into nested arrays + */ + protected function _normalizeAssociations($associations) + { + $result = []; + foreach ($associations as $table => $options) { + $pointer =& $result; - if (!strpos($table, '.')) { - $result[$table] = $options; - continue; - } + if (is_int($table)) { + $table = $options; + $options = []; + } - $path = explode('.', $table); - $table = array_pop($path); - $first = array_shift($path); - $pointer += [$first => []]; - $pointer =& $pointer[$first]; - $pointer += ['associated' => []]; + if (!strpos($table, '.')) { + $result[$table] = $options; + continue; + } - foreach ($path as $t) { - $pointer += ['associated' => []]; - $pointer['associated'] += [$t => []]; - $pointer['associated'][$t] += ['associated' => []]; - $pointer =& $pointer['associated'][$t]; - } + $path = explode('.', $table); + $table = array_pop($path); + $first = array_shift($path); + $pointer += [$first => []]; + $pointer =& $pointer[$first]; + $pointer += ['associated' => []]; - $pointer['associated'] += [$table => []]; - $pointer['associated'][$table] = $options + $pointer['associated'][$table]; - } + foreach ($path as $t) { + $pointer += ['associated' => []]; + $pointer['associated'] += [$t => []]; + $pointer['associated'][$t] += ['associated' => []]; + $pointer =& $pointer['associated'][$t]; + } - return isset($result['associated']) ? $result['associated'] : $result; - } + $pointer['associated'] += [$table => []]; + $pointer['associated'][$table] = $options + $pointer['associated'][$table]; + } + return isset($result['associated']) ? $result['associated'] : $result; + } } diff --git a/Behavior.php b/Behavior.php index 44112ef4..7b3e808a 100644 --- a/Behavior.php +++ b/Behavior.php @@ -108,286 +108,294 @@ * @see \Cake\ORM\Table::addBehavior() * @see \Cake\Event\EventManager */ -class Behavior implements EventListenerInterface { +class Behavior implements EventListenerInterface +{ - use InstanceConfigTrait; + use InstanceConfigTrait; -/** - * Table instance. - * - * @var \Cake\ORM\Table - */ - protected $_table; + /** + * Table instance. + * + * @var \Cake\ORM\Table + */ + protected $_table; -/** - * Reflection method cache for behaviors. - * - * Stores the reflected method + finder methods per class. - * This prevents reflecting the same class multiple times in a single process. - * - * @var array - */ - protected static $_reflectionCache = []; + /** + * Reflection method cache for behaviors. + * + * Stores the reflected method + finder methods per class. + * This prevents reflecting the same class multiple times in a single process. + * + * @var array + */ + protected static $_reflectionCache = []; -/** - * Default configuration - * - * These are merged with user-provided configuration when the behavior is used. - * - * @var array - */ - protected $_defaultConfig = []; + /** + * Default configuration + * + * These are merged with user-provided configuration when the behavior is used. + * + * @var array + */ + protected $_defaultConfig = []; -/** - * Constructor - * - * Merges config with the default and store in the config property - * - * Does not retain a reference to the Table object. If you need this - * you should override the constructor. - * - * @param \Cake\ORM\Table $table The table this behavior is attached to. - * @param array $config The config for this behavior. - */ - public function __construct(Table $table, array $config = []) { - $config = $this->_resolveMethodAliases( - 'implementedFinders', - $this->_defaultConfig, - $config - ); - $config = $this->_resolveMethodAliases( - 'implementedMethods', - $this->_defaultConfig, - $config - ); - $this->_table = $table; - $this->config($config); - $this->initialize($config); - } + /** + * Constructor + * + * Merges config with the default and store in the config property + * + * Does not retain a reference to the Table object. If you need this + * you should override the constructor. + * + * @param \Cake\ORM\Table $table The table this behavior is attached to. + * @param array $config The config for this behavior. + */ + public function __construct(Table $table, array $config = []) + { + $config = $this->_resolveMethodAliases( + 'implementedFinders', + $this->_defaultConfig, + $config + ); + $config = $this->_resolveMethodAliases( + 'implementedMethods', + $this->_defaultConfig, + $config + ); + $this->_table = $table; + $this->config($config); + $this->initialize($config); + } -/** - * Constructor hook method. - * - * Implement this method to avoid having to overwrite - * the constructor and call parent. - * - * @param array $config The configuration settings provided to this behavior. - * @return void - */ - public function initialize(array $config) { - } + /** + * Constructor hook method. + * + * Implement this method to avoid having to overwrite + * the constructor and call parent. + * + * @param array $config The configuration settings provided to this behavior. + * @return void + */ + public function initialize(array $config) + { + } -/** - * Removes aliased methods that would otherwise be duplicated by userland configuration. - * - * @param string $key The key to filter. - * @param array $defaults The default method mappings. - * @param array $config The customized method mappings. - * @return array A de-duped list of config data. - */ - protected function _resolveMethodAliases($key, $defaults, $config) { - if (!isset($defaults[$key], $config[$key])) { - return $config; - } - if (isset($config[$key]) && $config[$key] === []) { - $this->config($key, [], false); - unset($config[$key]); - return $config; - } + /** + * Removes aliased methods that would otherwise be duplicated by userland configuration. + * + * @param string $key The key to filter. + * @param array $defaults The default method mappings. + * @param array $config The customized method mappings. + * @return array A de-duped list of config data. + */ + protected function _resolveMethodAliases($key, $defaults, $config) + { + if (!isset($defaults[$key], $config[$key])) { + return $config; + } + if (isset($config[$key]) && $config[$key] === []) { + $this->config($key, [], false); + unset($config[$key]); + return $config; + } - $indexed = array_flip($defaults[$key]); - $indexedCustom = array_flip($config[$key]); - foreach ($indexed as $method => $alias) { - if (!isset($indexedCustom[$method])) { - $indexedCustom[$method] = $alias; - } - } - $this->config($key, array_flip($indexedCustom), false); - unset($config[$key]); - return $config; - } + $indexed = array_flip($defaults[$key]); + $indexedCustom = array_flip($config[$key]); + foreach ($indexed as $method => $alias) { + if (!isset($indexedCustom[$method])) { + $indexedCustom[$method] = $alias; + } + } + $this->config($key, array_flip($indexedCustom), false); + unset($config[$key]); + return $config; + } -/** - * verifyConfig - * - * Checks that implemented keys contain values pointing at callable. - * - * @return void - * @throws \Cake\Core\Exception\Exception if config are invalid - */ - public function verifyConfig() { - $keys = ['implementedFinders', 'implementedMethods']; - foreach ($keys as $key) { - if (!isset($this->_config[$key])) { - continue; - } + /** + * verifyConfig + * + * Checks that implemented keys contain values pointing at callable. + * + * @return void + * @throws \Cake\Core\Exception\Exception if config are invalid + */ + public function verifyConfig() + { + $keys = ['implementedFinders', 'implementedMethods']; + foreach ($keys as $key) { + if (!isset($this->_config[$key])) { + continue; + } - foreach ($this->_config[$key] as $method) { - if (!is_callable([$this, $method])) { - throw new Exception(sprintf('The method %s is not callable on class %s', $method, get_class($this))); - } - } - } - } + foreach ($this->_config[$key] as $method) { + if (!is_callable([$this, $method])) { + throw new Exception(sprintf('The method %s is not callable on class %s', $method, get_class($this))); + } + } + } + } -/** - * Gets the Model callbacks this behavior is interested in. - * - * By defining one of the callback methods a behavior is assumed - * to be interested in the related event. - * - * Override this method if you need to add non-conventional event listeners. - * Or if you want your behavior to listen to non-standard events. - * - * @return array - */ - public function implementedEvents() { - $eventMap = [ - 'Model.beforeFind' => 'beforeFind', - 'Model.beforeSave' => 'beforeSave', - 'Model.afterSave' => 'afterSave', - 'Model.beforeDelete' => 'beforeDelete', - 'Model.afterDelete' => 'afterDelete', - 'Model.buildValidator' => 'buildValidator', - 'Model.buildRules' => 'buildRules', - 'Model.beforeRules' => 'beforeRules', - 'Model.afterRules' => 'afterRules', - ]; - $config = $this->config(); - $priority = isset($config['priority']) ? $config['priority'] : null; - $events = []; + /** + * Gets the Model callbacks this behavior is interested in. + * + * By defining one of the callback methods a behavior is assumed + * to be interested in the related event. + * + * Override this method if you need to add non-conventional event listeners. + * Or if you want your behavior to listen to non-standard events. + * + * @return array + */ + public function implementedEvents() + { + $eventMap = [ + 'Model.beforeFind' => 'beforeFind', + 'Model.beforeSave' => 'beforeSave', + 'Model.afterSave' => 'afterSave', + 'Model.beforeDelete' => 'beforeDelete', + 'Model.afterDelete' => 'afterDelete', + 'Model.buildValidator' => 'buildValidator', + 'Model.buildRules' => 'buildRules', + 'Model.beforeRules' => 'beforeRules', + 'Model.afterRules' => 'afterRules', + ]; + $config = $this->config(); + $priority = isset($config['priority']) ? $config['priority'] : null; + $events = []; - foreach ($eventMap as $event => $method) { - if (!method_exists($this, $method)) { - continue; - } - if ($priority === null) { - $events[$event] = $method; - } else { - $events[$event] = [ - 'callable' => $method, - 'priority' => $priority - ]; - } - } - return $events; - } + foreach ($eventMap as $event => $method) { + if (!method_exists($this, $method)) { + continue; + } + if ($priority === null) { + $events[$event] = $method; + } else { + $events[$event] = [ + 'callable' => $method, + 'priority' => $priority + ]; + } + } + return $events; + } -/** - * implementedFinders - * - * Provides an alias->methodname map of which finders a behavior implements. Example: - * - * {{{ - * [ - * 'this' => 'findThis', - * 'alias' => 'findMethodName' - * ] - * }}} - * - * With the above example, a call to `$Table->find('this')` will call `$Behavior->findThis()` - * and a call to `$Table->find('alias')` will call `$Behavior->findMethodName()` - * - * It is recommended, though not required, to define implementedFinders in the config property - * of child classes such that it is not necessary to use reflections to derive the available - * method list. See core behaviors for examples - * - * @return array - */ - public function implementedFinders() { - $methods = $this->config('implementedFinders'); - if (isset($methods)) { - return $methods; - } + /** + * implementedFinders + * + * Provides an alias->methodname map of which finders a behavior implements. Example: + * + * {{{ + * [ + * 'this' => 'findThis', + * 'alias' => 'findMethodName' + * ] + * }}} + * + * With the above example, a call to `$Table->find('this')` will call `$Behavior->findThis()` + * and a call to `$Table->find('alias')` will call `$Behavior->findMethodName()` + * + * It is recommended, though not required, to define implementedFinders in the config property + * of child classes such that it is not necessary to use reflections to derive the available + * method list. See core behaviors for examples + * + * @return array + */ + public function implementedFinders() + { + $methods = $this->config('implementedFinders'); + if (isset($methods)) { + return $methods; + } - return $this->_reflectionCache()['finders']; - } + return $this->_reflectionCache()['finders']; + } -/** - * implementedMethods - * - * Provides an alias->methodname map of which methods a behavior implements. Example: - * - * {{{ - * [ - * 'method' => 'method', - * 'aliasedmethod' => 'somethingElse' - * ] - * }}} - * - * With the above example, a call to `$Table->method()` will call `$Behavior->method()` - * and a call to `$Table->aliasedmethod()` will call `$Behavior->somethingElse()` - * - * It is recommended, though not required, to define implementedFinders in the config property - * of child classes such that it is not necessary to use reflections to derive the available - * method list. See core behaviors for examples - * - * @return array - */ - public function implementedMethods() { - $methods = $this->config('implementedMethods'); - if (isset($methods)) { - return $methods; - } - - return $this->_reflectionCache()['methods']; - } + /** + * implementedMethods + * + * Provides an alias->methodname map of which methods a behavior implements. Example: + * + * {{{ + * [ + * 'method' => 'method', + * 'aliasedmethod' => 'somethingElse' + * ] + * }}} + * + * With the above example, a call to `$Table->method()` will call `$Behavior->method()` + * and a call to `$Table->aliasedmethod()` will call `$Behavior->somethingElse()` + * + * It is recommended, though not required, to define implementedFinders in the config property + * of child classes such that it is not necessary to use reflections to derive the available + * method list. See core behaviors for examples + * + * @return array + */ + public function implementedMethods() + { + $methods = $this->config('implementedMethods'); + if (isset($methods)) { + return $methods; + } -/** - * Gets the methods implemented by this behavior - * - * Uses the implementedEvents() method to exclude callback methods. - * Methods starting with `_` will be ignored, as will methods - * declared on Cake\ORM\Behavior - * - * @return array - */ - protected function _reflectionCache() { - $class = get_class($this); - if (isset(self::$_reflectionCache[$class])) { - return self::$_reflectionCache[$class]; - } + return $this->_reflectionCache()['methods']; + } - $events = $this->implementedEvents(); - $eventMethods = []; - foreach ($events as $e => $binding) { - if (is_array($binding) && isset($binding['callable'])) { - $binding = $binding['callable']; - } - $eventMethods[$binding] = true; - } + /** + * Gets the methods implemented by this behavior + * + * Uses the implementedEvents() method to exclude callback methods. + * Methods starting with `_` will be ignored, as will methods + * declared on Cake\ORM\Behavior + * + * @return array + */ + protected function _reflectionCache() + { + $class = get_class($this); + if (isset(self::$_reflectionCache[$class])) { + return self::$_reflectionCache[$class]; + } - $baseClass = 'Cake\ORM\Behavior'; - if (isset(self::$_reflectionCache[$baseClass])) { - $baseMethods = self::$_reflectionCache[$baseClass]; - } else { - $baseMethods = get_class_methods($baseClass); - self::$_reflectionCache[$baseClass] = $baseMethods; - } + $events = $this->implementedEvents(); + $eventMethods = []; + foreach ($events as $e => $binding) { + if (is_array($binding) && isset($binding['callable'])) { + $binding = $binding['callable']; + } + $eventMethods[$binding] = true; + } - $return = [ - 'finders' => [], - 'methods' => [] - ]; + $baseClass = 'Cake\ORM\Behavior'; + if (isset(self::$_reflectionCache[$baseClass])) { + $baseMethods = self::$_reflectionCache[$baseClass]; + } else { + $baseMethods = get_class_methods($baseClass); + self::$_reflectionCache[$baseClass] = $baseMethods; + } - $reflection = new \ReflectionClass($class); + $return = [ + 'finders' => [], + 'methods' => [] + ]; - foreach ($reflection->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) { - $methodName = $method->getName(); - if (in_array($methodName, $baseMethods) || - isset($eventMethods[$methodName]) - ) { - continue; - } + $reflection = new \ReflectionClass($class); - if (substr($methodName, 0, 4) === 'find') { - $return['finders'][lcfirst(substr($methodName, 4))] = $methodName; - } else { - $return['methods'][$methodName] = $methodName; - } - } + foreach ($reflection->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) { + $methodName = $method->getName(); + if (in_array($methodName, $baseMethods) || + isset($eventMethods[$methodName]) + ) { + continue; + } - return self::$_reflectionCache[$class] = $return; - } + if (substr($methodName, 0, 4) === 'find') { + $return['finders'][lcfirst(substr($methodName, 4))] = $methodName; + } else { + $return['methods'][$methodName] = $methodName; + } + } + return self::$_reflectionCache[$class] = $return; + } } diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index cf76726e..3a80802a 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -29,208 +29,217 @@ * * This class also provides method for checking and dispatching behavior methods. */ -class BehaviorRegistry extends ObjectRegistry { - - use EventManagerTrait; - -/** - * The table using this registry. - * - * @var \Cake\ORM\Table - */ - protected $_table; - -/** - * Method mappings. - * - * @var array - */ - protected $_methodMap = []; - -/** - * Finder method mappings. - * - * @var array - */ - protected $_finderMap = []; - -/** - * Constructor - * - * @param \Cake\ORM\Table $table The table this registry is attached to - */ - public function __construct(Table $table) { - $this->_table = $table; - $this->eventManager($table->eventManager()); - } - -/** - * Resolve a behavior classname. - * - * Part of the template method for Cake\Core\ObjectRegistry::load() - * - * @param string $class Partial classname to resolve. - * @return string|false Either the correct classname or false. - */ - protected function _resolveClassName($class) { - return App::className($class, 'Model/Behavior', 'Behavior'); - } - -/** - * Throws an exception when a behavior is missing. - * - * Part of the template method for Cake\Core\ObjectRegistry::load() - * - * @param string $class The classname that is missing. - * @param string $plugin The plugin the behavior is missing in. - * @return void - * @throws \Cake\ORM\Exception\MissingBehaviorException - */ - protected function _throwMissingClassError($class, $plugin) { - throw new MissingBehaviorException([ - 'class' => $class . 'Behavior', - 'plugin' => $plugin - ]); - } - -/** - * Create the behavior instance. - * - * Part of the template method for Cake\Core\ObjectRegistry::load() - * Enabled behaviors will be registered with the event manager. - * - * @param string $class The classname that is missing. - * @param string $alias The alias of the object. - * @param array $config An array of config to use for the behavior. - * @return Behavior The constructed behavior class. - */ - protected function _create($class, $alias, $config) { - $instance = new $class($this->_table, $config); - $enable = isset($config['enabled']) ? $config['enabled'] : true; - if ($enable) { - $this->eventManager()->attach($instance); - } - $methods = $this->_getMethods($instance, $class, $alias); - $this->_methodMap += $methods['methods']; - $this->_finderMap += $methods['finders']; - return $instance; - } - -/** - * Get the behavior methods and ensure there are no duplicates. - * - * Use the implementedEvents() method to exclude callback methods. - * Methods starting with `_` will be ignored, as will methods - * declared on Cake\ORM\Behavior - * - * @param \Cake\ORM\Behavior $instance The behavior to get methods from. - * @param string $class The classname that is missing. - * @param string $alias The alias of the object. - * @return array A list of implemented finders and methods. - * @throws \LogicException when duplicate methods are connected. - */ - protected function _getMethods(Behavior $instance, $class, $alias) { - $finders = array_change_key_case($instance->implementedFinders()); - $methods = array_change_key_case($instance->implementedMethods()); - - foreach ($finders as $finder => $methodName) { - if (isset($this->_finderMap[$finder]) && $this->has($this->_finderMap[$finder][0])) { - $duplicate = $this->_finderMap[$finder]; - $error = sprintf( - '%s contains duplicate finder "%s" which is already provided by "%s"', - $class, - $finder, - $duplicate[0] - ); - throw new LogicException($error); - } - $finders[$finder] = [$alias, $methodName]; - } - - foreach ($methods as $method => $methodName) { - if (isset($this->_methodMap[$method]) && $this->has($this->_methodMap[$method][0])) { - $duplicate = $this->_methodMap[$method]; - $error = sprintf( - '%s contains duplicate method "%s" which is already provided by "%s"', - $class, - $method, - $duplicate[0] - ); - throw new LogicException($error); - } - $methods[$method] = [$alias, $methodName]; - } - - return compact('methods', 'finders'); - } - -/** - * Check if any loaded behavior implements a method. - * - * Will return true if any behavior provides a public non-finder method - * with the chosen name. - * - * @param string $method The method to check for. - * @return bool - */ - public function hasMethod($method) { - $method = strtolower($method); - return isset($this->_methodMap[$method]); - } - -/** - * Check if any loaded behavior implements the named finder. - * - * Will return true if any behavior provides a public method with - * the chosen name. - * - * @param string $method The method to check for. - * @return bool - */ - public function hasFinder($method) { - $method = strtolower($method); - return isset($this->_finderMap[$method]); - } - -/** - * Invoke a method on a behavior. - * - * @param string $method The method to invoke. - * @param array $args The arguments you want to invoke the method with. - * @return mixed The return value depends on the underlying behavior method. - * @throws \BadMethodCallException When the method is unknown. - */ - public function call($method, array $args = []) { - $method = strtolower($method); - if ($this->hasMethod($method) && $this->has($this->_methodMap[$method][0])) { - list($behavior, $callMethod) = $this->_methodMap[$method]; - return call_user_func_array([$this->_loaded[$behavior], $callMethod], $args); - } - - throw new BadMethodCallException( - sprintf('Cannot call "%s" it does not belong to any attached behavior.', $method) - ); - } - -/** - * Invoke a finder on a behavior. - * - * @param string $type The finder type to invoke. - * @param array $args The arguments you want to invoke the method with. - * @return mixed The return value depends on the underlying behavior method. - * @throws \BadMethodCallException When the method is unknown. - */ - public function callFinder($type, array $args = []) { - $type = strtolower($type); - - if ($this->hasFinder($type) && $this->has($this->_finderMap[$type][0])) { - list($behavior, $callMethod) = $this->_finderMap[$type]; - return call_user_func_array([$this->_loaded[$behavior], $callMethod], $args); - } - - throw new BadMethodCallException( - sprintf('Cannot call finder "%s" it does not belong to any attached behavior.', $type) - ); - } - +class BehaviorRegistry extends ObjectRegistry +{ + + use EventManagerTrait; + + /** + * The table using this registry. + * + * @var \Cake\ORM\Table + */ + protected $_table; + + /** + * Method mappings. + * + * @var array + */ + protected $_methodMap = []; + + /** + * Finder method mappings. + * + * @var array + */ + protected $_finderMap = []; + + /** + * Constructor + * + * @param \Cake\ORM\Table $table The table this registry is attached to + */ + public function __construct(Table $table) + { + $this->_table = $table; + $this->eventManager($table->eventManager()); + } + + /** + * Resolve a behavior classname. + * + * Part of the template method for Cake\Core\ObjectRegistry::load() + * + * @param string $class Partial classname to resolve. + * @return string|false Either the correct classname or false. + */ + protected function _resolveClassName($class) + { + return App::className($class, 'Model/Behavior', 'Behavior'); + } + + /** + * Throws an exception when a behavior is missing. + * + * Part of the template method for Cake\Core\ObjectRegistry::load() + * + * @param string $class The classname that is missing. + * @param string $plugin The plugin the behavior is missing in. + * @return void + * @throws \Cake\ORM\Exception\MissingBehaviorException + */ + protected function _throwMissingClassError($class, $plugin) + { + throw new MissingBehaviorException([ + 'class' => $class . 'Behavior', + 'plugin' => $plugin + ]); + } + + /** + * Create the behavior instance. + * + * Part of the template method for Cake\Core\ObjectRegistry::load() + * Enabled behaviors will be registered with the event manager. + * + * @param string $class The classname that is missing. + * @param string $alias The alias of the object. + * @param array $config An array of config to use for the behavior. + * @return Behavior The constructed behavior class. + */ + protected function _create($class, $alias, $config) + { + $instance = new $class($this->_table, $config); + $enable = isset($config['enabled']) ? $config['enabled'] : true; + if ($enable) { + $this->eventManager()->attach($instance); + } + $methods = $this->_getMethods($instance, $class, $alias); + $this->_methodMap += $methods['methods']; + $this->_finderMap += $methods['finders']; + return $instance; + } + + /** + * Get the behavior methods and ensure there are no duplicates. + * + * Use the implementedEvents() method to exclude callback methods. + * Methods starting with `_` will be ignored, as will methods + * declared on Cake\ORM\Behavior + * + * @param \Cake\ORM\Behavior $instance The behavior to get methods from. + * @param string $class The classname that is missing. + * @param string $alias The alias of the object. + * @return array A list of implemented finders and methods. + * @throws \LogicException when duplicate methods are connected. + */ + protected function _getMethods(Behavior $instance, $class, $alias) + { + $finders = array_change_key_case($instance->implementedFinders()); + $methods = array_change_key_case($instance->implementedMethods()); + + foreach ($finders as $finder => $methodName) { + if (isset($this->_finderMap[$finder]) && $this->has($this->_finderMap[$finder][0])) { + $duplicate = $this->_finderMap[$finder]; + $error = sprintf( + '%s contains duplicate finder "%s" which is already provided by "%s"', + $class, + $finder, + $duplicate[0] + ); + throw new LogicException($error); + } + $finders[$finder] = [$alias, $methodName]; + } + + foreach ($methods as $method => $methodName) { + if (isset($this->_methodMap[$method]) && $this->has($this->_methodMap[$method][0])) { + $duplicate = $this->_methodMap[$method]; + $error = sprintf( + '%s contains duplicate method "%s" which is already provided by "%s"', + $class, + $method, + $duplicate[0] + ); + throw new LogicException($error); + } + $methods[$method] = [$alias, $methodName]; + } + + return compact('methods', 'finders'); + } + + /** + * Check if any loaded behavior implements a method. + * + * Will return true if any behavior provides a public non-finder method + * with the chosen name. + * + * @param string $method The method to check for. + * @return bool + */ + public function hasMethod($method) + { + $method = strtolower($method); + return isset($this->_methodMap[$method]); + } + + /** + * Check if any loaded behavior implements the named finder. + * + * Will return true if any behavior provides a public method with + * the chosen name. + * + * @param string $method The method to check for. + * @return bool + */ + public function hasFinder($method) + { + $method = strtolower($method); + return isset($this->_finderMap[$method]); + } + + /** + * Invoke a method on a behavior. + * + * @param string $method The method to invoke. + * @param array $args The arguments you want to invoke the method with. + * @return mixed The return value depends on the underlying behavior method. + * @throws \BadMethodCallException When the method is unknown. + */ + public function call($method, array $args = []) + { + $method = strtolower($method); + if ($this->hasMethod($method) && $this->has($this->_methodMap[$method][0])) { + list($behavior, $callMethod) = $this->_methodMap[$method]; + return call_user_func_array([$this->_loaded[$behavior], $callMethod], $args); + } + + throw new BadMethodCallException( + sprintf('Cannot call "%s" it does not belong to any attached behavior.', $method) + ); + } + + /** + * Invoke a finder on a behavior. + * + * @param string $type The finder type to invoke. + * @param array $args The arguments you want to invoke the method with. + * @return mixed The return value depends on the underlying behavior method. + * @throws \BadMethodCallException When the method is unknown. + */ + public function callFinder($type, array $args = []) + { + $type = strtolower($type); + + if ($this->hasFinder($type) && $this->has($this->_finderMap[$type][0])) { + list($behavior, $callMethod) = $this->_finderMap[$type]; + return call_user_func_array([$this->_loaded[$behavior], $callMethod], $args); + } + + throw new BadMethodCallException( + sprintf('Cannot call finder "%s" it does not belong to any attached behavior.', $type) + ); + } } diff --git a/EagerLoader.php b/EagerLoader.php index 3f2a8efe..0f063939 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -27,605 +27,621 @@ * required joins and decorating the results so that those associations can be * part of the result set. */ -class EagerLoader { - -/** - * Nested array describing the association to be fetched - * and the options to apply for each of them, if any - * - * @var array - */ - protected $_containments = []; - -/** - * Contains a nested array with the compiled containments tree - * This is a normalized version of the user provided containments array. - * - * @var array - */ - protected $_normalized; - -/** - * List of options accepted by associations in contain() - * index by key for faster access - * - * @var array - */ - protected $_containOptions = [ - 'associations' => 1, - 'foreignKey' => 1, - 'conditions' => 1, - 'fields' => 1, - 'sort' => 1, - 'matching' => 1, - 'queryBuilder' => 1, - 'finder' => 1, - 'joinType' => 1, - 'strategy' => 1 - ]; - -/** - * A list of associations that should be loaded with a separate query - * - * @var array - */ - protected $_loadExternal = []; - -/** - * Contains a list of the association names that are to be eagerly loaded - * - * @var array - */ - protected $_aliasList = []; - -/** - * Another EagerLoader instance that will be used for 'matching' associations. - * - * @var \Cake\ORM\EagerLoader - */ - protected $_matching; - -/** - * A map of table aliases pointing to the association objects they represent - * for the query. - * - * @var array - */ - protected $_joinsMap = []; - -/** - * Sets the list of associations that should be eagerly loaded along for a - * specific table using when a query is provided. The list of associated tables - * passed to this method must have been previously set as associations using the - * Table API. - * - * Associations can be arbitrarily nested using dot notation or nested arrays, - * this allows this object to calculate joins or any additional queries that - * must be executed to bring the required associated data. - * - * Accepted options per passed association: - * - * - foreignKey: Used to set a different field to match both tables, if set to false - * no join conditions will be generated automatically - * - fields: An array with the fields that should be fetched from the association - * - queryBuilder: Equivalent to passing a callable instead of an options array - * - matching: Whether to inform the association class that it should filter the - * main query by the results fetched by that class. - * - joinType: For joinable associations, the SQL join type to use. - * - * @param array|string $associations list of table aliases to be queried. - * When this method is called multiple times it will merge previous list with - * the new one. - * @return array Containments. - */ - public function contain($associations = []) { - if (empty($associations)) { - return $this->_containments; - } - - $associations = (array)$associations; - $associations = $this->_reformatContain($associations, $this->_containments); - $this->_normalized = $this->_loadExternal = null; - $this->_aliasList = []; - return $this->_containments = $associations; - } - -/** - * Adds a new association to the list that will be used to filter the results of - * any given query based on the results of finding records for that association. - * You can pass a dot separated path of associations to this method as its first - * parameter, this will translate in setting all those associations with the - * `matching` option. - * - * If called with no arguments it will return the current tree of associations to - * be matched. - * - * @param string|null $assoc A single association or a dot separated path of associations. - * @param callable|null $builder the callback function to be used for setting extra - * options to the filtering query - * @return array The resulting containments array - */ - public function matching($assoc = null, callable $builder = null) { - if ($this->_matching === null) { - $this->_matching = new self(); - } - - if ($assoc === null) { - return $this->_matching->contain(); - } - - $assocs = explode('.', $assoc); - $last = array_pop($assocs); - $containments = []; - $pointer =& $containments; - - foreach ($assocs as $name) { - $pointer[$name] = ['matching' => true]; - $pointer =& $pointer[$name]; - } - - $pointer[$last] = ['queryBuilder' => $builder, 'matching' => true]; - return $this->_matching->contain($containments); - } - -/** - * Returns the fully normalized array of associations that should be eagerly - * loaded for a table. The normalized array will restructure the original array - * by sorting all associations under one key and special options under another. - * - * Additionally it will set an 'instance' key per association containing the - * association instance from the corresponding source table - * - * @param \Cake\ORM\Table $repository The table containing the association that - * will be normalized - * @return array - */ - public function normalized(Table $repository) { - if ($this->_normalized !== null || empty($this->_containments)) { - return (array)$this->_normalized; - } - - $contain = []; - foreach ($this->_containments as $alias => $options) { - if (!empty($options['instance'])) { - $contain = (array)$this->_containments; - break; - } - $contain[$alias] =& $this->_normalizeContain( - $repository, - $alias, - $options, - ['root' => null] - ); - } - - $this->_fixStrategies(); - return $this->_normalized = $contain; - } - -/** - * Formats the containments array so that associations are always set as keys - * in the array. This function merges the original associations array with - * the new associations provided - * - * @param array $associations user provided containments array - * @param array $original The original containments array to merge - * with the new one - * @return array - */ - protected function _reformatContain($associations, $original) { - $result = $original; - - foreach ((array)$associations as $table => $options) { - $pointer =& $result; - if (is_int($table)) { - $table = $options; - $options = []; - } - - if (isset($this->_containOptions[$table])) { - $pointer[$table] = $options; - continue; - } - - if (strpos($table, '.')) { - $path = explode('.', $table); - $table = array_pop($path); - foreach ($path as $t) { - $pointer += [$t => []]; - $pointer =& $pointer[$t]; - } - } - - if (is_array($options)) { - $options = isset($options['config']) ? - $options['config'] + $options['associations'] : - $options; - $options = $this->_reformatContain($options, []); - } - - if ($options instanceof Closure) { - $options = ['queryBuilder' => $options]; - } - - $pointer += [$table => []]; - $pointer[$table] = $options + $pointer[$table]; - } - - return $result; - } - -/** - * Modifies the passed query to apply joins or any other transformation required - * in order to eager load the associations described in the `contain` array. - * This method will not modify the query for loading external associations, i.e. - * those that cannot be loaded without executing a separate query. - * - * @param \Cake\ORM\Query $query The query to be modified - * @param \Cake\ORM\Table $repository The repository containing the associations - * @param bool $includeFields whether to append all fields from the associations - * to the passed query. This can be overridden according to the settings defined - * per association in the containments array - * @return void - */ - public function attachAssociations(Query $query, Table $repository, $includeFields) { - if (empty($this->_containments) && $this->_matching === null) { - return; - } - - foreach ($this->attachableAssociations($repository) as $options) { - $config = $options['config'] + [ - 'aliasPath' => $options['aliasPath'], - 'propertyPath' => $options['propertyPath'], - 'includeFields' => $includeFields - ]; - $options['instance']->attachTo($query, $config); - } - } - -/** - * Returns an array with the associations that can be fetched using a single query, - * the array keys are the association aliases and the values will contain an array - * with the following keys: - * - * - instance: the association object instance - * - config: the options set for fetching such association - * - * @param \Cake\ORM\Table $repository The table containing the associations to be - * attached - * @return array - */ - public function attachableAssociations(Table $repository) { - $contain = $this->normalized($repository); - $matching = $this->_matching ? $this->_matching->normalized($repository) : []; - return $this->_resolveJoins($contain, $matching); - } - -/** - * Returns an array with the associations that need to be fetched using a - * separate query, each array value will contain the following keys: - * - * - instance: the association object instance - * - config: the options set for fetching such association - * - * @param \Cake\ORM\Table $repository The table containing the associations - * to be loaded - * @return array - */ - public function externalAssociations(Table $repository) { - if ($this->_loadExternal) { - return $this->_loadExternal; - } - - $contain = $this->normalized($repository); - $this->_resolveJoins($contain, []); - return $this->_loadExternal; - } - -/** - * Auxiliary function responsible for fully normalizing deep associations defined - * using `contain()` - * - * @param Table $parent owning side of the association - * @param string $alias name of the association to be loaded - * @param array $options list of extra options to use for this association - * @param array $paths An array with two values, the first one is a list of dot - * separated strings representing associations that lead to this `$alias` in the - * chain of associations to be loaded. The second value is the path to follow in - * entities' properties to fetch a record of the corresponding association. - * @return array normalized associations - * @throws \InvalidArgumentException When containments refer to associations that do not exist. - */ - protected function &_normalizeContain(Table $parent, $alias, $options, $paths) { - $defaults = $this->_containOptions; - $instance = $parent->association($alias); - if (!$instance) { - throw new \InvalidArgumentException( - sprintf('%s is not associated with %s', $parent->alias(), $alias) - ); - } - - $paths += ['aliasPath' => '', 'propertyPath' => '', 'root' => $alias]; - $paths['aliasPath'] .= '.' . $alias; - $paths['propertyPath'] .= '.' . $instance->property(); - - $table = $instance->target(); - - $extra = array_diff_key($options, $defaults); - $config = [ - 'associations' => [], - 'instance' => $instance, - 'config' => array_diff_key($options, $extra), - 'aliasPath' => trim($paths['aliasPath'], '.'), - 'propertyPath' => trim($paths['propertyPath'], '.') - ]; - $config['canBeJoined'] = $instance->canBeJoined($config['config']); - - if ($config['canBeJoined']) { - $this->_aliasList[$paths['root']][$alias][] =& $config; - } else { - $paths['root'] = $config['aliasPath']; - } - - foreach ($extra as $t => $assoc) { - $config['associations'][$t] =& $this->_normalizeContain($table, $t, $assoc, $paths); - } - - return $config; - } - -/** - * Iterates over the joinable aliases list and corrects the fetching strategies - * in order to avoid aliases collision in the generated queries. - * - * This function operates on the array references that were generated by the - * _normalizeContain() function. - * - * @return void - */ - protected function _fixStrategies() { - foreach ($this->_aliasList as &$aliases) { - foreach ($aliases as $alias => &$configs) { - if (count($configs) < 2) { - continue; - } - foreach ($configs as &$config) { - if (strpos($config['aliasPath'], '.')) { - $this->_correctStrategy($config, $alias); - } - } - } - } - } - -/** - * Changes the association fetching strategy if required because of duplicate - * under the same direct associations chain - * - * This function modifies the $config variable - * - * @param array &$config The association config - * @param string $alias the name of the association to evaluate - * @return void|array - * @throws \RuntimeException if a duplicate association in the same chain is detected - * but is not possible to change the strategy due to conflicting settings - */ - protected function _correctStrategy(&$config, $alias) { - $currentStrategy = isset($config['config']['strategy']) ? - $config['config']['strategy'] : - 'join'; - - if (!$config['canBeJoined'] || $currentStrategy !== 'join') { - return $config; - } - - $config['canBeJoined'] = false; - $config['config']['strategy'] = $config['instance']::STRATEGY_SELECT; - } - -/** - * Helper function used to compile a list of all associations that can be - * joined in the query. - * - * @param array $associations list of associations from which to obtain joins. - * @param array $matching list of associations that should be forcibly joined. - * @return array - */ - protected function _resolveJoins($associations, $matching = []) { - $result = []; - foreach ($matching as $table => $options) { - $result[$table] = $options; - $result += $this->_resolveJoins($options['associations'], []); - } - foreach ($associations as $table => $options) { - $inMatching = isset($matching[$table]); - if (!$inMatching && $options['canBeJoined']) { - $result[$table] = $options; - $result += $this->_resolveJoins($options['associations'], $inMatching ? $mathching[$table] : []); - } else { - $options['canBeJoined'] = false; - $this->_loadExternal[] = $options; - } - } - return $result; - } - -/** - * Decorates the passed statement object in order to inject data from associations - * that cannot be joined directly. - * - * @param \Cake\ORM\Query $query The query for which to eager load external - * associations - * @param \Cake\Database\StatementInterface $statement The statement created after executing the $query - * @return CallbackStatement statement modified statement with extra loaders - */ - public function loadExternal($query, $statement) { - $external = $this->externalAssociations($query->repository()); - if (empty($external)) { - return $statement; - } - - $driver = $query->connection()->driver(); - list($collected, $statement) = $this->_collectKeys($external, $query, $statement); - foreach ($external as $meta) { - $contain = $meta['associations']; - $alias = $meta['instance']->source()->alias(); - - $requiresKeys = $meta['instance']->requiresKeys($meta['config']); - if ($requiresKeys && empty($collected[$alias])) { - continue; - } - - $keys = isset($collected[$alias]) ? $collected[$alias] : null; - $f = $meta['instance']->eagerLoader( - $meta['config'] + [ - 'query' => $query, - 'contain' => $contain, - 'keys' => $keys, - 'nestKey' => $meta['aliasPath'] - ] - ); - $statement = new CallbackStatement($statement, $driver, $f); - } - return $statement; - } - -/** - * Returns an array having as keys a dotted path of associations that participate - * in this eager loader. The values of the array will contain the following keys - * - * - alias: The association alias - * - instance: The association instance - * - canBeJoined: Whether or not the association will be loaded using a JOIN - * - entityClass: The entity that should be used for hydrating the results - * - nestKey: A dotted path that can be used to correctly insert the data into the results. - * - mathcing: Whether or not it is an association loaded through `matching()`. - * - * @param \Cake\ORM\Table $table The table containing the association that - * will be normalized - * @return array - */ - public function associationsMap($table) { - $map = []; - - if (!$this->matching() && !$this->contain() && empty($this->_joinsMap)) { - return $map; - } - - $visitor = function ($level, $matching = false) use (&$visitor, &$map) { - foreach ($level as $assoc => $meta) { - $map[] = [ - 'alias' => $assoc, - 'instance' => $meta['instance'], - 'canBeJoined' => $meta['canBeJoined'], - 'entityClass' => $meta['instance']->target()->entityClass(), - 'nestKey' => $meta['canBeJoined'] ? $assoc : $meta['aliasPath'], - 'matching' => isset($meta['matching']) ? $meta['matching'] : $matching - ]; - if ($meta['canBeJoined'] && !empty($meta['associations'])) { - $visitor($meta['associations'], $matching); - } - } - }; - $visitor($this->_matching->normalized($table), true); - $visitor($this->normalized($table)); - $visitor($this->_joinsMap); - return $map; - } - -/** - * Registers a table alias, typically loaded as a join in a query, as belonging to - * an association. This helps hydrators know what to do with the columns coming - * from such joined table. - * - * @param string $alias The table alias as it appears in the query. - * @param \Cake\ORM\Association $assoc The association object the alias represents; - * will be normalized - * @param bool $asMatching Whether or not this join results should be treated as a - * 'matching' association. - * @return void - */ - public function addToJoinsMap($alias, Association $assoc, $asMatching = false) { - $this->_joinsMap[$alias] = [ - 'aliasPath' => $alias, - 'instance' => $assoc, - 'canBeJoined' => true, - 'matching' => $asMatching, - 'associations' => [] - ]; - } - -/** - * Helper function used to return the keys from the query records that will be used - * to eagerly load associations. - * - * @param array $external the list of external associations to be loaded - * @param \Cake\ORM\Query $query The query from which the results where generated - * @param BufferedStatement $statement The statement to work on - * @return array - */ - protected function _collectKeys($external, $query, $statement) { - $collectKeys = []; - foreach ($external as $meta) { - if (!$meta['instance']->requiresKeys($meta['config'])) { - continue; - } - - $source = $meta['instance']->source(); - $keys = $meta['instance']->type() === $meta['instance']::MANY_TO_ONE ? - (array)$meta['instance']->foreignKey() : - (array)$source->primaryKey(); - - $alias = $source->alias(); - $pkFields = []; - foreach ($keys as $key) { - $pkFields[] = key($query->aliasField($key, $alias)); - } - $collectKeys[$alias] = [$alias, $pkFields, count($pkFields) === 1]; - } - - if (empty($collectKeys)) { - return [[], $statement]; - } - - if (!($statement instanceof BufferedStatement)) { - $statement = new BufferedStatement($statement, $query->connection()->driver()); - } - - return [$this->_groupKeys($statement, $collectKeys), $statement]; - } - -/** - * Helper function used to iterate a statement and extract the columns - * defined in $collectKeys - * - * @param \Cake\Database\StatementInterface $statement The statement to read from. - * @param array $collectKeys The keys to collect - * @return array - */ - protected function _groupKeys($statement, $collectKeys) { - $keys = []; - while ($result = $statement->fetch('assoc')) { - foreach ($collectKeys as $parts) { - // Missed joins will have null in the results. - if ($parts[2] && !isset($result[$parts[1][0]])) { - continue; - } - if ($parts[2]) { - $keys[$parts[0]][] = $result[$parts[1][0]]; - continue; - } - - $collected = []; - foreach ($parts[1] as $key) { - $collected[] = $result[$key]; - } - $keys[$parts[0]][] = $collected; - } - } - - $statement->rewind(); - return $keys; - } - +class EagerLoader +{ + + /** + * Nested array describing the association to be fetched + * and the options to apply for each of them, if any + * + * @var array + */ + protected $_containments = []; + + /** + * Contains a nested array with the compiled containments tree + * This is a normalized version of the user provided containments array. + * + * @var array + */ + protected $_normalized; + + /** + * List of options accepted by associations in contain() + * index by key for faster access + * + * @var array + */ + protected $_containOptions = [ + 'associations' => 1, + 'foreignKey' => 1, + 'conditions' => 1, + 'fields' => 1, + 'sort' => 1, + 'matching' => 1, + 'queryBuilder' => 1, + 'finder' => 1, + 'joinType' => 1, + 'strategy' => 1 + ]; + + /** + * A list of associations that should be loaded with a separate query + * + * @var array + */ + protected $_loadExternal = []; + + /** + * Contains a list of the association names that are to be eagerly loaded + * + * @var array + */ + protected $_aliasList = []; + + /** + * Another EagerLoader instance that will be used for 'matching' associations. + * + * @var \Cake\ORM\EagerLoader + */ + protected $_matching; + + /** + * A map of table aliases pointing to the association objects they represent + * for the query. + * + * @var array + */ + protected $_joinsMap = []; + + /** + * Sets the list of associations that should be eagerly loaded along for a + * specific table using when a query is provided. The list of associated tables + * passed to this method must have been previously set as associations using the + * Table API. + * + * Associations can be arbitrarily nested using dot notation or nested arrays, + * this allows this object to calculate joins or any additional queries that + * must be executed to bring the required associated data. + * + * Accepted options per passed association: + * + * - foreignKey: Used to set a different field to match both tables, if set to false + * no join conditions will be generated automatically + * - fields: An array with the fields that should be fetched from the association + * - queryBuilder: Equivalent to passing a callable instead of an options array + * - matching: Whether to inform the association class that it should filter the + * main query by the results fetched by that class. + * - joinType: For joinable associations, the SQL join type to use. + * + * @param array|string $associations list of table aliases to be queried. + * When this method is called multiple times it will merge previous list with + * the new one. + * @return array Containments. + */ + public function contain($associations = []) + { + if (empty($associations)) { + return $this->_containments; + } + + $associations = (array)$associations; + $associations = $this->_reformatContain($associations, $this->_containments); + $this->_normalized = $this->_loadExternal = null; + $this->_aliasList = []; + return $this->_containments = $associations; + } + + /** + * Adds a new association to the list that will be used to filter the results of + * any given query based on the results of finding records for that association. + * You can pass a dot separated path of associations to this method as its first + * parameter, this will translate in setting all those associations with the + * `matching` option. + * + * If called with no arguments it will return the current tree of associations to + * be matched. + * + * @param string|null $assoc A single association or a dot separated path of associations. + * @param callable|null $builder the callback function to be used for setting extra + * options to the filtering query + * @return array The resulting containments array + */ + public function matching($assoc = null, callable $builder = null) + { + if ($this->_matching === null) { + $this->_matching = new self(); + } + + if ($assoc === null) { + return $this->_matching->contain(); + } + + $assocs = explode('.', $assoc); + $last = array_pop($assocs); + $containments = []; + $pointer =& $containments; + + foreach ($assocs as $name) { + $pointer[$name] = ['matching' => true]; + $pointer =& $pointer[$name]; + } + + $pointer[$last] = ['queryBuilder' => $builder, 'matching' => true]; + return $this->_matching->contain($containments); + } + + /** + * Returns the fully normalized array of associations that should be eagerly + * loaded for a table. The normalized array will restructure the original array + * by sorting all associations under one key and special options under another. + * + * Additionally it will set an 'instance' key per association containing the + * association instance from the corresponding source table + * + * @param \Cake\ORM\Table $repository The table containing the association that + * will be normalized + * @return array + */ + public function normalized(Table $repository) + { + if ($this->_normalized !== null || empty($this->_containments)) { + return (array)$this->_normalized; + } + + $contain = []; + foreach ($this->_containments as $alias => $options) { + if (!empty($options['instance'])) { + $contain = (array)$this->_containments; + break; + } + $contain[$alias] =& $this->_normalizeContain( + $repository, + $alias, + $options, + ['root' => null] + ); + } + + $this->_fixStrategies(); + return $this->_normalized = $contain; + } + + /** + * Formats the containments array so that associations are always set as keys + * in the array. This function merges the original associations array with + * the new associations provided + * + * @param array $associations user provided containments array + * @param array $original The original containments array to merge + * with the new one + * @return array + */ + protected function _reformatContain($associations, $original) + { + $result = $original; + + foreach ((array)$associations as $table => $options) { + $pointer =& $result; + if (is_int($table)) { + $table = $options; + $options = []; + } + + if (isset($this->_containOptions[$table])) { + $pointer[$table] = $options; + continue; + } + + if (strpos($table, '.')) { + $path = explode('.', $table); + $table = array_pop($path); + foreach ($path as $t) { + $pointer += [$t => []]; + $pointer =& $pointer[$t]; + } + } + + if (is_array($options)) { + $options = isset($options['config']) ? + $options['config'] + $options['associations'] : + $options; + $options = $this->_reformatContain($options, []); + } + + if ($options instanceof Closure) { + $options = ['queryBuilder' => $options]; + } + + $pointer += [$table => []]; + $pointer[$table] = $options + $pointer[$table]; + } + + return $result; + } + + /** + * Modifies the passed query to apply joins or any other transformation required + * in order to eager load the associations described in the `contain` array. + * This method will not modify the query for loading external associations, i.e. + * those that cannot be loaded without executing a separate query. + * + * @param \Cake\ORM\Query $query The query to be modified + * @param \Cake\ORM\Table $repository The repository containing the associations + * @param bool $includeFields whether to append all fields from the associations + * to the passed query. This can be overridden according to the settings defined + * per association in the containments array + * @return void + */ + public function attachAssociations(Query $query, Table $repository, $includeFields) + { + if (empty($this->_containments) && $this->_matching === null) { + return; + } + + foreach ($this->attachableAssociations($repository) as $options) { + $config = $options['config'] + [ + 'aliasPath' => $options['aliasPath'], + 'propertyPath' => $options['propertyPath'], + 'includeFields' => $includeFields + ]; + $options['instance']->attachTo($query, $config); + } + } + + /** + * Returns an array with the associations that can be fetched using a single query, + * the array keys are the association aliases and the values will contain an array + * with the following keys: + * + * - instance: the association object instance + * - config: the options set for fetching such association + * + * @param \Cake\ORM\Table $repository The table containing the associations to be + * attached + * @return array + */ + public function attachableAssociations(Table $repository) + { + $contain = $this->normalized($repository); + $matching = $this->_matching ? $this->_matching->normalized($repository) : []; + return $this->_resolveJoins($contain, $matching); + } + + /** + * Returns an array with the associations that need to be fetched using a + * separate query, each array value will contain the following keys: + * + * - instance: the association object instance + * - config: the options set for fetching such association + * + * @param \Cake\ORM\Table $repository The table containing the associations + * to be loaded + * @return array + */ + public function externalAssociations(Table $repository) + { + if ($this->_loadExternal) { + return $this->_loadExternal; + } + + $contain = $this->normalized($repository); + $this->_resolveJoins($contain, []); + return $this->_loadExternal; + } + + /** + * Auxiliary function responsible for fully normalizing deep associations defined + * using `contain()` + * + * @param Table $parent owning side of the association + * @param string $alias name of the association to be loaded + * @param array $options list of extra options to use for this association + * @param array $paths An array with two values, the first one is a list of dot + * separated strings representing associations that lead to this `$alias` in the + * chain of associations to be loaded. The second value is the path to follow in + * entities' properties to fetch a record of the corresponding association. + * @return array normalized associations + * @throws \InvalidArgumentException When containments refer to associations that do not exist. + */ + protected function &_normalizeContain(Table $parent, $alias, $options, $paths) + { + $defaults = $this->_containOptions; + $instance = $parent->association($alias); + if (!$instance) { + throw new \InvalidArgumentException( + sprintf('%s is not associated with %s', $parent->alias(), $alias) + ); + } + + $paths += ['aliasPath' => '', 'propertyPath' => '', 'root' => $alias]; + $paths['aliasPath'] .= '.' . $alias; + $paths['propertyPath'] .= '.' . $instance->property(); + + $table = $instance->target(); + + $extra = array_diff_key($options, $defaults); + $config = [ + 'associations' => [], + 'instance' => $instance, + 'config' => array_diff_key($options, $extra), + 'aliasPath' => trim($paths['aliasPath'], '.'), + 'propertyPath' => trim($paths['propertyPath'], '.') + ]; + $config['canBeJoined'] = $instance->canBeJoined($config['config']); + + if ($config['canBeJoined']) { + $this->_aliasList[$paths['root']][$alias][] =& $config; + } else { + $paths['root'] = $config['aliasPath']; + } + + foreach ($extra as $t => $assoc) { + $config['associations'][$t] =& $this->_normalizeContain($table, $t, $assoc, $paths); + } + + return $config; + } + + /** + * Iterates over the joinable aliases list and corrects the fetching strategies + * in order to avoid aliases collision in the generated queries. + * + * This function operates on the array references that were generated by the + * _normalizeContain() function. + * + * @return void + */ + protected function _fixStrategies() + { + foreach ($this->_aliasList as &$aliases) { + foreach ($aliases as $alias => &$configs) { + if (count($configs) < 2) { + continue; + } + foreach ($configs as &$config) { + if (strpos($config['aliasPath'], '.')) { + $this->_correctStrategy($config, $alias); + } + } + } + } + } + + /** + * Changes the association fetching strategy if required because of duplicate + * under the same direct associations chain + * + * This function modifies the $config variable + * + * @param array &$config The association config + * @param string $alias the name of the association to evaluate + * @return void|array + * @throws \RuntimeException if a duplicate association in the same chain is detected + * but is not possible to change the strategy due to conflicting settings + */ + protected function _correctStrategy(&$config, $alias) + { + $currentStrategy = isset($config['config']['strategy']) ? + $config['config']['strategy'] : + 'join'; + + if (!$config['canBeJoined'] || $currentStrategy !== 'join') { + return $config; + } + + $config['canBeJoined'] = false; + $config['config']['strategy'] = $config['instance']::STRATEGY_SELECT; + } + + /** + * Helper function used to compile a list of all associations that can be + * joined in the query. + * + * @param array $associations list of associations from which to obtain joins. + * @param array $matching list of associations that should be forcibly joined. + * @return array + */ + protected function _resolveJoins($associations, $matching = []) + { + $result = []; + foreach ($matching as $table => $options) { + $result[$table] = $options; + $result += $this->_resolveJoins($options['associations'], []); + } + foreach ($associations as $table => $options) { + $inMatching = isset($matching[$table]); + if (!$inMatching && $options['canBeJoined']) { + $result[$table] = $options; + $result += $this->_resolveJoins($options['associations'], $inMatching ? $mathching[$table] : []); + } else { + $options['canBeJoined'] = false; + $this->_loadExternal[] = $options; + } + } + return $result; + } + + /** + * Decorates the passed statement object in order to inject data from associations + * that cannot be joined directly. + * + * @param \Cake\ORM\Query $query The query for which to eager load external + * associations + * @param \Cake\Database\StatementInterface $statement The statement created after executing the $query + * @return CallbackStatement statement modified statement with extra loaders + */ + public function loadExternal($query, $statement) + { + $external = $this->externalAssociations($query->repository()); + if (empty($external)) { + return $statement; + } + + $driver = $query->connection()->driver(); + list($collected, $statement) = $this->_collectKeys($external, $query, $statement); + foreach ($external as $meta) { + $contain = $meta['associations']; + $alias = $meta['instance']->source()->alias(); + + $requiresKeys = $meta['instance']->requiresKeys($meta['config']); + if ($requiresKeys && empty($collected[$alias])) { + continue; + } + + $keys = isset($collected[$alias]) ? $collected[$alias] : null; + $f = $meta['instance']->eagerLoader( + $meta['config'] + [ + 'query' => $query, + 'contain' => $contain, + 'keys' => $keys, + 'nestKey' => $meta['aliasPath'] + ] + ); + $statement = new CallbackStatement($statement, $driver, $f); + } + return $statement; + } + + /** + * Returns an array having as keys a dotted path of associations that participate + * in this eager loader. The values of the array will contain the following keys + * + * - alias: The association alias + * - instance: The association instance + * - canBeJoined: Whether or not the association will be loaded using a JOIN + * - entityClass: The entity that should be used for hydrating the results + * - nestKey: A dotted path that can be used to correctly insert the data into the results. + * - mathcing: Whether or not it is an association loaded through `matching()`. + * + * @param \Cake\ORM\Table $table The table containing the association that + * will be normalized + * @return array + */ + public function associationsMap($table) + { + $map = []; + + if (!$this->matching() && !$this->contain() && empty($this->_joinsMap)) { + return $map; + } + + $visitor = function ($level, $matching = false) use (&$visitor, &$map) { + foreach ($level as $assoc => $meta) { + $map[] = [ + 'alias' => $assoc, + 'instance' => $meta['instance'], + 'canBeJoined' => $meta['canBeJoined'], + 'entityClass' => $meta['instance']->target()->entityClass(), + 'nestKey' => $meta['canBeJoined'] ? $assoc : $meta['aliasPath'], + 'matching' => isset($meta['matching']) ? $meta['matching'] : $matching + ]; + if ($meta['canBeJoined'] && !empty($meta['associations'])) { + $visitor($meta['associations'], $matching); + } + } + }; + $visitor($this->_matching->normalized($table), true); + $visitor($this->normalized($table)); + $visitor($this->_joinsMap); + return $map; + } + + /** + * Registers a table alias, typically loaded as a join in a query, as belonging to + * an association. This helps hydrators know what to do with the columns coming + * from such joined table. + * + * @param string $alias The table alias as it appears in the query. + * @param \Cake\ORM\Association $assoc The association object the alias represents; + * will be normalized + * @param bool $asMatching Whether or not this join results should be treated as a + * 'matching' association. + * @return void + */ + public function addToJoinsMap($alias, Association $assoc, $asMatching = false) + { + $this->_joinsMap[$alias] = [ + 'aliasPath' => $alias, + 'instance' => $assoc, + 'canBeJoined' => true, + 'matching' => $asMatching, + 'associations' => [] + ]; + } + + /** + * Helper function used to return the keys from the query records that will be used + * to eagerly load associations. + * + * @param array $external the list of external associations to be loaded + * @param \Cake\ORM\Query $query The query from which the results where generated + * @param BufferedStatement $statement The statement to work on + * @return array + */ + protected function _collectKeys($external, $query, $statement) + { + $collectKeys = []; + foreach ($external as $meta) { + if (!$meta['instance']->requiresKeys($meta['config'])) { + continue; + } + + $source = $meta['instance']->source(); + $keys = $meta['instance']->type() === $meta['instance']::MANY_TO_ONE ? + (array)$meta['instance']->foreignKey() : + (array)$source->primaryKey(); + + $alias = $source->alias(); + $pkFields = []; + foreach ($keys as $key) { + $pkFields[] = key($query->aliasField($key, $alias)); + } + $collectKeys[$alias] = [$alias, $pkFields, count($pkFields) === 1]; + } + + if (empty($collectKeys)) { + return [[], $statement]; + } + + if (!($statement instanceof BufferedStatement)) { + $statement = new BufferedStatement($statement, $query->connection()->driver()); + } + + return [$this->_groupKeys($statement, $collectKeys), $statement]; + } + + /** + * Helper function used to iterate a statement and extract the columns + * defined in $collectKeys + * + * @param \Cake\Database\StatementInterface $statement The statement to read from. + * @param array $collectKeys The keys to collect + * @return array + */ + protected function _groupKeys($statement, $collectKeys) + { + $keys = []; + while ($result = $statement->fetch('assoc')) { + foreach ($collectKeys as $parts) { + // Missed joins will have null in the results. + if ($parts[2] && !isset($result[$parts[1][0]])) { + continue; + } + if ($parts[2]) { + $keys[$parts[0]][] = $result[$parts[1][0]]; + continue; + } + + $collected = []; + foreach ($parts[1] as $key) { + $collected[] = $result[$key]; + } + $keys[$parts[0]][] = $collected; + } + } + + $statement->rewind(); + return $keys; + } } diff --git a/Entity.php b/Entity.php index 644e816d..6bcbf85a 100644 --- a/Entity.php +++ b/Entity.php @@ -21,57 +21,58 @@ * An entity represents a single result row from a repository. It exposes the * methods for retrieving and storing properties associated in this row. */ -class Entity implements EntityInterface { +class Entity implements EntityInterface +{ - use EntityTrait; + use EntityTrait; -/** - * Initializes the internal properties of this entity out of the - * keys in an array. The following list of options can be used: - * - * - useSetters: whether use internal setters for properties or not - * - markClean: whether to mark all properties as clean after setting them - * - markNew: whether this instance has not yet been persisted - * - guard: whether to prevent inaccessible properties from being set (default: false) - * - source: A string representing the alias of the repository this entity came from - * - * ### Example: - * - * {{{ - * $entity = new Entity(['id' => 1, 'name' => 'Andrew']) - * }}} - * - * @param array $properties hash of properties to set in this entity - * @param array $options list of options to use when creating this entity - */ - public function __construct(array $properties = [], array $options = []) { - $options += [ - 'useSetters' => true, - 'markClean' => false, - 'markNew' => null, - 'guard' => false, - 'source' => null - ]; - $this->_className = get_class($this); - - if (!empty($properties)) { - $this->set($properties, [ - 'setter' => $options['useSetters'], - 'guard' => $options['guard'] - ]); - } + /** + * Initializes the internal properties of this entity out of the + * keys in an array. The following list of options can be used: + * + * - useSetters: whether use internal setters for properties or not + * - markClean: whether to mark all properties as clean after setting them + * - markNew: whether this instance has not yet been persisted + * - guard: whether to prevent inaccessible properties from being set (default: false) + * - source: A string representing the alias of the repository this entity came from + * + * ### Example: + * + * {{{ + * $entity = new Entity(['id' => 1, 'name' => 'Andrew']) + * }}} + * + * @param array $properties hash of properties to set in this entity + * @param array $options list of options to use when creating this entity + */ + public function __construct(array $properties = [], array $options = []) + { + $options += [ + 'useSetters' => true, + 'markClean' => false, + 'markNew' => null, + 'guard' => false, + 'source' => null + ]; + $this->_className = get_class($this); - if ($options['markClean']) { - $this->clean(); - } + if (!empty($properties)) { + $this->set($properties, [ + 'setter' => $options['useSetters'], + 'guard' => $options['guard'] + ]); + } - if ($options['markNew'] !== null) { - $this->isNew($options['markNew']); - } + if ($options['markClean']) { + $this->clean(); + } - if (!empty($options['source'])) { - $this->source($options['source']); - } - } + if ($options['markNew'] !== null) { + $this->isNew($options['markNew']); + } + if (!empty($options['source'])) { + $this->source($options['source']); + } + } } diff --git a/EntityValidator.php b/EntityValidator.php index f12edf21..543a9f80 100644 --- a/EntityValidator.php +++ b/EntityValidator.php @@ -30,158 +30,163 @@ * @see \Cake\ORM\Table::validate() * @see \Cake\ORM\Table::validateMany() */ -class EntityValidator { - -/** - * The table instance this validator is for. - * - * @var \Cake\ORM\Table - */ - protected $_table; - -/** - * Constructor. - * - * @param \Cake\ORM\Table $table The table this validator is for - */ - public function __construct(Table $table) { - $this->_table = $table; - } - -/** - * Build the map of property => association names. - * - * @param array $include The array of included associations. - * @return array - */ - protected function _buildPropertyMap($include) { - if (empty($include['associated'])) { - return []; - } - - $map = []; - foreach ($include['associated'] as $key => $options) { - if (is_int($key) && is_scalar($options)) { - $key = $options; - $options = []; - } - - $options += ['validate' => true, 'associated' => []]; - $assoc = $this->_table->association($key); - if ($assoc) { - $map[$assoc->property()] = [ - 'association' => $assoc, - 'options' => $options - ]; - } - } - return $map; - } - -/** - * Validates a single entity by getting the correct validator object from - * the table and traverses associations passed in $options to validate them - * as well. - * - * @param \Cake\Datasource\EntityInterface $entity The entity to be validated - * @param array|\ArrayObject $options options for validation, including an optional key of - * associations to also be validated. This argument should use the same format as the $options - * argument to \Cake\ORM\Table::save(). - * @return bool true if all validations passed, false otherwise - */ - public function one(EntityInterface $entity, $options = []) { - $valid = true; - $types = [Association::ONE_TO_ONE, Association::MANY_TO_ONE]; - $propertyMap = $this->_buildPropertyMap($options); - $options = new ArrayObject($options); - - foreach ($propertyMap as $key => $assoc) { - $value = $entity->get($key); - $association = $assoc['association']; - - if (!$value) { - continue; - } - $isOne = in_array($association->type(), $types); - if ($isOne && !($value instanceof EntityInterface)) { - $valid = false; - continue; - } - - $validator = new self($association->target()); - if ($isOne) { - $valid = $validator->one($value, $assoc['options']) && $valid; - } else { - $valid = $validator->many($value, $assoc['options']) && $valid; - } - } - - if (!isset($options['validate'])) { - $options['validate'] = true; - } - - if (!($entity instanceof ValidatableInterface)) { - return $valid; - } - - return $this->_processValidation($entity, $options) && $valid; - } - -/** - * Validates a list of entities by getting the correct validator for the related - * table and traverses associations passed in $include to validate them as well. - * - * If any of the entities in `$entities` does not implement `Cake\Datasource\EntityInterface`, - * it will be treated as an invalid result. - * - * @param array $entities List of entities to be validated - * @param array|\ArrayObject $options options for validation, including an optional key of - * associations to also be validated. This argument should use the same format as the $options - * argument to \Cake\ORM\Table::save(). - * @return bool true if all validations passed, false otherwise - */ - public function many(array $entities, $options = []) { - $valid = true; - foreach ($entities as $entity) { - if (!($entity instanceof EntityInterface)) { - return false; - } - $valid = $this->one($entity, $options) && $valid; - } - return $valid; - } - -/** - * Validates the $entity if the 'validate' key is not set to false in $options - * If not empty it will construct a default validation object or get one with - * the name passed in the key - * - * @param \Cake\ORM\Entity $entity The entity to validate - * @param \ArrayObject $options The option for processing validation - * @return bool true if the entity is valid, false otherwise - */ - protected function _processValidation($entity, $options) { - $type = is_string($options['validate']) ? $options['validate'] : 'default'; - $validator = $this->_table->validator($type); - $pass = compact('entity', 'options', 'validator'); - $event = $this->_table->dispatchEvent('Model.beforeValidate', $pass); - - if ($event->isStopped()) { - return (bool)$event->result; - } - - if (!count($validator)) { - return true; - } - - $success = !$entity->validate($validator); - - $event = $this->_table->dispatchEvent('Model.afterValidate', $pass); - if ($event->isStopped()) { - $success = (bool)$event->result; - } - - return $success; - } - +class EntityValidator +{ + + /** + * The table instance this validator is for. + * + * @var \Cake\ORM\Table + */ + protected $_table; + + /** + * Constructor. + * + * @param \Cake\ORM\Table $table The table this validator is for + */ + public function __construct(Table $table) + { + $this->_table = $table; + } + + /** + * Build the map of property => association names. + * + * @param array $include The array of included associations. + * @return array + */ + protected function _buildPropertyMap($include) + { + if (empty($include['associated'])) { + return []; + } + + $map = []; + foreach ($include['associated'] as $key => $options) { + if (is_int($key) && is_scalar($options)) { + $key = $options; + $options = []; + } + + $options += ['validate' => true, 'associated' => []]; + $assoc = $this->_table->association($key); + if ($assoc) { + $map[$assoc->property()] = [ + 'association' => $assoc, + 'options' => $options + ]; + } + } + return $map; + } + + /** + * Validates a single entity by getting the correct validator object from + * the table and traverses associations passed in $options to validate them + * as well. + * + * @param \Cake\Datasource\EntityInterface $entity The entity to be validated + * @param array|\ArrayObject $options options for validation, including an optional key of + * associations to also be validated. This argument should use the same format as the $options + * argument to \Cake\ORM\Table::save(). + * @return bool true if all validations passed, false otherwise + */ + public function one(EntityInterface $entity, $options = []) + { + $valid = true; + $types = [Association::ONE_TO_ONE, Association::MANY_TO_ONE]; + $propertyMap = $this->_buildPropertyMap($options); + $options = new ArrayObject($options); + + foreach ($propertyMap as $key => $assoc) { + $value = $entity->get($key); + $association = $assoc['association']; + + if (!$value) { + continue; + } + $isOne = in_array($association->type(), $types); + if ($isOne && !($value instanceof EntityInterface)) { + $valid = false; + continue; + } + + $validator = new self($association->target()); + if ($isOne) { + $valid = $validator->one($value, $assoc['options']) && $valid; + } else { + $valid = $validator->many($value, $assoc['options']) && $valid; + } + } + + if (!isset($options['validate'])) { + $options['validate'] = true; + } + + if (!($entity instanceof ValidatableInterface)) { + return $valid; + } + + return $this->_processValidation($entity, $options) && $valid; + } + + /** + * Validates a list of entities by getting the correct validator for the related + * table and traverses associations passed in $include to validate them as well. + * + * If any of the entities in `$entities` does not implement `Cake\Datasource\EntityInterface`, + * it will be treated as an invalid result. + * + * @param array $entities List of entities to be validated + * @param array|\ArrayObject $options options for validation, including an optional key of + * associations to also be validated. This argument should use the same format as the $options + * argument to \Cake\ORM\Table::save(). + * @return bool true if all validations passed, false otherwise + */ + public function many(array $entities, $options = []) + { + $valid = true; + foreach ($entities as $entity) { + if (!($entity instanceof EntityInterface)) { + return false; + } + $valid = $this->one($entity, $options) && $valid; + } + return $valid; + } + + /** + * Validates the $entity if the 'validate' key is not set to false in $options + * If not empty it will construct a default validation object or get one with + * the name passed in the key + * + * @param \Cake\ORM\Entity $entity The entity to validate + * @param \ArrayObject $options The option for processing validation + * @return bool true if the entity is valid, false otherwise + */ + protected function _processValidation($entity, $options) + { + $type = is_string($options['validate']) ? $options['validate'] : 'default'; + $validator = $this->_table->validator($type); + $pass = compact('entity', 'options', 'validator'); + $event = $this->_table->dispatchEvent('Model.beforeValidate', $pass); + + if ($event->isStopped()) { + return (bool)$event->result; + } + + if (!count($validator)) { + return true; + } + + $success = !$entity->validate($validator); + + $event = $this->_table->dispatchEvent('Model.afterValidate', $pass); + if ($event->isStopped()) { + $success = (bool)$event->result; + } + + return $success; + } } diff --git a/EntityValidatorTrait.php b/EntityValidatorTrait.php index bf8afec3..9bbdb815 100644 --- a/EntityValidatorTrait.php +++ b/EntityValidatorTrait.php @@ -20,21 +20,22 @@ * Contains a method that can be used to apply a validator to an entity's internal * properties. This trait can be used to satisfy the Cake\Validation\ValidatableInterface */ -trait EntityValidatorTrait { - -/** - * Validates the internal properties using a validator object and returns any - * validation errors found. - * - * @param \Cake\Validation\Validator $validator The validator to use when validating the entity. - * @return array - */ - public function validate(Validator $validator) { - $data = $this->_properties; - $new = $this->isNew(); - $validator->provider('entity', $this); - $this->errors($validator->errors($data, $new === null ? true : $new)); - return $this->_errors; - } +trait EntityValidatorTrait +{ + /** + * Validates the internal properties using a validator object and returns any + * validation errors found. + * + * @param \Cake\Validation\Validator $validator The validator to use when validating the entity. + * @return array + */ + public function validate(Validator $validator) + { + $data = $this->_properties; + $new = $this->isNew(); + $validator->provider('entity', $this); + $this->errors($validator->errors($data, $new === null ? true : $new)); + return $this->_errors; + } } diff --git a/Exception/MissingBehaviorException.php b/Exception/MissingBehaviorException.php index e0626dbf..b71968ce 100644 --- a/Exception/MissingBehaviorException.php +++ b/Exception/MissingBehaviorException.php @@ -18,8 +18,8 @@ * Used when a behavior cannot be found. * */ -class MissingBehaviorException extends Exception { - - protected $_messageTemplate = 'Behavior class %s could not be found.'; +class MissingBehaviorException extends Exception +{ + protected $_messageTemplate = 'Behavior class %s could not be found.'; } diff --git a/Exception/MissingEntityException.php b/Exception/MissingEntityException.php index 25ef22d4..30ea06a7 100644 --- a/Exception/MissingEntityException.php +++ b/Exception/MissingEntityException.php @@ -22,8 +22,8 @@ * Exception raised when an Entity could not be found. * */ -class MissingEntityException extends Exception { - - protected $_messageTemplate = 'Entity class %s could not be found.'; +class MissingEntityException extends Exception +{ + protected $_messageTemplate = 'Entity class %s could not be found.'; } diff --git a/Exception/MissingTableClassException.php b/Exception/MissingTableClassException.php index 043f265c..7c95db40 100644 --- a/Exception/MissingTableClassException.php +++ b/Exception/MissingTableClassException.php @@ -20,8 +20,8 @@ * Exception raised when a Table could not be found. * */ -class MissingTableClassException extends Exception { - - protected $_messageTemplate = 'Table class %s could not be found.'; +class MissingTableClassException extends Exception +{ + protected $_messageTemplate = 'Table class %s could not be found.'; } diff --git a/Marshaller.php b/Marshaller.php index d32603f1..6abc79b9 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -33,513 +33,525 @@ * @see \Cake\ORM\Table::patchEntity() * @see \Cake\ORM\Table::patchEntities() */ -class Marshaller { - - use AssociationsNormalizerTrait; - -/** - * The table instance this marshaller is for. - * - * @var \Cake\ORM\Table - */ - protected $_table; - -/** - * Constructor. - * - * @param \Cake\ORM\Table $table The table this marshaller is for. - */ - public function __construct(Table $table) { - $this->_table = $table; - } - -/** - * Build the map of property => association names. - * - * @param array $options List of options containing the 'associated' key. - * @return array - */ - protected function _buildPropertyMap($options) { - if (empty($options['associated'])) { - return []; - } - - $include = $options['associated']; - $map = []; - $include = $this->_normalizeAssociations($include); - foreach ($include as $key => $nested) { - if (is_int($key) && is_scalar($nested)) { - $key = $nested; - $nested = []; - } - $assoc = $this->_table->association($key); - if ($assoc) { - $map[$assoc->property()] = ['association' => $assoc] + $nested + ['associated' => []]; - } - } - return $map; - } - -/** - * Hydrate one entity and its associated data. - * - * ### Options: - * - * * associated: Associations listed here will be marshalled as well. - * * fieldList: A whitelist of fields to be assigned to the entity. If not present, - * the accessible fields list in the entity will be used. - * * accessibleFields: A list of fields to allow or deny in entity accessible fields. - * - * @param array $data The data to hydrate. - * @param array $options List of options - * @return \Cake\ORM\Entity - * @see \Cake\ORM\Table::newEntity() - */ - public function one(array $data, array $options = []) { - $options += ['validate' => true]; - $propertyMap = $this->_buildPropertyMap($options); - - $schema = $this->_table->schema(); - $tableName = $this->_table->alias(); - $entityClass = $this->_table->entityClass(); - $entity = new $entityClass(); - $entity->source($this->_table->alias()); - - if (isset($data[$tableName])) { - $data = $data[$tableName]; - } - - if (isset($options['accessibleFields'])) { - foreach ((array)$options['accessibleFields'] as $key => $value) { - $entity->accessible($key, $value); - } - } - - $errors = $this->_validate($data, $options, true); - $primaryKey = $schema->primaryKey(); - $properties = []; - foreach ($data as $key => $value) { - if (!empty($errors[$key])) { - continue; - } - $columnType = $schema->columnType($key); - if (isset($propertyMap[$key])) { - $assoc = $propertyMap[$key]['association']; - $value = $this->_marshalAssociation($assoc, $value, $propertyMap[$key]); - } elseif ($value === '' && in_array($key, $primaryKey, true)) { - // Skip marshalling '' for pk fields. - continue; - } elseif ($columnType) { - $converter = Type::build($columnType); - $value = $converter->marshal($value); - } - $properties[$key] = $value; - } - - if (!isset($options['fieldList'])) { - $entity->set($properties); - $entity->errors($errors); - return $entity; - } - - foreach ((array)$options['fieldList'] as $field) { - if (isset($properties[$field])) { - $entity->set($field, $properties[$field]); - } - } - - $entity->errors($errors); - return $entity; - } - -/** - * Returns the validation errors for a data set based on the passed options - * - * @param array $data The data to validate. - * @param array $options The options passed to this marshaller. - * @param bool $isNew Whether it is a new entity or one to be updated. - * @return array The list of validation errors. - * @throws \RuntimeException If no validator can be created. - */ - protected function _validate($data, $options, $isNew) { - if (!$options['validate']) { - return []; - } - if ($options['validate'] === true) { - $options['validate'] = $this->_table->validator('default'); - } - if (is_string($options['validate'])) { - $options['validate'] = $this->_table->validator($options['validate']); - } - if (!is_object($options['validate'])) { - throw new RuntimeException( - sprintf('validate must be a boolean, a string or an object. Got %s.', gettype($options['validate'])) - ); - } - - return $options['validate']->errors($data, $isNew); - } - -/** - * Create a new sub-marshaller and marshal the associated data. - * - * @param \Cake\ORM\Association $assoc The association to marshall - * @param array $value The data to hydrate - * @param array $options List of options. - * @return mixed - */ - protected function _marshalAssociation($assoc, $value, $options) { - $targetTable = $assoc->target(); - $marshaller = $targetTable->marshaller(); - $types = [Association::ONE_TO_ONE, Association::MANY_TO_ONE]; - if (in_array($assoc->type(), $types)) { - return $marshaller->one($value, (array)$options); - } - if ($assoc->type() === Association::MANY_TO_MANY) { - return $marshaller->_belongsToMany($assoc, $value, (array)$options); - } - return $marshaller->many($value, (array)$options); - } - -/** - * Hydrate many entities and their associated data. - * - * ### Options: - * - * * associated: Associations listed here will be marshalled as well. - * * fieldList: A whitelist of fields to be assigned to the entity. If not present, - * the accessible fields list in the entity will be used. - * - * @param array $data The data to hydrate. - * @param array $options List of options - * @return array An array of hydrated records. - * @see \Cake\ORM\Table::newEntities() - */ - public function many(array $data, array $options = []) { - $output = []; - foreach ($data as $record) { - $output[] = $this->one($record, $options); - } - return $output; - } - -/** - * Marshals data for belongsToMany associations. - * - * Builds the related entities and handles the special casing - * for junction table entities. - * - * @param Association $assoc The association to marshal. - * @param array $data The data to convert into entities. - * @param array $options List of options. - * @return array An array of built entities. - */ - protected function _belongsToMany(Association $assoc, array $data, $options = []) { - $associated = isset($options['associated']) ? $options['associated'] : []; - $hasIds = array_key_exists('_ids', $data); - if ($hasIds && is_array($data['_ids'])) { - return $this->_loadBelongsToMany($assoc, $data['_ids']); - } - if ($hasIds) { - return []; - } - - $records = $this->many($data, $options); - $joint = $assoc->junction(); - $jointMarshaller = $joint->marshaller(); - - $nested = []; - if (isset($associated['_joinData'])) { - $nested = (array)$associated['_joinData']; - } - - foreach ($records as $i => $record) { - if (isset($data[$i]['_joinData'])) { - $joinData = $jointMarshaller->one($data[$i]['_joinData'], $nested); - $record->set('_joinData', $joinData); - } - } - return $records; - } - -/** - * Loads a list of belongs to many from ids. - * - * @param Association $assoc The association class for the belongsToMany association. - * @param array $ids The list of ids to load. - * @return array An array of entities. - */ - protected function _loadBelongsToMany($assoc, $ids) { - $target = $assoc->target(); - $primaryKey = (array)$target->primaryKey(); - $multi = count($primaryKey) > 1; - $primaryKey = array_map(function ($key) use ($target) { - return $target->alias() . '.' . $key; - }, $primaryKey); - - if ($multi) { - if (count(current($ids)) !== count($primaryKey)) { - return []; - } - $filter = new TupleComparison($primaryKey, $ids, [], 'IN'); - } else { - $filter = [$primaryKey[0] . ' IN' => $ids]; - } - - return $target->find()->where($filter)->toArray(); - } - -/** - * Merges `$data` into `$entity` and recursively does the same for each one of - * the association names passed in `$options`. When merging associations, if an - * entity is not present in the parent entity for a given association, a new one - * will be created. - * - * When merging HasMany or BelongsToMany associations, all the entities in the - * `$data` array will appear, those that can be matched by primary key will get - * the data merged, but those that cannot, will be discarded. - * - * ### Options: - * - * * associated: Associations listed here will be marshalled as well. - * * validate: Whether or not to validate data before hydrating the entities. Can - * also be set to a string to use a specific validator. Defaults to true/default. - * * fieldList: A whitelist of fields to be assigned to the entity. If not present - * the accessible fields list in the entity will be used. - * - * @param \Cake\Datasource\EntityInterface $entity the entity that will get the - * data merged in - * @param array $data key value list of fields to be merged into the entity - * @param array $options List of options. - * @return \Cake\Datasource\EntityInterface - */ - public function merge(EntityInterface $entity, array $data, array $options = []) { - $options += ['validate' => true]; - $propertyMap = $this->_buildPropertyMap($options); - $tableName = $this->_table->alias(); - $isNew = $entity->isNew(); - $keys = []; - - if (isset($data[$tableName])) { - $data = $data[$tableName]; - } - - if (!$isNew) { - $keys = $entity->extract((array)$this->_table->primaryKey()); - } - - $errors = $this->_validate($data + $keys, $options, $isNew); - $schema = $this->_table->schema(); - $properties = []; - foreach ($data as $key => $value) { - if (!empty($errors[$key])) { - continue; - } - - $columnType = $schema->columnType($key); - $original = $entity->get($key); - - if (isset($propertyMap[$key])) { - $assoc = $propertyMap[$key]['association']; - $value = $this->_mergeAssociation($original, $assoc, $value, $propertyMap[$key]); - } elseif ($columnType) { - $converter = Type::build($columnType); - $value = $converter->marshal($value); - $isObject = is_object($value); - if ( - (!$isObject && $original === $value) || - ($isObject && $original == $value) - ) { - continue; - } - } - - $properties[$key] = $value; - } - - if (!isset($options['fieldList'])) { - $entity->set($properties); - $entity->errors($errors); - return $entity; - } - - foreach ((array)$options['fieldList'] as $field) { - if (isset($properties[$field])) { - $entity->set($field, $properties[$field]); - } - } - - $entity->errors($errors); - return $entity; - } - -/** - * Merges each of the elements from `$data` into each of the entities in `$entities - * and recursively does the same for each of the association names passed in - * `$options`. When merging associations, if an entity is not present in the parent - * entity for a given association, a new one will be created. - * - * Records in `$data` are matched against the entities using the primary key - * column. Entries in `$entities` that cannot be matched to any record in - * `$data` will be discarded. Records in `$data` that could not be matched will - * be marshalled as a new entity. - * - * When merging HasMany or BelongsToMany associations, all the entities in the - * `$data` array will appear, those that can be matched by primary key will get - * the data merged, but those that cannot, will be discarded. - * - * ### Options: - * - * - associated: Associations listed here will be marshalled as well. - * - fieldList: A whitelist of fields to be assigned to the entity. If not present, - * the accessible fields list in the entity will be used. - * - * @param array|\Traversable $entities the entities that will get the - * data merged in - * @param array $data list of arrays to be merged into the entities - * @param array $options List of options. - * @return array - */ - public function mergeMany($entities, array $data, array $options = []) { - $primary = (array)$this->_table->primaryKey(); - - $indexed = (new Collection($data)) - ->groupBy(function ($el) use ($primary) { - $keys = []; - foreach ($primary as $key) { - $keys[] = isset($el[$key]) ? $el[$key] : ''; - } - return implode(';', $keys); - }) - ->map(function ($element, $key) { - return $key === '' ? $element : $element[0]; - }) - ->toArray(); - - $new = isset($indexed[null]) ? $indexed[null] : []; - unset($indexed[null]); - $output = []; - - foreach ($entities as $entity) { - if (!($entity instanceof EntityInterface)) { - continue; - } - - $key = implode(';', $entity->extract($primary)); - - if ($key === null || !isset($indexed[$key])) { - continue; - } - - $output[] = $this->merge($entity, $indexed[$key], $options); - unset($indexed[$key]); - } - - $maybeExistentQuery = (new Collection($indexed)) - ->map(function ($data, $key) { - return explode(';', $key); - }) - ->filter(function ($keys) use ($primary) { - return count(array_filter($keys, 'strlen')) === count($primary); - }) - ->reduce(function ($query, $keys) use ($primary) { - return $query->orWhere($query->newExpr()->and_(array_combine($primary, $keys))); - }, $this->_table->find()); - - if (count($maybeExistentQuery->clause('where'))) { - foreach ($maybeExistentQuery as $entity) { - $key = implode(';', $entity->extract($primary)); - $output[] = $this->merge($entity, $indexed[$key], $options); - unset($indexed[$key]); - } - } - - foreach ((new Collection($indexed))->append($new) as $value) { - $output[] = $this->one($value, $options); - } - - return $output; - } - -/** - * Creates a new sub-marshaller and merges the associated data. - * - * @param \Cake\Datasource\EntityInterface $original The original entity - * @param \Cake\ORM\Association $assoc The association to merge - * @param array $value The data to hydrate - * @param array $options List of options. - * @return mixed - */ - protected function _mergeAssociation($original, $assoc, $value, $options) { - if (!$original) { - return $this->_marshalAssociation($assoc, $value, $options); - } - - $targetTable = $assoc->target(); - $marshaller = $targetTable->marshaller(); - $types = [Association::ONE_TO_ONE, Association::MANY_TO_ONE]; - if (in_array($assoc->type(), $types)) { - return $marshaller->merge($original, $value, (array)$options); - } - if ($assoc->type() === Association::MANY_TO_MANY) { - return $marshaller->_mergeBelongsToMany($original, $assoc, $value, (array)$options); - } - return $marshaller->mergeMany($original, $value, (array)$options); - } - -/** - * Creates a new sub-marshaller and merges the associated data for a BelongstoMany - * association. - * - * @param \Cake\Datasource\EntityInterface $original The original entity - * @param \Cake\ORM\Association $assoc The association to marshall - * @param array $value The data to hydrate - * @param array $options List of options. - * @return mixed - */ - protected function _mergeBelongsToMany($original, $assoc, $value, $options) { - $hasIds = array_key_exists('_ids', $value); - $associated = isset($options['associated']) ? $options['associated'] : []; - if ($hasIds && is_array($value['_ids'])) { - return $this->_loadBelongsToMany($assoc, $value['_ids']); - } - if ($hasIds) { - return []; - } - - if (!in_array('_joinData', $associated) && !isset($associated['_joinData'])) { - return $this->mergeMany($original, $value, $options); - } - - $extra = []; - foreach ($original as $entity) { - $joinData = $entity->get('_joinData'); - if ($joinData && $joinData instanceof EntityInterface) { - $extra[spl_object_hash($entity)] = $joinData; - } - } - - $joint = $assoc->junction(); - $marshaller = $joint->marshaller(); - - $nested = []; - if (isset($associated['_joinData'])) { - $nested = (array)$associated['_joinData']; - } - - $records = $this->mergeMany($original, $value, $options); - foreach ($records as $record) { - $hash = spl_object_hash($record); - $value = $record->get('_joinData'); - if (isset($extra[$hash])) { - $record->set('_joinData', $marshaller->merge($extra[$hash], (array)$value, $nested)); - } else { - $joinData = $marshaller->one($value, $nested); - $record->set('_joinData', $joinData); - } - } - - return $records; - } - +class Marshaller +{ + + use AssociationsNormalizerTrait; + + /** + * The table instance this marshaller is for. + * + * @var \Cake\ORM\Table + */ + protected $_table; + + /** + * Constructor. + * + * @param \Cake\ORM\Table $table The table this marshaller is for. + */ + public function __construct(Table $table) + { + $this->_table = $table; + } + + /** + * Build the map of property => association names. + * + * @param array $options List of options containing the 'associated' key. + * @return array + */ + protected function _buildPropertyMap($options) + { + if (empty($options['associated'])) { + return []; + } + + $include = $options['associated']; + $map = []; + $include = $this->_normalizeAssociations($include); + foreach ($include as $key => $nested) { + if (is_int($key) && is_scalar($nested)) { + $key = $nested; + $nested = []; + } + $assoc = $this->_table->association($key); + if ($assoc) { + $map[$assoc->property()] = ['association' => $assoc] + $nested + ['associated' => []]; + } + } + return $map; + } + + /** + * Hydrate one entity and its associated data. + * + * ### Options: + * + * * associated: Associations listed here will be marshalled as well. + * * fieldList: A whitelist of fields to be assigned to the entity. If not present, + * the accessible fields list in the entity will be used. + * * accessibleFields: A list of fields to allow or deny in entity accessible fields. + * + * @param array $data The data to hydrate. + * @param array $options List of options + * @return \Cake\ORM\Entity + * @see \Cake\ORM\Table::newEntity() + */ + public function one(array $data, array $options = []) + { + $options += ['validate' => true]; + $propertyMap = $this->_buildPropertyMap($options); + + $schema = $this->_table->schema(); + $tableName = $this->_table->alias(); + $entityClass = $this->_table->entityClass(); + $entity = new $entityClass(); + $entity->source($this->_table->alias()); + + if (isset($data[$tableName])) { + $data = $data[$tableName]; + } + + if (isset($options['accessibleFields'])) { + foreach ((array)$options['accessibleFields'] as $key => $value) { + $entity->accessible($key, $value); + } + } + + $errors = $this->_validate($data, $options, true); + $primaryKey = $schema->primaryKey(); + $properties = []; + foreach ($data as $key => $value) { + if (!empty($errors[$key])) { + continue; + } + $columnType = $schema->columnType($key); + if (isset($propertyMap[$key])) { + $assoc = $propertyMap[$key]['association']; + $value = $this->_marshalAssociation($assoc, $value, $propertyMap[$key]); + } elseif ($value === '' && in_array($key, $primaryKey, true)) { + // Skip marshalling '' for pk fields. + continue; + } elseif ($columnType) { + $converter = Type::build($columnType); + $value = $converter->marshal($value); + } + $properties[$key] = $value; + } + + if (!isset($options['fieldList'])) { + $entity->set($properties); + $entity->errors($errors); + return $entity; + } + + foreach ((array)$options['fieldList'] as $field) { + if (isset($properties[$field])) { + $entity->set($field, $properties[$field]); + } + } + + $entity->errors($errors); + return $entity; + } + + /** + * Returns the validation errors for a data set based on the passed options + * + * @param array $data The data to validate. + * @param array $options The options passed to this marshaller. + * @param bool $isNew Whether it is a new entity or one to be updated. + * @return array The list of validation errors. + * @throws \RuntimeException If no validator can be created. + */ + protected function _validate($data, $options, $isNew) + { + if (!$options['validate']) { + return []; + } + if ($options['validate'] === true) { + $options['validate'] = $this->_table->validator('default'); + } + if (is_string($options['validate'])) { + $options['validate'] = $this->_table->validator($options['validate']); + } + if (!is_object($options['validate'])) { + throw new RuntimeException( + sprintf('validate must be a boolean, a string or an object. Got %s.', gettype($options['validate'])) + ); + } + + return $options['validate']->errors($data, $isNew); + } + + /** + * Create a new sub-marshaller and marshal the associated data. + * + * @param \Cake\ORM\Association $assoc The association to marshall + * @param array $value The data to hydrate + * @param array $options List of options. + * @return mixed + */ + protected function _marshalAssociation($assoc, $value, $options) + { + $targetTable = $assoc->target(); + $marshaller = $targetTable->marshaller(); + $types = [Association::ONE_TO_ONE, Association::MANY_TO_ONE]; + if (in_array($assoc->type(), $types)) { + return $marshaller->one($value, (array)$options); + } + if ($assoc->type() === Association::MANY_TO_MANY) { + return $marshaller->_belongsToMany($assoc, $value, (array)$options); + } + return $marshaller->many($value, (array)$options); + } + + /** + * Hydrate many entities and their associated data. + * + * ### Options: + * + * * associated: Associations listed here will be marshalled as well. + * * fieldList: A whitelist of fields to be assigned to the entity. If not present, + * the accessible fields list in the entity will be used. + * + * @param array $data The data to hydrate. + * @param array $options List of options + * @return array An array of hydrated records. + * @see \Cake\ORM\Table::newEntities() + */ + public function many(array $data, array $options = []) + { + $output = []; + foreach ($data as $record) { + $output[] = $this->one($record, $options); + } + return $output; + } + + /** + * Marshals data for belongsToMany associations. + * + * Builds the related entities and handles the special casing + * for junction table entities. + * + * @param Association $assoc The association to marshal. + * @param array $data The data to convert into entities. + * @param array $options List of options. + * @return array An array of built entities. + */ + protected function _belongsToMany(Association $assoc, array $data, $options = []) + { + $associated = isset($options['associated']) ? $options['associated'] : []; + $hasIds = array_key_exists('_ids', $data); + if ($hasIds && is_array($data['_ids'])) { + return $this->_loadBelongsToMany($assoc, $data['_ids']); + } + if ($hasIds) { + return []; + } + + $records = $this->many($data, $options); + $joint = $assoc->junction(); + $jointMarshaller = $joint->marshaller(); + + $nested = []; + if (isset($associated['_joinData'])) { + $nested = (array)$associated['_joinData']; + } + + foreach ($records as $i => $record) { + if (isset($data[$i]['_joinData'])) { + $joinData = $jointMarshaller->one($data[$i]['_joinData'], $nested); + $record->set('_joinData', $joinData); + } + } + return $records; + } + + /** + * Loads a list of belongs to many from ids. + * + * @param Association $assoc The association class for the belongsToMany association. + * @param array $ids The list of ids to load. + * @return array An array of entities. + */ + protected function _loadBelongsToMany($assoc, $ids) + { + $target = $assoc->target(); + $primaryKey = (array)$target->primaryKey(); + $multi = count($primaryKey) > 1; + $primaryKey = array_map(function ($key) use ($target) { + return $target->alias() . '.' . $key; + }, $primaryKey); + + if ($multi) { + if (count(current($ids)) !== count($primaryKey)) { + return []; + } + $filter = new TupleComparison($primaryKey, $ids, [], 'IN'); + } else { + $filter = [$primaryKey[0] . ' IN' => $ids]; + } + + return $target->find()->where($filter)->toArray(); + } + + /** + * Merges `$data` into `$entity` and recursively does the same for each one of + * the association names passed in `$options`. When merging associations, if an + * entity is not present in the parent entity for a given association, a new one + * will be created. + * + * When merging HasMany or BelongsToMany associations, all the entities in the + * `$data` array will appear, those that can be matched by primary key will get + * the data merged, but those that cannot, will be discarded. + * + * ### Options: + * + * * associated: Associations listed here will be marshalled as well. + * * validate: Whether or not to validate data before hydrating the entities. Can + * also be set to a string to use a specific validator. Defaults to true/default. + * * fieldList: A whitelist of fields to be assigned to the entity. If not present + * the accessible fields list in the entity will be used. + * + * @param \Cake\Datasource\EntityInterface $entity the entity that will get the + * data merged in + * @param array $data key value list of fields to be merged into the entity + * @param array $options List of options. + * @return \Cake\Datasource\EntityInterface + */ + public function merge(EntityInterface $entity, array $data, array $options = []) + { + $options += ['validate' => true]; + $propertyMap = $this->_buildPropertyMap($options); + $tableName = $this->_table->alias(); + $isNew = $entity->isNew(); + $keys = []; + + if (isset($data[$tableName])) { + $data = $data[$tableName]; + } + + if (!$isNew) { + $keys = $entity->extract((array)$this->_table->primaryKey()); + } + + $errors = $this->_validate($data + $keys, $options, $isNew); + $schema = $this->_table->schema(); + $properties = []; + foreach ($data as $key => $value) { + if (!empty($errors[$key])) { + continue; + } + + $columnType = $schema->columnType($key); + $original = $entity->get($key); + + if (isset($propertyMap[$key])) { + $assoc = $propertyMap[$key]['association']; + $value = $this->_mergeAssociation($original, $assoc, $value, $propertyMap[$key]); + } elseif ($columnType) { + $converter = Type::build($columnType); + $value = $converter->marshal($value); + $isObject = is_object($value); + if ( + (!$isObject && $original === $value) || + ($isObject && $original == $value) + ) { + continue; + } + } + + $properties[$key] = $value; + } + + if (!isset($options['fieldList'])) { + $entity->set($properties); + $entity->errors($errors); + return $entity; + } + + foreach ((array)$options['fieldList'] as $field) { + if (isset($properties[$field])) { + $entity->set($field, $properties[$field]); + } + } + + $entity->errors($errors); + return $entity; + } + + /** + * Merges each of the elements from `$data` into each of the entities in `$entities + * and recursively does the same for each of the association names passed in + * `$options`. When merging associations, if an entity is not present in the parent + * entity for a given association, a new one will be created. + * + * Records in `$data` are matched against the entities using the primary key + * column. Entries in `$entities` that cannot be matched to any record in + * `$data` will be discarded. Records in `$data` that could not be matched will + * be marshalled as a new entity. + * + * When merging HasMany or BelongsToMany associations, all the entities in the + * `$data` array will appear, those that can be matched by primary key will get + * the data merged, but those that cannot, will be discarded. + * + * ### Options: + * + * - associated: Associations listed here will be marshalled as well. + * - fieldList: A whitelist of fields to be assigned to the entity. If not present, + * the accessible fields list in the entity will be used. + * + * @param array|\Traversable $entities the entities that will get the + * data merged in + * @param array $data list of arrays to be merged into the entities + * @param array $options List of options. + * @return array + */ + public function mergeMany($entities, array $data, array $options = []) + { + $primary = (array)$this->_table->primaryKey(); + + $indexed = (new Collection($data)) + ->groupBy(function ($el) use ($primary) { + $keys = []; + foreach ($primary as $key) { + $keys[] = isset($el[$key]) ? $el[$key] : ''; + } + return implode(';', $keys); + }) + ->map(function ($element, $key) { + return $key === '' ? $element : $element[0]; + }) + ->toArray(); + + $new = isset($indexed[null]) ? $indexed[null] : []; + unset($indexed[null]); + $output = []; + + foreach ($entities as $entity) { + if (!($entity instanceof EntityInterface)) { + continue; + } + + $key = implode(';', $entity->extract($primary)); + + if ($key === null || !isset($indexed[$key])) { + continue; + } + + $output[] = $this->merge($entity, $indexed[$key], $options); + unset($indexed[$key]); + } + + $maybeExistentQuery = (new Collection($indexed)) + ->map(function ($data, $key) { + return explode(';', $key); + }) + ->filter(function ($keys) use ($primary) { + return count(array_filter($keys, 'strlen')) === count($primary); + }) + ->reduce(function ($query, $keys) use ($primary) { + return $query->orWhere($query->newExpr()->and_(array_combine($primary, $keys))); + }, $this->_table->find()); + + if (count($maybeExistentQuery->clause('where'))) { + foreach ($maybeExistentQuery as $entity) { + $key = implode(';', $entity->extract($primary)); + $output[] = $this->merge($entity, $indexed[$key], $options); + unset($indexed[$key]); + } + } + + foreach ((new Collection($indexed))->append($new) as $value) { + $output[] = $this->one($value, $options); + } + + return $output; + } + + /** + * Creates a new sub-marshaller and merges the associated data. + * + * @param \Cake\Datasource\EntityInterface $original The original entity + * @param \Cake\ORM\Association $assoc The association to merge + * @param array $value The data to hydrate + * @param array $options List of options. + * @return mixed + */ + protected function _mergeAssociation($original, $assoc, $value, $options) + { + if (!$original) { + return $this->_marshalAssociation($assoc, $value, $options); + } + + $targetTable = $assoc->target(); + $marshaller = $targetTable->marshaller(); + $types = [Association::ONE_TO_ONE, Association::MANY_TO_ONE]; + if (in_array($assoc->type(), $types)) { + return $marshaller->merge($original, $value, (array)$options); + } + if ($assoc->type() === Association::MANY_TO_MANY) { + return $marshaller->_mergeBelongsToMany($original, $assoc, $value, (array)$options); + } + return $marshaller->mergeMany($original, $value, (array)$options); + } + + /** + * Creates a new sub-marshaller and merges the associated data for a BelongstoMany + * association. + * + * @param \Cake\Datasource\EntityInterface $original The original entity + * @param \Cake\ORM\Association $assoc The association to marshall + * @param array $value The data to hydrate + * @param array $options List of options. + * @return mixed + */ + protected function _mergeBelongsToMany($original, $assoc, $value, $options) + { + $hasIds = array_key_exists('_ids', $value); + $associated = isset($options['associated']) ? $options['associated'] : []; + if ($hasIds && is_array($value['_ids'])) { + return $this->_loadBelongsToMany($assoc, $value['_ids']); + } + if ($hasIds) { + return []; + } + + if (!in_array('_joinData', $associated) && !isset($associated['_joinData'])) { + return $this->mergeMany($original, $value, $options); + } + + $extra = []; + foreach ($original as $entity) { + $joinData = $entity->get('_joinData'); + if ($joinData && $joinData instanceof EntityInterface) { + $extra[spl_object_hash($entity)] = $joinData; + } + } + + $joint = $assoc->junction(); + $marshaller = $joint->marshaller(); + + $nested = []; + if (isset($associated['_joinData'])) { + $nested = (array)$associated['_joinData']; + } + + $records = $this->mergeMany($original, $value, $options); + foreach ($records as $record) { + $hash = spl_object_hash($record); + $value = $record->get('_joinData'); + if (isset($extra[$hash])) { + $record->set('_joinData', $marshaller->merge($extra[$hash], (array)$value, $nested)); + } else { + $joinData = $marshaller->one($value, $nested); + $record->set('_joinData', $joinData); + } + } + + return $records; + } } diff --git a/Query.php b/Query.php index 5fe7f72d..1338c120 100644 --- a/Query.php +++ b/Query.php @@ -29,829 +29,859 @@ * required. * */ -class Query extends DatabaseQuery implements JsonSerializable { +class Query extends DatabaseQuery implements JsonSerializable +{ - use QueryTrait { - cache as private _cache; - all as private _all; - _decorateResults as private _applyDecorators; - __call as private _call; - } + use QueryTrait { + cache as private _cache; + all as private _all; + _decorateResults as private _applyDecorators; + __call as private _call; + } -/** + /** * Indicates that the operation should append to the list * * @var int */ - const APPEND = 0; + const APPEND = 0; -/** + /** * Indicates that the operation should prepend to the list * * @var int */ - const PREPEND = 1; + const PREPEND = 1; -/** + /** * Indicates that the operation should overwrite the list * * @var bool */ - const OVERWRITE = true; - -/** - * Whether the user select any fields before being executed, this is used - * to determined if any fields should be automatically be selected. - * - * @var bool - */ - protected $_hasFields; - -/** - * Tracks whether or not the original query should include - * fields from the top level table. - * - * @var bool - */ - protected $_autoFields; - -/** - * Boolean for tracking whether or not buffered results - * are enabled. - * - * @var bool - */ - protected $_useBufferedResults = true; - -/** - * Whether to hydrate results into entity objects - * - * @var bool - */ - protected $_hydrate = true; - -/** - * A callable function that can be used to calculate the total amount of - * records this query will match when not using `limit` - * - * @var callable - */ - protected $_counter; - -/** - * Instance of a class responsible for storing association containments and - * for eager loading them when this query is executed - * - * @var \Cake\ORM\EagerLoader - */ - protected $_eagerLoader; - -/** - * True if the beforeFind event has already been triggered for this query - * - * @var bool - */ - protected $_beforeFindFired = false; - -/** - * Constructor - * - * @param \Cake\Database\Connection $connection The connection object - * @param \Cake\ORM\Table $table The table this query is starting on - */ - public function __construct($connection, $table) { - parent::__construct($connection); - $this->repository($table); - - if ($this->_repository) { - $this->addDefaultTypes($this->_repository); - } - } - -/** - * Hints this object to associate the correct types when casting conditions - * for the database. This is done by extracting the field types from the schema - * associated to the passed table object. This prevents the user from repeating - * himself when specifying conditions. - * - * This method returns the same query object for chaining. - * - * @param \Cake\ORM\Table $table The table to pull types from - * @return $this - */ - public function addDefaultTypes(Table $table) { - $alias = $table->alias(); - $schema = $table->schema(); - $fields = []; - foreach ($schema->columns() as $f) { - $fields[$f] = $fields[$alias . '.' . $f] = $schema->columnType($f); - } - $this->defaultTypes($fields); - - return $this; - } - -/** - * Sets the instance of the eager loader class to use for loading associations - * and storing containments. If called with no arguments, it will return the - * currently configured instance. - * - * @param \Cake\ORM\EagerLoader $instance The eager loader to use. Pass null - * to get the current eagerloader. - * @return \Cake\ORM\EagerLoader|$this - */ - public function eagerLoader(EagerLoader $instance = null) { - if ($instance === null) { - if ($this->_eagerLoader === null) { - $this->_eagerLoader = new EagerLoader; - } - return $this->_eagerLoader; - } - $this->_eagerLoader = $instance; - return $this; - } - -/** - * Sets the list of associations that should be eagerly loaded along with this - * query. The list of associated tables passed must have been previously set as - * associations using the Table API. - * - * ### Example: - * - * {{{ - * // Bring articles' author information - * $query->contain('Author'); - * - * // Also bring the category and tags associated to each article - * $query->contain(['Category', 'Tag']); - * }}} - * - * Associations can be arbitrarily nested using dot notation or nested arrays, - * this allows this object to calculate joins or any additional queries that - * must be executed to bring the required associated data. - * - * ### Example: - * - * {{{ - * // Eager load the product info, and for each product load other 2 associations - * $query->contain(['Product' => ['Manufacturer', 'Distributor']); - * - * // Which is equivalent to calling - * $query->contain(['Products.Manufactures', 'Products.Distributors']); - * - * // For an author query, load his region, state and country - * $query->contain('Regions.States.Countries'); - * }}} - * - * It is possible to control the conditions and fields selected for each of the - * contained associations: - * - * ### Example: - * - * {{{ - * $query->contain(['Tags' => function ($q) { - * return $q->where(['Tags.is_popular' => true]); - * }]); - * - * $query->contain(['Products.Manufactures' => function ($q) { - * return $q->select(['name'])->where(['Manufactures.active' => true]); - * }]); - * }}} - * - * Each association might define special options when eager loaded, the allowed - * options that can be set per association are: - * - * - foreignKey: Used to set a different field to match both tables, if set to false - * no join conditions will be generated automatically - * - fields: An array with the fields that should be fetched from the association - * - queryBuilder: Equivalent to passing a callable instead of an options array - * - * ### Example: - * - * {{{ - * // Set options for the hasMany articles that will be eagerly loaded for an author - * $query->contain([ - * 'Articles' => [ - * 'fields' => ['title', 'author_id'] - * ] - * ]); - * }}} - * - * When containing associations, it is important to include foreign key columns. - * Failing to do so will trigger exceptions. - * - * {{{ - * // Use special join conditions for getting an author's hasMany 'likes' - * $query->contain([ - * 'Likes' => [ - * 'foreignKey' => false, - * 'queryBuilder' => function ($q) { - * return $q->where(...); // Add full filtering conditions - * } - * ] - * ]); - * }}} - * - * If called with no arguments, this function will return an array with - * with the list of previously configured associations to be contained in the - * result. - * - * If called with an empty first argument and $override is set to true, the - * previous list will be emptied. - * - * @param array|string $associations list of table aliases to be queried - * @param bool $override whether override previous list with the one passed - * defaults to merging previous list with the new one. - * @return array|$this - */ - public function contain($associations = null, $override = false) { - if (empty($associations) && $override) { - $this->_eagerLoader = null; - } - - $result = $this->eagerLoader()->contain($associations); - if ($associations !== null || $override) { - $this->_dirty(); - } - if ($associations === null) { - return $result; - } - - return $this; - } - -/** - * Adds filtering conditions to this query to only bring rows that have a relation - * to another from an associated table, based on conditions in the associated table. - * - * This function will add entries in the `contain` graph. - * - * ### Example: - * - * {{{ - * // Bring only articles that were tagged with 'cake' - * $query->matching('Tags', function ($q) { - * return $q->where(['name' => 'cake']); - * ); - * }}} - * - * It is possible to filter by deep associations by using dot notation: - * - * ### Example: - * - * {{{ - * // Bring only articles that were commented by 'markstory' - * $query->matching('Comments.Users', function ($q) { - * return $q->where(['username' => 'markstory']); - * ); - * }}} - * - * As this function will create `INNER JOIN`, you might want to consider - * calling `distinct` on this query as you might get duplicate rows if - * your conditions don't filter them already. This might be the case, for example, - * of the same user commenting more than once in the same article. - * - * ### Example: - * - * {{{ - * // Bring unique articles that were commented by 'markstory' - * $query->distinct(['Articles.id']) - * ->matching('Comments.Users', function ($q) { - * return $q->where(['username' => 'markstory']); - * ); - * }}} - * - * Please note that the query passed to the closure will only accept calling - * `select`, `where`, `andWhere` and `orWhere` on it. If you wish to - * add more complex clauses you can do it directly in the main query. - * - * @param string $assoc The association to filter by - * @param callable $builder a function that will receive a pre-made query object - * that can be used to add custom conditions or selecting some fields - * @return $this - */ - public function matching($assoc, callable $builder = null) { - $this->eagerLoader()->matching($assoc, $builder); - $this->_dirty(); - return $this; - } - -/** - * Enable/Disable buffered results. - * - * When enabled the ResultSet returned by this Query will be - * buffered. This enables you to iterate a ResultSet multiple times, or - * both cache and iterate the ResultSet. - * - * When disabled it will consume less memory as fetched results are not - * remembered in the ResultSet. - * - * If called with no arguments, it will return whether or not buffering is - * enabled. - * - * @param bool $enable whether or not to enable buffering - * @return bool|$this - */ - public function bufferResults($enable = null) { - if ($enable === null) { - return $this->_useBufferedResults; - } - - $this->_dirty(); - $this->_useBufferedResults = (bool)$enable; - return $this; - } - -/** - * Returns a key => value array representing a single aliased field - * that can be passed directly to the select() method. - * The key will contain the alias and the value the actual field name. - * - * If the field is already aliased, then it will not be changed. - * If no $alias is passed, the default table for this query will be used. - * - * @param string $field The field to alias - * @param string $alias the alias used to prefix the field - * @return array - */ - public function aliasField($field, $alias = null) { - $namespaced = strpos($field, '.') !== false; - $aliasedField = $field; - - if ($namespaced) { - list($alias, $field) = explode('.', $field); - } - - if (!$alias) { - $alias = $this->repository()->alias(); - } - - $key = sprintf('%s__%s', $alias, $field); - if (!$namespaced) { - $aliasedField = $alias . '.' . $field; - } - - return [$key => $aliasedField]; - } - -/** - * Runs `aliasField()` for each field in the provided list and returns - * the result under a single array. - * - * @param array $fields The fields to alias - * @param string|null $defaultAlias The default alias - * @return array - */ - public function aliasFields($fields, $defaultAlias = null) { - $aliased = []; - foreach ($fields as $alias => $field) { - if (is_numeric($alias) && is_string($field)) { - $aliased += $this->aliasField($field, $defaultAlias); - continue; - } - $aliased[$alias] = $field; - } - - return $aliased; - } - -/** - * Populates or adds parts to current query clauses using an array. - * This is handy for passing all query clauses at once. The option array accepts: - * - * - fields: Maps to the select method - * - conditions: Maps to the where method - * - limit: Maps to the limit method - * - order: Maps to the order method - * - offset: Maps to the offset method - * - group: Maps to the group method - * - having: Maps to the having method - * - contain: Maps to the contain options for eager loading - * - join: Maps to the join method - * - page: Maps to the page method - * - * ### Example: - * - * {{{ - * $query->applyOptions([ - * 'fields' => ['id', 'name'], - * 'conditions' => [ - * 'created >=' => '2013-01-01' - * ], - * 'limit' => 10 - * ]); - * }}} - * - * Is equivalent to: - * - * {{{ - * $query - * ->select(['id', 'name']) - * ->where(['created >=' => '2013-01-01']) - * ->limit(10) - * }}} - * - * @param array $options list of query clauses to apply new parts to. - * @return $this - */ - public function applyOptions(array $options) { - $valid = [ - 'fields' => 'select', - 'conditions' => 'where', - 'join' => 'join', - 'order' => 'order', - 'limit' => 'limit', - 'offset' => 'offset', - 'group' => 'group', - 'having' => 'having', - 'contain' => 'contain', - 'page' => 'page', - ]; - - ksort($options); - foreach ($options as $option => $values) { - if (isset($valid[$option]) && isset($values)) { - $this->{$valid[$option]}($values); - } else { - $this->_options[$option] = $values; - } - } - - return $this; - } - -/** - * Creates a copy of this current query, triggers beforeFind and resets some state. - * - * The following state will be cleared: - * - * - autoFields - * - limit - * - offset - * - map/reduce functions - * - result formatters - * - order - * - containments - * - * This method creates query clones that are useful when working with subqueries. - * - * @return \Cake\ORM\Query - */ - public function cleanCopy() { - $query = clone $this; - $query->triggerBeforeFind(); - $query->autoFields(false); - $query->limit(null); - $query->order([], true); - $query->offset(null); - $query->mapReduce(null, null, true); - $query->formatResults(null, true); - return $query; - } - -/** - * Returns the COUNT(*) for the query. - * - * @return int - */ - public function count() { - $query = $this->cleanCopy(); - $counter = $this->_counter; - - if ($counter) { - $query->counter(null); - return (int)$counter($query); - } - - $count = ['count' => $query->func()->count('*')]; - $complex = count($query->clause('group')) || $query->clause('distinct'); - $complex = $complex || count($query->clause('union')); - - if (!$complex) { - $statement = $query - ->select($count, true) - ->autoFields(false) - ->execute(); - } else { - $statement = $this->connection()->newQuery() - ->select($count) - ->from(['count_source' => $query]) - ->execute(); - } - - $result = $statement->fetch('assoc')['count']; - $statement->closeCursor(); - return (int)$result; - } - -/** - * Registers a callable function that will be executed when the `count` method in - * this query is called. The return value for the function will be set as the - * return value of the `count` method. - * - * This is particularly useful when you need to optimize a query for returning the - * count, for example removing unnecessary joins, removing group by or just return - * an estimated number of rows. - * - * The callback will receive as first argument a clone of this query and not this - * query itself. - * - * @param callable $counter The counter value - * @return $this - */ - public function counter($counter) { - $this->_counter = $counter; - return $this; - } - -/** - * Toggle hydrating entities. - * - * If set to false array results will be returned - * - * @param bool|null $enable Use a boolean to set the hydration mode. - * Null will fetch the current hydration mode. - * @return bool|$this A boolean when reading, and $this when setting the mode. - */ - public function hydrate($enable = null) { - if ($enable === null) { - return $this->_hydrate; - } - - $this->_dirty(); - $this->_hydrate = (bool)$enable; - return $this; - } - -/** - * {@inheritDoc} - * - * @return $this - * @throws \RuntimeException When you attempt to cache a non-select query. - */ - public function cache($key, $config = 'default') { - if ($this->_type !== 'select' && $this->_type !== null) { - throw new \RuntimeException('You cannot cache the results of non-select queries.'); - } - return $this->_cache($key, $config); - } - -/** - * {@inheritDoc} - * - * @throws \RuntimeException if this method is called on a non-select Query. - */ - public function all() { - if ($this->_type !== 'select' && $this->_type !== null) { - throw new \RuntimeException( - 'You cannot call all() on a non-select query. Use execute() instead.' - ); - } - return $this->_all(); - } - -/** - * Trigger the beforeFind event on the query's repository object. - * - * Will not trigger more than once, and only for select queries. - * - * @return void - */ - public function triggerBeforeFind() { - if (!$this->_beforeFindFired && $this->_type === 'select') { - $table = $this->repository(); - $table->dispatchEvent('Model.beforeFind', [$this, $this->_options, !$this->eagerLoaded()]); - $this->_beforeFindFired = true; - } - } - -/** - * {@inheritDoc} - */ - public function sql(ValueBinder $binder = null) { - $this->triggerBeforeFind(); - - $this->_transformQuery(); - $sql = parent::sql($binder); - return $sql; - } - -/** - * Executes this query and returns a ResultSet object containing the results. - * This will also setup the correct statement class in order to eager load deep - * associations. - * - * @return \Cake\ORM\ResultSet - */ - protected function _execute() { - $this->triggerBeforeFind(); - if ($this->_results) { - $decorator = $this->_decoratorClass(); - return new $decorator($this->_results); - } - $statement = $this->eagerLoader()->loadExternal($this, $this->execute()); - return new ResultSet($this, $statement); - } - -/** - * Applies some defaults to the query object before it is executed. - * - * Specifically add the FROM clause, adds default table fields if none are - * specified and applies the joins required to eager load associations defined - * using `contain` - * - * @see \Cake\Database\Query::execute() - * @return $this - */ - protected function _transformQuery() { - if (!$this->_dirty) { - return; - } - - if ($this->_type === 'select') { - if (empty($this->_parts['from'])) { - $this->from([$this->_repository->alias() => $this->_repository->table()]); - } - $this->_addDefaultFields(); - $this->eagerLoader()->attachAssociations($this, $this->_repository, !$this->_hasFields); - } - } - -/** - * Inspects if there are any set fields for selecting, otherwise adds all - * the fields for the default table. - * - * @return void - */ - protected function _addDefaultFields() { - $select = $this->clause('select'); - $this->_hasFields = true; - - if (!count($select) || $this->_autoFields === true) { - $this->_hasFields = false; - $this->select($this->repository()->schema()->columns()); - $select = $this->clause('select'); - } - - $aliased = $this->aliasFields($select, $this->repository()->alias()); - $this->select($aliased, true); - } - -/** - * Apply custom finds to against an existing query object. - * - * Allows custom find methods to be combined and applied to each other. - * - * {{{ - * $table->find('all')->find('recent'); - * }}} - * - * The above is an example of stacking multiple finder methods onto - * a single query. - * - * @param string $finder The finder method to use. - * @param array $options The options for the finder. - * @return $this Returns a modified query. - * @see \Cake\ORM\Table::find() - */ - public function find($finder, array $options = []) { - return $this->repository()->callFinder($finder, $this, $options); - } - -/** - * Marks a query as dirty, removing any preprocessed information - * from in memory caching such as previous results - * - * @return void - */ - protected function _dirty() { - $this->_results = null; - parent::_dirty(); - } - -/** - * Create an update query. - * - * This changes the query type to be 'update'. - * Can be combined with set() and where() methods to create update queries. - * - * @param string|null $table Unused parameter. - * @return $this - */ - public function update($table = null) { - $table = $this->repository()->table(); - return parent::update($table); - } - -/** - * Create a delete query. - * - * This changes the query type to be 'delete'. - * Can be combined with the where() method to create delete queries. - * - * @param string|null $table Unused parameter. - * @return $this - */ - public function delete($table = null) { - $repo = $this->repository(); - $this->from([$repo->alias() => $repo->table()]); - return parent::delete(); - } - -/** - * Create an insert query. - * - * This changes the query type to be 'insert'. - * Note calling this method will reset any data previously set - * with Query::values() - * - * Can be combined with the where() method to create delete queries. - * - * @param array $columns The columns to insert into. - * @param array $types A map between columns & their datatypes. - * @return $this - */ - public function insert(array $columns, array $types = []) { - $table = $this->repository()->table(); - $this->into($table); - return parent::insert($columns, $types); - } - -/** - * {@inheritDoc} - * - * @throws \BadMethodCallException if the method is called for a non-select query - */ - public function __call($method, $arguments) { - if ($this->type() === 'select') { - return $this->_call($method, $arguments); - } - - throw new \BadMethodCallException( - sprintf('Cannot call method "%s" on a "%s" query', $method, $this->type()) - ); - } - -/** - * {@inheritDoc} - */ - public function __debugInfo() { - $eagerLoader = $this->eagerLoader(); - return parent::__debugInfo() + [ - 'hydrate' => $this->_hydrate, - 'buffered' => $this->_useBufferedResults, - 'formatters' => count($this->_formatters), - 'mapReducers' => count($this->_mapReduce), - 'contain' => $eagerLoader->contain(), - 'matching' => $eagerLoader->matching(), - 'extraOptions' => $this->_options, - 'repository' => $this->_repository - ]; - } - -/** - * Executes the query and converts the result set into JSON. - * - * Part of JsonSerializable interface. - * - * @return \Cake\Datasource\ResultSetInterface The data to convert to JSON. - */ - public function jsonSerialize() { - return $this->all(); - } - -/** - * Get/Set whether or not the ORM should automatically append fields. - * - * By default calling select() will disable auto-fields. You can re-enable - * auto-fields with this method. - * - * @param bool|null $value The value to set or null to read the current value. - * @return bool|$this Either the current value or the query object. - */ - public function autoFields($value = null) { - if ($value === null) { - return $this->_autoFields; - } - $this->_autoFields = (bool)$value; - return $this; - } - -/** - * Decorates the results iterator with MapReduce routines and formatters - * - * @param \Traversable $result Original results - * @return \Cake\Datasource\ResultSetInterface - */ - protected function _decorateResults($result) { - $result = $this->_applyDecorators($result); - - if (!($result instanceof ResultSet) && $this->bufferResults()) { - $class = $this->_decoratorClass(); - $result = new $class($result->buffered()); - } - - return $result; - } - + const OVERWRITE = true; + + /** + * Whether the user select any fields before being executed, this is used + * to determined if any fields should be automatically be selected. + * + * @var bool + */ + protected $_hasFields; + + /** + * Tracks whether or not the original query should include + * fields from the top level table. + * + * @var bool + */ + protected $_autoFields; + + /** + * Boolean for tracking whether or not buffered results + * are enabled. + * + * @var bool + */ + protected $_useBufferedResults = true; + + /** + * Whether to hydrate results into entity objects + * + * @var bool + */ + protected $_hydrate = true; + + /** + * A callable function that can be used to calculate the total amount of + * records this query will match when not using `limit` + * + * @var callable + */ + protected $_counter; + + /** + * Instance of a class responsible for storing association containments and + * for eager loading them when this query is executed + * + * @var \Cake\ORM\EagerLoader + */ + protected $_eagerLoader; + + /** + * True if the beforeFind event has already been triggered for this query + * + * @var bool + */ + protected $_beforeFindFired = false; + + /** + * Constructor + * + * @param \Cake\Database\Connection $connection The connection object + * @param \Cake\ORM\Table $table The table this query is starting on + */ + public function __construct($connection, $table) + { + parent::__construct($connection); + $this->repository($table); + + if ($this->_repository) { + $this->addDefaultTypes($this->_repository); + } + } + + /** + * Hints this object to associate the correct types when casting conditions + * for the database. This is done by extracting the field types from the schema + * associated to the passed table object. This prevents the user from repeating + * himself when specifying conditions. + * + * This method returns the same query object for chaining. + * + * @param \Cake\ORM\Table $table The table to pull types from + * @return $this + */ + public function addDefaultTypes(Table $table) + { + $alias = $table->alias(); + $schema = $table->schema(); + $fields = []; + foreach ($schema->columns() as $f) { + $fields[$f] = $fields[$alias . '.' . $f] = $schema->columnType($f); + } + $this->defaultTypes($fields); + + return $this; + } + + /** + * Sets the instance of the eager loader class to use for loading associations + * and storing containments. If called with no arguments, it will return the + * currently configured instance. + * + * @param \Cake\ORM\EagerLoader $instance The eager loader to use. Pass null + * to get the current eagerloader. + * @return \Cake\ORM\EagerLoader|$this + */ + public function eagerLoader(EagerLoader $instance = null) + { + if ($instance === null) { + if ($this->_eagerLoader === null) { + $this->_eagerLoader = new EagerLoader; + } + return $this->_eagerLoader; + } + $this->_eagerLoader = $instance; + return $this; + } + + /** + * Sets the list of associations that should be eagerly loaded along with this + * query. The list of associated tables passed must have been previously set as + * associations using the Table API. + * + * ### Example: + * + * {{{ + * // Bring articles' author information + * $query->contain('Author'); + * + * // Also bring the category and tags associated to each article + * $query->contain(['Category', 'Tag']); + * }}} + * + * Associations can be arbitrarily nested using dot notation or nested arrays, + * this allows this object to calculate joins or any additional queries that + * must be executed to bring the required associated data. + * + * ### Example: + * + * {{{ + * // Eager load the product info, and for each product load other 2 associations + * $query->contain(['Product' => ['Manufacturer', 'Distributor']); + * + * // Which is equivalent to calling + * $query->contain(['Products.Manufactures', 'Products.Distributors']); + * + * // For an author query, load his region, state and country + * $query->contain('Regions.States.Countries'); + * }}} + * + * It is possible to control the conditions and fields selected for each of the + * contained associations: + * + * ### Example: + * + * {{{ + * $query->contain(['Tags' => function ($q) { + * return $q->where(['Tags.is_popular' => true]); + * }]); + * + * $query->contain(['Products.Manufactures' => function ($q) { + * return $q->select(['name'])->where(['Manufactures.active' => true]); + * }]); + * }}} + * + * Each association might define special options when eager loaded, the allowed + * options that can be set per association are: + * + * - foreignKey: Used to set a different field to match both tables, if set to false + * no join conditions will be generated automatically + * - fields: An array with the fields that should be fetched from the association + * - queryBuilder: Equivalent to passing a callable instead of an options array + * + * ### Example: + * + * {{{ + * // Set options for the hasMany articles that will be eagerly loaded for an author + * $query->contain([ + * 'Articles' => [ + * 'fields' => ['title', 'author_id'] + * ] + * ]); + * }}} + * + * When containing associations, it is important to include foreign key columns. + * Failing to do so will trigger exceptions. + * + * {{{ + * // Use special join conditions for getting an author's hasMany 'likes' + * $query->contain([ + * 'Likes' => [ + * 'foreignKey' => false, + * 'queryBuilder' => function ($q) { + * return $q->where(...); // Add full filtering conditions + * } + * ] + * ]); + * }}} + * + * If called with no arguments, this function will return an array with + * with the list of previously configured associations to be contained in the + * result. + * + * If called with an empty first argument and $override is set to true, the + * previous list will be emptied. + * + * @param array|string $associations list of table aliases to be queried + * @param bool $override whether override previous list with the one passed + * defaults to merging previous list with the new one. + * @return array|$this + */ + public function contain($associations = null, $override = false) + { + if (empty($associations) && $override) { + $this->_eagerLoader = null; + } + + $result = $this->eagerLoader()->contain($associations); + if ($associations !== null || $override) { + $this->_dirty(); + } + if ($associations === null) { + return $result; + } + + return $this; + } + + /** + * Adds filtering conditions to this query to only bring rows that have a relation + * to another from an associated table, based on conditions in the associated table. + * + * This function will add entries in the `contain` graph. + * + * ### Example: + * + * {{{ + * // Bring only articles that were tagged with 'cake' + * $query->matching('Tags', function ($q) { + * return $q->where(['name' => 'cake']); + * ); + * }}} + * + * It is possible to filter by deep associations by using dot notation: + * + * ### Example: + * + * {{{ + * // Bring only articles that were commented by 'markstory' + * $query->matching('Comments.Users', function ($q) { + * return $q->where(['username' => 'markstory']); + * ); + * }}} + * + * As this function will create `INNER JOIN`, you might want to consider + * calling `distinct` on this query as you might get duplicate rows if + * your conditions don't filter them already. This might be the case, for example, + * of the same user commenting more than once in the same article. + * + * ### Example: + * + * {{{ + * // Bring unique articles that were commented by 'markstory' + * $query->distinct(['Articles.id']) + * ->matching('Comments.Users', function ($q) { + * return $q->where(['username' => 'markstory']); + * ); + * }}} + * + * Please note that the query passed to the closure will only accept calling + * `select`, `where`, `andWhere` and `orWhere` on it. If you wish to + * add more complex clauses you can do it directly in the main query. + * + * @param string $assoc The association to filter by + * @param callable $builder a function that will receive a pre-made query object + * that can be used to add custom conditions or selecting some fields + * @return $this + */ + public function matching($assoc, callable $builder = null) + { + $this->eagerLoader()->matching($assoc, $builder); + $this->_dirty(); + return $this; + } + + /** + * Enable/Disable buffered results. + * + * When enabled the ResultSet returned by this Query will be + * buffered. This enables you to iterate a ResultSet multiple times, or + * both cache and iterate the ResultSet. + * + * When disabled it will consume less memory as fetched results are not + * remembered in the ResultSet. + * + * If called with no arguments, it will return whether or not buffering is + * enabled. + * + * @param bool $enable whether or not to enable buffering + * @return bool|$this + */ + public function bufferResults($enable = null) + { + if ($enable === null) { + return $this->_useBufferedResults; + } + + $this->_dirty(); + $this->_useBufferedResults = (bool)$enable; + return $this; + } + + /** + * Returns a key => value array representing a single aliased field + * that can be passed directly to the select() method. + * The key will contain the alias and the value the actual field name. + * + * If the field is already aliased, then it will not be changed. + * If no $alias is passed, the default table for this query will be used. + * + * @param string $field The field to alias + * @param string $alias the alias used to prefix the field + * @return array + */ + public function aliasField($field, $alias = null) + { + $namespaced = strpos($field, '.') !== false; + $aliasedField = $field; + + if ($namespaced) { + list($alias, $field) = explode('.', $field); + } + + if (!$alias) { + $alias = $this->repository()->alias(); + } + + $key = sprintf('%s__%s', $alias, $field); + if (!$namespaced) { + $aliasedField = $alias . '.' . $field; + } + + return [$key => $aliasedField]; + } + + /** + * Runs `aliasField()` for each field in the provided list and returns + * the result under a single array. + * + * @param array $fields The fields to alias + * @param string|null $defaultAlias The default alias + * @return array + */ + public function aliasFields($fields, $defaultAlias = null) + { + $aliased = []; + foreach ($fields as $alias => $field) { + if (is_numeric($alias) && is_string($field)) { + $aliased += $this->aliasField($field, $defaultAlias); + continue; + } + $aliased[$alias] = $field; + } + + return $aliased; + } + + /** + * Populates or adds parts to current query clauses using an array. + * This is handy for passing all query clauses at once. The option array accepts: + * + * - fields: Maps to the select method + * - conditions: Maps to the where method + * - limit: Maps to the limit method + * - order: Maps to the order method + * - offset: Maps to the offset method + * - group: Maps to the group method + * - having: Maps to the having method + * - contain: Maps to the contain options for eager loading + * - join: Maps to the join method + * - page: Maps to the page method + * + * ### Example: + * + * {{{ + * $query->applyOptions([ + * 'fields' => ['id', 'name'], + * 'conditions' => [ + * 'created >=' => '2013-01-01' + * ], + * 'limit' => 10 + * ]); + * }}} + * + * Is equivalent to: + * + * {{{ + * $query + * ->select(['id', 'name']) + * ->where(['created >=' => '2013-01-01']) + * ->limit(10) + * }}} + * + * @param array $options list of query clauses to apply new parts to. + * @return $this + */ + public function applyOptions(array $options) + { + $valid = [ + 'fields' => 'select', + 'conditions' => 'where', + 'join' => 'join', + 'order' => 'order', + 'limit' => 'limit', + 'offset' => 'offset', + 'group' => 'group', + 'having' => 'having', + 'contain' => 'contain', + 'page' => 'page', + ]; + + ksort($options); + foreach ($options as $option => $values) { + if (isset($valid[$option]) && isset($values)) { + $this->{$valid[$option]}($values); + } else { + $this->_options[$option] = $values; + } + } + + return $this; + } + + /** + * Creates a copy of this current query, triggers beforeFind and resets some state. + * + * The following state will be cleared: + * + * - autoFields + * - limit + * - offset + * - map/reduce functions + * - result formatters + * - order + * - containments + * + * This method creates query clones that are useful when working with subqueries. + * + * @return \Cake\ORM\Query + */ + public function cleanCopy() + { + $query = clone $this; + $query->triggerBeforeFind(); + $query->autoFields(false); + $query->limit(null); + $query->order([], true); + $query->offset(null); + $query->mapReduce(null, null, true); + $query->formatResults(null, true); + return $query; + } + + /** + * Returns the COUNT(*) for the query. + * + * @return int + */ + public function count() + { + $query = $this->cleanCopy(); + $counter = $this->_counter; + + if ($counter) { + $query->counter(null); + return (int)$counter($query); + } + + $count = ['count' => $query->func()->count('*')]; + $complex = count($query->clause('group')) || $query->clause('distinct'); + $complex = $complex || count($query->clause('union')); + + if (!$complex) { + $statement = $query + ->select($count, true) + ->autoFields(false) + ->execute(); + } else { + $statement = $this->connection()->newQuery() + ->select($count) + ->from(['count_source' => $query]) + ->execute(); + } + + $result = $statement->fetch('assoc')['count']; + $statement->closeCursor(); + return (int)$result; + } + + /** + * Registers a callable function that will be executed when the `count` method in + * this query is called. The return value for the function will be set as the + * return value of the `count` method. + * + * This is particularly useful when you need to optimize a query for returning the + * count, for example removing unnecessary joins, removing group by or just return + * an estimated number of rows. + * + * The callback will receive as first argument a clone of this query and not this + * query itself. + * + * @param callable $counter The counter value + * @return $this + */ + public function counter($counter) + { + $this->_counter = $counter; + return $this; + } + + /** + * Toggle hydrating entities. + * + * If set to false array results will be returned + * + * @param bool|null $enable Use a boolean to set the hydration mode. + * Null will fetch the current hydration mode. + * @return bool|$this A boolean when reading, and $this when setting the mode. + */ + public function hydrate($enable = null) + { + if ($enable === null) { + return $this->_hydrate; + } + + $this->_dirty(); + $this->_hydrate = (bool)$enable; + return $this; + } + + /** + * {@inheritDoc} + * + * @return $this + * @throws \RuntimeException When you attempt to cache a non-select query. + */ + public function cache($key, $config = 'default') + { + if ($this->_type !== 'select' && $this->_type !== null) { + throw new \RuntimeException('You cannot cache the results of non-select queries.'); + } + return $this->_cache($key, $config); + } + + /** + * {@inheritDoc} + * + * @throws \RuntimeException if this method is called on a non-select Query. + */ + public function all() + { + if ($this->_type !== 'select' && $this->_type !== null) { + throw new \RuntimeException( + 'You cannot call all() on a non-select query. Use execute() instead.' + ); + } + return $this->_all(); + } + + /** + * Trigger the beforeFind event on the query's repository object. + * + * Will not trigger more than once, and only for select queries. + * + * @return void + */ + public function triggerBeforeFind() + { + if (!$this->_beforeFindFired && $this->_type === 'select') { + $table = $this->repository(); + $table->dispatchEvent('Model.beforeFind', [$this, $this->_options, !$this->eagerLoaded()]); + $this->_beforeFindFired = true; + } + } + + /** + * {@inheritDoc} + */ + public function sql(ValueBinder $binder = null) + { + $this->triggerBeforeFind(); + + $this->_transformQuery(); + $sql = parent::sql($binder); + return $sql; + } + + /** + * Executes this query and returns a ResultSet object containing the results. + * This will also setup the correct statement class in order to eager load deep + * associations. + * + * @return \Cake\ORM\ResultSet + */ + protected function _execute() + { + $this->triggerBeforeFind(); + if ($this->_results) { + $decorator = $this->_decoratorClass(); + return new $decorator($this->_results); + } + $statement = $this->eagerLoader()->loadExternal($this, $this->execute()); + return new ResultSet($this, $statement); + } + + /** + * Applies some defaults to the query object before it is executed. + * + * Specifically add the FROM clause, adds default table fields if none are + * specified and applies the joins required to eager load associations defined + * using `contain` + * + * @see \Cake\Database\Query::execute() + * @return $this + */ + protected function _transformQuery() + { + if (!$this->_dirty) { + return; + } + + if ($this->_type === 'select') { + if (empty($this->_parts['from'])) { + $this->from([$this->_repository->alias() => $this->_repository->table()]); + } + $this->_addDefaultFields(); + $this->eagerLoader()->attachAssociations($this, $this->_repository, !$this->_hasFields); + } + } + + /** + * Inspects if there are any set fields for selecting, otherwise adds all + * the fields for the default table. + * + * @return void + */ + protected function _addDefaultFields() + { + $select = $this->clause('select'); + $this->_hasFields = true; + + if (!count($select) || $this->_autoFields === true) { + $this->_hasFields = false; + $this->select($this->repository()->schema()->columns()); + $select = $this->clause('select'); + } + + $aliased = $this->aliasFields($select, $this->repository()->alias()); + $this->select($aliased, true); + } + + /** + * Apply custom finds to against an existing query object. + * + * Allows custom find methods to be combined and applied to each other. + * + * {{{ + * $table->find('all')->find('recent'); + * }}} + * + * The above is an example of stacking multiple finder methods onto + * a single query. + * + * @param string $finder The finder method to use. + * @param array $options The options for the finder. + * @return $this Returns a modified query. + * @see \Cake\ORM\Table::find() + */ + public function find($finder, array $options = []) + { + return $this->repository()->callFinder($finder, $this, $options); + } + + /** + * Marks a query as dirty, removing any preprocessed information + * from in memory caching such as previous results + * + * @return void + */ + protected function _dirty() + { + $this->_results = null; + parent::_dirty(); + } + + /** + * Create an update query. + * + * This changes the query type to be 'update'. + * Can be combined with set() and where() methods to create update queries. + * + * @param string|null $table Unused parameter. + * @return $this + */ + public function update($table = null) + { + $table = $this->repository()->table(); + return parent::update($table); + } + + /** + * Create a delete query. + * + * This changes the query type to be 'delete'. + * Can be combined with the where() method to create delete queries. + * + * @param string|null $table Unused parameter. + * @return $this + */ + public function delete($table = null) + { + $repo = $this->repository(); + $this->from([$repo->alias() => $repo->table()]); + return parent::delete(); + } + + /** + * Create an insert query. + * + * This changes the query type to be 'insert'. + * Note calling this method will reset any data previously set + * with Query::values() + * + * Can be combined with the where() method to create delete queries. + * + * @param array $columns The columns to insert into. + * @param array $types A map between columns & their datatypes. + * @return $this + */ + public function insert(array $columns, array $types = []) + { + $table = $this->repository()->table(); + $this->into($table); + return parent::insert($columns, $types); + } + + /** + * {@inheritDoc} + * + * @throws \BadMethodCallException if the method is called for a non-select query + */ + public function __call($method, $arguments) + { + if ($this->type() === 'select') { + return $this->_call($method, $arguments); + } + + throw new \BadMethodCallException( + sprintf('Cannot call method "%s" on a "%s" query', $method, $this->type()) + ); + } + + /** + * {@inheritDoc} + */ + public function __debugInfo() + { + $eagerLoader = $this->eagerLoader(); + return parent::__debugInfo() + [ + 'hydrate' => $this->_hydrate, + 'buffered' => $this->_useBufferedResults, + 'formatters' => count($this->_formatters), + 'mapReducers' => count($this->_mapReduce), + 'contain' => $eagerLoader->contain(), + 'matching' => $eagerLoader->matching(), + 'extraOptions' => $this->_options, + 'repository' => $this->_repository + ]; + } + + /** + * Executes the query and converts the result set into JSON. + * + * Part of JsonSerializable interface. + * + * @return \Cake\Datasource\ResultSetInterface The data to convert to JSON. + */ + public function jsonSerialize() + { + return $this->all(); + } + + /** + * Get/Set whether or not the ORM should automatically append fields. + * + * By default calling select() will disable auto-fields. You can re-enable + * auto-fields with this method. + * + * @param bool|null $value The value to set or null to read the current value. + * @return bool|$this Either the current value or the query object. + */ + public function autoFields($value = null) + { + if ($value === null) { + return $this->_autoFields; + } + $this->_autoFields = (bool)$value; + return $this; + } + + /** + * Decorates the results iterator with MapReduce routines and formatters + * + * @param \Traversable $result Original results + * @return \Cake\Datasource\ResultSetInterface + */ + protected function _decorateResults($result) + { + $result = $this->_applyDecorators($result); + + if (!($result instanceof ResultSet) && $this->bufferResults()) { + $class = $this->_decoratorClass(); + $result = new $class($result->buffered()); + } + + return $result; + } } diff --git a/ResultSet.php b/ResultSet.php index 82f31a70..f791db52 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -29,472 +29,487 @@ * queries required for eager loading external associations. * */ -class ResultSet implements ResultSetInterface { - - use CollectionTrait; - -/** - * Original query from where results were generated - * - * @var Query - */ - protected $_query; - -/** - * Database statement holding the results - * - * @var \Cake\Database\StatementInterface - */ - protected $_statement; - -/** - * Points to the next record number that should be fetched - * - * @var int - */ - protected $_index = 0; - -/** - * Last record fetched from the statement - * - * @var array - */ - protected $_current; - -/** - * Default table instance - * - * @var \Cake\ORM\Table - */ - protected $_defaultTable; - -/** - * List of associations that should be placed under the `_matchingData` - * result key. - * - * @var array - */ - protected $_matchingMap = []; - -/** - * List of associations that should be eager loaded. - * - * @var array - */ - protected $_containMap = []; - -/** - * Map of fields that are fetched from the statement with - * their type and the table they belong to - * - * @var string - */ - protected $_map; - -/** - * Results that have been fetched or hydrated into the results. - * - * @var array - */ - protected $_results = []; - -/** - * Whether to hydrate results into objects or not - * - * @var bool - */ - protected $_hydrate = true; - -/** - * The fully namespaced name of the class to use for hydrating results - * - * @var string - */ - protected $_entityClass; - -/** - * Whether or not to buffer results fetched from the statement - * - * @var bool - */ - protected $_useBuffering = true; - -/** - * Holds the count of records in this result set - * - * @var int - */ - protected $_count; - -/** - * Constructor - * - * @param \Cake\ORM\Query $query Query from where results come - * @param \Cake\Database\StatementInterface $statement The statement to fetch from - */ - public function __construct($query, $statement) { - $repository = $query->repository(); - $this->_query = $query; - $this->_statement = $statement; - $this->_defaultTable = $this->_query->repository(); - $this->_calculateAssociationMap(); - $this->_hydrate = $this->_query->hydrate(); - $this->_entityClass = $repository->entityClass(); - $this->_useBuffering = $query->bufferResults(); - $this->count(); - - if ($this->_useBuffering) { - $this->_results = new SplFixedArray($this->_count); - } - } - -/** - * Returns the current record in the result iterator - * - * Part of Iterator interface. - * - * @return array|object - */ - public function current() { - return $this->_current; - } - -/** - * Returns the key of the current record in the iterator - * - * Part of Iterator interface. - * - * @return int - */ - public function key() { - return $this->_index; - } - -/** - * Advances the iterator pointer to the next record - * - * Part of Iterator interface. - * - * @return void - */ - public function next() { - $this->_index++; - } - -/** - * Rewinds a ResultSet. - * - * Part of Iterator interface. - * - * @throws \Cake\Database\Exception - * @return void - */ - public function rewind() { - if ($this->_index == 0) { - return; - } - - if (!$this->_useBuffering) { - $msg = 'You cannot rewind an un-buffered ResultSet. Use Query::bufferResults() to get a buffered ResultSet.'; - throw new Exception($msg); - } - - $this->_index = 0; - } - -/** - * Whether there are more results to be fetched from the iterator - * - * Part of Iterator interface. - * - * @return bool - */ - public function valid() { - if (isset($this->_results[$this->_index])) { - $this->_current = $this->_results[$this->_index]; - return true; - } - - $this->_current = $this->_fetchResult(); - $valid = $this->_current !== false; - $hasNext = $this->_index < $this->_count; - - if ($this->_statement && !($valid && $hasNext)) { - $this->_statement->closeCursor(); - } - - if ($valid) { - $this->_bufferResult($this->_current); - } - - return $valid; - } - -/** - * Serializes a resultset. - * - * Part of Serializable interface. - * - * @return string Serialized object - */ - public function serialize() { - while ($this->valid()) { - $this->next(); - } - return serialize($this->_results); - } - -/** - * Unserializes a resultset. - * - * Part of Serializable interface. - * - * @param string $serialized Serialized object - * @return void - */ - public function unserialize($serialized) { - $this->_results = unserialize($serialized); - } - -/** - * Gives the number of rows in the result set. - * - * Part of the Countable interface. - * - * @return int - */ - public function count() { - if ($this->_count !== null) { - return $this->_count; - } - if ($this->_statement) { - return $this->_count = $this->_statement->rowCount(); - } - return $this->_count = count($this->_results); - } - -/** - * Calculates the list of associations that should get eager loaded - * when fetching each record - * - * @return void - */ - protected function _calculateAssociationMap() { - $map = $this->_query->eagerLoader()->associationsMap($this->_defaultTable); - $this->_matchingMap = (new Collection($map)) - ->match(['matching' => true]) - ->toArray(); - - $this->_containMap = (new Collection(array_reverse($map))) - ->match(['matching' => false]) - ->indexBy('nestKey') - ->toArray(); - } - -/** - * Helper function to fetch the next result from the statement or - * seeded results. - * - * @return mixed - */ - protected function _fetchResult() { - if (!$this->_statement) { - return false; - } - - $row = $this->_statement->fetch('assoc'); - if ($row === false) { - return $row; - } - return $this->_groupResult($row); - } - -/** - * Correctly nests results keys including those coming from associations - * - * @param mixed $row Array containing columns and values or false if there is no results - * @return array Results - */ - protected function _groupResult($row) { - $defaultAlias = $this->_defaultTable->alias(); - $results = $presentAliases = []; - $options = [ - 'useSetters' => false, - 'markClean' => true, - 'markNew' => false, - 'guard' => false - ]; - - foreach ($this->_matchingMap as $matching) { - foreach ($row as $key => $value) { - if (strpos($key, $matching['alias'] . '__') !== 0) { - continue; - } - list($table, $field) = explode('__', $key); - $results['_matchingData'][$table][$field] = $value; - - if (!isset($this->_containMap[$table])) { - unset($row[$key]); - } - } - if (empty($results['_matchingData'][$matching['alias']])) { - continue; - } - - $results['_matchingData'][$matching['alias']] = $this->_castValues( - $matching['instance']->target(), - $results['_matchingData'][$matching['alias']] - ); - - if ($this->_hydrate) { - $options['source'] = $matching['alias']; - $entity = new $matching['entityClass']($results['_matchingData'][$matching['alias']], $options); - $entity->clean(); - $results['_matchingData'][$matching['alias']] = $entity; - } - } - - foreach ($row as $key => $value) { - $table = $defaultAlias; - $field = $key; - - if ($value !== null && !is_scalar($value)) { - $results[$key] = $value; - continue; - } - - if (empty($this->_map[$key])) { - $parts = explode('__', $key); - if (count($parts) > 1) { - $this->_map[$key] = $parts; - } - } - - if (!empty($this->_map[$key])) { - list($table, $field) = $this->_map[$key]; - } - - $presentAliases[$table] = true; - $results[$table][$field] = $value; - } - - if (isset($presentAliases[$defaultAlias])) { - $results[$defaultAlias] = $this->_castValues( - $this->_defaultTable, - $results[$defaultAlias] - ); - } - unset($presentAliases[$defaultAlias]); - - foreach ($this->_containMap as $assoc) { - $alias = $assoc['nestKey']; - $instance = $assoc['instance']; - - if (!isset($results[$alias])) { - $results = $instance->defaultRowValue($results, $assoc['canBeJoined']); - continue; - } - - $target = $instance->target(); - $options['source'] = $target->alias(); - unset($presentAliases[$alias]); - - if ($assoc['canBeJoined']) { - $results[$alias] = $this->_castValues($target, $results[$alias]); - - $hasData = false; - foreach ($results[$alias] as $v) { - if ($v !== null && $v !== []) { - $hasData = true; - break; - } - } - - if (!$hasData) { - $results[$alias] = null; - } - } - - if ($this->_hydrate && $results[$alias] !== null && $assoc['canBeJoined']) { - $entity = new $assoc['entityClass']($results[$alias], $options); - $entity->clean(); - $results[$alias] = $entity; - } - - $results = $instance->transformRow($results, $alias, $assoc['canBeJoined']); - } - - foreach ($presentAliases as $alias => $present) { - if (!isset($results[$alias])) { - continue; - } - $results[$defaultAlias][$alias] = $results[$alias]; - } - - if (isset($results['_matchingData'])) { - $results[$defaultAlias]['_matchingData'] = $results['_matchingData']; - } - - $options['source'] = $defaultAlias; - $results = $results[$defaultAlias]; - if ($this->_hydrate && !($results instanceof Entity)) { - $results = new $this->_entityClass($results, $options); - } - - return $results; - } - -/** - * Casts all values from a row brought from a table to the correct - * PHP type. - * - * @param Table $table The table object - * @param array $values The values to cast - * @return array - */ - protected function _castValues($table, $values) { - $alias = $table->alias(); - $driver = $this->_query->connection()->driver(); - if (empty($this->types[$alias])) { - $schema = $table->schema(); - foreach ($schema->columns() as $col) { - $this->types[$alias][$col] = Type::build($schema->columnType($col)); - } - } - - foreach ($values as $field => $value) { - if (!isset($this->types[$alias][$field])) { - continue; - } - $values[$field] = $this->types[$alias][$field]->toPHP($value, $driver); - } - - return $values; - } - -/** - * Conditionally buffer the passed result - * - * @param array $result the result fetch from the database - * @return void - */ - protected function _bufferResult($result) { - if ($this->_useBuffering) { - $this->_results[$this->_index] = $result; - } - } - -/** - * Returns an array that can be used to describe the internal state of this - * object. - * - * @return array - */ - public function __debugInfo() { - return [ - 'query' => $this->_query, - 'items' => $this->toArray(), - ]; - } - +class ResultSet implements ResultSetInterface +{ + + use CollectionTrait; + + /** + * Original query from where results were generated + * + * @var Query + */ + protected $_query; + + /** + * Database statement holding the results + * + * @var \Cake\Database\StatementInterface + */ + protected $_statement; + + /** + * Points to the next record number that should be fetched + * + * @var int + */ + protected $_index = 0; + + /** + * Last record fetched from the statement + * + * @var array + */ + protected $_current; + + /** + * Default table instance + * + * @var \Cake\ORM\Table + */ + protected $_defaultTable; + + /** + * List of associations that should be placed under the `_matchingData` + * result key. + * + * @var array + */ + protected $_matchingMap = []; + + /** + * List of associations that should be eager loaded. + * + * @var array + */ + protected $_containMap = []; + + /** + * Map of fields that are fetched from the statement with + * their type and the table they belong to + * + * @var string + */ + protected $_map; + + /** + * Results that have been fetched or hydrated into the results. + * + * @var array + */ + protected $_results = []; + + /** + * Whether to hydrate results into objects or not + * + * @var bool + */ + protected $_hydrate = true; + + /** + * The fully namespaced name of the class to use for hydrating results + * + * @var string + */ + protected $_entityClass; + + /** + * Whether or not to buffer results fetched from the statement + * + * @var bool + */ + protected $_useBuffering = true; + + /** + * Holds the count of records in this result set + * + * @var int + */ + protected $_count; + + /** + * Constructor + * + * @param \Cake\ORM\Query $query Query from where results come + * @param \Cake\Database\StatementInterface $statement The statement to fetch from + */ + public function __construct($query, $statement) + { + $repository = $query->repository(); + $this->_query = $query; + $this->_statement = $statement; + $this->_defaultTable = $this->_query->repository(); + $this->_calculateAssociationMap(); + $this->_hydrate = $this->_query->hydrate(); + $this->_entityClass = $repository->entityClass(); + $this->_useBuffering = $query->bufferResults(); + $this->count(); + + if ($this->_useBuffering) { + $this->_results = new SplFixedArray($this->_count); + } + } + + /** + * Returns the current record in the result iterator + * + * Part of Iterator interface. + * + * @return array|object + */ + public function current() + { + return $this->_current; + } + + /** + * Returns the key of the current record in the iterator + * + * Part of Iterator interface. + * + * @return int + */ + public function key() + { + return $this->_index; + } + + /** + * Advances the iterator pointer to the next record + * + * Part of Iterator interface. + * + * @return void + */ + public function next() + { + $this->_index++; + } + + /** + * Rewinds a ResultSet. + * + * Part of Iterator interface. + * + * @throws \Cake\Database\Exception + * @return void + */ + public function rewind() + { + if ($this->_index == 0) { + return; + } + + if (!$this->_useBuffering) { + $msg = 'You cannot rewind an un-buffered ResultSet. Use Query::bufferResults() to get a buffered ResultSet.'; + throw new Exception($msg); + } + + $this->_index = 0; + } + + /** + * Whether there are more results to be fetched from the iterator + * + * Part of Iterator interface. + * + * @return bool + */ + public function valid() + { + if (isset($this->_results[$this->_index])) { + $this->_current = $this->_results[$this->_index]; + return true; + } + + $this->_current = $this->_fetchResult(); + $valid = $this->_current !== false; + $hasNext = $this->_index < $this->_count; + + if ($this->_statement && !($valid && $hasNext)) { + $this->_statement->closeCursor(); + } + + if ($valid) { + $this->_bufferResult($this->_current); + } + + return $valid; + } + + /** + * Serializes a resultset. + * + * Part of Serializable interface. + * + * @return string Serialized object + */ + public function serialize() + { + while ($this->valid()) { + $this->next(); + } + return serialize($this->_results); + } + + /** + * Unserializes a resultset. + * + * Part of Serializable interface. + * + * @param string $serialized Serialized object + * @return void + */ + public function unserialize($serialized) + { + $this->_results = unserialize($serialized); + } + + /** + * Gives the number of rows in the result set. + * + * Part of the Countable interface. + * + * @return int + */ + public function count() + { + if ($this->_count !== null) { + return $this->_count; + } + if ($this->_statement) { + return $this->_count = $this->_statement->rowCount(); + } + return $this->_count = count($this->_results); + } + + /** + * Calculates the list of associations that should get eager loaded + * when fetching each record + * + * @return void + */ + protected function _calculateAssociationMap() + { + $map = $this->_query->eagerLoader()->associationsMap($this->_defaultTable); + $this->_matchingMap = (new Collection($map)) + ->match(['matching' => true]) + ->toArray(); + + $this->_containMap = (new Collection(array_reverse($map))) + ->match(['matching' => false]) + ->indexBy('nestKey') + ->toArray(); + } + + /** + * Helper function to fetch the next result from the statement or + * seeded results. + * + * @return mixed + */ + protected function _fetchResult() + { + if (!$this->_statement) { + return false; + } + + $row = $this->_statement->fetch('assoc'); + if ($row === false) { + return $row; + } + return $this->_groupResult($row); + } + + /** + * Correctly nests results keys including those coming from associations + * + * @param mixed $row Array containing columns and values or false if there is no results + * @return array Results + */ + protected function _groupResult($row) + { + $defaultAlias = $this->_defaultTable->alias(); + $results = $presentAliases = []; + $options = [ + 'useSetters' => false, + 'markClean' => true, + 'markNew' => false, + 'guard' => false + ]; + + foreach ($this->_matchingMap as $matching) { + foreach ($row as $key => $value) { + if (strpos($key, $matching['alias'] . '__') !== 0) { + continue; + } + list($table, $field) = explode('__', $key); + $results['_matchingData'][$table][$field] = $value; + + if (!isset($this->_containMap[$table])) { + unset($row[$key]); + } + } + if (empty($results['_matchingData'][$matching['alias']])) { + continue; + } + + $results['_matchingData'][$matching['alias']] = $this->_castValues( + $matching['instance']->target(), + $results['_matchingData'][$matching['alias']] + ); + + if ($this->_hydrate) { + $options['source'] = $matching['alias']; + $entity = new $matching['entityClass']($results['_matchingData'][$matching['alias']], $options); + $entity->clean(); + $results['_matchingData'][$matching['alias']] = $entity; + } + } + + foreach ($row as $key => $value) { + $table = $defaultAlias; + $field = $key; + + if ($value !== null && !is_scalar($value)) { + $results[$key] = $value; + continue; + } + + if (empty($this->_map[$key])) { + $parts = explode('__', $key); + if (count($parts) > 1) { + $this->_map[$key] = $parts; + } + } + + if (!empty($this->_map[$key])) { + list($table, $field) = $this->_map[$key]; + } + + $presentAliases[$table] = true; + $results[$table][$field] = $value; + } + + if (isset($presentAliases[$defaultAlias])) { + $results[$defaultAlias] = $this->_castValues( + $this->_defaultTable, + $results[$defaultAlias] + ); + } + unset($presentAliases[$defaultAlias]); + + foreach ($this->_containMap as $assoc) { + $alias = $assoc['nestKey']; + $instance = $assoc['instance']; + + if (!isset($results[$alias])) { + $results = $instance->defaultRowValue($results, $assoc['canBeJoined']); + continue; + } + + $target = $instance->target(); + $options['source'] = $target->alias(); + unset($presentAliases[$alias]); + + if ($assoc['canBeJoined']) { + $results[$alias] = $this->_castValues($target, $results[$alias]); + + $hasData = false; + foreach ($results[$alias] as $v) { + if ($v !== null && $v !== []) { + $hasData = true; + break; + } + } + + if (!$hasData) { + $results[$alias] = null; + } + } + + if ($this->_hydrate && $results[$alias] !== null && $assoc['canBeJoined']) { + $entity = new $assoc['entityClass']($results[$alias], $options); + $entity->clean(); + $results[$alias] = $entity; + } + + $results = $instance->transformRow($results, $alias, $assoc['canBeJoined']); + } + + foreach ($presentAliases as $alias => $present) { + if (!isset($results[$alias])) { + continue; + } + $results[$defaultAlias][$alias] = $results[$alias]; + } + + if (isset($results['_matchingData'])) { + $results[$defaultAlias]['_matchingData'] = $results['_matchingData']; + } + + $options['source'] = $defaultAlias; + $results = $results[$defaultAlias]; + if ($this->_hydrate && !($results instanceof Entity)) { + $results = new $this->_entityClass($results, $options); + } + + return $results; + } + + /** + * Casts all values from a row brought from a table to the correct + * PHP type. + * + * @param Table $table The table object + * @param array $values The values to cast + * @return array + */ + protected function _castValues($table, $values) + { + $alias = $table->alias(); + $driver = $this->_query->connection()->driver(); + if (empty($this->types[$alias])) { + $schema = $table->schema(); + foreach ($schema->columns() as $col) { + $this->types[$alias][$col] = Type::build($schema->columnType($col)); + } + } + + foreach ($values as $field => $value) { + if (!isset($this->types[$alias][$field])) { + continue; + } + $values[$field] = $this->types[$alias][$field]->toPHP($value, $driver); + } + + return $values; + } + + /** + * Conditionally buffer the passed result + * + * @param array $result the result fetch from the database + * @return void + */ + protected function _bufferResult($result) + { + if ($this->_useBuffering) { + $this->_results[$this->_index] = $result; + } + } + + /** + * Returns an array that can be used to describe the internal state of this + * object. + * + * @return array + */ + public function __debugInfo() + { + return [ + 'query' => $this->_query, + 'items' => $this->toArray(), + ]; + } } diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index ef1fcc1f..e7f81fdd 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -21,65 +21,67 @@ * Checks that the value provided in a field exists as the primary key of another * table. */ -class ExistsIn { +class ExistsIn +{ -/** - * The list of fields to check - * - * @var array - */ - protected $_fields; + /** + * The list of fields to check + * + * @var array + */ + protected $_fields; -/** - * The repository where the field will be looked for - * - * @var array - */ - protected $_repository; + /** + * The repository where the field will be looked for + * + * @var array + */ + protected $_repository; -/** - * Constructor. - * - * @param string|array $fields The field or fields to check existence as primary key. - * @param object|string $repository The repository where the field will be looked for, - * or the association name for the repository. - */ - public function __construct($fields, $repository) { - $this->_fields = (array)$fields; - $this->_repository = $repository; - } - -/** - * Performs the existence check - * - * @param \Cake\Datasource\EntityInterface $entity The entity from where to extract the fields - * @param array $options Options passed to the check, - * where the `repository` key is required. - * @return bool - */ - public function __invoke(EntityInterface $entity, array $options) { - if (is_string($this->_repository)) { - $this->_repository = $options['repository']->association($this->_repository); - } + /** + * Constructor. + * + * @param string|array $fields The field or fields to check existence as primary key. + * @param object|string $repository The repository where the field will be looked for, + * or the association name for the repository. + */ + public function __construct($fields, $repository) + { + $this->_fields = (array)$fields; + $this->_repository = $repository; + } - if (!empty($options['_sourceTable'])) { - $source = $this->_repository instanceof Association ? - $this->_repository->target() : - $this->_repository; - if ($source === $options['_sourceTable']) { - return true; - } - } + /** + * Performs the existence check + * + * @param \Cake\Datasource\EntityInterface $entity The entity from where to extract the fields + * @param array $options Options passed to the check, + * where the `repository` key is required. + * @return bool + */ + public function __invoke(EntityInterface $entity, array $options) + { + if (is_string($this->_repository)) { + $this->_repository = $options['repository']->association($this->_repository); + } - if (!$entity->extract($this->_fields, true)) { - return true; - } + if (!empty($options['_sourceTable'])) { + $source = $this->_repository instanceof Association ? + $this->_repository->target() : + $this->_repository; + if ($source === $options['_sourceTable']) { + return true; + } + } - $conditions = array_combine( - (array)$this->_repository->primaryKey(), - $entity->extract($this->_fields) - ); - return $this->_repository->exists($conditions); - } + if (!$entity->extract($this->_fields, true)) { + return true; + } + $conditions = array_combine( + (array)$this->_repository->primaryKey(), + $entity->extract($this->_fields) + ); + return $this->_repository->exists($conditions); + } } diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index e7f2c922..36c241fa 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -19,47 +19,49 @@ /** * Checks that a list of fields from an entity are unique in the table */ -class IsUnique { +class IsUnique +{ -/** - * The list of fields to check - * - * @var array - */ - protected $_fields; - -/** - * Constructor. - * - * @param array $fields The list of fields to check uniqueness for - */ - public function __construct(array $fields) { - $this->_fields = $fields; - } + /** + * The list of fields to check + * + * @var array + */ + protected $_fields; -/** - * Performs the uniqueness check - * - * @param \Cake\Datasource\EntityInterface $entity The entity from where to extract the fields - * @param array $options Options passed to the check, - * where the `repository` key is required. - * @return bool - */ - public function __invoke(EntityInterface $entity, array $options) { - if (!$entity->extract($this->_fields, true)) { - return true; - } + /** + * Constructor. + * + * @param array $fields The list of fields to check uniqueness for + */ + public function __construct(array $fields) + { + $this->_fields = $fields; + } - $conditions = $entity->extract($this->_fields); - if ($entity->isNew() === false) { - $keys = (array)$options['repository']->primaryKey(); - $keys = $entity->extract($keys); - if (array_filter($keys, 'strlen')) { - $conditions['NOT'] = $keys; - } - } + /** + * Performs the uniqueness check + * + * @param \Cake\Datasource\EntityInterface $entity The entity from where to extract the fields + * @param array $options Options passed to the check, + * where the `repository` key is required. + * @return bool + */ + public function __invoke(EntityInterface $entity, array $options) + { + if (!$entity->extract($this->_fields, true)) { + return true; + } - return !$options['repository']->exists($conditions); - } + $conditions = $entity->extract($this->_fields); + if ($entity->isNew() === false) { + $keys = (array)$options['repository']->primaryKey(); + $keys = $entity->extract($keys); + if (array_filter($keys, 'strlen')) { + $conditions['NOT'] = $keys; + } + } + return !$options['repository']->exists($conditions); + } } diff --git a/RulesChecker.php b/RulesChecker.php index 1bf21e08..f0fa71c7 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -40,304 +40,316 @@ * invoke the checks by calling RulesChecker::checkCreate(), RulesChecker::checkUpdate() or * RulesChecker::checkDelete(). */ -class RulesChecker { +class RulesChecker +{ -/** + /** * Indicates that the checking rules to apply are those used for creating entities * * @var string */ - const CREATE = 'create'; + const CREATE = 'create'; -/** + /** * Indicates that the checking rules to apply are those used for updating entities * * @var string */ - const UPDATE = 'update'; + const UPDATE = 'update'; -/** + /** * Indicates that the checking rules to apply are those used for deleting entities * * @var string */ - const DELETE = 'delete'; + const DELETE = 'delete'; -/** - * The list of rules to be checked on both create and update operations - * - * @var array - */ - protected $_rules = []; + /** + * The list of rules to be checked on both create and update operations + * + * @var array + */ + protected $_rules = []; -/** - * The list of rules to check during create operations - * - * @var array - */ - protected $_createRules = []; + /** + * The list of rules to check during create operations + * + * @var array + */ + protected $_createRules = []; -/** - * The list of rules to check during update operations - * - * @var array - */ - protected $_updateRules = []; + /** + * The list of rules to check during update operations + * + * @var array + */ + protected $_updateRules = []; -/** - * The list of rules to check during delete operations - * - * @var array - */ - protected $_deleteRules = []; + /** + * The list of rules to check during delete operations + * + * @var array + */ + protected $_deleteRules = []; -/** - * List of options to pass to every callable rule - * - * @var array - */ - protected $_options = []; + /** + * List of options to pass to every callable rule + * + * @var array + */ + protected $_options = []; -/** - * Constructor. Takes the options to be passed to all rules. - * - * @param array $options The options to pass to every rule - */ - public function __construct(array $options) { - $this->_options = $options; - } + /** + * Constructor. Takes the options to be passed to all rules. + * + * @param array $options The options to pass to every rule + */ + public function __construct(array $options) + { + $this->_options = $options; + } -/** - * Adds a rule that will be applied to the entity both on create and update - * operations. - * - * ### Options - * - * The options array accept the following special keys: - * - * - `errorField`: The name of the entity field that will be marked as invalid - * if the rule does not pass. - * - `message`: The error message to set to `errorField` if the rule does not pass. - * - * @param callable $rule A callable function or object that will return whether - * the entity is valid or not. - * @param array $options List of extra options to pass to the rule callable as - * second argument. - * @return $this - */ - public function add(callable $rule, array $options = []) { - $this->_rules[] = $this->_addError($rule, $options); - return $this; - } + /** + * Adds a rule that will be applied to the entity both on create and update + * operations. + * + * ### Options + * + * The options array accept the following special keys: + * + * - `errorField`: The name of the entity field that will be marked as invalid + * if the rule does not pass. + * - `message`: The error message to set to `errorField` if the rule does not pass. + * + * @param callable $rule A callable function or object that will return whether + * the entity is valid or not. + * @param array $options List of extra options to pass to the rule callable as + * second argument. + * @return $this + */ + public function add(callable $rule, array $options = []) + { + $this->_rules[] = $this->_addError($rule, $options); + return $this; + } -/** - * Adds a rule that will be applied to the entity on create operations. - * - * ### Options - * - * The options array accept the following special keys: - * - * - `errorField`: The name of the entity field that will be marked as invalid - * if the rule does not pass. - * - `message`: The error message to set to `errorField` if the rule does not pass. - * - * @param callable $rule A callable function or object that will return whether - * the entity is valid or not. - * @param array $options List of extra options to pass to the rule callable as - * second argument. - * @return $this - */ - public function addCreate(callable $rule, array $options = []) { - $this->_createRules[] = $this->_addError($rule, $options); - return $this; - } + /** + * Adds a rule that will be applied to the entity on create operations. + * + * ### Options + * + * The options array accept the following special keys: + * + * - `errorField`: The name of the entity field that will be marked as invalid + * if the rule does not pass. + * - `message`: The error message to set to `errorField` if the rule does not pass. + * + * @param callable $rule A callable function or object that will return whether + * the entity is valid or not. + * @param array $options List of extra options to pass to the rule callable as + * second argument. + * @return $this + */ + public function addCreate(callable $rule, array $options = []) + { + $this->_createRules[] = $this->_addError($rule, $options); + return $this; + } -/** - * Adds a rule that will be applied to the entity on update operations. - * - * ### Options - * - * The options array accept the following special keys: - * - * - `errorField`: The name of the entity field that will be marked as invalid - * if the rule does not pass. - * - `message`: The error message to set to `errorField` if the rule does not pass. - * - * @param callable $rule A callable function or object that will return whether - * the entity is valid or not. - * @param array $options List of extra options to pass to the rule callable as - * second argument. - * @return $this - */ - public function addUpdate(callable $rule, array $options = []) { - $this->_updateRules[] = $this->_addError($rule, $options); - return $this; - } - -/** - * Adds a rule that will be applied to the entity on delete operations. - * - * ### Options - * - * The options array accept the following special keys: - * - * - `errorField`: The name of the entity field that will be marked as invalid - * if the rule does not pass. - * - `message`: The error message to set to `errorField` if the rule does not pass. - * - * @param callable $rule A callable function or object that will return whether - * the entity is valid or not. - * @param array $options List of extra options to pass to the rule callable as - * second argument. - * @return $this - */ - public function addDelete(callable $rule, array $options = []) { - $this->_deleteRules[] = $this->_addError($rule, $options); - return $this; - } + /** + * Adds a rule that will be applied to the entity on update operations. + * + * ### Options + * + * The options array accept the following special keys: + * + * - `errorField`: The name of the entity field that will be marked as invalid + * if the rule does not pass. + * - `message`: The error message to set to `errorField` if the rule does not pass. + * + * @param callable $rule A callable function or object that will return whether + * the entity is valid or not. + * @param array $options List of extra options to pass to the rule callable as + * second argument. + * @return $this + */ + public function addUpdate(callable $rule, array $options = []) + { + $this->_updateRules[] = $this->_addError($rule, $options); + return $this; + } -/** - * Runs each of the rules by passing the provided entity and returns true if all - * of them pass. The rules to be applied are depended on the $mode parameter which - * can only be RulesChecker::CREATE, RulesChecker::UPDATE or RulesChecker::DELETE - * - * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. - * @param string $mode Either 'create, 'update' or 'delete'. - * @param array $options Extra options to pass to checker functions. - * @return bool - * @throws \InvalidArgumentException if an invalid mode is passed. - */ - public function check(EntityInterface $entity, $mode, array $options = []) { - if ($mode === self::CREATE) { - return $this->checkCreate($entity, $options); - } + /** + * Adds a rule that will be applied to the entity on delete operations. + * + * ### Options + * + * The options array accept the following special keys: + * + * - `errorField`: The name of the entity field that will be marked as invalid + * if the rule does not pass. + * - `message`: The error message to set to `errorField` if the rule does not pass. + * + * @param callable $rule A callable function or object that will return whether + * the entity is valid or not. + * @param array $options List of extra options to pass to the rule callable as + * second argument. + * @return $this + */ + public function addDelete(callable $rule, array $options = []) + { + $this->_deleteRules[] = $this->_addError($rule, $options); + return $this; + } - if ($mode === self::UPDATE) { - return $this->checkUpdate($entity, $options); - } + /** + * Runs each of the rules by passing the provided entity and returns true if all + * of them pass. The rules to be applied are depended on the $mode parameter which + * can only be RulesChecker::CREATE, RulesChecker::UPDATE or RulesChecker::DELETE + * + * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. + * @param string $mode Either 'create, 'update' or 'delete'. + * @param array $options Extra options to pass to checker functions. + * @return bool + * @throws \InvalidArgumentException if an invalid mode is passed. + */ + public function check(EntityInterface $entity, $mode, array $options = []) + { + if ($mode === self::CREATE) { + return $this->checkCreate($entity, $options); + } - if ($mode === self::DELETE) { - return $this->checkDelete($entity, $options); - } + if ($mode === self::UPDATE) { + return $this->checkUpdate($entity, $options); + } - throw new InvalidArgumentException('Wrong checking mode: ' . $mode); - } + if ($mode === self::DELETE) { + return $this->checkDelete($entity, $options); + } -/** - * Runs each of the rules by passing the provided entity and returns true if all - * of them pass. The rules selected will be only those specified to be run on 'create' - * - * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. - * @param array $options Extra options to pass to checker functions. - * @return bool - */ - public function checkCreate(EntityInterface $entity, array $options = []) { - $success = true; - $options = $options + $this->_options; - foreach (array_merge($this->_rules, $this->_createRules) as $rule) { - $success = $rule($entity, $options) && $success; - } - return $success; - } + throw new InvalidArgumentException('Wrong checking mode: ' . $mode); + } -/** - * Runs each of the rules by passing the provided entity and returns true if all - * of them pass. The rules selected will be only those specified to be run on 'update' - * - * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. - * @param array $options Extra options to pass to checker functions. - * @return bool - */ - public function checkUpdate(EntityInterface $entity, array $options = []) { - $success = true; - $options = $options + $this->_options; - foreach (array_merge($this->_rules, $this->_updateRules) as $rule) { - $success = $rule($entity, $options) && $success; - } - return $success; - } + /** + * Runs each of the rules by passing the provided entity and returns true if all + * of them pass. The rules selected will be only those specified to be run on 'create' + * + * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. + * @param array $options Extra options to pass to checker functions. + * @return bool + */ + public function checkCreate(EntityInterface $entity, array $options = []) + { + $success = true; + $options = $options + $this->_options; + foreach (array_merge($this->_rules, $this->_createRules) as $rule) { + $success = $rule($entity, $options) && $success; + } + return $success; + } -/** - * Runs each of the rules by passing the provided entity and returns true if all - * of them pass. The rules selected will be only those specified to be run on 'delete' - * - * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. - * @param array $options Extra options to pass to checker functions. - * @return bool - */ - public function checkDelete(EntityInterface $entity, array $options = []) { - $success = true; - $options = $options + $this->_options; - foreach ($this->_deleteRules as $rule) { - $success = $rule($entity, $options) && $success; - } - return $success; - } + /** + * Runs each of the rules by passing the provided entity and returns true if all + * of them pass. The rules selected will be only those specified to be run on 'update' + * + * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. + * @param array $options Extra options to pass to checker functions. + * @return bool + */ + public function checkUpdate(EntityInterface $entity, array $options = []) + { + $success = true; + $options = $options + $this->_options; + foreach (array_merge($this->_rules, $this->_updateRules) as $rule) { + $success = $rule($entity, $options) && $success; + } + return $success; + } -/** - * Returns a callable that can be used as a rule for checking the uniqueness of a value - * in the table. - * - * ### Example: - * - * {{{ - * $rules->add($rules->isUnique('email', 'The email should be unique')); - * }}} - * - * @param array $fields The list of fields to check for uniqueness. - * @param string $message The error message to show in case the rule does not pass. - * @return callable - */ - public function isUnique(array $fields, $message = 'This value is already in use') { - $errorField = current($fields); - return $this->_addError(new IsUnique($fields), compact('errorField', 'message')); - } + /** + * Runs each of the rules by passing the provided entity and returns true if all + * of them pass. The rules selected will be only those specified to be run on 'delete' + * + * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. + * @param array $options Extra options to pass to checker functions. + * @return bool + */ + public function checkDelete(EntityInterface $entity, array $options = []) + { + $success = true; + $options = $options + $this->_options; + foreach ($this->_deleteRules as $rule) { + $success = $rule($entity, $options) && $success; + } + return $success; + } -/** - * Returns a callable that can be used as a rule for checking that the values - * extracted from the entity to check exist as the primary key in another table. - * - * This is useful for enforcing foreign key integrity checks. - * - * ### Example: - * - * {{{ - * $rules->add($rules->existsIn('author_id', 'Authors', 'Invalid Author')); - * - * $rules->add($rules->existsIn('site_id', new SitesTable(), 'Invalid Site')); - * }}} - * - * @param string|array $field The field or list of fields to check for existence by - * primary key lookup in the other table. - * @param object|string $table The table name where the fields existence will be checked. - * @param string $message The error message to show in case the rule does not pass. - * @return callable - */ - public function existsIn($field, $table, $message = 'This value does not exist') { - $errorField = $field; - return $this->_addError(new ExistsIn($field, $table), compact('errorField', 'message')); - } + /** + * Returns a callable that can be used as a rule for checking the uniqueness of a value + * in the table. + * + * ### Example: + * + * {{{ + * $rules->add($rules->isUnique('email', 'The email should be unique')); + * }}} + * + * @param array $fields The list of fields to check for uniqueness. + * @param string $message The error message to show in case the rule does not pass. + * @return callable + */ + public function isUnique(array $fields, $message = 'This value is already in use') + { + $errorField = current($fields); + return $this->_addError(new IsUnique($fields), compact('errorField', 'message')); + } -/** - * Utility method for decorating any callable so that if it returns false, the correct - * property in the entity is marked as invalid. - * - * @param callable $rule The rule to decorate - * @param array $options The options containing the error message and field - * @return callable - */ - protected function _addError($rule, $options) { - return function ($entity, $scope) use ($rule, $options) { - $pass = $rule($entity, $options + $scope); + /** + * Returns a callable that can be used as a rule for checking that the values + * extracted from the entity to check exist as the primary key in another table. + * + * This is useful for enforcing foreign key integrity checks. + * + * ### Example: + * + * {{{ + * $rules->add($rules->existsIn('author_id', 'Authors', 'Invalid Author')); + * + * $rules->add($rules->existsIn('site_id', new SitesTable(), 'Invalid Site')); + * }}} + * + * @param string|array $field The field or list of fields to check for existence by + * primary key lookup in the other table. + * @param object|string $table The table name where the fields existence will be checked. + * @param string $message The error message to show in case the rule does not pass. + * @return callable + */ + public function existsIn($field, $table, $message = 'This value does not exist') + { + $errorField = $field; + return $this->_addError(new ExistsIn($field, $table), compact('errorField', 'message')); + } - if ($pass || empty($options['errorField'])) { - return $pass; - } + /** + * Utility method for decorating any callable so that if it returns false, the correct + * property in the entity is marked as invalid. + * + * @param callable $rule The rule to decorate + * @param array $options The options containing the error message and field + * @return callable + */ + protected function _addError($rule, $options) + { + return function ($entity, $scope) use ($rule, $options) { + $pass = $rule($entity, $options + $scope); - $message = isset($options['message']) ? $options['message'] : 'invalid'; - $entity->errors($options['errorField'], (array)$message); - return $pass; - }; - } + if ($pass || empty($options['errorField'])) { + return $pass; + } + $message = isset($options['message']) ? $options['message'] : 'invalid'; + $entity->errors($options['errorField'], (array)$message); + return $pass; + }; + } } diff --git a/Table.php b/Table.php index 1641ed7d..ad3e97d3 100644 --- a/Table.php +++ b/Table.php @@ -115,1920 +115,1984 @@ * * @see \Cake\Event\EventManager for reference on the events system. */ -class Table implements RepositoryInterface, EventListenerInterface { - - use EventManagerTrait; - -/** - * Name of the table as it can be found in the database - * - * @var string - */ - protected $_table; - -/** - * Human name giving to this particular instance. Multiple objects representing - * the same database table can exist by using different aliases. - * - * @var string - */ - protected $_alias; - -/** - * Connection instance - * - * @var \Cake\Database\Connection - */ - protected $_connection; - -/** - * The schema object containing a description of this table fields - * - * @var \Cake\Database\Schema\Table - */ - protected $_schema; - -/** - * The name of the field that represents the primary key in the table - * - * @var string|array - */ - protected $_primaryKey; - -/** - * The name of the field that represents a human readable representation of a row - * - * @var string - */ - protected $_displayField; - -/** - * The associations container for this Table. - * - * @var \Cake\ORM\AssociationCollection - */ - protected $_associations; - -/** - * BehaviorRegistry for this table - * - * @var \Cake\ORM\BehaviorRegistry - */ - protected $_behaviors; - -/** - * The name of the class that represent a single row for this table - * - * @var string - */ - protected $_entityClass; - -/** - * A list of validation objects indexed by name - * - * @var array - */ - protected $_validators = []; - -/** - * The domain rules to be applied to entities saved by this table - * - * @var \Cake\ORM\RulesChecker - */ - protected $_rulesChecker; - -/** - * Initializes a new instance - * - * The $config array understands the following keys: - * - * - table: Name of the database table to represent - * - alias: Alias to be assigned to this table (default to table name) - * - connection: The connection instance to use - * - entityClass: The fully namespaced class name of the entity class that will - * represent rows in this table. - * - schema: A \Cake\Database\Schema\Table object or an array that can be - * passed to it. - * - eventManager: An instance of an event manager to use for internal events - * - behaviors: A BehaviorRegistry. Generally not used outside of tests. - * - associations: An AssociationCollection instance. - * - * @param array $config List of options for this table - */ - public function __construct(array $config = []) { - if (!empty($config['table'])) { - $this->table($config['table']); - } - if (!empty($config['alias'])) { - $this->alias($config['alias']); - } - if (!empty($config['connection'])) { - $this->connection($config['connection']); - } - if (!empty($config['schema'])) { - $this->schema($config['schema']); - } - if (!empty($config['entityClass'])) { - $this->entityClass($config['entityClass']); - } - $eventManager = $behaviors = $associations = null; - if (!empty($config['eventManager'])) { - $eventManager = $config['eventManager']; - } - if (!empty($config['behaviors'])) { - $behaviors = $config['behaviors']; - } - if (!empty($config['associations'])) { - $associations = $config['associations']; - } - $this->_eventManager = $eventManager ?: new EventManager(); - $this->_behaviors = $behaviors ?: new BehaviorRegistry($this); - $this->_associations = $associations ?: new AssociationCollection(); - - $this->initialize($config); - $this->_eventManager->attach($this); - $this->dispatchEvent('Model.initialize'); - } - -/** - * Get the default connection name. - * - * This method is used to get the fallback connection name if an - * instance is created through the TableRegistry without a connection. - * - * @return string - * @see \Cake\ORM\TableRegistry::get() - */ - public static function defaultConnectionName() { - return 'default'; - } - -/** - * Initialize a table instance. Called after the constructor. - * - * You can use this method to define associations, attach behaviors - * define validation and do any other initialization logic you need. - * - * {{{ - * public function initialize(array $config) { - * $this->belongsTo('Users'); - * $this->belongsToMany('Tagging.Tags'); - * $this->primaryKey('something_else'); - * } - * }}} - * - * @param array $config Configuration options passed to the constructor - * @return void - */ - public function initialize(array $config) { - } - -/** - * Returns the database table name or sets a new one - * - * @param string|null $table the new table name - * @return string - */ - public function table($table = null) { - if ($table !== null) { - $this->_table = $table; - } - if ($this->_table === null) { - $table = namespaceSplit(get_class($this)); - $table = substr(end($table), 0, -5); - if (empty($table)) { - $table = $this->alias(); - } - $this->_table = Inflector::underscore($table); - } - return $this->_table; - } - -/** - * Returns the table alias or sets a new one - * - * @param string|null $alias the new table alias - * @return string - */ - public function alias($alias = null) { - if ($alias !== null) { - $this->_alias = $alias; - } - if ($this->_alias === null) { - $alias = namespaceSplit(get_class($this)); - $alias = substr(end($alias), 0, -5) ?: $this->_table; - $this->_alias = $alias; - } - return $this->_alias; - } - -/** - * Returns the connection instance or sets a new one - * - * @param \Cake\Database\Connection|null $conn The new connection instance - * @return \Cake\Database\Connection - */ - public function connection($conn = null) { - if ($conn === null) { - return $this->_connection; - } - return $this->_connection = $conn; - } - -/** - * Returns the schema table object describing this table's properties. - * - * If an \Cake\Database\Schema\Table is passed, it will be used for this table - * instead of the default one. - * - * If an array is passed, a new \Cake\Database\Schema\Table will be constructed - * out of it and used as the schema for this table. - * - * @param array|\Cake\Database\Schema\Table|null $schema New schema to be used for this table - * @return \Cake\Database\Schema\Table - */ - public function schema($schema = null) { - if ($schema === null) { - if ($this->_schema === null) { - $this->_schema = $this->_initializeSchema( - $this->connection() - ->schemaCollection() - ->describe($this->table()) - ); - } - return $this->_schema; - } - - if (is_array($schema)) { - $constraints = []; - - if (isset($schema['_constraints'])) { - $constraints = $schema['_constraints']; - unset($schema['_constraints']); - } - - $schema = new Schema($this->table(), $schema); - - foreach ($constraints as $name => $value) { - $schema->addConstraint($name, $value); - } - } - - return $this->_schema = $schema; - } - -/** - * Override this function in order to alter the schema used by this table. - * This function is only called after fetching the schema out of the database. - * If you wish to provide your own schema to this table without touching the - * database, you can override schema() or inject the definitions though that - * method. - * - * ### Example: - * - * {{{ - * protected function _initializeSchema(\Cake\Database\Schema\Table $table) { - * $table->columnType('preferences', 'json'); - * return $table; - * } - * }}} - * - * @param \Cake\Database\Schema\Table $table The table definition fetched from database. - * @return \Cake\Database\Schema\Table the altered schema - * @api - */ - protected function _initializeSchema(Schema $table) { - return $table; - } - -/** - * Test to see if a Table has a specific field/column. - * - * Delegates to the schema object and checks for column presence - * using the Schema\Table instance. - * - * @param string $field The field to check for. - * @return bool True if the field exists, false if it does not. - */ - public function hasField($field) { - $schema = $this->schema(); - return $schema->column($field) !== null; - } - -/** - * Returns the primary key field name or sets a new one - * - * @param string|array|null $key sets a new name to be used as primary key - * @return string|array - */ - public function primaryKey($key = null) { - if ($key !== null) { - $this->_primaryKey = $key; - } - if ($this->_primaryKey === null) { - $key = (array)$this->schema()->primaryKey(); - if (count($key) === 1) { - $key = $key[0]; - } - $this->_primaryKey = $key; - } - return $this->_primaryKey; - } - -/** - * Returns the display field or sets a new one - * - * @param string|null $key sets a new name to be used as display field - * @return string - */ - public function displayField($key = null) { - if ($key !== null) { - $this->_displayField = $key; - } - if ($this->_displayField === null) { - $schema = $this->schema(); - $primary = (array)$this->primaryKey(); - $this->_displayField = array_shift($primary); - if ($schema->column('title')) { - $this->_displayField = 'title'; - } - if ($schema->column('name')) { - $this->_displayField = 'name'; - } - } - return $this->_displayField; - } - -/** - * Returns the class used to hydrate rows for this table or sets - * a new one - * - * @param string|null $name the name of the class to use - * @throws \Cake\ORM\Exception\MissingEntityException when the entity class cannot be found - * @return string - */ - public function entityClass($name = null) { - if ($name === null && !$this->_entityClass) { - $default = '\Cake\ORM\Entity'; - $self = get_called_class(); - $parts = explode('\\', $self); - - if ($self === __CLASS__ || count($parts) < 3) { - return $this->_entityClass = $default; - } - - $alias = Inflector::singularize(substr(array_pop($parts), 0, -5)); - $name = implode('\\', array_slice($parts, 0, -1)) . '\Entity\\' . $alias; - if (!class_exists($name)) { - return $this->_entityClass = $default; - } - } - - if ($name !== null) { - $class = App::className($name, 'Model/Entity'); - $this->_entityClass = $class; - } - - if (!$this->_entityClass) { - throw new MissingEntityException([$name]); - } - - return $this->_entityClass; - } - -/** - * Add a behavior. - * - * Adds a behavior to this table's behavior collection. Behaviors - * provide an easy way to create horizontally re-usable features - * that can provide trait like functionality, and allow for events - * to be listened to. - * - * Example: - * - * Load a behavior, with some settings. - * - * {{{ - * $this->addBehavior('Tree', ['parent' => 'parentId']); - * }}} - * - * Behaviors are generally loaded during Table::initialize(). - * - * @param string $name The name of the behavior. Can be a short class reference. - * @param array $options The options for the behavior to use. - * @return void - * @throws \RuntimeException If a behavior is being reloaded. - * @see \Cake\ORM\Behavior - */ - public function addBehavior($name, array $options = []) { - $this->_behaviors->load($name, $options); - } - -/** - * Removes a behavior from this table's behavior registry. - * - * Example: - * - * Remove a behavior from this table. - * - * {{{ - * $this->removeBehavior('Tree'); - * }}} - * - * @param string $name The alias that the behavior was added with. - * @return void - * @see \Cake\ORM\Behavior - */ - public function removeBehavior($name) { - $this->_behaviors->unload($name); - } - -/** - * Returns the behavior registry for this table. - * - * @return \Cake\ORM\BehaviorRegistry - */ - public function behaviors() { - return $this->_behaviors; - } - -/** - * Check if a behavior with the given alias has been loaded. - * - * @param string $name The behavior alias to check. - * @return array - */ - public function hasBehavior($name) { - return $this->_behaviors->has($name); - } - -/** - * Returns an association object configured for the specified alias if any - * - * @param string $name the alias used for the association - * @return \Cake\ORM\Association - */ - public function association($name) { - return $this->_associations->get($name); - } - -/** - * Get the associations collection for this table. - * - * @return \Cake\ORM\AssociationCollection - */ - public function associations() { - return $this->_associations; - } - -/** - * Setup multiple associations. - * - * It takes an array containing set of table names indexed by association type - * as argument: - * - * {{{ - * $this->Posts->addAssociations([ - * 'belongsTo' => [ - * 'Users' => ['className' => 'App\Model\Table\UsersTable'] - * ], - * 'hasMany' => ['Comments'], - * 'belongsToMany' => ['Tags'] - * ]); - * }}} - * - * Each association type accepts multiple associations where the keys - * are the aliases, and the values are association config data. If numeric - * keys are used the values will be treated as association aliases. - * - * @param array $params Set of associations to bind (indexed by association type) - * @return void - * @see \Cake\ORM\Table::belongsTo() - * @see \Cake\ORM\Table::hasOne() - * @see \Cake\ORM\Table::hasMany() - * @see \Cake\ORM\Table::belongsToMany() - */ - public function addAssociations(array $params) { - foreach ($params as $assocType => $tables) { - foreach ($tables as $associated => $options) { - if (is_numeric($associated)) { - $associated = $options; - $options = []; - } - $this->{$assocType}($associated, $options); - } - } - } - -/** - * Creates a new BelongsTo association between this table and a target - * table. A "belongs to" association is a N-1 relationship where this table - * is the N side, and where there is a single associated record in the target - * table for each one in this table. - * - * Target table can be inferred by its name, which is provided in the - * first argument, or you can either pass the to be instantiated or - * an instance of it directly. - * - * The options array accept the following keys: - * - * - className: The class name of the target table object - * - targetTable: An instance of a table object to be used as the target table - * - foreignKey: The name of the field to use as foreign key, if false none - * will be used - * - conditions: array with a list of conditions to filter the join with - * - joinType: The type of join to be used (e.g. INNER) - * - * This method will return the association object that was built. - * - * @param string $associated the alias for the target table. This is used to - * uniquely identify the association - * @param array $options list of options to configure the association definition - * @return \Cake\ORM\Association\BelongsTo - */ - public function belongsTo($associated, array $options = []) { - $options += ['sourceTable' => $this]; - $association = new BelongsTo($associated, $options); - return $this->_associations->add($association->name(), $association); - } - -/** - * Creates a new HasOne association between this table and a target - * table. A "has one" association is a 1-1 relationship. - * - * Target table can be inferred by its name, which is provided in the - * first argument, or you can either pass the class name to be instantiated or - * an instance of it directly. - * - * The options array accept the following keys: - * - * - className: The class name of the target table object - * - targetTable: An instance of a table object to be used as the target table - * - foreignKey: The name of the field to use as foreign key, if false none - * will be used - * - dependent: Set to true if you want CakePHP to cascade deletes to the - * associated table when an entity is removed on this table. Set to false - * if you don't want CakePHP to remove associated data, for when you are using - * database constraints. - * - cascadeCallbacks: Set to true if you want CakePHP to fire callbacks on - * cascaded deletes. If false the ORM will use deleteAll() to remove data. - * When true records will be loaded and then deleted. - * - conditions: array with a list of conditions to filter the join with - * - joinType: The type of join to be used (e.g. LEFT) - * - * This method will return the association object that was built. - * - * @param string $associated the alias for the target table. This is used to - * uniquely identify the association - * @param array $options list of options to configure the association definition - * @return \Cake\ORM\Association\HasOne - */ - public function hasOne($associated, array $options = []) { - $options += ['sourceTable' => $this]; - $association = new HasOne($associated, $options); - return $this->_associations->add($association->name(), $association); - } - -/** - * Creates a new HasMany association between this table and a target - * table. A "has many" association is a 1-N relationship. - * - * Target table can be inferred by its name, which is provided in the - * first argument, or you can either pass the class name to be instantiated or - * an instance of it directly. - * - * The options array accept the following keys: - * - * - className: The class name of the target table object - * - targetTable: An instance of a table object to be used as the target table - * - foreignKey: The name of the field to use as foreign key, if false none - * will be used - * - dependent: Set to true if you want CakePHP to cascade deletes to the - * associated table when an entity is removed on this table. Set to false - * if you don't want CakePHP to remove associated data, for when you are using - * database constraints. - * - cascadeCallbacks: Set to true if you want CakePHP to fire callbacks on - * cascaded deletes. If false the ORM will use deleteAll() to remove data. - * When true records will be loaded and then deleted. - * - conditions: array with a list of conditions to filter the join with - * - sort: The order in which results for this association should be returned - * - strategy: The strategy to be used for selecting results Either 'select' - * or 'subquery'. If subquery is selected the query used to return results - * in the source table will be used as conditions for getting rows in the - * target table. - * - * This method will return the association object that was built. - * - * @param string $associated the alias for the target table. This is used to - * uniquely identify the association - * @param array $options list of options to configure the association definition - * @return \Cake\ORM\Association\HasMany - */ - public function hasMany($associated, array $options = []) { - $options += ['sourceTable' => $this]; - $association = new HasMany($associated, $options); - return $this->_associations->add($association->name(), $association); - } - -/** - * Creates a new BelongsToMany association between this table and a target - * table. A "belongs to many" association is a M-N relationship. - * - * Target table can be inferred by its name, which is provided in the - * first argument, or you can either pass the class name to be instantiated or - * an instance of it directly. - * - * The options array accept the following keys: - * - * - className: The class name of the target table object. - * - targetTable: An instance of a table object to be used as the target table. - * - foreignKey: The name of the field to use as foreign key. - * - targetForeignKey: The name of the field to use as the target foreign key. - * - joinTable: The name of the table representing the link between the two - * - through: If you choose to use an already instantiated link table, set this - * key to a configured Table instance containing associations to both the source - * and target tables in this association. - * - cascadeCallbacks: Set to true if you want CakePHP to fire callbacks on - * cascaded deletes. If false the ORM will use deleteAll() to remove data. - * When true join/junction table records will be loaded and then deleted. - * - conditions: array with a list of conditions to filter the join with. - * - sort: The order in which results for this association should be returned. - * - strategy: The strategy to be used for selecting results Either 'select' - * or 'subquery'. If subquery is selected the query used to return results - * in the source table will be used as conditions for getting rows in the - * target table. - * - saveStrategy: Either 'append' or 'replace'. Indicates the mode to be used - * for saving associated entities. The former will only create new links - * between both side of the relation and the latter will do a wipe and - * replace to create the links between the passed entities when saving. - * - * This method will return the association object that was built. - * - * @param string $associated the alias for the target table. This is used to - * uniquely identify the association - * @param array $options list of options to configure the association definition - * @return \Cake\ORM\Association\BelongsToMany - */ - public function belongsToMany($associated, array $options = []) { - $options += ['sourceTable' => $this]; - $association = new BelongsToMany($associated, $options); - return $this->_associations->add($association->name(), $association); - } - -/** - * {@inheritDoc} - * - * By default, `$options` will recognize the following keys: - * - * - fields - * - conditions - * - order - * - limit - * - offset - * - page - * - order - * - group - * - having - * - contain - * - join - * @return \Cake\ORM\Query - */ - public function find($type = 'all', $options = []) { - $query = $this->query(); - $query->select(); - return $this->callFinder($type, $query, $options); - } - -/** - * Returns the query as passed - * - * @param \Cake\ORM\Query $query The query to find with - * @param array $options The options to use for the find - * @return \Cake\ORM\Query - */ - public function findAll(Query $query, array $options) { - return $query; - } - -/** - * Sets up a query object so results appear as an indexed array, useful for any - * place where you would want a list such as for populating input select boxes. - * - * When calling this finder, the fields passed are used to determine what should - * be used as the array key, value and optionally what to group the results by. - * By default the primary key for the model is used for the key, and the display - * field as value. - * - * The results of this finder will be in the following form: - * - * {{{ - * [ - * 1 => 'value for id 1', - * 2 => 'value for id 2', - * 4 => 'value for id 4' - * ] - * }}} - * - * You can specify which property will be used as the key and which as value - * by using the `$options` array, when not specified, it will use the results - * of calling `primaryKey` and `displayField` respectively in this table: - * - * {{{ - * $table->find('list', [ - * 'idField' => 'name', - * 'valueField' => 'age' - * ]); - * }}} - * - * Results can be put together in bigger groups when they share a property, you - * can customize the property to use for grouping by setting `groupField`: - * - * {{{ - * $table->find('list', [ - * 'groupField' => 'category_id', - * ]); - * }}} - * - * When using a `groupField` results will be returned in this format: - * - * {{{ - * [ - * 'group_1' => [ - * 1 => 'value for id 1', - * 2 => 'value for id 2', - * ] - * 'group_2' => [ - * 4 => 'value for id 4' - * ] - * ] - * }}} - * - * @param \Cake\ORM\Query $query The query to find with - * @param array $options The options for the find - * @return \Cake\ORM\Query - */ - public function findList(Query $query, array $options) { - $options += [ - 'idField' => $this->primaryKey(), - 'valueField' => $this->displayField(), - 'groupField' => null - ]; - $options = $this->_setFieldMatchers( - $options, - ['idField', 'valueField', 'groupField'] - ); - - return $query->formatResults(function ($results) use ($options) { - return $results->combine( - $options['idField'], - $options['valueField'], - $options['groupField'] - ); - }); - } - -/** - * Results for this finder will be a nested array, and is appropriate if you want - * to use the parent_id field of your model data to build nested results. - * - * Values belonging to a parent row based on their parent_id value will be - * recursively nested inside the parent row values using the `children` property - * - * You can customize what fields are used for nesting results, by default the - * primary key and the `parent_id` fields are used. If you you wish to change - * these defaults you need to provide the keys `idField` or `parentField` in - * `$options`: - * - * {{{ - * $table->find('threaded', [ - * 'idField' => 'id', - * 'parentField' => 'ancestor_id' - * ]); - * }}} - * - * @param \Cake\ORM\Query $query The query to find with - * @param array $options The options to find with - * @return \Cake\ORM\Query - */ - public function findThreaded(Query $query, array $options) { - $options += [ - 'idField' => $this->primaryKey(), - 'parentField' => 'parent_id', - ]; - $options = $this->_setFieldMatchers($options, ['idField', 'parentField']); - - return $query->formatResults(function ($results) use ($options) { - return $results->nest($options['idField'], $options['parentField']); - }); - } - -/** - * Out of an options array, check if the keys described in `$keys` are arrays - * and change the values for closures that will concatenate the each of the - * properties in the value array when passed a row. - * - * This is an auxiliary function used for result formatters that can accept - * composite keys when comparing values. - * - * @param array $options the original options passed to a finder - * @param array $keys the keys to check in $options to build matchers from - * the associated value - * @return array - */ - protected function _setFieldMatchers($options, $keys) { - foreach ($keys as $field) { - if (!is_array($options[$field])) { - continue; - } - - if (count($options[$field]) === 1) { - $options[$field] = current($options[$field]); - continue; - } - - $fields = $options[$field]; - $options[$field] = function ($row) use ($fields) { - $matches = []; - foreach ($fields as $field) { - $matches[] = $row[$field]; - } - return implode(';', $matches); - }; - } - - return $options; - } - -/** - * {@inheritDoc} - * - * @throws Cake\Datasource\Exception\InvalidPrimaryKeyException When $primaryKey has an - * incorrect number of elements. - */ - public function get($primaryKey, $options = []) { - $key = (array)$this->primaryKey(); - $alias = $this->alias(); - foreach ($key as $index => $keyname) { - $key[$index] = $alias . '.' . $keyname; - } - $primaryKey = (array)$primaryKey; - if (count($key) !== count($primaryKey)) { - $primaryKey = $primaryKey ?: [null]; - $primaryKey = array_map(function ($key) { - return var_export($key, true); - }, $primaryKey); - - throw new InvalidPrimaryKeyException(sprintf( - 'Record not found in table "%s" with primary key [%s]', - $this->table(), - implode($primaryKey, ', ') - )); - } - $conditions = array_combine($key, $primaryKey); - - $cacheConfig = isset($options['cache']) ? $options['cache'] : false; - $cacheKey = isset($options['key']) ? $options['key'] : false; - unset($options['key'], $options['cache']); - - $query = $this->find('all', $options)->where($conditions); - - if ($cacheConfig) { - if (!$cacheKey) { - $cacheKey = sprintf( - "get:%s.%s%s", - $this->connection()->configName(), $this->table(), json_encode($primaryKey) - ); - } - $query->cache($cacheKey, $cacheConfig); - } - return $query->firstOrFail(); - } - -/** - * Finds an existing record or creates a new one. - * - * Using the attributes defined in $search a find() will be done to locate - * an existing record. If that record exists it will be returned. If it does - * not exist, a new entity will be created with the $search properties, and - * the $defaults. When a new entity is created, it will be saved. - * - * @param array $search The criteria to find existing records by. - * @param callable|null $callback A callback that will be invoked for newly - * created entities. This callback will be called *before* the entity - * is persisted. - * @return \Cake\Datasource\EntityInterface An entity. - */ - public function findOrCreate($search, callable $callback = null) { - $query = $this->find()->where($search); - $row = $query->first(); - if ($row) { - return $row; - } - $entity = $this->newEntity(); - $entity->set($search, ['guard' => false]); - if ($callback) { - $callback($entity); - } - return $this->save($entity) ?: $entity; - } - -/** - * {@inheritDoc} - */ - public function query() { - return new Query($this->connection(), $this); - } - -/** - * {@inheritDoc} - */ - public function updateAll($fields, $conditions) { - $query = $this->query(); - $query->update() - ->set($fields) - ->where($conditions); - $statement = $query->execute(); - $statement->closeCursor(); - return $statement->rowCount(); - } - -/** - * Returns the validation rules tagged with $name. It is possible to have - * multiple different named validation sets, this is useful when you need - * to use varying rules when saving from different routines in your system. - * - * There are two different ways of creating and naming validation sets: by - * creating a new method inside your own Table subclass, or by building - * the validator object yourself and storing it using this method. - * - * For example, if you wish to create a validation set called 'forSubscription', - * you will need to create a method in your Table subclass as follows: - * - * {{{ - * public function validationForSubscription($validator) { - * return $validator - * ->add('email', 'valid-email', ['rule' => 'email']) - * ->add('password', 'valid', ['rule' => 'notEmpty']) - * ->requirePresence('username'); - * } - * }}} - * - * Otherwise, you can build the object by yourself and store it in the Table object: - * - * {{{ - * $validator = new \Cake\Validation\Validator($table); - * $validator - * ->add('email', 'valid-email', ['rule' => 'email']) - * ->add('password', 'valid', ['rule' => 'notEmpty']) - * ->allowEmpty('bio'); - * $table->validator('forSubscription', $validator); - * }}} - * - * You can implement the method in `validationDefault` in your Table subclass - * should you wish to have a validation set that applies in cases where no other - * set is specified. - * - * @param string $name the name of the validation set to return - * @param \Cake\Validation\Validator|null $validator The validator instance to store, - * use null to get a validator. - * @return \Cake\Validation\Validator - */ - public function validator($name = 'default', Validator $validator = null) { - if ($validator === null && isset($this->_validators[$name])) { - return $this->_validators[$name]; - } - - if ($validator === null) { - $validator = new Validator(); - $validator = $this->{'validation' . ucfirst($name)}($validator); - $this->dispatchEvent('Model.buildValidator', compact('validator', 'name')); - } - - $validator->provider('table', $this); - return $this->_validators[$name] = $validator; - } - -/** - * Returns the default validator object. Subclasses can override this function - * to add a default validation set to the validator object. - * - * @param \Cake\Validation\Validator $validator The validator that can be modified to - * add some rules to it. - * @return \Cake\Validation\Validator - */ - public function validationDefault(Validator $validator) { - return $validator; - } - -/** - * {@inheritDoc} - */ - public function deleteAll($conditions) { - $query = $this->query() - ->delete() - ->where($conditions); - $statement = $query->execute(); - $statement->closeCursor(); - return $statement->rowCount(); - } - -/** - * {@inheritDoc} - */ - public function exists($conditions) { - return (bool)count($this->find('all') - ->select(['existing' => 1]) - ->where($conditions) - ->limit(1) - ->hydrate(false) - ->toArray()); - } - -/** - * {@inheritDoc} - * - * ### Options - * - * The options array can receive the following keys: - * - * - atomic: Whether to execute the save and callbacks inside a database - * transaction (default: true) - * - checkRules: Whether or not to check the rules on entity before saving, if the checking - * fails, it will abort the save operation. (default:true) - * - associated: If true it will save all associated entities as they are found - * in the passed `$entity` whenever the property defined for the association - * is marked as dirty. Associated records are saved recursively unless told - * otherwise. If an array, it will be interpreted as the list of associations - * to be saved. It is possible to provide different options for saving on associated - * table objects using this key by making the custom options the array value. - * If false no associated records will be saved. (default: true) - * - * ### Events - * - * When saving, this method will trigger four events: - * - * - Model.beforeRules: Will be triggered right before any rule checking is done - * for the passed entity if the `checkRules` key in $options is not set to false. - * Listeners will receive as arguments the entity, options array and the operation type. - * If the event is stopped the checking result will be set to the result of the event itself. - * - Model.afterRules: Will be triggered right after the `checkRules()` method is - * called for the entity. Listeners will receive as arguments the entity, - * options array, the result of checking the rules and the operation type. - * If the event is stopped the checking result will be set to the result of - * the event itself. - * - Model.beforeSave: Will be triggered just before the list of fields to be - * persisted is calculated. It receives both the entity and the options as - * arguments. The options array is passed as an ArrayObject, so any changes in - * it will be reflected in every listener and remembered at the end of the event - * so it can be used for the rest of the save operation. Returning false in any - * of the listeners will abort the saving process. If the event is stopped - * using the event API, the event object's `result` property will be returned. - * This can be useful when having your own saving strategy implemented inside a - * listener. - * - Model.afterSave: Will be triggered after a successful insert or save, - * listeners will receive the entity and the options array as arguments. The type - * of operation performed (insert or update) can be determined by checking the - * entity's method `isNew`, true meaning an insert and false an update. - * - * This method will determine whether the passed entity needs to be - * inserted or updated in the database. It does that by checking the `isNew` - * method on the entity. If the entity to be saved returns a non-empty value from - * its `errors()` method, it will not be saved. - * - * ### Saving on associated tables - * - * This method will by default persist entities belonging to associated tables, - * whenever a dirty property matching the name of the property name set for an - * association in this table. It is possible to control what associations will - * be saved and to pass additional option for saving them. - * - * {{{ - * // Only save the comments association - * $articles->save($entity, ['associated' => ['Comments']); - * - * // Save the company, the employees and related addresses for each of them. - * // For employees do not check the entity rules - * $companies->save($entity, [ - * 'associated' => [ - * 'Employees' => [ - * 'associated' => ['Addresses'], - * 'checkRules' => false - * ] - * ] - * ]); - * - * // Save no associations - * $articles->save($entity, ['associated' => false]); - * }}} - * - */ - public function save(EntityInterface $entity, $options = []) { - $options = new ArrayObject($options + [ - 'atomic' => true, - 'associated' => true, - 'checkRules' => true - ]); - - if ($entity->errors()) { - return false; - } - - if ($entity->isNew() === false && !$entity->dirty()) { - return $entity; - } - - if ($options['atomic']) { - $connection = $this->connection(); - $success = $connection->transactional(function () use ($entity, $options) { - return $this->_processSave($entity, $options); - }); - } else { - $success = $this->_processSave($entity, $options); - } - - return $success; - } - -/** - * Performs the actual saving of an entity based on the passed options. - * - * @param \Cake\Datasource\EntityInterface $entity the entity to be saved - * @param array $options the options to use for the save operation - * @return \Cake\Datasource\EntityInterface|bool - * @throws \RuntimeException When an entity is missing some of the primary keys. - */ - protected function _processSave($entity, $options) { - $primaryColumns = (array)$this->primaryKey(); - - if ($primaryColumns && $entity->isNew() && $entity->has($primaryColumns)) { - $alias = $this->alias(); - $conditions = []; - foreach ($entity->extract($primaryColumns) as $k => $v) { - $conditions["$alias.$k"] = $v; - } - $entity->isNew(!$this->exists($conditions)); - } - - $mode = $entity->isNew() ? RulesChecker::CREATE : RulesChecker::UPDATE; - if ($options['checkRules'] && !$this->checkRules($entity, $mode, $options)) { - return false; - } - - $options['associated'] = $this->_associations->normalizeKeys($options['associated']); - $event = $this->dispatchEvent('Model.beforeSave', compact('entity', 'options')); - - if ($event->isStopped()) { - return $event->result; - } - - $saved = $this->_associations->saveParents( - $this, - $entity, - $options['associated'], - $options->getArrayCopy() - ); - - if (!$saved && $options['atomic']) { - return false; - } - - $data = $entity->extract($this->schema()->columns(), true); - $isNew = $entity->isNew(); - - if ($isNew) { - $success = $this->_insert($entity, $data); - } else { - $success = $this->_update($entity, $data); - } - - if ($success) { - $success = $this->_associations->saveChildren( - $this, - $entity, - $options['associated'], - $options->getArrayCopy() - ); - if ($success || !$options['atomic']) { - $entity->clean(); - $this->dispatchEvent('Model.afterSave', compact('entity', 'options')); - $entity->isNew(false); - $entity->source($this->alias()); - $success = true; - } - } - - if (!$success && $isNew) { - $entity->unsetProperty($this->primaryKey()); - $entity->isNew(true); - } - if ($success) { - return $entity; - } - return false; - } - -/** - * Auxiliary function to handle the insert of an entity's data in the table - * - * @param \Cake\Datasource\EntityInterface $entity the subject entity from were $data was extracted - * @param array $data The actual data that needs to be saved - * @return \Cake\Datasource\EntityInterface|bool - * @throws \RuntimeException if not all the primary keys where supplied or could - * be generated when the table has composite primary keys. Or when the table has no primary key. - */ - protected function _insert($entity, $data) { - $primary = (array)$this->primaryKey(); - if (empty($primary)) { - $msg = sprintf( - 'Cannot insert row in "%s" table, it has no primary key.', - $this->table() - ); - throw new \RuntimeException($msg); - } - $keys = array_fill(0, count($primary), null); - $id = (array)$this->_newId($primary) + $keys; - $primary = array_combine($primary, $id); - $filteredKeys = array_filter($primary, 'strlen'); - $data = $filteredKeys + $data; - - if (count($primary) > 1) { - foreach ($primary as $k => $v) { - if (!isset($data[$k])) { - $msg = 'Cannot insert row, some of the primary key values are missing. '; - $msg .= sprintf( - 'Got (%s), expecting (%s)', - implode(', ', $filteredKeys + $entity->extract(array_keys($primary))), - implode(', ', array_keys($primary)) - ); - throw new \RuntimeException($msg); - } - } - } - - $success = false; - if (empty($data)) { - return $success; - } - - $statement = $this->query()->insert(array_keys($data)) - ->values($data) - ->execute(); - - if ($statement->rowCount() !== 0) { - $success = $entity; - $entity->set($filteredKeys, ['guard' => false]); - foreach ($primary as $key => $v) { - if (!isset($data[$key])) { - $id = $statement->lastInsertId($this->table(), $key); - $entity->set($key, $id); - break; - } - } - } - $statement->closeCursor(); - return $success; - } - -/** - * Generate a primary key value for a new record. - * - * By default, this uses the type system to generate a new primary key - * value if possible. You can override this method if you have specific requirements - * for id generation. - * - * @param array $primary The primary key columns to get a new ID for. - * @return mixed Either null or the new primary key value. - */ - protected function _newId($primary) { - if (!$primary || count((array)$primary) > 1) { - return null; - } - $typeName = $this->schema()->columnType($primary[0]); - $type = Type::build($typeName); - return $type->newId(); - } - -/** - * Auxiliary function to handle the update of an entity's data in the table - * - * @param \Cake\Datasource\EntityInterface $entity the subject entity from were $data was extracted - * @param array $data The actual data that needs to be saved - * @return \Cake\Datasource\EntityInterface|bool - * @throws \InvalidArgumentException When primary key data is missing. - */ - protected function _update($entity, $data) { - $primaryColumns = (array)$this->primaryKey(); - $primaryKey = $entity->extract($primaryColumns); - - $data = array_diff_key($data, $primaryKey); - if (empty($data)) { - return $entity; - } - - if (!$entity->has($primaryColumns)) { - $message = 'All primary key value(s) are needed for updating'; - throw new \InvalidArgumentException($message); - } - - $query = $this->query(); - $statement = $query->update() - ->set($data) - ->where($primaryKey) - ->execute(); - - $success = false; - if ($statement->errorCode() === '00000') { - $success = $entity; - } - $statement->closeCursor(); - return $success; - } - -/** - * {@inheritDoc} - * - * For HasMany and HasOne associations records will be removed based on - * the dependent option. Join table records in BelongsToMany associations - * will always be removed. You can use the `cascadeCallbacks` option - * when defining associations to change how associated data is deleted. - * - * ### Options - * - * - `atomic` Defaults to true. When true the deletion happens within a transaction. - * - `checkRules` Defaults to true. Check deletion rules before deleting the record. - * - * ### Events - * - * - `beforeDelete` Fired before the delete occurs. If stopped the delete - * will be aborted. Receives the event, entity, and options. - * - `afterDelete` Fired after the delete has been successful. Receives - * the event, entity, and options. - * - * The options argument will be converted into an \ArrayObject instance - * for the duration of the callbacks, this allows listeners to modify - * the options used in the delete operation. - * - */ - public function delete(EntityInterface $entity, $options = []) { - $options = new ArrayObject($options + ['atomic' => true, 'checkRules' => true]); - - $process = function () use ($entity, $options) { - return $this->_processDelete($entity, $options); - }; - - if ($options['atomic']) { - return $this->connection()->transactional($process); - } - return $process(); - } - -/** - * Perform the delete operation. - * - * Will delete the entity provided. Will remove rows from any - * dependent associations, and clear out join tables for BelongsToMany associations. - * - * @param \Cake\DataSource\EntityInterface $entity The entity to delete. - * @param \ArrayObject $options The options for the delete. - * @throws \InvalidArgumentException if there are no primary key values of the - * passed entity - * @return bool success - */ - protected function _processDelete($entity, $options) { - if ($entity->isNew()) { - return false; - } - - $primaryKey = (array)$this->primaryKey(); - if (!$entity->has($primaryKey)) { - $msg = 'Deleting requires all primary key values.'; - throw new \InvalidArgumentException($msg); - } - - if ($options['checkRules'] && !$this->checkRules($entity, RulesChecker::DELETE, $options)) { - return false; - } - - $event = $this->dispatchEvent('Model.beforeDelete', [ - 'entity' => $entity, - 'options' => $options - ]); - - if ($event->isStopped()) { - return $event->result; - } - - $this->_associations->cascadeDelete($entity, $options->getArrayCopy()); - - $query = $this->query(); - $conditions = (array)$entity->extract($primaryKey); - $statement = $query->delete() - ->where($conditions) - ->execute(); - - $success = $statement->rowCount() > 0; - if (!$success) { - return $success; - } - - $this->dispatchEvent('Model.afterDelete', [ - 'entity' => $entity, - 'options' => $options - ]); - - return $success; - } - -/** - * Returns true if the finder exists for the table - * - * @param string $type name of finder to check - * - * @return bool - */ - public function hasFinder($type) { - $finder = 'find' . $type; - - return method_exists($this, $finder) || ($this->_behaviors && $this->_behaviors->hasFinder($type)); - } - -/** - * Calls a finder method directly and applies it to the passed query, - * if no query is passed a new one will be created and returned - * - * @param string $type name of the finder to be called - * @param \Cake\ORM\Query $query The query object to apply the finder options to - * @param array $options List of options to pass to the finder - * @return \Cake\ORM\Query - * @throws \BadMethodCallException - */ - public function callFinder($type, Query $query, array $options = []) { - $query->applyOptions($options); - $options = $query->getOptions(); - $finder = 'find' . $type; - if (method_exists($this, $finder)) { - return $this->{$finder}($query, $options); - } - - if ($this->_behaviors && $this->_behaviors->hasFinder($type)) { - return $this->_behaviors->callFinder($type, [$query, $options]); - } - - throw new \BadMethodCallException( - sprintf('Unknown finder method "%s"', $type) - ); - } - -/** - * Provides the dynamic findBy and findByAll methods. - * - * @param string $method The method name that was fired. - * @param array $args List of arguments passed to the function. - * @return mixed - * @throws \BadMethodCallException when there are missing arguments, or when - * and & or are combined. - */ - protected function _dynamicFinder($method, $args) { - $method = Inflector::underscore($method); - preg_match('/^find_([\w]+)_by_/', $method, $matches); - if (empty($matches)) { - // find_by_ is 8 characters. - $fields = substr($method, 8); - $findType = 'all'; - } else { - $fields = substr($method, strlen($matches[0])); - $findType = Inflector::variable($matches[1]); - } - $hasOr = strpos($fields, '_or_'); - $hasAnd = strpos($fields, '_and_'); - - $makeConditions = function ($fields, $args) { - $conditions = []; - if (count($args) < count($fields)) { - throw new BadMethodCallException(sprintf( - 'Not enough arguments for magic finder. Got %s required %s', - count($args), - count($fields) - )); - } - foreach ($fields as $field) { - $conditions[$field] = array_shift($args); - } - return $conditions; - }; - - if ($hasOr !== false && $hasAnd !== false) { - throw new BadMethodCallException( - 'Cannot mix "and" & "or" in a magic finder. Use find() instead.' - ); - } - - if ($hasOr === false && $hasAnd === false) { - $conditions = $makeConditions([$fields], $args); - } elseif ($hasOr !== false) { - $fields = explode('_or_', $fields); - $conditions = [ - 'OR' => $makeConditions($fields, $args) - ]; - } elseif ($hasAnd !== false) { - $fields = explode('_and_', $fields); - $conditions = $makeConditions($fields, $args); - } - - return $this->find($findType, [ - 'conditions' => $conditions, - ]); - } - -/** - * Handles behavior delegation + dynamic finders. - * - * If your Table uses any behaviors you can call them as if - * they were on the table object. - * - * @param string $method name of the method to be invoked - * @param array $args List of arguments passed to the function - * @return mixed - * @throws \BadMethodCallException - */ - public function __call($method, $args) { - if ($this->_behaviors && $this->_behaviors->hasMethod($method)) { - return $this->_behaviors->call($method, $args); - } - if (preg_match('/^find(?:\w+)?By/', $method) > 0) { - return $this->_dynamicFinder($method, $args); - } - - throw new \BadMethodCallException( - sprintf('Unknown method "%s"', $method) - ); - } - -/** - * Returns the association named after the passed value if exists, otherwise - * throws an exception. - * - * @param string $property the association name - * @return \Cake\ORM\Association - * @throws \RuntimeException if no association with such name exists - */ - public function __get($property) { - $association = $this->_associations->get($property); - if (!$association) { - throw new \RuntimeException(sprintf( - 'Table "%s" is not associated with "%s"', - get_class($this), - $property - )); - } - return $association; - } - -/** - * Returns whether an association named after the passed value - * exists for this table. - * - * @param string $property the association name - * @return bool - */ - public function __isset($property) { - return $this->_associations->has($property); - } - -/** - * Get the object used to marshal/convert array data into objects. - * - * Override this method if you want a table object to use custom - * marshalling logic. - * - * @return \Cake\ORM\Marshaller - * @see \Cake\ORM\Marshaller - */ - public function marshaller() { - return new Marshaller($this); - } - -/** - * {@inheritDoc} - * - * By default all the associations on this table will be hydrated. You can - * limit which associations are built, or include deeper associations - * using the options parameter: - * - * {{{ - * $article = $this->Articles->newEntity( - * $this->request->data(), - * ['associated' => ['Tags', 'Comments.Users']] - * ); - * }}} - * - * You can limit fields that will be present in the constructed entity by - * passing the `fieldList` option, which is also accepted for associations: - * - * {{{ - * $article = $this->Articles->newEntity($this->request->data(), [ - * 'fieldList' => ['title', 'body'], - * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']] - * ] - * ); - * }}} - * - * The `fieldList` option lets remove or restrict input data from ending up in - * the entity. If you'd like to relax the entity's default accessible fields, - * you can use the `accessibleFields` option: - * - * {{{ - * $article = $this->Articles->newEntity( - * $this->request->data(), - * ['accessibleFields' => ['protected_field' => true]] - * ); - * }}} - * - * By default, the data is validated before being passed to the new entity. In - * the case of invalid fields, those will not be present in the resulting object. - * The `validate` option can be used to disable validation on the passed data: - * - * {{{ - * $article = $this->Articles->newEntity( - * $this->request->data(), - * ['validate' => false] - * ); - * }}} - * - * You can also pass the name of the validator to use in the `validate` option. - * If `null` is passed to the first param of this function, no validation will - * be performed. - */ - public function newEntity($data = null, array $options = []) { - if ($data === null) { - $class = $this->entityClass(); - return new $class; - } - if (!isset($options['associated'])) { - $options['associated'] = $this->_associations->keys(); - } - $marshaller = $this->marshaller(); - return $marshaller->one($data, $options); - } - -/** - * {@inheritDoc} - * - * By default all the associations on this table will be hydrated. You can - * limit which associations are built, or include deeper associations - * using the options parameter: - * - * {{{ - * $articles = $this->Articles->newEntities( - * $this->request->data(), - * ['associated' => ['Tags', 'Comments.Users']] - * ); - * }}} - * - * You can limit fields that will be present in the constructed entities by - * passing the `fieldList` option, which is also accepted for associations: - * - * {{{ - * $articles = $this->Articles->newEntities($this->request->data(), [ - * 'fieldList' => ['title', 'body'], - * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']] - * ] - * ); - * }}} - * - */ - public function newEntities(array $data, array $options = []) { - if (!isset($options['associated'])) { - $options['associated'] = $this->_associations->keys(); - } - $marshaller = $this->marshaller(); - return $marshaller->many($data, $options); - } - -/** - * {@inheritDoc} - * - * When merging HasMany or BelongsToMany associations, all the entities in the - * `$data` array will appear, those that can be matched by primary key will get - * the data merged, but those that cannot, will be discarded. - * - * You can limit fields that will be present in the merged entity by - * passing the `fieldList` option, which is also accepted for associations: - * - * {{{ - * $articles = $this->Articles->patchEntity($article, $this->request->data(), [ - * 'fieldList' => ['title', 'body'], - * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']] - * ] - * ); - * }}} - * - * By default, the data is validated before being passed to the entity. In - * the case of invalid fields, those will not be assigned to the entity. - * The `validate` option can be used to disable validation on the passed data: - * - * {{{ - * $article = $this->patchEntity($article, $this->request->data(),[ - * 'validate' => false - * ]); - * }}} - */ - public function patchEntity(EntityInterface $entity, array $data, array $options = []) { - if (!isset($options['associated'])) { - $options['associated'] = $this->_associations->keys(); - } - $marshaller = $this->marshaller(); - return $marshaller->merge($entity, $data, $options); - } - -/** - * {@inheritDoc} - * - * Those entries in `$entities` that cannot be matched to any record in - * `$data` will be discarded. Records in `$data` that could not be matched will - * be marshalled as a new entity. - * - * When merging HasMany or BelongsToMany associations, all the entities in the - * `$data` array will appear, those that can be matched by primary key will get - * the data merged, but those that cannot, will be discarded. - * - * You can limit fields that will be present in the merged entities by - * passing the `fieldList` option, which is also accepted for associations: - * - * {{{ - * $articles = $this->Articles->patchEntities($articles, $this->request->data(), [ - * 'fieldList' => ['title', 'body'], - * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']] - * ] - * ); - * }}} - */ - public function patchEntities($entities, array $data, array $options = []) { - if (!isset($options['associated'])) { - $options['associated'] = $this->_associations->keys(); - } - $marshaller = $this->marshaller(); - return $marshaller->mergeMany($entities, $data, $options); - } - -/** - * Validator method used to check the uniqueness of a value for a column. - * This is meant to be used with the validation API and not to be called - * directly. - * - * ### Example: - * - * {{{ - * $validator->add('email', [ - * 'unique' => ['rule' => 'validateUnique', 'provider' => 'table'] - * ]) - * }}} - * - * Unique validation can be scoped to the value of another column: - * - * {{{ - * $validator->add('email', [ - * 'unique' => [ - * 'rule' => ['validateUnique', ['scope' => 'site_id']], - * 'provider' => 'table' - * ] - * ]); - * }}} - * - * In the above example, the email uniqueness will be scoped to only rows having - * the same site_id. Scoping will only be used if the scoping field is present in - * the data to be validated. - * - * @param mixed $value The value of column to be checked for uniqueness - * @param array $options The options array, optionally containing the 'scope' key - * @return bool true if the value is unique - */ - public function validateUnique($value, array $options) { - $entity = new Entity( - $options['data'], - ['useSetters' => false, 'markNew' => $options['newRecord']] - ); - $fields = array_merge( - [$options['field']], - isset($options['scope']) ? (array)$options['scope'] : [] - ); - $rule = new IsUnique($fields); - return $rule($entity, ['repository' => $this]); - } - -/** - * Returns whether or not the passed entity complies with all the rules stored in - * the rules checker. - * - * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. - * @param string $operation The operation being run. Either 'create', 'update' or 'delete'. - * @param \ArrayObject|array $options The options To be passed to the rules. - * @return bool - */ - public function checkRules(EntityInterface $entity, $operation = RulesChecker::CREATE, $options = null) { - $rules = $this->rulesChecker(); - $options = $options ?: new ArrayObject; - $options = is_array($options) ? new ArrayObject($options) : $options; - - $event = $this->dispatchEvent( - 'Model.beforeRules', - compact('entity', 'options', 'operation') - ); - - if ($event->isStopped()) { - return $event->result; - } - - $result = $rules->check($entity, $operation, $options->getArrayCopy()); - $event = $this->dispatchEvent( - 'Model.afterRules', - compact('entity', 'options', 'result', 'operation') - ); - - if ($event->isStopped()) { - return $event->result; - } - - return $result; - } - -/** - * Returns the rule checker for this table. A rules checker object is used to - * test an entity for validity on rules that may involve complex logic or data that - * needs to be fetched from the database or other sources. - * - * @return \Cake\ORM\RulesChecker - */ - public function rulesChecker() { - if ($this->_rulesChecker !== null) { - return $this->_rulesChecker; - } - $this->_rulesChecker = $this->buildRules(new RulesChecker(['repository' => $this])); - $this->dispatchEvent('Model.buildRules', ['rules' => $this->_rulesChecker]); - return $this->_rulesChecker; - } - -/** - * Returns rules checker object after modifying the one that was passed. Subclasses - * can override this method in order to initialize the rules to be applied to - * entities saved by this table. - * - * @param \Cake\ORM\RulesChecker $rules The rules object to be modified. - * @return \Cake\ORM\RulesChecker - */ - public function buildRules(RulesChecker $rules) { - return $rules; - } - -/** - * Get the Model callbacks this table is interested in. - * - * By implementing the conventional methods a table class is assumed - * to be interested in the related event. - * - * Override this method if you need to add non-conventional event listeners. - * Or if you want you table to listen to non-standard events. - * - * @return array - */ - public function implementedEvents() { - $eventMap = [ - 'Model.beforeFind' => 'beforeFind', - 'Model.beforeSave' => 'beforeSave', - 'Model.afterSave' => 'afterSave', - 'Model.beforeDelete' => 'beforeDelete', - 'Model.afterDelete' => 'afterDelete', - 'Model.beforeRules' => 'beforeRules', - 'Model.afterRules' => 'afterRules', - ]; - $events = []; - - foreach ($eventMap as $event => $method) { - if (!method_exists($this, $method)) { - continue; - } - $events[$event] = $method; - } - return $events; - } - -/** - * Returns an array that can be used to describe the internal state of this - * object. - * - * @return array - */ - public function __debugInfo() { - $conn = $this->connection(); - return [ - 'table' => $this->table(), - 'alias' => $this->alias(), - 'entityClass' => $this->entityClass(), - 'associations' => $this->_associations->keys(), - 'behaviors' => $this->_behaviors->loaded(), - 'defaultConnection' => $this->defaultConnectionName(), - 'connectionName' => $conn ? $conn->configName() : null - ]; - } - +class Table implements RepositoryInterface, EventListenerInterface +{ + + use EventManagerTrait; + + /** + * Name of the table as it can be found in the database + * + * @var string + */ + protected $_table; + + /** + * Human name giving to this particular instance. Multiple objects representing + * the same database table can exist by using different aliases. + * + * @var string + */ + protected $_alias; + + /** + * Connection instance + * + * @var \Cake\Database\Connection + */ + protected $_connection; + + /** + * The schema object containing a description of this table fields + * + * @var \Cake\Database\Schema\Table + */ + protected $_schema; + + /** + * The name of the field that represents the primary key in the table + * + * @var string|array + */ + protected $_primaryKey; + + /** + * The name of the field that represents a human readable representation of a row + * + * @var string + */ + protected $_displayField; + + /** + * The associations container for this Table. + * + * @var \Cake\ORM\AssociationCollection + */ + protected $_associations; + + /** + * BehaviorRegistry for this table + * + * @var \Cake\ORM\BehaviorRegistry + */ + protected $_behaviors; + + /** + * The name of the class that represent a single row for this table + * + * @var string + */ + protected $_entityClass; + + /** + * A list of validation objects indexed by name + * + * @var array + */ + protected $_validators = []; + + /** + * The domain rules to be applied to entities saved by this table + * + * @var \Cake\ORM\RulesChecker + */ + protected $_rulesChecker; + + /** + * Initializes a new instance + * + * The $config array understands the following keys: + * + * - table: Name of the database table to represent + * - alias: Alias to be assigned to this table (default to table name) + * - connection: The connection instance to use + * - entityClass: The fully namespaced class name of the entity class that will + * represent rows in this table. + * - schema: A \Cake\Database\Schema\Table object or an array that can be + * passed to it. + * - eventManager: An instance of an event manager to use for internal events + * - behaviors: A BehaviorRegistry. Generally not used outside of tests. + * - associations: An AssociationCollection instance. + * + * @param array $config List of options for this table + */ + public function __construct(array $config = []) + { + if (!empty($config['table'])) { + $this->table($config['table']); + } + if (!empty($config['alias'])) { + $this->alias($config['alias']); + } + if (!empty($config['connection'])) { + $this->connection($config['connection']); + } + if (!empty($config['schema'])) { + $this->schema($config['schema']); + } + if (!empty($config['entityClass'])) { + $this->entityClass($config['entityClass']); + } + $eventManager = $behaviors = $associations = null; + if (!empty($config['eventManager'])) { + $eventManager = $config['eventManager']; + } + if (!empty($config['behaviors'])) { + $behaviors = $config['behaviors']; + } + if (!empty($config['associations'])) { + $associations = $config['associations']; + } + $this->_eventManager = $eventManager ?: new EventManager(); + $this->_behaviors = $behaviors ?: new BehaviorRegistry($this); + $this->_associations = $associations ?: new AssociationCollection(); + + $this->initialize($config); + $this->_eventManager->attach($this); + $this->dispatchEvent('Model.initialize'); + } + + /** + * Get the default connection name. + * + * This method is used to get the fallback connection name if an + * instance is created through the TableRegistry without a connection. + * + * @return string + * @see \Cake\ORM\TableRegistry::get() + */ + public static function defaultConnectionName() + { + return 'default'; + } + + /** + * Initialize a table instance. Called after the constructor. + * + * You can use this method to define associations, attach behaviors + * define validation and do any other initialization logic you need. + * + * {{{ + * public function initialize(array $config) { + * $this->belongsTo('Users'); + * $this->belongsToMany('Tagging.Tags'); + * $this->primaryKey('something_else'); + * } + * }}} + * + * @param array $config Configuration options passed to the constructor + * @return void + */ + public function initialize(array $config) + { + } + + /** + * Returns the database table name or sets a new one + * + * @param string|null $table the new table name + * @return string + */ + public function table($table = null) + { + if ($table !== null) { + $this->_table = $table; + } + if ($this->_table === null) { + $table = namespaceSplit(get_class($this)); + $table = substr(end($table), 0, -5); + if (empty($table)) { + $table = $this->alias(); + } + $this->_table = Inflector::underscore($table); + } + return $this->_table; + } + + /** + * Returns the table alias or sets a new one + * + * @param string|null $alias the new table alias + * @return string + */ + public function alias($alias = null) + { + if ($alias !== null) { + $this->_alias = $alias; + } + if ($this->_alias === null) { + $alias = namespaceSplit(get_class($this)); + $alias = substr(end($alias), 0, -5) ?: $this->_table; + $this->_alias = $alias; + } + return $this->_alias; + } + + /** + * Returns the connection instance or sets a new one + * + * @param \Cake\Database\Connection|null $conn The new connection instance + * @return \Cake\Database\Connection + */ + public function connection($conn = null) + { + if ($conn === null) { + return $this->_connection; + } + return $this->_connection = $conn; + } + + /** + * Returns the schema table object describing this table's properties. + * + * If an \Cake\Database\Schema\Table is passed, it will be used for this table + * instead of the default one. + * + * If an array is passed, a new \Cake\Database\Schema\Table will be constructed + * out of it and used as the schema for this table. + * + * @param array|\Cake\Database\Schema\Table|null $schema New schema to be used for this table + * @return \Cake\Database\Schema\Table + */ + public function schema($schema = null) + { + if ($schema === null) { + if ($this->_schema === null) { + $this->_schema = $this->_initializeSchema( + $this->connection() + ->schemaCollection() + ->describe($this->table()) + ); + } + return $this->_schema; + } + + if (is_array($schema)) { + $constraints = []; + + if (isset($schema['_constraints'])) { + $constraints = $schema['_constraints']; + unset($schema['_constraints']); + } + + $schema = new Schema($this->table(), $schema); + + foreach ($constraints as $name => $value) { + $schema->addConstraint($name, $value); + } + } + + return $this->_schema = $schema; + } + + /** + * Override this function in order to alter the schema used by this table. + * This function is only called after fetching the schema out of the database. + * If you wish to provide your own schema to this table without touching the + * database, you can override schema() or inject the definitions though that + * method. + * + * ### Example: + * + * {{{ + * protected function _initializeSchema(\Cake\Database\Schema\Table $table) { + * $table->columnType('preferences', 'json'); + * return $table; + * } + * }}} + * + * @param \Cake\Database\Schema\Table $table The table definition fetched from database. + * @return \Cake\Database\Schema\Table the altered schema + * @api + */ + protected function _initializeSchema(Schema $table) + { + return $table; + } + + /** + * Test to see if a Table has a specific field/column. + * + * Delegates to the schema object and checks for column presence + * using the Schema\Table instance. + * + * @param string $field The field to check for. + * @return bool True if the field exists, false if it does not. + */ + public function hasField($field) + { + $schema = $this->schema(); + return $schema->column($field) !== null; + } + + /** + * Returns the primary key field name or sets a new one + * + * @param string|array|null $key sets a new name to be used as primary key + * @return string|array + */ + public function primaryKey($key = null) + { + if ($key !== null) { + $this->_primaryKey = $key; + } + if ($this->_primaryKey === null) { + $key = (array)$this->schema()->primaryKey(); + if (count($key) === 1) { + $key = $key[0]; + } + $this->_primaryKey = $key; + } + return $this->_primaryKey; + } + + /** + * Returns the display field or sets a new one + * + * @param string|null $key sets a new name to be used as display field + * @return string + */ + public function displayField($key = null) + { + if ($key !== null) { + $this->_displayField = $key; + } + if ($this->_displayField === null) { + $schema = $this->schema(); + $primary = (array)$this->primaryKey(); + $this->_displayField = array_shift($primary); + if ($schema->column('title')) { + $this->_displayField = 'title'; + } + if ($schema->column('name')) { + $this->_displayField = 'name'; + } + } + return $this->_displayField; + } + + /** + * Returns the class used to hydrate rows for this table or sets + * a new one + * + * @param string|null $name the name of the class to use + * @throws \Cake\ORM\Exception\MissingEntityException when the entity class cannot be found + * @return string + */ + public function entityClass($name = null) + { + if ($name === null && !$this->_entityClass) { + $default = '\Cake\ORM\Entity'; + $self = get_called_class(); + $parts = explode('\\', $self); + + if ($self === __CLASS__ || count($parts) < 3) { + return $this->_entityClass = $default; + } + + $alias = Inflector::singularize(substr(array_pop($parts), 0, -5)); + $name = implode('\\', array_slice($parts, 0, -1)) . '\Entity\\' . $alias; + if (!class_exists($name)) { + return $this->_entityClass = $default; + } + } + + if ($name !== null) { + $class = App::className($name, 'Model/Entity'); + $this->_entityClass = $class; + } + + if (!$this->_entityClass) { + throw new MissingEntityException([$name]); + } + + return $this->_entityClass; + } + + /** + * Add a behavior. + * + * Adds a behavior to this table's behavior collection. Behaviors + * provide an easy way to create horizontally re-usable features + * that can provide trait like functionality, and allow for events + * to be listened to. + * + * Example: + * + * Load a behavior, with some settings. + * + * {{{ + * $this->addBehavior('Tree', ['parent' => 'parentId']); + * }}} + * + * Behaviors are generally loaded during Table::initialize(). + * + * @param string $name The name of the behavior. Can be a short class reference. + * @param array $options The options for the behavior to use. + * @return void + * @throws \RuntimeException If a behavior is being reloaded. + * @see \Cake\ORM\Behavior + */ + public function addBehavior($name, array $options = []) + { + $this->_behaviors->load($name, $options); + } + + /** + * Removes a behavior from this table's behavior registry. + * + * Example: + * + * Remove a behavior from this table. + * + * {{{ + * $this->removeBehavior('Tree'); + * }}} + * + * @param string $name The alias that the behavior was added with. + * @return void + * @see \Cake\ORM\Behavior + */ + public function removeBehavior($name) + { + $this->_behaviors->unload($name); + } + + /** + * Returns the behavior registry for this table. + * + * @return \Cake\ORM\BehaviorRegistry + */ + public function behaviors() + { + return $this->_behaviors; + } + + /** + * Check if a behavior with the given alias has been loaded. + * + * @param string $name The behavior alias to check. + * @return array + */ + public function hasBehavior($name) + { + return $this->_behaviors->has($name); + } + + /** + * Returns an association object configured for the specified alias if any + * + * @param string $name the alias used for the association + * @return \Cake\ORM\Association + */ + public function association($name) + { + return $this->_associations->get($name); + } + + /** + * Get the associations collection for this table. + * + * @return \Cake\ORM\AssociationCollection + */ + public function associations() + { + return $this->_associations; + } + + /** + * Setup multiple associations. + * + * It takes an array containing set of table names indexed by association type + * as argument: + * + * {{{ + * $this->Posts->addAssociations([ + * 'belongsTo' => [ + * 'Users' => ['className' => 'App\Model\Table\UsersTable'] + * ], + * 'hasMany' => ['Comments'], + * 'belongsToMany' => ['Tags'] + * ]); + * }}} + * + * Each association type accepts multiple associations where the keys + * are the aliases, and the values are association config data. If numeric + * keys are used the values will be treated as association aliases. + * + * @param array $params Set of associations to bind (indexed by association type) + * @return void + * @see \Cake\ORM\Table::belongsTo() + * @see \Cake\ORM\Table::hasOne() + * @see \Cake\ORM\Table::hasMany() + * @see \Cake\ORM\Table::belongsToMany() + */ + public function addAssociations(array $params) + { + foreach ($params as $assocType => $tables) { + foreach ($tables as $associated => $options) { + if (is_numeric($associated)) { + $associated = $options; + $options = []; + } + $this->{$assocType}($associated, $options); + } + } + } + + /** + * Creates a new BelongsTo association between this table and a target + * table. A "belongs to" association is a N-1 relationship where this table + * is the N side, and where there is a single associated record in the target + * table for each one in this table. + * + * Target table can be inferred by its name, which is provided in the + * first argument, or you can either pass the to be instantiated or + * an instance of it directly. + * + * The options array accept the following keys: + * + * - className: The class name of the target table object + * - targetTable: An instance of a table object to be used as the target table + * - foreignKey: The name of the field to use as foreign key, if false none + * will be used + * - conditions: array with a list of conditions to filter the join with + * - joinType: The type of join to be used (e.g. INNER) + * + * This method will return the association object that was built. + * + * @param string $associated the alias for the target table. This is used to + * uniquely identify the association + * @param array $options list of options to configure the association definition + * @return \Cake\ORM\Association\BelongsTo + */ + public function belongsTo($associated, array $options = []) + { + $options += ['sourceTable' => $this]; + $association = new BelongsTo($associated, $options); + return $this->_associations->add($association->name(), $association); + } + + /** + * Creates a new HasOne association between this table and a target + * table. A "has one" association is a 1-1 relationship. + * + * Target table can be inferred by its name, which is provided in the + * first argument, or you can either pass the class name to be instantiated or + * an instance of it directly. + * + * The options array accept the following keys: + * + * - className: The class name of the target table object + * - targetTable: An instance of a table object to be used as the target table + * - foreignKey: The name of the field to use as foreign key, if false none + * will be used + * - dependent: Set to true if you want CakePHP to cascade deletes to the + * associated table when an entity is removed on this table. Set to false + * if you don't want CakePHP to remove associated data, for when you are using + * database constraints. + * - cascadeCallbacks: Set to true if you want CakePHP to fire callbacks on + * cascaded deletes. If false the ORM will use deleteAll() to remove data. + * When true records will be loaded and then deleted. + * - conditions: array with a list of conditions to filter the join with + * - joinType: The type of join to be used (e.g. LEFT) + * + * This method will return the association object that was built. + * + * @param string $associated the alias for the target table. This is used to + * uniquely identify the association + * @param array $options list of options to configure the association definition + * @return \Cake\ORM\Association\HasOne + */ + public function hasOne($associated, array $options = []) + { + $options += ['sourceTable' => $this]; + $association = new HasOne($associated, $options); + return $this->_associations->add($association->name(), $association); + } + + /** + * Creates a new HasMany association between this table and a target + * table. A "has many" association is a 1-N relationship. + * + * Target table can be inferred by its name, which is provided in the + * first argument, or you can either pass the class name to be instantiated or + * an instance of it directly. + * + * The options array accept the following keys: + * + * - className: The class name of the target table object + * - targetTable: An instance of a table object to be used as the target table + * - foreignKey: The name of the field to use as foreign key, if false none + * will be used + * - dependent: Set to true if you want CakePHP to cascade deletes to the + * associated table when an entity is removed on this table. Set to false + * if you don't want CakePHP to remove associated data, for when you are using + * database constraints. + * - cascadeCallbacks: Set to true if you want CakePHP to fire callbacks on + * cascaded deletes. If false the ORM will use deleteAll() to remove data. + * When true records will be loaded and then deleted. + * - conditions: array with a list of conditions to filter the join with + * - sort: The order in which results for this association should be returned + * - strategy: The strategy to be used for selecting results Either 'select' + * or 'subquery'. If subquery is selected the query used to return results + * in the source table will be used as conditions for getting rows in the + * target table. + * + * This method will return the association object that was built. + * + * @param string $associated the alias for the target table. This is used to + * uniquely identify the association + * @param array $options list of options to configure the association definition + * @return \Cake\ORM\Association\HasMany + */ + public function hasMany($associated, array $options = []) + { + $options += ['sourceTable' => $this]; + $association = new HasMany($associated, $options); + return $this->_associations->add($association->name(), $association); + } + + /** + * Creates a new BelongsToMany association between this table and a target + * table. A "belongs to many" association is a M-N relationship. + * + * Target table can be inferred by its name, which is provided in the + * first argument, or you can either pass the class name to be instantiated or + * an instance of it directly. + * + * The options array accept the following keys: + * + * - className: The class name of the target table object. + * - targetTable: An instance of a table object to be used as the target table. + * - foreignKey: The name of the field to use as foreign key. + * - targetForeignKey: The name of the field to use as the target foreign key. + * - joinTable: The name of the table representing the link between the two + * - through: If you choose to use an already instantiated link table, set this + * key to a configured Table instance containing associations to both the source + * and target tables in this association. + * - cascadeCallbacks: Set to true if you want CakePHP to fire callbacks on + * cascaded deletes. If false the ORM will use deleteAll() to remove data. + * When true join/junction table records will be loaded and then deleted. + * - conditions: array with a list of conditions to filter the join with. + * - sort: The order in which results for this association should be returned. + * - strategy: The strategy to be used for selecting results Either 'select' + * or 'subquery'. If subquery is selected the query used to return results + * in the source table will be used as conditions for getting rows in the + * target table. + * - saveStrategy: Either 'append' or 'replace'. Indicates the mode to be used + * for saving associated entities. The former will only create new links + * between both side of the relation and the latter will do a wipe and + * replace to create the links between the passed entities when saving. + * + * This method will return the association object that was built. + * + * @param string $associated the alias for the target table. This is used to + * uniquely identify the association + * @param array $options list of options to configure the association definition + * @return \Cake\ORM\Association\BelongsToMany + */ + public function belongsToMany($associated, array $options = []) + { + $options += ['sourceTable' => $this]; + $association = new BelongsToMany($associated, $options); + return $this->_associations->add($association->name(), $association); + } + + /** + * {@inheritDoc} + * + * By default, `$options` will recognize the following keys: + * + * - fields + * - conditions + * - order + * - limit + * - offset + * - page + * - order + * - group + * - having + * - contain + * - join + * @return \Cake\ORM\Query + */ + public function find($type = 'all', $options = []) + { + $query = $this->query(); + $query->select(); + return $this->callFinder($type, $query, $options); + } + + /** + * Returns the query as passed + * + * @param \Cake\ORM\Query $query The query to find with + * @param array $options The options to use for the find + * @return \Cake\ORM\Query + */ + public function findAll(Query $query, array $options) + { + return $query; + } + + /** + * Sets up a query object so results appear as an indexed array, useful for any + * place where you would want a list such as for populating input select boxes. + * + * When calling this finder, the fields passed are used to determine what should + * be used as the array key, value and optionally what to group the results by. + * By default the primary key for the model is used for the key, and the display + * field as value. + * + * The results of this finder will be in the following form: + * + * {{{ + * [ + * 1 => 'value for id 1', + * 2 => 'value for id 2', + * 4 => 'value for id 4' + * ] + * }}} + * + * You can specify which property will be used as the key and which as value + * by using the `$options` array, when not specified, it will use the results + * of calling `primaryKey` and `displayField` respectively in this table: + * + * {{{ + * $table->find('list', [ + * 'idField' => 'name', + * 'valueField' => 'age' + * ]); + * }}} + * + * Results can be put together in bigger groups when they share a property, you + * can customize the property to use for grouping by setting `groupField`: + * + * {{{ + * $table->find('list', [ + * 'groupField' => 'category_id', + * ]); + * }}} + * + * When using a `groupField` results will be returned in this format: + * + * {{{ + * [ + * 'group_1' => [ + * 1 => 'value for id 1', + * 2 => 'value for id 2', + * ] + * 'group_2' => [ + * 4 => 'value for id 4' + * ] + * ] + * }}} + * + * @param \Cake\ORM\Query $query The query to find with + * @param array $options The options for the find + * @return \Cake\ORM\Query + */ + public function findList(Query $query, array $options) + { + $options += [ + 'idField' => $this->primaryKey(), + 'valueField' => $this->displayField(), + 'groupField' => null + ]; + $options = $this->_setFieldMatchers( + $options, + ['idField', 'valueField', 'groupField'] + ); + + return $query->formatResults(function ($results) use ($options) { + return $results->combine( + $options['idField'], + $options['valueField'], + $options['groupField'] + ); + }); + } + + /** + * Results for this finder will be a nested array, and is appropriate if you want + * to use the parent_id field of your model data to build nested results. + * + * Values belonging to a parent row based on their parent_id value will be + * recursively nested inside the parent row values using the `children` property + * + * You can customize what fields are used for nesting results, by default the + * primary key and the `parent_id` fields are used. If you you wish to change + * these defaults you need to provide the keys `idField` or `parentField` in + * `$options`: + * + * {{{ + * $table->find('threaded', [ + * 'idField' => 'id', + * 'parentField' => 'ancestor_id' + * ]); + * }}} + * + * @param \Cake\ORM\Query $query The query to find with + * @param array $options The options to find with + * @return \Cake\ORM\Query + */ + public function findThreaded(Query $query, array $options) + { + $options += [ + 'idField' => $this->primaryKey(), + 'parentField' => 'parent_id', + ]; + $options = $this->_setFieldMatchers($options, ['idField', 'parentField']); + + return $query->formatResults(function ($results) use ($options) { + return $results->nest($options['idField'], $options['parentField']); + }); + } + + /** + * Out of an options array, check if the keys described in `$keys` are arrays + * and change the values for closures that will concatenate the each of the + * properties in the value array when passed a row. + * + * This is an auxiliary function used for result formatters that can accept + * composite keys when comparing values. + * + * @param array $options the original options passed to a finder + * @param array $keys the keys to check in $options to build matchers from + * the associated value + * @return array + */ + protected function _setFieldMatchers($options, $keys) + { + foreach ($keys as $field) { + if (!is_array($options[$field])) { + continue; + } + + if (count($options[$field]) === 1) { + $options[$field] = current($options[$field]); + continue; + } + + $fields = $options[$field]; + $options[$field] = function ($row) use ($fields) { + $matches = []; + foreach ($fields as $field) { + $matches[] = $row[$field]; + } + return implode(';', $matches); + }; + } + + return $options; + } + + /** + * {@inheritDoc} + * + * @throws Cake\Datasource\Exception\InvalidPrimaryKeyException When $primaryKey has an + * incorrect number of elements. + */ + public function get($primaryKey, $options = []) + { + $key = (array)$this->primaryKey(); + $alias = $this->alias(); + foreach ($key as $index => $keyname) { + $key[$index] = $alias . '.' . $keyname; + } + $primaryKey = (array)$primaryKey; + if (count($key) !== count($primaryKey)) { + $primaryKey = $primaryKey ?: [null]; + $primaryKey = array_map(function ($key) { + return var_export($key, true); + }, $primaryKey); + + throw new InvalidPrimaryKeyException(sprintf( + 'Record not found in table "%s" with primary key [%s]', + $this->table(), + implode($primaryKey, ', ') + )); + } + $conditions = array_combine($key, $primaryKey); + + $cacheConfig = isset($options['cache']) ? $options['cache'] : false; + $cacheKey = isset($options['key']) ? $options['key'] : false; + unset($options['key'], $options['cache']); + + $query = $this->find('all', $options)->where($conditions); + + if ($cacheConfig) { + if (!$cacheKey) { + $cacheKey = sprintf( + "get:%s.%s%s", + $this->connection()->configName(), + $this->table(), + json_encode($primaryKey) + ); + } + $query->cache($cacheKey, $cacheConfig); + } + return $query->firstOrFail(); + } + + /** + * Finds an existing record or creates a new one. + * + * Using the attributes defined in $search a find() will be done to locate + * an existing record. If that record exists it will be returned. If it does + * not exist, a new entity will be created with the $search properties, and + * the $defaults. When a new entity is created, it will be saved. + * + * @param array $search The criteria to find existing records by. + * @param callable|null $callback A callback that will be invoked for newly + * created entities. This callback will be called *before* the entity + * is persisted. + * @return \Cake\Datasource\EntityInterface An entity. + */ + public function findOrCreate($search, callable $callback = null) + { + $query = $this->find()->where($search); + $row = $query->first(); + if ($row) { + return $row; + } + $entity = $this->newEntity(); + $entity->set($search, ['guard' => false]); + if ($callback) { + $callback($entity); + } + return $this->save($entity) ?: $entity; + } + + /** + * {@inheritDoc} + */ + public function query() + { + return new Query($this->connection(), $this); + } + + /** + * {@inheritDoc} + */ + public function updateAll($fields, $conditions) + { + $query = $this->query(); + $query->update() + ->set($fields) + ->where($conditions); + $statement = $query->execute(); + $statement->closeCursor(); + return $statement->rowCount(); + } + + /** + * Returns the validation rules tagged with $name. It is possible to have + * multiple different named validation sets, this is useful when you need + * to use varying rules when saving from different routines in your system. + * + * There are two different ways of creating and naming validation sets: by + * creating a new method inside your own Table subclass, or by building + * the validator object yourself and storing it using this method. + * + * For example, if you wish to create a validation set called 'forSubscription', + * you will need to create a method in your Table subclass as follows: + * + * {{{ + * public function validationForSubscription($validator) { + * return $validator + * ->add('email', 'valid-email', ['rule' => 'email']) + * ->add('password', 'valid', ['rule' => 'notEmpty']) + * ->requirePresence('username'); + * } + * }}} + * + * Otherwise, you can build the object by yourself and store it in the Table object: + * + * {{{ + * $validator = new \Cake\Validation\Validator($table); + * $validator + * ->add('email', 'valid-email', ['rule' => 'email']) + * ->add('password', 'valid', ['rule' => 'notEmpty']) + * ->allowEmpty('bio'); + * $table->validator('forSubscription', $validator); + * }}} + * + * You can implement the method in `validationDefault` in your Table subclass + * should you wish to have a validation set that applies in cases where no other + * set is specified. + * + * @param string $name the name of the validation set to return + * @param \Cake\Validation\Validator|null $validator The validator instance to store, + * use null to get a validator. + * @return \Cake\Validation\Validator + */ + public function validator($name = 'default', Validator $validator = null) + { + if ($validator === null && isset($this->_validators[$name])) { + return $this->_validators[$name]; + } + + if ($validator === null) { + $validator = new Validator(); + $validator = $this->{'validation' . ucfirst($name)}($validator); + $this->dispatchEvent('Model.buildValidator', compact('validator', 'name')); + } + + $validator->provider('table', $this); + return $this->_validators[$name] = $validator; + } + + /** + * Returns the default validator object. Subclasses can override this function + * to add a default validation set to the validator object. + * + * @param \Cake\Validation\Validator $validator The validator that can be modified to + * add some rules to it. + * @return \Cake\Validation\Validator + */ + public function validationDefault(Validator $validator) + { + return $validator; + } + + /** + * {@inheritDoc} + */ + public function deleteAll($conditions) + { + $query = $this->query() + ->delete() + ->where($conditions); + $statement = $query->execute(); + $statement->closeCursor(); + return $statement->rowCount(); + } + + /** + * {@inheritDoc} + */ + public function exists($conditions) + { + return (bool)count( + $this->find('all') + ->select(['existing' => 1]) + ->where($conditions) + ->limit(1) + ->hydrate(false) + ->toArray() + ); + } + + /** + * {@inheritDoc} + * + * ### Options + * + * The options array can receive the following keys: + * + * - atomic: Whether to execute the save and callbacks inside a database + * transaction (default: true) + * - checkRules: Whether or not to check the rules on entity before saving, if the checking + * fails, it will abort the save operation. (default:true) + * - associated: If true it will save all associated entities as they are found + * in the passed `$entity` whenever the property defined for the association + * is marked as dirty. Associated records are saved recursively unless told + * otherwise. If an array, it will be interpreted as the list of associations + * to be saved. It is possible to provide different options for saving on associated + * table objects using this key by making the custom options the array value. + * If false no associated records will be saved. (default: true) + * + * ### Events + * + * When saving, this method will trigger four events: + * + * - Model.beforeRules: Will be triggered right before any rule checking is done + * for the passed entity if the `checkRules` key in $options is not set to false. + * Listeners will receive as arguments the entity, options array and the operation type. + * If the event is stopped the checking result will be set to the result of the event itself. + * - Model.afterRules: Will be triggered right after the `checkRules()` method is + * called for the entity. Listeners will receive as arguments the entity, + * options array, the result of checking the rules and the operation type. + * If the event is stopped the checking result will be set to the result of + * the event itself. + * - Model.beforeSave: Will be triggered just before the list of fields to be + * persisted is calculated. It receives both the entity and the options as + * arguments. The options array is passed as an ArrayObject, so any changes in + * it will be reflected in every listener and remembered at the end of the event + * so it can be used for the rest of the save operation. Returning false in any + * of the listeners will abort the saving process. If the event is stopped + * using the event API, the event object's `result` property will be returned. + * This can be useful when having your own saving strategy implemented inside a + * listener. + * - Model.afterSave: Will be triggered after a successful insert or save, + * listeners will receive the entity and the options array as arguments. The type + * of operation performed (insert or update) can be determined by checking the + * entity's method `isNew`, true meaning an insert and false an update. + * + * This method will determine whether the passed entity needs to be + * inserted or updated in the database. It does that by checking the `isNew` + * method on the entity. If the entity to be saved returns a non-empty value from + * its `errors()` method, it will not be saved. + * + * ### Saving on associated tables + * + * This method will by default persist entities belonging to associated tables, + * whenever a dirty property matching the name of the property name set for an + * association in this table. It is possible to control what associations will + * be saved and to pass additional option for saving them. + * + * {{{ + * // Only save the comments association + * $articles->save($entity, ['associated' => ['Comments']); + * + * // Save the company, the employees and related addresses for each of them. + * // For employees do not check the entity rules + * $companies->save($entity, [ + * 'associated' => [ + * 'Employees' => [ + * 'associated' => ['Addresses'], + * 'checkRules' => false + * ] + * ] + * ]); + * + * // Save no associations + * $articles->save($entity, ['associated' => false]); + * }}} + * + */ + public function save(EntityInterface $entity, $options = []) + { + $options = new ArrayObject($options + [ + 'atomic' => true, + 'associated' => true, + 'checkRules' => true + ]); + + if ($entity->errors()) { + return false; + } + + if ($entity->isNew() === false && !$entity->dirty()) { + return $entity; + } + + if ($options['atomic']) { + $connection = $this->connection(); + $success = $connection->transactional(function () use ($entity, $options) { + return $this->_processSave($entity, $options); + }); + } else { + $success = $this->_processSave($entity, $options); + } + + return $success; + } + + /** + * Performs the actual saving of an entity based on the passed options. + * + * @param \Cake\Datasource\EntityInterface $entity the entity to be saved + * @param array $options the options to use for the save operation + * @return \Cake\Datasource\EntityInterface|bool + * @throws \RuntimeException When an entity is missing some of the primary keys. + */ + protected function _processSave($entity, $options) + { + $primaryColumns = (array)$this->primaryKey(); + + if ($primaryColumns && $entity->isNew() && $entity->has($primaryColumns)) { + $alias = $this->alias(); + $conditions = []; + foreach ($entity->extract($primaryColumns) as $k => $v) { + $conditions["$alias.$k"] = $v; + } + $entity->isNew(!$this->exists($conditions)); + } + + $mode = $entity->isNew() ? RulesChecker::CREATE : RulesChecker::UPDATE; + if ($options['checkRules'] && !$this->checkRules($entity, $mode, $options)) { + return false; + } + + $options['associated'] = $this->_associations->normalizeKeys($options['associated']); + $event = $this->dispatchEvent('Model.beforeSave', compact('entity', 'options')); + + if ($event->isStopped()) { + return $event->result; + } + + $saved = $this->_associations->saveParents( + $this, + $entity, + $options['associated'], + $options->getArrayCopy() + ); + + if (!$saved && $options['atomic']) { + return false; + } + + $data = $entity->extract($this->schema()->columns(), true); + $isNew = $entity->isNew(); + + if ($isNew) { + $success = $this->_insert($entity, $data); + } else { + $success = $this->_update($entity, $data); + } + + if ($success) { + $success = $this->_associations->saveChildren( + $this, + $entity, + $options['associated'], + $options->getArrayCopy() + ); + if ($success || !$options['atomic']) { + $entity->clean(); + $this->dispatchEvent('Model.afterSave', compact('entity', 'options')); + $entity->isNew(false); + $entity->source($this->alias()); + $success = true; + } + } + + if (!$success && $isNew) { + $entity->unsetProperty($this->primaryKey()); + $entity->isNew(true); + } + if ($success) { + return $entity; + } + return false; + } + + /** + * Auxiliary function to handle the insert of an entity's data in the table + * + * @param \Cake\Datasource\EntityInterface $entity the subject entity from were $data was extracted + * @param array $data The actual data that needs to be saved + * @return \Cake\Datasource\EntityInterface|bool + * @throws \RuntimeException if not all the primary keys where supplied or could + * be generated when the table has composite primary keys. Or when the table has no primary key. + */ + protected function _insert($entity, $data) + { + $primary = (array)$this->primaryKey(); + if (empty($primary)) { + $msg = sprintf( + 'Cannot insert row in "%s" table, it has no primary key.', + $this->table() + ); + throw new \RuntimeException($msg); + } + $keys = array_fill(0, count($primary), null); + $id = (array)$this->_newId($primary) + $keys; + $primary = array_combine($primary, $id); + $filteredKeys = array_filter($primary, 'strlen'); + $data = $filteredKeys + $data; + + if (count($primary) > 1) { + foreach ($primary as $k => $v) { + if (!isset($data[$k])) { + $msg = 'Cannot insert row, some of the primary key values are missing. '; + $msg .= sprintf( + 'Got (%s), expecting (%s)', + implode(', ', $filteredKeys + $entity->extract(array_keys($primary))), + implode(', ', array_keys($primary)) + ); + throw new \RuntimeException($msg); + } + } + } + + $success = false; + if (empty($data)) { + return $success; + } + + $statement = $this->query()->insert(array_keys($data)) + ->values($data) + ->execute(); + + if ($statement->rowCount() !== 0) { + $success = $entity; + $entity->set($filteredKeys, ['guard' => false]); + foreach ($primary as $key => $v) { + if (!isset($data[$key])) { + $id = $statement->lastInsertId($this->table(), $key); + $entity->set($key, $id); + break; + } + } + } + $statement->closeCursor(); + return $success; + } + + /** + * Generate a primary key value for a new record. + * + * By default, this uses the type system to generate a new primary key + * value if possible. You can override this method if you have specific requirements + * for id generation. + * + * @param array $primary The primary key columns to get a new ID for. + * @return mixed Either null or the new primary key value. + */ + protected function _newId($primary) + { + if (!$primary || count((array)$primary) > 1) { + return null; + } + $typeName = $this->schema()->columnType($primary[0]); + $type = Type::build($typeName); + return $type->newId(); + } + + /** + * Auxiliary function to handle the update of an entity's data in the table + * + * @param \Cake\Datasource\EntityInterface $entity the subject entity from were $data was extracted + * @param array $data The actual data that needs to be saved + * @return \Cake\Datasource\EntityInterface|bool + * @throws \InvalidArgumentException When primary key data is missing. + */ + protected function _update($entity, $data) + { + $primaryColumns = (array)$this->primaryKey(); + $primaryKey = $entity->extract($primaryColumns); + + $data = array_diff_key($data, $primaryKey); + if (empty($data)) { + return $entity; + } + + if (!$entity->has($primaryColumns)) { + $message = 'All primary key value(s) are needed for updating'; + throw new \InvalidArgumentException($message); + } + + $query = $this->query(); + $statement = $query->update() + ->set($data) + ->where($primaryKey) + ->execute(); + + $success = false; + if ($statement->errorCode() === '00000') { + $success = $entity; + } + $statement->closeCursor(); + return $success; + } + + /** + * {@inheritDoc} + * + * For HasMany and HasOne associations records will be removed based on + * the dependent option. Join table records in BelongsToMany associations + * will always be removed. You can use the `cascadeCallbacks` option + * when defining associations to change how associated data is deleted. + * + * ### Options + * + * - `atomic` Defaults to true. When true the deletion happens within a transaction. + * - `checkRules` Defaults to true. Check deletion rules before deleting the record. + * + * ### Events + * + * - `beforeDelete` Fired before the delete occurs. If stopped the delete + * will be aborted. Receives the event, entity, and options. + * - `afterDelete` Fired after the delete has been successful. Receives + * the event, entity, and options. + * + * The options argument will be converted into an \ArrayObject instance + * for the duration of the callbacks, this allows listeners to modify + * the options used in the delete operation. + * + */ + public function delete(EntityInterface $entity, $options = []) + { + $options = new ArrayObject($options + ['atomic' => true, 'checkRules' => true]); + + $process = function () use ($entity, $options) { + return $this->_processDelete($entity, $options); + }; + + if ($options['atomic']) { + return $this->connection()->transactional($process); + } + return $process(); + } + + /** + * Perform the delete operation. + * + * Will delete the entity provided. Will remove rows from any + * dependent associations, and clear out join tables for BelongsToMany associations. + * + * @param \Cake\DataSource\EntityInterface $entity The entity to delete. + * @param \ArrayObject $options The options for the delete. + * @throws \InvalidArgumentException if there are no primary key values of the + * passed entity + * @return bool success + */ + protected function _processDelete($entity, $options) + { + if ($entity->isNew()) { + return false; + } + + $primaryKey = (array)$this->primaryKey(); + if (!$entity->has($primaryKey)) { + $msg = 'Deleting requires all primary key values.'; + throw new \InvalidArgumentException($msg); + } + + if ($options['checkRules'] && !$this->checkRules($entity, RulesChecker::DELETE, $options)) { + return false; + } + + $event = $this->dispatchEvent('Model.beforeDelete', [ + 'entity' => $entity, + 'options' => $options + ]); + + if ($event->isStopped()) { + return $event->result; + } + + $this->_associations->cascadeDelete($entity, $options->getArrayCopy()); + + $query = $this->query(); + $conditions = (array)$entity->extract($primaryKey); + $statement = $query->delete() + ->where($conditions) + ->execute(); + + $success = $statement->rowCount() > 0; + if (!$success) { + return $success; + } + + $this->dispatchEvent('Model.afterDelete', [ + 'entity' => $entity, + 'options' => $options + ]); + + return $success; + } + + /** + * Returns true if the finder exists for the table + * + * @param string $type name of finder to check + * + * @return bool + */ + public function hasFinder($type) + { + $finder = 'find' . $type; + + return method_exists($this, $finder) || ($this->_behaviors && $this->_behaviors->hasFinder($type)); + } + + /** + * Calls a finder method directly and applies it to the passed query, + * if no query is passed a new one will be created and returned + * + * @param string $type name of the finder to be called + * @param \Cake\ORM\Query $query The query object to apply the finder options to + * @param array $options List of options to pass to the finder + * @return \Cake\ORM\Query + * @throws \BadMethodCallException + */ + public function callFinder($type, Query $query, array $options = []) + { + $query->applyOptions($options); + $options = $query->getOptions(); + $finder = 'find' . $type; + if (method_exists($this, $finder)) { + return $this->{$finder}($query, $options); + } + + if ($this->_behaviors && $this->_behaviors->hasFinder($type)) { + return $this->_behaviors->callFinder($type, [$query, $options]); + } + + throw new \BadMethodCallException( + sprintf('Unknown finder method "%s"', $type) + ); + } + + /** + * Provides the dynamic findBy and findByAll methods. + * + * @param string $method The method name that was fired. + * @param array $args List of arguments passed to the function. + * @return mixed + * @throws \BadMethodCallException when there are missing arguments, or when + * and & or are combined. + */ + protected function _dynamicFinder($method, $args) + { + $method = Inflector::underscore($method); + preg_match('/^find_([\w]+)_by_/', $method, $matches); + if (empty($matches)) { + // find_by_ is 8 characters. + $fields = substr($method, 8); + $findType = 'all'; + } else { + $fields = substr($method, strlen($matches[0])); + $findType = Inflector::variable($matches[1]); + } + $hasOr = strpos($fields, '_or_'); + $hasAnd = strpos($fields, '_and_'); + + $makeConditions = function ($fields, $args) { + $conditions = []; + if (count($args) < count($fields)) { + throw new BadMethodCallException(sprintf( + 'Not enough arguments for magic finder. Got %s required %s', + count($args), + count($fields) + )); + } + foreach ($fields as $field) { + $conditions[$field] = array_shift($args); + } + return $conditions; + }; + + if ($hasOr !== false && $hasAnd !== false) { + throw new BadMethodCallException( + 'Cannot mix "and" & "or" in a magic finder. Use find() instead.' + ); + } + + if ($hasOr === false && $hasAnd === false) { + $conditions = $makeConditions([$fields], $args); + } elseif ($hasOr !== false) { + $fields = explode('_or_', $fields); + $conditions = [ + 'OR' => $makeConditions($fields, $args) + ]; + } elseif ($hasAnd !== false) { + $fields = explode('_and_', $fields); + $conditions = $makeConditions($fields, $args); + } + + return $this->find($findType, [ + 'conditions' => $conditions, + ]); + } + + /** + * Handles behavior delegation + dynamic finders. + * + * If your Table uses any behaviors you can call them as if + * they were on the table object. + * + * @param string $method name of the method to be invoked + * @param array $args List of arguments passed to the function + * @return mixed + * @throws \BadMethodCallException + */ + public function __call($method, $args) + { + if ($this->_behaviors && $this->_behaviors->hasMethod($method)) { + return $this->_behaviors->call($method, $args); + } + if (preg_match('/^find(?:\w+)?By/', $method) > 0) { + return $this->_dynamicFinder($method, $args); + } + + throw new \BadMethodCallException( + sprintf('Unknown method "%s"', $method) + ); + } + + /** + * Returns the association named after the passed value if exists, otherwise + * throws an exception. + * + * @param string $property the association name + * @return \Cake\ORM\Association + * @throws \RuntimeException if no association with such name exists + */ + public function __get($property) + { + $association = $this->_associations->get($property); + if (!$association) { + throw new \RuntimeException(sprintf( + 'Table "%s" is not associated with "%s"', + get_class($this), + $property + )); + } + return $association; + } + + /** + * Returns whether an association named after the passed value + * exists for this table. + * + * @param string $property the association name + * @return bool + */ + public function __isset($property) + { + return $this->_associations->has($property); + } + + /** + * Get the object used to marshal/convert array data into objects. + * + * Override this method if you want a table object to use custom + * marshalling logic. + * + * @return \Cake\ORM\Marshaller + * @see \Cake\ORM\Marshaller + */ + public function marshaller() + { + return new Marshaller($this); + } + + /** + * {@inheritDoc} + * + * By default all the associations on this table will be hydrated. You can + * limit which associations are built, or include deeper associations + * using the options parameter: + * + * {{{ + * $article = $this->Articles->newEntity( + * $this->request->data(), + * ['associated' => ['Tags', 'Comments.Users']] + * ); + * }}} + * + * You can limit fields that will be present in the constructed entity by + * passing the `fieldList` option, which is also accepted for associations: + * + * {{{ + * $article = $this->Articles->newEntity($this->request->data(), [ + * 'fieldList' => ['title', 'body'], + * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']] + * ] + * ); + * }}} + * + * The `fieldList` option lets remove or restrict input data from ending up in + * the entity. If you'd like to relax the entity's default accessible fields, + * you can use the `accessibleFields` option: + * + * {{{ + * $article = $this->Articles->newEntity( + * $this->request->data(), + * ['accessibleFields' => ['protected_field' => true]] + * ); + * }}} + * + * By default, the data is validated before being passed to the new entity. In + * the case of invalid fields, those will not be present in the resulting object. + * The `validate` option can be used to disable validation on the passed data: + * + * {{{ + * $article = $this->Articles->newEntity( + * $this->request->data(), + * ['validate' => false] + * ); + * }}} + * + * You can also pass the name of the validator to use in the `validate` option. + * If `null` is passed to the first param of this function, no validation will + * be performed. + */ + public function newEntity($data = null, array $options = []) + { + if ($data === null) { + $class = $this->entityClass(); + return new $class; + } + if (!isset($options['associated'])) { + $options['associated'] = $this->_associations->keys(); + } + $marshaller = $this->marshaller(); + return $marshaller->one($data, $options); + } + + /** + * {@inheritDoc} + * + * By default all the associations on this table will be hydrated. You can + * limit which associations are built, or include deeper associations + * using the options parameter: + * + * {{{ + * $articles = $this->Articles->newEntities( + * $this->request->data(), + * ['associated' => ['Tags', 'Comments.Users']] + * ); + * }}} + * + * You can limit fields that will be present in the constructed entities by + * passing the `fieldList` option, which is also accepted for associations: + * + * {{{ + * $articles = $this->Articles->newEntities($this->request->data(), [ + * 'fieldList' => ['title', 'body'], + * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']] + * ] + * ); + * }}} + * + */ + public function newEntities(array $data, array $options = []) + { + if (!isset($options['associated'])) { + $options['associated'] = $this->_associations->keys(); + } + $marshaller = $this->marshaller(); + return $marshaller->many($data, $options); + } + + /** + * {@inheritDoc} + * + * When merging HasMany or BelongsToMany associations, all the entities in the + * `$data` array will appear, those that can be matched by primary key will get + * the data merged, but those that cannot, will be discarded. + * + * You can limit fields that will be present in the merged entity by + * passing the `fieldList` option, which is also accepted for associations: + * + * {{{ + * $articles = $this->Articles->patchEntity($article, $this->request->data(), [ + * 'fieldList' => ['title', 'body'], + * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']] + * ] + * ); + * }}} + * + * By default, the data is validated before being passed to the entity. In + * the case of invalid fields, those will not be assigned to the entity. + * The `validate` option can be used to disable validation on the passed data: + * + * {{{ + * $article = $this->patchEntity($article, $this->request->data(),[ + * 'validate' => false + * ]); + * }}} + */ + public function patchEntity(EntityInterface $entity, array $data, array $options = []) + { + if (!isset($options['associated'])) { + $options['associated'] = $this->_associations->keys(); + } + $marshaller = $this->marshaller(); + return $marshaller->merge($entity, $data, $options); + } + + /** + * {@inheritDoc} + * + * Those entries in `$entities` that cannot be matched to any record in + * `$data` will be discarded. Records in `$data` that could not be matched will + * be marshalled as a new entity. + * + * When merging HasMany or BelongsToMany associations, all the entities in the + * `$data` array will appear, those that can be matched by primary key will get + * the data merged, but those that cannot, will be discarded. + * + * You can limit fields that will be present in the merged entities by + * passing the `fieldList` option, which is also accepted for associations: + * + * {{{ + * $articles = $this->Articles->patchEntities($articles, $this->request->data(), [ + * 'fieldList' => ['title', 'body'], + * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']] + * ] + * ); + * }}} + */ + public function patchEntities($entities, array $data, array $options = []) + { + if (!isset($options['associated'])) { + $options['associated'] = $this->_associations->keys(); + } + $marshaller = $this->marshaller(); + return $marshaller->mergeMany($entities, $data, $options); + } + + /** + * Validator method used to check the uniqueness of a value for a column. + * This is meant to be used with the validation API and not to be called + * directly. + * + * ### Example: + * + * {{{ + * $validator->add('email', [ + * 'unique' => ['rule' => 'validateUnique', 'provider' => 'table'] + * ]) + * }}} + * + * Unique validation can be scoped to the value of another column: + * + * {{{ + * $validator->add('email', [ + * 'unique' => [ + * 'rule' => ['validateUnique', ['scope' => 'site_id']], + * 'provider' => 'table' + * ] + * ]); + * }}} + * + * In the above example, the email uniqueness will be scoped to only rows having + * the same site_id. Scoping will only be used if the scoping field is present in + * the data to be validated. + * + * @param mixed $value The value of column to be checked for uniqueness + * @param array $options The options array, optionally containing the 'scope' key + * @return bool true if the value is unique + */ + public function validateUnique($value, array $options) + { + $entity = new Entity( + $options['data'], + ['useSetters' => false, 'markNew' => $options['newRecord']] + ); + $fields = array_merge( + [$options['field']], + isset($options['scope']) ? (array)$options['scope'] : [] + ); + $rule = new IsUnique($fields); + return $rule($entity, ['repository' => $this]); + } + + /** + * Returns whether or not the passed entity complies with all the rules stored in + * the rules checker. + * + * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. + * @param string $operation The operation being run. Either 'create', 'update' or 'delete'. + * @param \ArrayObject|array $options The options To be passed to the rules. + * @return bool + */ + public function checkRules(EntityInterface $entity, $operation = RulesChecker::CREATE, $options = null) + { + $rules = $this->rulesChecker(); + $options = $options ?: new ArrayObject; + $options = is_array($options) ? new ArrayObject($options) : $options; + + $event = $this->dispatchEvent( + 'Model.beforeRules', + compact('entity', 'options', 'operation') + ); + + if ($event->isStopped()) { + return $event->result; + } + + $result = $rules->check($entity, $operation, $options->getArrayCopy()); + $event = $this->dispatchEvent( + 'Model.afterRules', + compact('entity', 'options', 'result', 'operation') + ); + + if ($event->isStopped()) { + return $event->result; + } + + return $result; + } + + /** + * Returns the rule checker for this table. A rules checker object is used to + * test an entity for validity on rules that may involve complex logic or data that + * needs to be fetched from the database or other sources. + * + * @return \Cake\ORM\RulesChecker + */ + public function rulesChecker() + { + if ($this->_rulesChecker !== null) { + return $this->_rulesChecker; + } + $this->_rulesChecker = $this->buildRules(new RulesChecker(['repository' => $this])); + $this->dispatchEvent('Model.buildRules', ['rules' => $this->_rulesChecker]); + return $this->_rulesChecker; + } + + /** + * Returns rules checker object after modifying the one that was passed. Subclasses + * can override this method in order to initialize the rules to be applied to + * entities saved by this table. + * + * @param \Cake\ORM\RulesChecker $rules The rules object to be modified. + * @return \Cake\ORM\RulesChecker + */ + public function buildRules(RulesChecker $rules) + { + return $rules; + } + + /** + * Get the Model callbacks this table is interested in. + * + * By implementing the conventional methods a table class is assumed + * to be interested in the related event. + * + * Override this method if you need to add non-conventional event listeners. + * Or if you want you table to listen to non-standard events. + * + * @return array + */ + public function implementedEvents() + { + $eventMap = [ + 'Model.beforeFind' => 'beforeFind', + 'Model.beforeSave' => 'beforeSave', + 'Model.afterSave' => 'afterSave', + 'Model.beforeDelete' => 'beforeDelete', + 'Model.afterDelete' => 'afterDelete', + 'Model.beforeRules' => 'beforeRules', + 'Model.afterRules' => 'afterRules', + ]; + $events = []; + + foreach ($eventMap as $event => $method) { + if (!method_exists($this, $method)) { + continue; + } + $events[$event] = $method; + } + return $events; + } + + /** + * Returns an array that can be used to describe the internal state of this + * object. + * + * @return array + */ + public function __debugInfo() + { + $conn = $this->connection(); + return [ + 'table' => $this->table(), + 'alias' => $this->alias(), + 'entityClass' => $this->entityClass(), + 'associations' => $this->_associations->keys(), + 'behaviors' => $this->_behaviors->loaded(), + 'defaultConnection' => $this->defaultConnectionName(), + 'connectionName' => $conn ? $conn->configName() : null + ]; + } } diff --git a/TableRegistry.php b/TableRegistry.php index 978b1ce5..585310ea 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -51,214 +51,221 @@ * }}} * */ -class TableRegistry { +class TableRegistry +{ -/** - * Configuration for aliases. - * - * @var array - */ - protected static $_config = []; + /** + * Configuration for aliases. + * + * @var array + */ + protected static $_config = []; -/** - * Instances that belong to the registry. - * - * @var array - */ - protected static $_instances = []; + /** + * Instances that belong to the registry. + * + * @var array + */ + protected static $_instances = []; -/** - * Contains a list of Table objects that were created out of the - * built-in Table class. The list is indexed by table alias - * - * @var array - */ - protected static $_fallbacked = []; - -/** - * Contains a list of options that were passed to get() method. - * - * @var array - */ - protected static $_options = []; + /** + * Contains a list of Table objects that were created out of the + * built-in Table class. The list is indexed by table alias + * + * @var array + */ + protected static $_fallbacked = []; -/** - * Stores a list of options to be used when instantiating an object - * with a matching alias. - * - * The options that can be stored are those that are recognized by `get()` - * If second argument is omitted, it will return the current settings - * for $alias. - * - * If no arguments are passed it will return the full configuration array for - * all aliases - * - * @param string|null $alias Name of the alias - * @param array|null $options list of options for the alias - * @return array The config data. - * @throws RuntimeException When you attempt to configure an existing table instance. - */ - public static function config($alias = null, $options = null) { - list(, $alias) = pluginSplit($alias); - if ($alias === null) { - return static::$_config; - } - if (!is_string($alias)) { - return static::$_config = $alias; - } - if ($options === null) { - return isset(static::$_config[$alias]) ? static::$_config[$alias] : []; - } - if (isset(static::$_instances[$alias])) { - throw new RuntimeException(sprintf( - 'You cannot configure "%s", it has already been constructed.', - $alias - )); - } - return static::$_config[$alias] = $options; - } + /** + * Contains a list of options that were passed to get() method. + * + * @var array + */ + protected static $_options = []; -/** - * Get a table instance from the registry. - * - * Tables are only created once until the registry is flushed. - * This means that aliases must be unique across your application. - * This is important because table associations are resolved at runtime - * and cyclic references need to be handled correctly. - * - * The options that can be passed are the same as in `Table::__construct()`, but the - * key `className` is also recognized. - * - * If $options does not contain `className` CakePHP will attempt to construct the - * class name based on the alias. For example 'Users' would result in - * `App\Model\Table\UsersTable` being attempted. If this class does not exist, - * then the default `Cake\ORM\Table` class will be used. By setting the `className` - * option you can define the specific class to use. This className can - * use a plugin short class reference. - * - * If you use a `$name` that uses plugin syntax only the name part will be used as - * key in the registry. This means that if two plugins, or a plugin and app provide - * the same alias, the registry will only store the first instance. - * - * If no `table` option is passed, the table name will be the underscored version - * of the provided $alias. - * - * If no `connection` option is passed the table's defaultConnectionName() method - * will be called to get the default connection name to use. - * - * @param string $name The alias name you want to get. - * @param array $options The options you want to build the table with. - * If a table has already been loaded the options will be ignored. - * @return \Cake\ORM\Table - * @throws RuntimeException When you try to configure an alias that already exists. - */ - public static function get($name, array $options = []) { - list(, $alias) = pluginSplit($name); - $exists = isset(static::$_instances[$alias]); + /** + * Stores a list of options to be used when instantiating an object + * with a matching alias. + * + * The options that can be stored are those that are recognized by `get()` + * If second argument is omitted, it will return the current settings + * for $alias. + * + * If no arguments are passed it will return the full configuration array for + * all aliases + * + * @param string|null $alias Name of the alias + * @param array|null $options list of options for the alias + * @return array The config data. + * @throws RuntimeException When you attempt to configure an existing table instance. + */ + public static function config($alias = null, $options = null) + { + list(, $alias) = pluginSplit($alias); + if ($alias === null) { + return static::$_config; + } + if (!is_string($alias)) { + return static::$_config = $alias; + } + if ($options === null) { + return isset(static::$_config[$alias]) ? static::$_config[$alias] : []; + } + if (isset(static::$_instances[$alias])) { + throw new RuntimeException(sprintf( + 'You cannot configure "%s", it has already been constructed.', + $alias + )); + } + return static::$_config[$alias] = $options; + } - if ($exists && !empty($options)) { - if (static::$_options[$alias] !== $options) { - throw new RuntimeException(sprintf( - 'You cannot configure "%s", it already exists in the registry.', - $alias - )); - } - } - if ($exists) { - return static::$_instances[$alias]; - } - static::$_options[$alias] = $options; - $options = ['alias' => $alias] + $options; + /** + * Get a table instance from the registry. + * + * Tables are only created once until the registry is flushed. + * This means that aliases must be unique across your application. + * This is important because table associations are resolved at runtime + * and cyclic references need to be handled correctly. + * + * The options that can be passed are the same as in `Table::__construct()`, but the + * key `className` is also recognized. + * + * If $options does not contain `className` CakePHP will attempt to construct the + * class name based on the alias. For example 'Users' would result in + * `App\Model\Table\UsersTable` being attempted. If this class does not exist, + * then the default `Cake\ORM\Table` class will be used. By setting the `className` + * option you can define the specific class to use. This className can + * use a plugin short class reference. + * + * If you use a `$name` that uses plugin syntax only the name part will be used as + * key in the registry. This means that if two plugins, or a plugin and app provide + * the same alias, the registry will only store the first instance. + * + * If no `table` option is passed, the table name will be the underscored version + * of the provided $alias. + * + * If no `connection` option is passed the table's defaultConnectionName() method + * will be called to get the default connection name to use. + * + * @param string $name The alias name you want to get. + * @param array $options The options you want to build the table with. + * If a table has already been loaded the options will be ignored. + * @return \Cake\ORM\Table + * @throws RuntimeException When you try to configure an alias that already exists. + */ + public static function get($name, array $options = []) + { + list(, $alias) = pluginSplit($name); + $exists = isset(static::$_instances[$alias]); - if (empty($options['className'])) { - $options['className'] = Inflector::camelize($name); - } - $className = App::className($options['className'], 'Model/Table', 'Table'); - $options['className'] = $className ?: 'Cake\ORM\Table'; + if ($exists && !empty($options)) { + if (static::$_options[$alias] !== $options) { + throw new RuntimeException(sprintf( + 'You cannot configure "%s", it already exists in the registry.', + $alias + )); + } + } + if ($exists) { + return static::$_instances[$alias]; + } + static::$_options[$alias] = $options; + $options = ['alias' => $alias] + $options; - if (isset(static::$_config[$alias])) { - $options += static::$_config[$alias]; - } - if (empty($options['connection'])) { - $connectionName = $options['className']::defaultConnectionName(); - $options['connection'] = ConnectionManager::get($connectionName); - } + if (empty($options['className'])) { + $options['className'] = Inflector::camelize($name); + } + $className = App::className($options['className'], 'Model/Table', 'Table'); + $options['className'] = $className ?: 'Cake\ORM\Table'; - static::$_instances[$alias] = new $options['className']($options); + if (isset(static::$_config[$alias])) { + $options += static::$_config[$alias]; + } + if (empty($options['connection'])) { + $connectionName = $options['className']::defaultConnectionName(); + $options['connection'] = ConnectionManager::get($connectionName); + } - if ($options['className'] === 'Cake\ORM\Table') { - static::$_fallbacked[$alias] = static::$_instances[$alias]; - } + static::$_instances[$alias] = new $options['className']($options); - return static::$_instances[$alias]; - } + if ($options['className'] === 'Cake\ORM\Table') { + static::$_fallbacked[$alias] = static::$_instances[$alias]; + } -/** - * Check to see if an instance exists in the registry. - * - * Plugin names will be trimmed off of aliases as instances - * stored in the registry will be without the plugin name as well. - * - * @param string $alias The alias to check for. - * @return bool - */ - public static function exists($alias) { - list(, $alias) = pluginSplit($alias); - return isset(static::$_instances[$alias]); - } + return static::$_instances[$alias]; + } -/** - * Set an instance. - * - * @param string $alias The alias to set. - * @param \Cake\ORM\Table $object The table to set. - * @return \Cake\ORM\Table - */ - public static function set($alias, Table $object) { - list(, $alias) = pluginSplit($alias); - return static::$_instances[$alias] = $object; - } + /** + * Check to see if an instance exists in the registry. + * + * Plugin names will be trimmed off of aliases as instances + * stored in the registry will be without the plugin name as well. + * + * @param string $alias The alias to check for. + * @return bool + */ + public static function exists($alias) + { + list(, $alias) = pluginSplit($alias); + return isset(static::$_instances[$alias]); + } -/** - * Clears the registry of configuration and instances. - * - * @return void - */ - public static function clear() { - static::$_instances = []; - static::$_config = []; - static::$_fallbacked = []; - } + /** + * Set an instance. + * + * @param string $alias The alias to set. + * @param \Cake\ORM\Table $object The table to set. + * @return \Cake\ORM\Table + */ + public static function set($alias, Table $object) + { + list(, $alias) = pluginSplit($alias); + return static::$_instances[$alias] = $object; + } -/** - * Returns the list of tables that were created by this registry that could - * not be instantiated from a specific subclass. This method is useful for - * debugging common mistakes when setting up associations or created new table - * classes. - * - * @return array - */ - public static function genericInstances() { - return static::$_fallbacked; - } + /** + * Clears the registry of configuration and instances. + * + * @return void + */ + public static function clear() + { + static::$_instances = []; + static::$_config = []; + static::$_fallbacked = []; + } -/** - * Removes an instance from the registry. - * - * Plugin name will be trimmed off of aliases as instances - * stored in the registry will be without the plugin name as well. - * - * @param string $alias The alias to remove. - * @return void - */ - public static function remove($alias) { - list(, $alias) = pluginSplit($alias); + /** + * Returns the list of tables that were created by this registry that could + * not be instantiated from a specific subclass. This method is useful for + * debugging common mistakes when setting up associations or created new table + * classes. + * + * @return array + */ + public static function genericInstances() + { + return static::$_fallbacked; + } - unset( - static::$_instances[$alias], - static::$_config[$alias], - static::$_fallbacked[$alias] - ); - } + /** + * Removes an instance from the registry. + * + * Plugin name will be trimmed off of aliases as instances + * stored in the registry will be without the plugin name as well. + * + * @param string $alias The alias to remove. + * @return void + */ + public static function remove($alias) + { + list(, $alias) = pluginSplit($alias); + unset( + static::$_instances[$alias], + static::$_config[$alias], + static::$_fallbacked[$alias] + ); + } } From 4a824cb8139051c790fac899af404f3d45b954d8 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Thu, 1 Jan 2015 16:06:50 +0100 Subject: [PATCH 0107/2059] Moved the Model namespace into ORM so it can be distributed easier Also moved ModelAwareTrait to Datasource it I think it makes more sense there --- Behavior/CounterCacheBehavior.php | 195 +++++++ Behavior/TimestampBehavior.php | 198 +++++++ Behavior/Translate/TranslateTrait.php | 62 ++ Behavior/TranslateBehavior.php | 478 ++++++++++++++++ Behavior/TreeBehavior.php | 796 ++++++++++++++++++++++++++ BehaviorRegistry.php | 6 +- 6 files changed, 1734 insertions(+), 1 deletion(-) create mode 100644 Behavior/CounterCacheBehavior.php create mode 100644 Behavior/TimestampBehavior.php create mode 100644 Behavior/Translate/TranslateTrait.php create mode 100644 Behavior/TranslateBehavior.php create mode 100644 Behavior/TreeBehavior.php diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php new file mode 100644 index 00000000..0458bd0c --- /dev/null +++ b/Behavior/CounterCacheBehavior.php @@ -0,0 +1,195 @@ + [ + * 'post_count' + * ] + * ] + * }}} + * + * Counter cache with scope + * {{{ + * [ + * 'Users' => [ + * 'posts_published' => [ + * 'conditions' => [ + * 'published' => true + * ] + * ] + * ] + * ] + * }}} + * + * Counter cache using custom find + * {{{ + * [ + * 'Users' => [ + * 'posts_published' => [ + * 'finder' => 'published' // Will be using findPublished() + * ] + * ] + * ] + * }}} + * + * Counter cache using lambda function returning the count + * This is equivalent to example #2 + * {{{ + * [ + * 'Users' => [ + * 'posts_published' => function (Event $event, Entity $entity, Table $table) { + * $query = $table->find('all')->where([ + * 'published' => true, + * 'user_id' => $entity->get('user_id') + * ]); + * return $query->count(); + * } + * ] + * ] + * }}} + * + */ +class CounterCacheBehavior extends Behavior +{ + + /** + * afterSave callback. + * + * Makes sure to update counter cache when a new record is created or updated. + * + * @param \Cake\Event\Event $event The afterSave event that was fired. + * @param \Cake\ORM\Entity $entity The entity that was saved. + * @return void + */ + public function afterSave(Event $event, Entity $entity) + { + $this->_processAssociations($event, $entity); + } + + /** + * afterDelete callback. + * + * Makes sure to update counter cache when a record is deleted. + * + * @param \Cake\Event\Event $event The afterDelete event that was fired. + * @param \Cake\ORM\Entity $entity The entity that was deleted. + * @return void + */ + public function afterDelete(Event $event, Entity $entity) + { + $this->_processAssociations($event, $entity); + } + + /** + * Iterate all associations and update counter caches. + * + * @param \Cake\Event\Event $event Event instance. + * @param \Cake\ORM\Entity $entity Entity. + * @return void + */ + protected function _processAssociations(Event $event, Entity $entity) + { + foreach ($this->_config as $assoc => $settings) { + $assoc = $this->_table->association($assoc); + $this->_processAssociation($event, $entity, $assoc, $settings); + } + } + + /** + * Updates counter cache for a single association + * + * @param \Cake\Event\Event $event Event instance. + * @param \Cake\ORM\Entity $entity Entity + * @param Association $assoc The association object + * @param array $settings The settings for for counter cache for this association + * @return void + */ + protected function _processAssociation(Event $event, Entity $entity, Association $assoc, array $settings) + { + $foreignKeys = (array)$assoc->foreignKey(); + $primaryKeys = (array)$assoc->target()->primaryKey(); + $countConditions = $entity->extract($foreignKeys); + $updateConditions = array_combine($primaryKeys, $countConditions); + + $countOriginalConditions = $entity->extractOriginal($foreignKeys); + if ($countOriginalConditions !== []) { + $updateOriginalConditions = array_combine($primaryKeys, $countOriginalConditions); + } + + foreach ($settings as $field => $config) { + if (is_int($field)) { + $field = $config; + $config = []; + } + + if (!is_string($config) && is_callable($config)) { + $count = $config($event, $entity, $this->_table, false); + } else { + $count = $this->_getCount($config, $countConditions); + } + + $assoc->target()->updateAll([$field => $count], $updateConditions); + + if (isset($updateOriginalConditions)) { + if (!is_string($config) && is_callable($config)) { + $count = $config($event, $entity, $this->_table, true); + } else { + $count = $this->_getCount($config, $countOriginalConditions); + } + $assoc->target()->updateAll([$field => $count], $updateOriginalConditions); + } + } + } + + /** + * Fetches and returns the count for a single field in an association + * + * @param array $config The counter cache configuration for a single field + * @param array $conditions Additional conditions given to the query + * @return int The number of relations matching the given config and conditions + */ + protected function _getCount(array $config, array $conditions) + { + $finder = 'all'; + if (!empty($config['finder'])) { + $finder = $config['finder']; + unset($config['finder']); + } + + if (!isset($config['conditions'])) { + $config['conditions'] = []; + } + $config['conditions'] = array_merge($conditions, $config['conditions']); + $query = $this->_table->find($finder, $config); + + return $query->count(); + } +} diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php new file mode 100644 index 00000000..401cfe5f --- /dev/null +++ b/Behavior/TimestampBehavior.php @@ -0,0 +1,198 @@ + [], + 'implementedMethods' => [ + 'timestamp' => 'timestamp', + 'touch' => 'touch' + ], + 'events' => [ + 'Model.beforeSave' => [ + 'created' => 'new', + 'modified' => 'always' + ] + ], + 'refreshTimestamp' => true + ]; + + /** + * Current timestamp + * + * @var \DateTime + */ + protected $_ts; + + /** + * Initialize hook + * + * If events are specified - do *not* merge them with existing events, + * overwrite the events to listen on + * + * @param array $config The config for this behavior. + * @return void + */ + public function initialize(array $config) + { + if (isset($config['events'])) { + $this->config('events', $config['events'], false); + } + } + + /** + * There is only one event handler, it can be configured to be called for any event + * + * @param \Cake\Event\Event $event Event instance. + * @param \Cake\ORM\Entity $entity Entity instance. + * @throws \UnexpectedValueException if a field's when value is misdefined + * @return true (irrespective of the behavior logic, the save will not be prevented) + * @throws \UnexpectedValueException When the value for an event is not 'always', 'new' or 'existing' + */ + public function handleEvent(Event $event, Entity $entity) + { + $eventName = $event->name(); + $events = $this->_config['events']; + + $new = $entity->isNew() !== false; + $refresh = $this->_config['refreshTimestamp']; + + foreach ($events[$eventName] as $field => $when) { + if (!in_array($when, ['always', 'new', 'existing'])) { + throw new \UnexpectedValueException( + sprintf('When should be one of "always", "new" or "existing". The passed value "%s" is invalid', $when) + ); + } + if ( + $when === 'always' || + ($when === 'new' && $new) || + ($when === 'existing' && !$new) + ) { + $this->_updateField($entity, $field, $refresh); + } + } + + return true; + } + + /** + * implementedEvents + * + * The implemented events of this behavior depend on configuration + * + * @return array + */ + public function implementedEvents() + { + return array_fill_keys(array_keys($this->_config['events']), 'handleEvent'); + } + + /** + * Get or set the timestamp to be used + * + * Set the timestamp to the given DateTime object, or if not passed a new DateTime object + * If an explicit date time is passed, the config option `refreshTimestamp` is + * automatically set to false. + * + * @param \DateTime $ts Timestamp + * @param bool $refreshTimestamp If true timestamp is refreshed. + * @return \Cake\I18n\Time + */ + public function timestamp(\DateTime $ts = null, $refreshTimestamp = false) + { + if ($ts) { + if ($this->_config['refreshTimestamp']) { + $this->_config['refreshTimestamp'] = false; + } + $this->_ts = new Time($ts); + } elseif ($this->_ts === null || $refreshTimestamp) { + $this->_ts = new Time(); + } + + return $this->_ts; + } + + /** + * Touch an entity + * + * Bumps timestamp fields for an entity. For any fields configured to be updated + * "always" or "existing", update the timestamp value. This method will overwrite + * any pre-existing value. + * + * @param \Cake\ORM\Entity $entity Entity instance. + * @param string $eventName Event name. + * @return bool true if a field is updated, false if no action performed + */ + public function touch(Entity $entity, $eventName = 'Model.beforeSave') + { + $events = $this->_config['events']; + if (empty($events[$eventName])) { + return false; + } + + $return = false; + $refresh = $this->_config['refreshTimestamp']; + + foreach ($events[$eventName] as $field => $when) { + if (in_array($when, ['always', 'existing'])) { + $return = true; + $entity->dirty($field, false); + $this->_updateField($entity, $field, $refresh); + } + } + + return $return; + } + + /** + * Update a field, if it hasn't been updated already + * + * @param \Cake\ORM\Entity $entity Entity instance. + * @param string $field Field name + * @param bool $refreshTimestamp Whether to refresh timestamp. + * @return void + */ + protected function _updateField(Entity $entity, $field, $refreshTimestamp) + { + if ($entity->dirty($field)) { + return; + } + $entity->set($field, $this->timestamp(null, $refreshTimestamp)); + } +} diff --git a/Behavior/Translate/TranslateTrait.php b/Behavior/Translate/TranslateTrait.php new file mode 100644 index 00000000..8f97251c --- /dev/null +++ b/Behavior/Translate/TranslateTrait.php @@ -0,0 +1,62 @@ +get('_locale')) { + return $this; + } + + $i18n = $this->get('_translations'); + $created = false; + + if (empty($i18n)) { + $i18n = []; + $created = true; + } + + if ($created || empty($i18n[$language]) || !($i18n[$language] instanceof Entity)) { + $i18n[$language] = new Entity(); + $created = true; + } + + if ($created) { + $this->set('_translations', $i18n); + } + + // Assume the user will modify any of the internal translations, helps with saving + $this->dirty('_translations', true); + return $i18n[$language]; + } +} diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php new file mode 100644 index 00000000..1734633d --- /dev/null +++ b/Behavior/TranslateBehavior.php @@ -0,0 +1,478 @@ + ['translations' => 'findTranslations'], + 'implementedMethods' => ['locale' => 'locale'], + 'fields' => [], + 'translationTable' => 'i18n', + 'defaultLocale' => '', + 'model' => '', + 'onlyTranslated' => false + ]; + + /** + * Constructor + * + * @param \Cake\ORM\Table $table The table this behavior is attached to. + * @param array $config The config for this behavior. + */ + public function __construct(Table $table, array $config = []) + { + $config += ['defaultLocale' => I18n::defaultLocale()]; + parent::__construct($table, $config); + } + + /** + * Initialize hook + * + * @param array $config The config for this behavior. + * @return void + */ + public function initialize(array $config) + { + $this->setupFieldAssociations( + $this->_config['fields'], + $this->_config['translationTable'], + $this->_config['model'] ? $this->_config['model'] : $this->_table->alias() + ); + } + + /** + * Creates the associations between the bound table and every field passed to + * this method. + * + * Additionally it creates a `i18n` HasMany association that will be + * used for fetching all translations for each record in the bound table + * + * @param array $fields list of fields to create associations for + * @param string $table the table name to use for storing each field translation + * @param string $model the model field value + * @return void + */ + public function setupFieldAssociations($fields, $table, $model) + { + $filter = $this->_config['onlyTranslated']; + foreach ($fields as $field) { + $name = $this->_table->alias() . '_' . $field . '_translation'; + $target = TableRegistry::get($name); + $target->table($table); + + $this->_table->hasOne($name, [ + 'targetTable' => $target, + 'foreignKey' => 'foreign_key', + 'joinType' => $filter ? 'INNER' : 'LEFT', + 'conditions' => [ + $name . '.model' => $model, + $name . '.field' => $field, + ], + 'propertyName' => $field . '_translation' + ]); + } + + $this->_table->hasMany($table, [ + 'foreignKey' => 'foreign_key', + 'strategy' => 'subquery', + 'conditions' => ["$table.model" => $model], + 'propertyName' => '_i18n', + 'dependent' => true + ]); + } + + /** + * Callback method that listens to the `beforeFind` event in the bound + * table. It modifies the passed query by eager loading the translated fields + * and adding a formatter to copy the values into the main table records. + * + * @param \Cake\Event\Event $event The beforeFind event that was fired. + * @param \Cake\ORM\Query $query Query + * @param \ArrayObject $options The options for the query + * @return void + */ + public function beforeFind(Event $event, Query $query, $options) + { + $locale = $this->locale(); + + if ($locale === $this->config('defaultLocale')) { + return; + } + + $conditions = function ($field, $locale, $query, $select) { + return function ($q) use ($field, $locale, $query, $select) { + $q->where([$q->repository()->alias() . '.locale' => $locale]); + $alias = $this->_table->alias(); + + if ($query->autoFields() || + in_array($field, $select, true) || + in_array("$alias.$field", $select, true) + ) { + $q->select(['id', 'content']); + } + + return $q; + }; + }; + + $contain = []; + $fields = $this->_config['fields']; + $alias = $this->_table->alias(); + $select = $query->clause('select'); + $changeFilter = isset($options['filterByCurrentLocale']) && + $options['filterByCurrentLocale'] !== $this->_config['onlyTranslated']; + + foreach ($fields as $field) { + $contain[$alias . '_' . $field . '_translation']['queryBuilder'] = $conditions( + $field, + $locale, + $query, + $select + ); + + if ($changeFilter) { + $filter = $options['filterByCurrentLocale'] ? 'INNER' : 'LEFT'; + $contain[$alias . '_' . $field . '_translation']['joinType'] = $filter; + } + } + + $query->contain($contain); + $query->formatResults(function ($results) use ($locale) { + return $this->_rowMapper($results, $locale); + }, $query::PREPEND); + } + + /** + * Modifies the entity before it is saved so that translated fields are persisted + * in the database too. + * + * @param \Cake\Event\Event $event The beforeSave event that was fired + * @param \Cake\ORM\Entity $entity The entity that is going to be saved + * @param \ArrayObject $options the options passed to the save method + * @return void + */ + public function beforeSave(Event $event, Entity $entity, ArrayObject $options) + { + $locale = $entity->get('_locale') ?: $this->locale(); + $table = $this->_config['translationTable']; + $newOptions = [$table => ['validate' => false]]; + $options['associated'] = $newOptions + $options['associated']; + + $this->_bundleTranslatedFields($entity); + $bundled = $entity->get('_i18n') ?: []; + + if ($locale === $this->config('defaultLocale')) { + return; + } + + $values = $entity->extract($this->_config['fields'], true); + $fields = array_keys($values); + $primaryKey = (array)$this->_table->primaryKey(); + $key = $entity->get(current($primaryKey)); + $model = $this->_table->alias(); + + $preexistent = TableRegistry::get($table)->find() + ->select(['id', 'field']) + ->where(['field IN' => $fields, 'locale' => $locale, 'foreign_key' => $key, 'model' => $model]) + ->bufferResults(false) + ->indexBy('field'); + + $modified = []; + foreach ($preexistent as $field => $translation) { + $translation->set('content', $values[$field]); + $modified[$field] = $translation; + } + + $new = array_diff_key($values, $modified); + foreach ($new as $field => $content) { + $new[$field] = new Entity(compact('locale', 'field', 'content', 'model'), [ + 'useSetters' => false, + 'markNew' => true + ]); + } + + $entity->set('_i18n', array_merge($bundled, array_values($modified + $new))); + $entity->set('_locale', $locale, ['setter' => false]); + $entity->dirty('_locale', false); + + foreach ($fields as $field) { + $entity->dirty($field, false); + } + } + + /** + * Unsets the temporary `_i18n` property after the entity has been saved + * + * @param \Cake\Event\Event $event The beforeSave event that was fired + * @param \Cake\ORM\Entity $entity The entity that is going to be saved + * @return void + */ + public function afterSave(Event $event, Entity $entity) + { + $entity->unsetProperty('_i18n'); + } + + /** + * Sets all future finds for the bound table to also fetch translated fields for + * the passed locale. If no value is passed, it returns the currently configured + * locale + * + * @param string|null $locale The locale to use for fetching translated records + * @return string + */ + public function locale($locale = null) + { + if ($locale === null) { + return $this->_locale ?: I18n::locale(); + } + return $this->_locale = (string)$locale; + } + + /** + * Custom finder method used to retrieve all translations for the found records. + * Fetched translations can be filtered by locale by passing the `locales` key + * in the options array. + * + * Translated values will be found for each entity under the property `_translations`, + * containing an array indexed by locale name. + * + * ### Example: + * + * {{{ + * $article = $articles->find('translations', ['locales' => ['eng', 'deu'])->first(); + * $englishTranslatedFields = $article->get('_translations')['eng']; + * }}} + * + * If the `locales` array is not passed, it will bring all translations found + * for each record. + * + * @param \Cake\ORM\Query $query The original query to modify + * @param array $options Options + * @return \Cake\ORM\Query + */ + public function findTranslations(Query $query, array $options) + { + $locales = isset($options['locales']) ? $options['locales'] : []; + $table = $this->_config['translationTable']; + return $query + ->contain([$table => function ($q) use ($locales, $table) { + if ($locales) { + $q->where(["$table.locale IN" => $locales]); + } + return $q; + }]) + ->formatResults([$this, 'groupTranslations'], $query::PREPEND); + } + + /** + * Modifies the results from a table find in order to merge the translated fields + * into each entity for a given locale. + * + * @param \Cake\Datasource\ResultSetInterface $results Results to map. + * @param string $locale Locale string + * @return \Cake\Collection\Collection + */ + protected function _rowMapper($results, $locale) + { + return $results->map(function ($row) use ($locale) { + if ($row === null) { + return $row; + } + $hydrated = !is_array($row); + + foreach ($this->_config['fields'] as $field) { + $name = $field . '_translation'; + $translation = isset($row[$name]) ? $row[$name] : null; + + if ($translation === null || $translation === false) { + unset($row[$name]); + continue; + } + + $content = isset($translation['content']) ? $translation['content'] : null; + if ($content !== null) { + $row[$field] = $content; + } + + unset($row[$name]); + } + + $row['_locale'] = $locale; + if ($hydrated) { + $row->clean(); + } + + return $row; + }); + } + + /** + * Modifies the results from a table find in order to merge full translation records + * into each entity under the `_translations` key + * + * @param \Cake\Datasource\ResultSetInterface $results Results to modify. + * @return \Cake\Collection\Collection + */ + public function groupTranslations($results) + { + return $results->map(function ($row) { + $translations = (array)$row->get('_i18n'); + $grouped = new Collection($translations); + + $result = []; + foreach ($grouped->combine('field', 'content', 'locale') as $locale => $keys) { + $translation = new Entity($keys + ['locale' => $locale], [ + 'markNew' => false, + 'useSetters' => false, + 'markClean' => true + ]); + $result[$locale] = $translation; + } + + $options = ['setter' => false, 'guard' => false]; + $row->set('_translations', $result, $options); + unset($row['_i18n']); + $row->clean(); + return $row; + }); + } + + /** + * Helper method used to generated multiple translated field entities + * out of the data found in the `_translations` property in the passed + * entity. The result will be put into its `_i18n` property + * + * @param \Cake\ORM\Entity $entity Entity + * @return void + */ + protected function _bundleTranslatedFields($entity) + { + $translations = (array)$entity->get('_translations'); + + if (empty($translations) && !$entity->dirty('_translations')) { + return; + } + + $fields = $this->_config['fields']; + $primaryKey = (array)$this->_table->primaryKey(); + $key = $entity->get(current($primaryKey)); + $find = []; + + foreach ($translations as $lang => $translation) { + foreach ($fields as $field) { + if (!$translation->dirty($field)) { + continue; + } + $find[] = ['locale' => $lang, 'field' => $field, 'foreign_key' => $key]; + $contents[] = new Entity(['content' => $translation->get($field)], [ + 'useSetters' => false + ]); + } + } + + if (empty($find)) { + return; + } + + $results = $this->_findExistingTranslations($find); + $alias = $this->_table->alias(); + + foreach ($find as $i => $translation) { + if (!empty($results[$i])) { + $contents[$i]->set('id', $results[$i], ['setter' => false]); + $contents[$i]->isNew(false); + } else { + $translation['model'] = $alias; + $contents[$i]->set($translation, ['setter' => false, 'guard' => false]); + $contents[$i]->isNew(true); + } + } + + $entity->set('_i18n', $contents); + } + + /** + * Returns the ids found for each of the condition arrays passed for the translations + * table. Each records is indexed by the corresponding position to the conditions array + * + * @param array $ruleSet an array of arary of conditions to be used for finding each + * @return array + */ + protected function _findExistingTranslations($ruleSet) + { + $association = $this->_table->association($this->_config['translationTable']); + $query = $association->find() + ->select(['id', 'num' => 0]) + ->where(current($ruleSet)) + ->hydrate(false) + ->bufferResults(false); + + unset($ruleSet[0]); + foreach ($ruleSet as $i => $conditions) { + $q = $association->find() + ->select(['id', 'num' => $i]) + ->where($conditions); + $query->unionAll($q); + } + + return $query->combine('num', 'id')->toArray(); + } +} diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php new file mode 100644 index 00000000..b5c5b8ef --- /dev/null +++ b/Behavior/TreeBehavior.php @@ -0,0 +1,796 @@ + [ + 'path' => 'findPath', + 'children' => 'findChildren', + 'treeList' => 'findTreeList' + ], + 'implementedMethods' => [ + 'childCount' => 'childCount', + 'moveUp' => 'moveUp', + 'moveDown' => 'moveDown', + 'recover' => 'recover', + 'removeFromTree' => 'removeFromTree' + ], + 'parent' => 'parent_id', + 'left' => 'lft', + 'right' => 'rght', + 'scope' => null + ]; + + /** + * Before save listener. + * Transparently manages setting the lft and rght fields if the parent field is + * included in the parameters to be saved. + * + * @param \Cake\Event\Event $event The beforeSave event that was fired + * @param \Cake\ORM\Entity $entity the entity that is going to be saved + * @return void + * @throws \RuntimeException if the parent to set for the node is invalid + */ + public function beforeSave(Event $event, Entity $entity) + { + $isNew = $entity->isNew(); + $config = $this->config(); + $parent = $entity->get($config['parent']); + $primaryKey = $this->_getPrimaryKey(); + $dirty = $entity->dirty($config['parent']); + + if ($isNew && $parent) { + if ($entity->get($primaryKey[0]) == $parent) { + throw new \RuntimeException("Cannot set a node's parent as itself"); + } + + $parentNode = $this->_getNode($parent); + $edge = $parentNode->get($config['right']); + $entity->set($config['left'], $edge); + $entity->set($config['right'], $edge + 1); + $this->_sync(2, '+', ">= {$edge}"); + } + + if ($isNew && !$parent) { + $edge = $this->_getMax(); + $entity->set($config['left'], $edge + 1); + $entity->set($config['right'], $edge + 2); + } + + if (!$isNew && $dirty && $parent) { + $this->_setParent($entity, $parent); + } + + if (!$isNew && $dirty && !$parent) { + $this->_setAsRoot($entity); + } + } + + /** + * Also deletes the nodes in the subtree of the entity to be delete + * + * @param \Cake\Event\Event $event The beforeDelete event that was fired + * @param \Cake\ORM\Entity $entity The entity that is going to be saved + * @return void + */ + public function beforeDelete(Event $event, Entity $entity) + { + $config = $this->config(); + $this->_ensureFields($entity); + $left = $entity->get($config['left']); + $right = $entity->get($config['right']); + $diff = $right - $left + 1; + + if ($diff > 2) { + $this->_table->deleteAll([ + "{$config['left']} >=" => $left + 1, + "{$config['left']} <=" => $right - 1 + ]); + } + + $this->_sync($diff, '-', "> {$right}"); + } + + /** + * Sets the correct left and right values for the passed entity so it can be + * updated to a new parent. It also makes the hole in the tree so the node + * move can be done without corrupting the structure. + * + * @param \Cake\ORM\Entity $entity The entity to re-parent + * @param mixed $parent the id of the parent to set + * @return void + * @throws \RuntimeException if the parent to set to the entity is not valid + */ + protected function _setParent($entity, $parent) + { + $config = $this->config(); + $parentNode = $this->_getNode($parent); + $this->_ensureFields($entity); + $parentLeft = $parentNode->get($config['left']); + $parentRight = $parentNode->get($config['right']); + $right = $entity->get($config['right']); + $left = $entity->get($config['left']); + + if ($parentLeft > $left && $parentLeft < $right) { + throw new \RuntimeException(sprintf( + 'Cannot use node "%s" as parent for entity "%s"', + $parent, + $entity->get($this->_getPrimaryKey()) + )); + } + + // Values for moving to the left + $diff = $right - $left + 1; + $targetLeft = $parentRight; + $targetRight = $diff + $parentRight - 1; + $min = $parentRight; + $max = $left - 1; + + if ($left < $targetLeft) { + // Moving to the right + $targetLeft = $parentRight - $diff; + $targetRight = $parentRight - 1; + $min = $right + 1; + $max = $parentRight - 1; + $diff *= -1; + } + + if ($right - $left > 1) { + // Correcting internal subtree + $internalLeft = $left + 1; + $internalRight = $right - 1; + $this->_sync($targetLeft - $left, '+', "BETWEEN {$internalLeft} AND {$internalRight}", true); + } + + $this->_sync($diff, '+', "BETWEEN {$min} AND {$max}"); + + if ($right - $left > 1) { + $this->_unmarkInternalTree(); + } + + // Allocating new position + $entity->set($config['left'], $targetLeft); + $entity->set($config['right'], $targetRight); + } + + /** + * Updates the left and right column for the passed entity so it can be set as + * a new root in the tree. It also modifies the ordering in the rest of the tree + * so the structure remains valid + * + * @param \Cake\ORM\Entity $entity The entity to set as a new root + * @return void + */ + protected function _setAsRoot($entity) + { + $config = $this->config(); + $edge = $this->_getMax(); + $this->_ensureFields($entity); + $right = $entity->get($config['right']); + $left = $entity->get($config['left']); + $diff = $right - $left; + + if ($right - $left > 1) { + //Correcting internal subtree + $internalLeft = $left + 1; + $internalRight = $right - 1; + $this->_sync($edge - $diff - $left, '+', "BETWEEN {$internalLeft} AND {$internalRight}", true); + } + + $this->_sync($diff + 1, '-', "BETWEEN {$right} AND {$edge}"); + + if ($right - $left > 1) { + $this->_unmarkInternalTree(); + } + + $entity->set($config['left'], $edge - $diff); + $entity->set($config['right'], $edge); + } + + /** + * Helper method used to invert the sign of the left and right columns that are + * less than 0. They were set to negative values before so their absolute value + * wouldn't change while performing other tree transformations. + * + * @return void + */ + protected function _unmarkInternalTree() + { + $config = $this->config(); + $query = $this->_table->query(); + $this->_table->updateAll([ + $query->newExpr()->add("{$config['left']} = {$config['left']} * -1"), + $query->newExpr()->add("{$config['right']} = {$config['right']} * -1"), + ], [$config['left'] . ' <' => 0]); + } + + /** + * Custom finder method which can be used to return the list of nodes from the root + * to a specific node in the tree. This custom finder requires that the key 'for' + * is passed in the options containing the id of the node to get its path for. + * + * @param \Cake\ORM\Query $query The constructed query to modify + * @param array $options the list of options for the query + * @return \Cake\ORM\Query + * @throws \InvalidArgumentException If the 'for' key is missing in options + */ + public function findPath(Query $query, array $options) + { + if (empty($options['for'])) { + throw new \InvalidArgumentException("The 'for' key is required for find('path')"); + } + + $config = $this->config(); + $alias = $this->_table->alias(); + list($left, $right) = array_map( + function ($field) use ($alias) { + return "$alias.$field"; + }, + [$config['left'], $config['right']] + ); + + $node = $this->_table->get($options['for'], ['fields' => [$left, $right]]); + + return $this->_scope($query) + ->where([ + "$left <=" => $node->get($config['left']), + "$right >=" => $node->get($config['right']) + ]); + } + + /** + * Get the number of children nodes. + * + * @param \Cake\ORM\Entity $node The entity to count children for + * @param bool $direct whether to count all nodes in the subtree or just + * direct children + * @return int Number of children nodes. + */ + public function childCount(Entity $node, $direct = false) + { + $config = $this->config(); + $alias = $this->_table->alias(); + $parent = $alias . '.' . $config['parent']; + + if ($direct) { + return $this->_scope($this->_table->find()) + ->where([$parent => $node->get($this->_getPrimaryKey())]) + ->count(); + } + + $this->_ensureFields($node); + return ($node->get($config['right']) - $node->get($config['left']) - 1) / 2; + } + + /** + * Get the children nodes of the current model + * + * Available options are: + * + * - for: The id of the record to read. + * - direct: Boolean, whether to return only the direct (true), or all (false) children, + * defaults to false (all children). + * + * If the direct option is set to true, only the direct children are returned (based upon the parent_id field) + * + * @param \Cake\ORM\Query $query Query. + * @param array $options Array of options as described above + * @return \Cake\ORM\Query + * @throws \InvalidArgumentException When the 'for' key is not passed in $options + */ + public function findChildren(Query $query, array $options) + { + $config = $this->config(); + $alias = $this->_table->alias(); + $options += ['for' => null, 'direct' => false]; + list($parent, $left, $right) = array_map( + function ($field) use ($alias) { + return "$alias.$field"; + }, + [$config['parent'], $config['left'], $config['right']] + ); + + list($for, $direct) = [$options['for'], $options['direct']]; + + if (empty($for)) { + throw new \InvalidArgumentException("The 'for' key is required for find('children')"); + } + + if ($query->clause('order') === null) { + $query->order([$left => 'ASC']); + } + + if ($direct) { + return $this->_scope($query)->where([$parent => $for]); + } + + $node = $this->_getNode($for); + return $this->_scope($query) + ->where([ + "{$right} <" => $node->get($config['right']), + "{$left} >" => $node->get($config['left']) + ]); + } + + /** + * Gets a representation of the elements in the tree as a flat list where the keys are + * the primary key for the table and the values are the display field for the table. + * Values are prefixed to visually indicate relative depth in the tree. + * + * Available options are: + * + * - keyPath: A dot separated path to fetch the field to use for the array key, or a closure to + * return the key out of the provided row. + * - valuePath: A dot separated path to fetch the field to use for the array value, or a closure to + * return the value out of the provided row. + * - spacer: A string to be used as prefix for denoting the depth in the tree for each item + * + * @param \Cake\ORM\Query $query Query. + * @param array $options Array of options as described above + * @return \Cake\ORM\Query + */ + public function findTreeList(Query $query, array $options) + { + return $this->_scope($query) + ->find('threaded', ['parentField' => $this->config()['parent'], 'order' => [$this->config()['left'] => 'ASC']]) + ->formatResults(function ($results) use ($options) { + $options += [ + 'keyPath' => $this->_getPrimaryKey(), + 'valuePath' => $this->_table->displayField(), + 'spacer' => '_' + ]; + return $results + ->listNested() + ->printer($options['valuePath'], $options['keyPath'], $options['spacer']); + }); + } + + /** + * Removes the current node from the tree, by positioning it as a new root + * and re-parents all children up one level. + * + * Note that the node will not be deleted just moved away from its current position + * without moving its children with it. + * + * @param \Cake\ORM\Entity $node The node to remove from the tree + * @return \Cake\ORM\Entity|false the node after being removed from the tree or + * false on error + */ + public function removeFromTree(Entity $node) + { + return $this->_table->connection()->transactional(function () use ($node) { + $this->_ensureFields($node); + return $this->_removeFromTree($node); + }); + } + + /** + * Helper function containing the actual code for removeFromTree + * + * @param \Cake\ORM\Entity $node The node to remove from the tree + * @return \Cake\ORM\Entity|false the node after being removed from the tree or + * false on error + */ + protected function _removeFromTree($node) + { + $config = $this->config(); + $left = $node->get($config['left']); + $right = $node->get($config['right']); + $parent = $node->get($config['parent']); + + $node->set($config['parent'], null); + + if ($right - $left == 1) { + return $this->_table->save($node); + } + + $primary = $this->_getPrimaryKey(); + $this->_table->updateAll( + [$config['parent'] => $parent], + [$config['parent'] => $node->get($primary)] + ); + $this->_sync(1, '-', 'BETWEEN ' . ($left + 1) . ' AND ' . ($right - 1)); + $this->_sync(2, '-', "> {$right}"); + $edge = $this->_getMax(); + $node->set($config['left'], $edge + 1); + $node->set($config['right'], $edge + 2); + $fields = [$config['parent'], $config['left'], $config['right']]; + + $this->_table->updateAll($node->extract($fields), [$primary => $node->get($primary)]); + + foreach ($fields as $field) { + $node->dirty($field, false); + } + return $node; + } + + /** + * Reorders the node without changing its parent. + * + * If the node is the first child, or is a top level node with no previous node + * this method will return false + * + * @param \Cake\ORM\Entity $node The node to move + * @param int|bool $number How many places to move the node, or true to move to first position + * @throws \Cake\ORM\Exception\RecordNotFoundException When node was not found + * @return \Cake\ORM\Entity|bool $node The node after being moved or false on failure + */ + public function moveUp(Entity $node, $number = 1) + { + return $this->_table->connection()->transactional(function () use ($node, $number) { + $this->_ensureFields($node); + return $this->_moveUp($node, $number); + }); + } + + /** + * Helper function used with the actual code for moveUp + * + * @param \Cake\ORM\Entity $node The node to move + * @param int|bool $number How many places to move the node, or true to move to first position + * @throws \Cake\ORM\Exception\RecordNotFoundException When node was not found + * @return \Cake\ORM\Entity|bool $node The node after being moved or false on failure + */ + protected function _moveUp($node, $number) + { + $config = $this->config(); + list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']]; + + if (!$number) { + return false; + } + + $parentLeft = 0; + if ($node->get($parent)) { + $parentLeft = $this->_getNode($node->get($parent))->get($left); + } + + $edge = $this->_getMax(); + while ($number-- > 0) { + list($nodeLeft, $nodeRight) = array_values($node->extract([$left, $right])); + + if ($parentLeft && ($nodeLeft - 1 == $parentLeft)) { + break; + } + + $nextNode = $this->_scope($this->_table->find()) + ->select([$left, $right]) + ->where([$right => ($nodeLeft - 1)]) + ->first(); + + if (!$nextNode) { + break; + } + + $this->_sync($edge - $nextNode->{$left} + 1, '+', "BETWEEN {$nextNode->{$left}} AND {$nextNode->{$right}}"); + $this->_sync($nodeLeft - $nextNode->{$left}, '-', "BETWEEN {$nodeLeft} AND {$nodeRight}"); + $this->_sync($edge - $nextNode->{$left} - ($nodeRight - $nodeLeft), '-', "> {$edge}"); + + $newLeft = $nodeLeft; + if ($nodeLeft >= $nextNode->{$left} || $nodeLeft <= $nextNode->{$right}) { + $newLeft -= $edge - $nextNode->{$left} + 1; + } + $newLeft = $nodeLeft - ($nodeLeft - $nextNode->{$left}); + + $node->set($left, $newLeft); + $node->set($right, $newLeft + ($nodeRight - $nodeLeft)); + } + + $node->dirty($left, false); + $node->dirty($right, false); + return $node; + } + + /** + * Reorders the node without changing the parent. + * + * If the node is the last child, or is a top level node with no subsequent node + * this method will return false + * + * @param \Cake\ORM\Entity $node The node to move + * @param int|bool $number How many places to move the node or true to move to last position + * @throws \Cake\ORM\Exception\RecordNotFoundException When node was not found + * @return \Cake\ORM\Entity|bool the entity after being moved or false on failure + */ + public function moveDown(Entity $node, $number = 1) + { + return $this->_table->connection()->transactional(function () use ($node, $number) { + $this->_ensureFields($node); + return $this->_moveDown($node, $number); + }); + } + + /** + * Helper function used with the actual code for moveDown + * + * @param \Cake\ORM\Entity $node The node to move + * @param int|bool $number How many places to move the node, or true to move to last position + * @throws \Cake\ORM\Exception\RecordNotFoundException When node was not found + * @return \Cake\ORM\Entity|bool $node The node after being moved or false on failure + */ + protected function _moveDown($node, $number) + { + $config = $this->config(); + list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']]; + + if (!$number) { + return false; + } + + $parentRight = 0; + if ($node->get($parent)) { + $parentRight = $this->_getNode($node->get($parent))->get($right); + } + + if ($number === true) { + $number = PHP_INT_MAX; + } + + $edge = $this->_getMax(); + while ($number-- > 0) { + list($nodeLeft, $nodeRight) = array_values($node->extract([$left, $right])); + + if ($parentRight && ($nodeRight + 1 == $parentRight)) { + break; + } + + $nextNode = $this->_scope($this->_table->find()) + ->select([$left, $right]) + ->where([$left => $nodeRight + 1]) + ->first(); + + if (!$nextNode) { + break; + } + + $this->_sync($edge - $nodeLeft + 1, '+', "BETWEEN {$nodeLeft} AND {$nodeRight}"); + $this->_sync($nextNode->{$left} - $nodeLeft, '-', "BETWEEN {$nextNode->{$left}} AND {$nextNode->{$right}}"); + $this->_sync($edge - $nodeLeft - ($nextNode->{$right} - $nextNode->{$left}), '-', "> {$edge}"); + + $newLeft = $edge + 1; + if ($newLeft >= $nextNode->{$left} || $newLeft <= $nextNode->{$right}) { + $newLeft -= $nextNode->{$left} - $nodeLeft; + } + $newLeft -= $nextNode->{$right} - $nextNode->{$left} - 1; + + $node->set($left, $newLeft); + $node->set($right, $newLeft + ($nodeRight - $nodeLeft)); + } + + $node->dirty($left, false); + $node->dirty($right, false); + return $node; + } + + /** + * Returns a single node from the tree from its primary key + * + * @param mixed $id Record id. + * @return \Cake\ORM\Entity + * @throws \Cake\ORM\Exception\RecordNotFoundException When node was not found + */ + protected function _getNode($id) + { + $config = $this->config(); + list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']]; + $primaryKey = $this->_getPrimaryKey(); + + $node = $this->_scope($this->_table->find()) + ->select([$parent, $left, $right]) + ->where([$this->_table->alias() . '.' . $primaryKey => $id]) + ->first(); + + if (!$node) { + throw new RecordNotFoundException("Node \"{$id}\" was not found in the tree."); + } + + return $node; + } + + /** + * Recovers the lft and right column values out of the hierarchy defined by the + * parent column. + * + * @return void + */ + public function recover() + { + $this->_table->connection()->transactional(function () { + $this->_recoverTree(); + }); + } + + /** + * Recursive method used to recover a single level of the tree + * + * @param int $counter The Last left column value that was assigned + * @param mixed $parentId the parent id of the level to be recovered + * @return int The next value to use for the left column + */ + protected function _recoverTree($counter = 0, $parentId = null) + { + $config = $this->config(); + list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']]; + $pk = (array)$this->_table->primaryKey(); + + $query = $this->_scope($this->_table->query()) + ->select($pk) + ->where([$parent . ' IS' => $parentId]) + ->order($pk) + ->hydrate(false) + ->bufferResults(false); + + $leftCounter = $counter; + foreach ($query as $row) { + $counter++; + $counter = $this->_recoverTree($counter, $row[$pk[0]]); + } + + if ($parentId === null) { + return $counter; + } + + $this->_table->updateAll( + [$left => $leftCounter, $right => $counter + 1], + [$pk[0] => $parentId] + ); + + return $counter + 1; + } + + /** + * Returns the maximum index value in the table. + * + * @return int + */ + protected function _getMax() + { + $config = $this->config(); + $field = $config['right']; + + $edge = $this->_scope($this->_table->find()) + ->select([$field]) + ->order([$field => 'DESC']) + ->first(); + + if (empty($edge->{$field})) { + return 0; + } + + return $edge->{$field}; + } + + /** + * Auxiliary function used to automatically alter the value of both the left and + * right columns by a certain amount that match the passed conditions + * + * @param int $shift the value to use for operating the left and right columns + * @param string $dir The operator to use for shifting the value (+/-) + * @param string $conditions a SQL snipped to be used for comparing left or right + * against it. + * @param bool $mark whether to mark the updated values so that they can not be + * modified by future calls to this function. + * @return void + */ + protected function _sync($shift, $dir, $conditions, $mark = false) + { + $config = $this->config(); + + foreach ([$config['left'], $config['right']] as $field) { + $query = $this->_scope($this->_table->query()); + + $mark = $mark ? '*-1' : ''; + $template = sprintf('%s = (%s %s %s)%s', $field, $field, $dir, $shift, $mark); + $query->update()->set($query->newExpr()->add($template)); + $query->where("{$field} {$conditions}"); + + $query->execute(); + } + } + + /** + * Alters the passed query so that it only returns scoped records as defined + * in the tree configuration. + * + * @param \Cake\ORM\Query $query the Query to modify + * @return \Cake\ORM\Query + */ + protected function _scope($query) + { + $config = $this->config(); + + if (is_array($config['scope'])) { + return $query->where($config['scope']); + } elseif (is_callable($config['scope'])) { + return $config['scope']($query); + } + + return $query; + } + + /** + * Ensures that the provided entity contains non-empty values for the left and + * right fields + * + * @param \Cake\ORM\Entity $entity The entity to ensure fields for + * @return void + */ + protected function _ensureFields($entity) + { + $config = $this->config(); + $fields = [$config['left'], $config['right']]; + $values = array_filter($entity->extract($fields)); + if (count($values) === count($fields)) { + return; + } + + $fresh = $this->_table->get($entity->get($this->_getPrimaryKey()), $fields); + $entity->set($fresh->extract($fields), ['guard' => false]); + + foreach ($fields as $field) { + $entity->dirty($field, false); + } + } + + /** + * Returns a single string value representing the primary key of the attached table + * + * @return string + */ + protected function _getPrimaryKey() + { + if (!$this->_primaryKey) { + $this->_primaryKey = (array)$this->_table->primaryKey(); + $this->_primaryKey = $this->_primaryKey[0]; + } + return $this->_primaryKey; + } +} diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 3a80802a..be2733bd 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -76,7 +76,11 @@ public function __construct(Table $table) */ protected function _resolveClassName($class) { - return App::className($class, 'Model/Behavior', 'Behavior'); + $result = App::className($class, 'Model/Behavior', 'Behavior'); + if (!$result && in_array($class, ['CounterCache', 'Timestamp', 'Tree', 'Translate'])) { + return 'Cake\ORM\Behavior\\' . $class . 'Behavior'; + } + return $result; } /** From aa34ccc8b9e77b38a158fc8f1c473230f53f83e6 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Fri, 2 Jan 2015 12:47:07 +0100 Subject: [PATCH 0108/2059] Not hardcoding the list of built-in behaviors --- BehaviorRegistry.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index be2733bd..006206af 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -77,8 +77,8 @@ public function __construct(Table $table) protected function _resolveClassName($class) { $result = App::className($class, 'Model/Behavior', 'Behavior'); - if (!$result && in_array($class, ['CounterCache', 'Timestamp', 'Tree', 'Translate'])) { - return 'Cake\ORM\Behavior\\' . $class . 'Behavior'; + if (!$result) { + $result = App::className($class, 'ORM/Behavior', 'Behavior'); } return $result; } From ac022ed5af6dca6c768bb6ae23fdff4f64a1b480 Mon Sep 17 00:00:00 2001 From: antograssiot Date: Fri, 2 Jan 2015 14:00:01 +0100 Subject: [PATCH 0109/2059] remove deprecated stuff for RC --- EntityValidator.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/EntityValidator.php b/EntityValidator.php index 543a9f80..ef6df65f 100644 --- a/EntityValidator.php +++ b/EntityValidator.php @@ -26,9 +26,6 @@ * This class is generally used by the internals of the ORM. It * provides methods for traversing a set of entities and their associated * properties. - * - * @see \Cake\ORM\Table::validate() - * @see \Cake\ORM\Table::validateMany() */ class EntityValidator { From f6cf96c0fcbcb3daf83ac1d5e037a8584bfe957a Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sat, 3 Jan 2015 00:26:50 +0100 Subject: [PATCH 0110/2059] Fixed some broken tests in HHVM for different behavior in SplFixedArray --- ResultSet.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ResultSet.php b/ResultSet.php index f791db52..b574dfcd 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -217,7 +217,7 @@ public function rewind() */ public function valid() { - if (isset($this->_results[$this->_index])) { + if ($this->_results[$this->_index] !== null) { $this->_current = $this->_results[$this->_index]; return true; } From ec52a3e9d886d3d1092a216a1419c12bddeeae1d Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sat, 3 Jan 2015 00:47:17 +0100 Subject: [PATCH 0111/2059] Refactoring to make all ORM tests pass on HHVM for SQlite --- ResultSet.php | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/ResultSet.php b/ResultSet.php index b574dfcd..b8befceb 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -217,21 +217,23 @@ public function rewind() */ public function valid() { - if ($this->_results[$this->_index] !== null) { + if ($this->_index >= $this->_count) { + if ($this->_statement !== null) { + $this->_statement->closeCursor(); + } + return false; + } + + if ($this->_useBuffering && $this->_results[$this->_index] !== null) { $this->_current = $this->_results[$this->_index]; return true; } $this->_current = $this->_fetchResult(); $valid = $this->_current !== false; - $hasNext = $this->_index < $this->_count; - if ($this->_statement && !($valid && $hasNext)) { - $this->_statement->closeCursor(); - } - - if ($valid) { - $this->_bufferResult($this->_current); + if ($this->_useBuffering) { + $this->_results[$this->_index] = $this->_current; } return $valid; @@ -263,6 +265,8 @@ public function serialize() public function unserialize($serialized) { $this->_results = unserialize($serialized); + $this->_useBuffering = true; + $this->_count = count($this->_results); } /** @@ -486,19 +490,6 @@ protected function _castValues($table, $values) return $values; } - /** - * Conditionally buffer the passed result - * - * @param array $result the result fetch from the database - * @return void - */ - protected function _bufferResult($result) - { - if ($this->_useBuffering) { - $this->_results[$this->_index] = $result; - } - } - /** * Returns an array that can be used to describe the internal state of this * object. From f95ba6aaaab91413513145cb9a524be29bc45424 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 3 Jan 2015 21:15:21 -0500 Subject: [PATCH 0112/2059] Fix documentation and add exception for foreignKey in contain foreignKey = false in a contain() call for hasMany associations will not work. While it could be made to work, I really don't think the effort is justified. In the rare case that someone needs this kind of behavior they can use the lower level join() API to generate the required queries. Refs #5143 --- Association/HasMany.php | 5 +++++ Query.php | 7 ++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index 57540e3c..d70e3147 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -128,6 +128,11 @@ protected function _linkField($options) { $links = []; $name = $this->alias(); + if ($options['foreignKey'] === false) { + $msg = 'Cannot have foreignKey = false for hasMany associations. ' . + 'You must provide a foreignKey column.'; + throw new \RuntimeException($msg); + } foreach ((array)$options['foreignKey'] as $key) { $links[] = sprintf('%s.%s', $name, $key); diff --git a/Query.php b/Query.php index 1338c120..06bdba80 100644 --- a/Query.php +++ b/Query.php @@ -226,7 +226,8 @@ public function eagerLoader(EagerLoader $instance = null) * options that can be set per association are: * * - foreignKey: Used to set a different field to match both tables, if set to false - * no join conditions will be generated automatically + * no join conditions will be generated automatically. `false` can only be used on + * joinable associations and cannot be used with hasMany or belongsToMany associations. * - fields: An array with the fields that should be fetched from the association * - queryBuilder: Equivalent to passing a callable instead of an options array * @@ -245,9 +246,9 @@ public function eagerLoader(EagerLoader $instance = null) * Failing to do so will trigger exceptions. * * {{{ - * // Use special join conditions for getting an author's hasMany 'likes' + * // Use special join conditions for getting an Articles's belongsTo 'authors' * $query->contain([ - * 'Likes' => [ + * 'Authors' => [ * 'foreignKey' => false, * 'queryBuilder' => function ($q) { * return $q->where(...); // Add full filtering conditions From ad54c9e6708b6d78d277ed534c43fa04f9ff6d27 Mon Sep 17 00:00:00 2001 From: Walther Lalk Date: Mon, 5 Jan 2015 13:57:07 +0200 Subject: [PATCH 0113/2059] Fix for error caused by belongsToMany query with a formatter on the join query. --- Association.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association.php b/Association.php index 4439d0d1..16c7c533 100644 --- a/Association.php +++ b/Association.php @@ -660,7 +660,7 @@ protected function _formatAssociationResults($query, $surrogate, $options) { $formatters = $surrogate->formatResults(); - if (!$formatters) { + if (!$formatters || empty($options['propertyPath'])) { return; } From 0ec628d0c928ca752bef83049d9ab617702c3ef4 Mon Sep 17 00:00:00 2001 From: Patrick Conroy Date: Tue, 6 Jan 2015 12:58:30 -0500 Subject: [PATCH 0114/2059] Allowing specifying Table when translating Implementing translations with Table class declarations instead of the actual DB table. This gives the user the ability to specify the table and datasource used for the translation. I also added a strategy specifier to fix issues that arise when trying to search i18n'd Tables. --- Behavior/TranslateBehavior.php | 41 +++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 1734633d..be56774f 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -23,6 +23,7 @@ use Cake\ORM\Query; use Cake\ORM\Table; use Cake\ORM\TableRegistry; +use Cake\Utility\Inflector; /** * This behavior provides a way to translate dynamic data by keeping translations @@ -65,10 +66,11 @@ class TranslateBehavior extends Behavior 'implementedFinders' => ['translations' => 'findTranslations'], 'implementedMethods' => ['locale' => 'locale'], 'fields' => [], - 'translationTable' => 'i18n', + 'translationTable' => 'I18n', 'defaultLocale' => '', 'model' => '', - 'onlyTranslated' => false + 'onlyTranslated' => false, + 'strategy' => 'subquery' ]; /** @@ -94,7 +96,8 @@ public function initialize(array $config) $this->setupFieldAssociations( $this->_config['fields'], $this->_config['translationTable'], - $this->_config['model'] ? $this->_config['model'] : $this->_table->alias() + $this->_config['model'] ? $this->_config['model'] : $this->_table->alias(), + $this->_config['strategy'] ); } @@ -110,16 +113,17 @@ public function initialize(array $config) * @param string $model the model field value * @return void */ - public function setupFieldAssociations($fields, $table, $model) + public function setupFieldAssociations($fields, $table, $model, $strategy) { + $targetAlias = Inflector::slug($table); + $alias = $this->_table->alias(); $filter = $this->_config['onlyTranslated']; + foreach ($fields as $field) { - $name = $this->_table->alias() . '_' . $field . '_translation'; - $target = TableRegistry::get($name); - $target->table($table); + $name = $alias . '_' . $field . '_translation'; $this->_table->hasOne($name, [ - 'targetTable' => $target, + 'className' => $table, 'foreignKey' => 'foreign_key', 'joinType' => $filter ? 'INNER' : 'LEFT', 'conditions' => [ @@ -130,10 +134,11 @@ public function setupFieldAssociations($fields, $table, $model) ]); } - $this->_table->hasMany($table, [ + $this->_table->hasMany($targetAlias, [ + 'className' => $table, 'foreignKey' => 'foreign_key', - 'strategy' => 'subquery', - 'conditions' => ["$table.model" => $model], + 'strategy' => $strategy, + 'conditions' => ["$targetAlias.model" => $model], 'propertyName' => '_i18n', 'dependent' => true ]); @@ -177,6 +182,7 @@ public function beforeFind(Event $event, Query $query, $options) $fields = $this->_config['fields']; $alias = $this->_table->alias(); $select = $query->clause('select'); + $changeFilter = isset($options['filterByCurrentLocale']) && $options['filterByCurrentLocale'] !== $this->_config['onlyTranslated']; @@ -213,7 +219,8 @@ public function beforeSave(Event $event, Entity $entity, ArrayObject $options) { $locale = $entity->get('_locale') ?: $this->locale(); $table = $this->_config['translationTable']; - $newOptions = [$table => ['validate' => false]]; + $targetAlias = Inflector::slug($table); + $newOptions = [$targetAlias => ['validate' => false]]; $options['associated'] = $newOptions + $options['associated']; $this->_bundleTranslatedFields($entity); @@ -312,10 +319,12 @@ public function findTranslations(Query $query, array $options) { $locales = isset($options['locales']) ? $options['locales'] : []; $table = $this->_config['translationTable']; + $targetAlias = Inflector::slug($table); + return $query - ->contain([$table => function ($q) use ($locales, $table) { + ->contain([$targetAlias => function ($q) use ($locales, $targetAlias) { if ($locales) { - $q->where(["$table.locale IN" => $locales]); + $q->where(["$targetAlias.locale IN" => $locales]); } return $q; }]) @@ -458,7 +467,9 @@ protected function _bundleTranslatedFields($entity) */ protected function _findExistingTranslations($ruleSet) { - $association = $this->_table->association($this->_config['translationTable']); + $target = TableRegistry::get($this->_config['translationTable']); + $association = $this->_table->association($target->alias()); + $query = $association->find() ->select(['id', 'num' => 0]) ->where(current($ruleSet)) From b3e45971edeb8052c1136ee1ebe4a8441bffd848 Mon Sep 17 00:00:00 2001 From: IWASE Date: Wed, 7 Jan 2015 20:20:02 +0900 Subject: [PATCH 0115/2059] Set entity source on create empty entity. --- Table.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Table.php b/Table.php index ad3e97d3..4083ac67 100644 --- a/Table.php +++ b/Table.php @@ -1818,7 +1818,9 @@ public function newEntity($data = null, array $options = []) { if ($data === null) { $class = $this->entityClass(); - return new $class; + $entity = new $class; + $entity->source($this->alias()); + return $entity; } if (!isset($options['associated'])) { $options['associated'] = $this->_associations->keys(); From 395f65b5f99c38db7cecc57ce15f2cf3144dfb2b Mon Sep 17 00:00:00 2001 From: Patrick Conroy Date: Wed, 7 Jan 2015 13:30:19 -0500 Subject: [PATCH 0116/2059] Cloning references to field tables from main I18n This should make any subsequent calls to TableRegistry::get work as expected. --- Behavior/TranslateBehavior.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index be56774f..5e5c4b8f 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -115,6 +115,7 @@ public function initialize(array $config) */ public function setupFieldAssociations($fields, $table, $model, $strategy) { + $targetTable = TableRegistry::get($table); $targetAlias = Inflector::slug($table); $alias = $this->_table->alias(); $filter = $this->_config['onlyTranslated']; @@ -122,13 +123,15 @@ public function setupFieldAssociations($fields, $table, $model, $strategy) foreach ($fields as $field) { $name = $alias . '_' . $field . '_translation'; + $fieldTable = TableRegistry::set($name, $targetTable); $this->_table->hasOne($name, [ 'className' => $table, 'foreignKey' => 'foreign_key', + 'targetTable' => $fieldTable, 'joinType' => $filter ? 'INNER' : 'LEFT', 'conditions' => [ - $name . '.model' => $model, - $name . '.field' => $field, + $fieldTable->alias() . '.model' => $model, + $fieldTable->alias() . '.field' => $field, ], 'propertyName' => $field . '_translation' ]); From b31e48f4d43f3f6a6a2cd3a40079000a9eff61a3 Mon Sep 17 00:00:00 2001 From: Patrick Conroy Date: Wed, 7 Jan 2015 14:47:42 -0500 Subject: [PATCH 0117/2059] Fixing broken tests. TableRegistry::get with options works better than TableRegistry::set. --- Behavior/TranslateBehavior.php | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 5e5c4b8f..6851298f 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -123,15 +123,19 @@ public function setupFieldAssociations($fields, $table, $model, $strategy) foreach ($fields as $field) { $name = $alias . '_' . $field . '_translation'; - $fieldTable = TableRegistry::set($name, $targetTable); - $this->_table->hasOne($name, [ + $fieldTable = TableRegistry::get($name, [ 'className' => $table, - 'foreignKey' => 'foreign_key', + 'alias' => $name, + 'table' => $targetTable->table() + ]); + + $this->_table->hasOne($name, [ 'targetTable' => $fieldTable, + 'foreignKey' => 'foreign_key', 'joinType' => $filter ? 'INNER' : 'LEFT', 'conditions' => [ - $fieldTable->alias() . '.model' => $model, - $fieldTable->alias() . '.field' => $field, + $name . '.model' => $model, + $name . '.field' => $field, ], 'propertyName' => $field . '_translation' ]); @@ -190,7 +194,9 @@ public function beforeFind(Event $event, Query $query, $options) $options['filterByCurrentLocale'] !== $this->_config['onlyTranslated']; foreach ($fields as $field) { - $contain[$alias . '_' . $field . '_translation']['queryBuilder'] = $conditions( + $name = $alias . '_' . $field . '_translation'; + + $contain[$name]['queryBuilder'] = $conditions( $field, $locale, $query, @@ -199,7 +205,7 @@ public function beforeFind(Event $event, Query $query, $options) if ($changeFilter) { $filter = $options['filterByCurrentLocale'] ? 'INNER' : 'LEFT'; - $contain[$alias . '_' . $field . '_translation']['joinType'] = $filter; + $contain[$name]['joinType'] = $filter; } } @@ -353,6 +359,7 @@ protected function _rowMapper($results, $locale) foreach ($this->_config['fields'] as $field) { $name = $field . '_translation'; $translation = isset($row[$name]) ? $row[$name] : null; + if ($translation === null || $translation === false) { unset($row[$name]); @@ -387,6 +394,7 @@ public function groupTranslations($results) { return $results->map(function ($row) { $translations = (array)$row->get('_i18n'); + $grouped = new Collection($translations); $result = []; @@ -403,6 +411,7 @@ public function groupTranslations($results) $row->set('_translations', $result, $options); unset($row['_i18n']); $row->clean(); + return $row; }); } From c4a2bc40da20ace17c8cb2f25f2eb76e61a1d4bb Mon Sep 17 00:00:00 2001 From: Patrick Conroy Date: Wed, 7 Jan 2015 15:44:16 -0500 Subject: [PATCH 0118/2059] Adding tests for custom i18n tables --- Behavior/TranslateBehavior.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 6851298f..5ebc1de1 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -111,6 +111,8 @@ public function initialize(array $config) * @param array $fields list of fields to create associations for * @param string $table the table name to use for storing each field translation * @param string $model the model field value + * @param string $strategy the strategy used in the _i18n association + * * @return void */ public function setupFieldAssociations($fields, $table, $model, $strategy) From 65afaba3502450faa19a955af636af9ed647cfeb Mon Sep 17 00:00:00 2001 From: Patrick Conroy Date: Wed, 7 Jan 2015 15:54:51 -0500 Subject: [PATCH 0119/2059] Fixing psr-2 and removing extra breaks --- Behavior/TranslateBehavior.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 5ebc1de1..0f6beeda 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -362,7 +362,6 @@ protected function _rowMapper($results, $locale) $name = $field . '_translation'; $translation = isset($row[$name]) ? $row[$name] : null; - if ($translation === null || $translation === false) { unset($row[$name]); continue; @@ -396,7 +395,6 @@ public function groupTranslations($results) { return $results->map(function ($row) { $translations = (array)$row->get('_i18n'); - $grouped = new Collection($translations); $result = []; @@ -413,7 +411,6 @@ public function groupTranslations($results) $row->set('_translations', $result, $options); unset($row['_i18n']); $row->clean(); - return $row; }); } From 829f49313c1277dffd4d5ac66190772c9aa0ca6f Mon Sep 17 00:00:00 2001 From: Patrick Conroy Date: Wed, 7 Jan 2015 16:05:40 -0500 Subject: [PATCH 0120/2059] Fixing psr-2 stuff --- Behavior/TranslateBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 0f6beeda..6d2019bb 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -112,7 +112,7 @@ public function initialize(array $config) * @param string $table the table name to use for storing each field translation * @param string $model the model field value * @param string $strategy the strategy used in the _i18n association - * + * * @return void */ public function setupFieldAssociations($fields, $table, $model, $strategy) From 2b9af4dbd9aed57c97bfe0301bd489c7363df767 Mon Sep 17 00:00:00 2001 From: Patrick Conroy Date: Wed, 7 Jan 2015 17:09:01 -0500 Subject: [PATCH 0121/2059] Dealing with the creation of i18n field tables elsewhere --- Behavior/TranslateBehavior.php | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 6d2019bb..45327967 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -125,11 +125,15 @@ public function setupFieldAssociations($fields, $table, $model, $strategy) foreach ($fields as $field) { $name = $alias . '_' . $field . '_translation'; - $fieldTable = TableRegistry::get($name, [ - 'className' => $table, - 'alias' => $name, - 'table' => $targetTable->table() - ]); + if (!TableRegistry::exists($name)) { + $fieldTable = TableRegistry::get($name, [ + 'className' => $table, + 'alias' => $name, + 'table' => $targetTable->table() + ]); + } else { + $fieldTable = TableRegistry::get($name); + } $this->_table->hasOne($name, [ 'targetTable' => $fieldTable, @@ -361,7 +365,7 @@ protected function _rowMapper($results, $locale) foreach ($this->_config['fields'] as $field) { $name = $field . '_translation'; $translation = isset($row[$name]) ? $row[$name] : null; - + if ($translation === null || $translation === false) { unset($row[$name]); continue; From 0c1f478ac41f865c9d784c0a59fcb0d8ffe82bf4 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Wed, 7 Jan 2015 23:22:31 +0100 Subject: [PATCH 0122/2059] Moving bufferResults from the ORM to Database Query --- Query.php | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/Query.php b/Query.php index 06bdba80..0c387a17 100644 --- a/Query.php +++ b/Query.php @@ -76,14 +76,6 @@ class Query extends DatabaseQuery implements JsonSerializable */ protected $_autoFields; - /** - * Boolean for tracking whether or not buffered results - * are enabled. - * - * @var bool - */ - protected $_useBufferedResults = true; - /** * Whether to hydrate results into entity objects * @@ -343,33 +335,6 @@ public function matching($assoc, callable $builder = null) return $this; } - /** - * Enable/Disable buffered results. - * - * When enabled the ResultSet returned by this Query will be - * buffered. This enables you to iterate a ResultSet multiple times, or - * both cache and iterate the ResultSet. - * - * When disabled it will consume less memory as fetched results are not - * remembered in the ResultSet. - * - * If called with no arguments, it will return whether or not buffering is - * enabled. - * - * @param bool $enable whether or not to enable buffering - * @return bool|$this - */ - public function bufferResults($enable = null) - { - if ($enable === null) { - return $this->_useBufferedResults; - } - - $this->_dirty(); - $this->_useBufferedResults = (bool)$enable; - return $this; - } - /** * Returns a key => value array representing a single aliased field * that can be passed directly to the select() method. From ada37044ca9af5f36883ad2b8de08bc06f5cdc0c Mon Sep 17 00:00:00 2001 From: Ceeram Date: Fri, 9 Jan 2015 11:54:54 +0100 Subject: [PATCH 0123/2059] Minor docblock fix --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 4083ac67..789efe9b 100644 --- a/Table.php +++ b/Table.php @@ -1875,7 +1875,7 @@ public function newEntities(array $data, array $options = []) * passing the `fieldList` option, which is also accepted for associations: * * {{{ - * $articles = $this->Articles->patchEntity($article, $this->request->data(), [ + * $article = $this->Articles->patchEntity($article, $this->request->data(), [ * 'fieldList' => ['title', 'body'], * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']] * ] From 63adc262c213435ec4233c6bd2c86d0bde3cc7fa Mon Sep 17 00:00:00 2001 From: Walther Lalk Date: Fri, 9 Jan 2015 15:21:33 +0200 Subject: [PATCH 0124/2059] Allow null values to be marshalled when using fieldList --- Marshaller.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 6abc79b9..ad767cd2 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -147,7 +147,7 @@ public function one(array $data, array $options = []) } foreach ((array)$options['fieldList'] as $field) { - if (isset($properties[$field])) { + if (array_key_exists($field, $properties)) { $entity->set($field, $properties[$field]); } } @@ -374,7 +374,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) } foreach ((array)$options['fieldList'] as $field) { - if (isset($properties[$field])) { + if (array_key_exists($field, $properties)) { $entity->set($field, $properties[$field]); } } From 14f0ee803a097e4a0d6df85f45328798f277557c Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 9 Jan 2015 09:31:47 +0530 Subject: [PATCH 0125/2059] Replace our custom code fence with markdown standard fence. --- Association/BelongsToMany.php | 12 ++-- Behavior.php | 16 ++--- Behavior/CounterCacheBehavior.php | 16 ++--- Behavior/TranslateBehavior.php | 4 +- Entity.php | 4 +- Query.php | 44 ++++++------- RulesChecker.php | 8 +-- Table.php | 104 +++++++++++++++--------------- TableRegistry.php | 8 +-- 9 files changed, 108 insertions(+), 108 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index ada79443..f1d36bec 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -552,10 +552,10 @@ protected function _saveLinks(EntityInterface $sourceEntity, $targetEntities, $o * * ### Example: * - * {{{ + * ``` * $newTags = $tags->find('relevant')->execute(); * $articles->association('tags')->link($article, $newTags); - * }}} + * ``` * * `$article->get('tags')` will contain all tags in `$newTags` after liking * @@ -593,11 +593,11 @@ function () use ($sourceEntity, $targetEntities, $options) { * * ### Example: * - * {{{ + * ``` * $article->tags = [$tag1, $tag2, $tag3, $tag4]; * $tags = [$tag1, $tag2, $tag3]; * $articles->association('tags')->unlink($article, $tags); - * }}} + * ``` * * `$article->get('tags')` will contain only `[$tag4]` after deleting in the database * @@ -676,12 +676,12 @@ function () use ($sourceEntity, $targetEntities) { * * ### Example: * - * {{{ + * ``` * $article->tags = [$tag1, $tag2, $tag3, $tag4]; * $articles->save($article); * $tags = [$tag1, $tag3]; * $articles->association('tags')->replaceLinks($article, $tags); - * }}} + * ``` * * `$article->get('tags')` will contain only `[$tag1, $tag3]` at the end * diff --git a/Behavior.php b/Behavior.php index 7b3e808a..224aecca 100644 --- a/Behavior.php +++ b/Behavior.php @@ -32,11 +32,11 @@ * methods. These methods will be accessible on the tables the * behavior has been added to. * - * {{{ + * ``` * function doSomething($arg1, $arg2) { * // do something * } - * }}} + * ``` * * Would be called like `$table->doSomething($arg1, $arg2);`. * @@ -101,9 +101,9 @@ * starting with `find` will be setup as a finder. Your finder * methods should expect the following arguments: * - * {{{ + * ``` * findSlugged(Query $query, array $options) - * }}} + * ``` * * @see \Cake\ORM\Table::addBehavior() * @see \Cake\Event\EventManager @@ -284,12 +284,12 @@ public function implementedEvents() * * Provides an alias->methodname map of which finders a behavior implements. Example: * - * {{{ + * ``` * [ * 'this' => 'findThis', * 'alias' => 'findMethodName' * ] - * }}} + * ``` * * With the above example, a call to `$Table->find('this')` will call `$Behavior->findThis()` * and a call to `$Table->find('alias')` will call `$Behavior->findMethodName()` @@ -315,12 +315,12 @@ public function implementedFinders() * * Provides an alias->methodname map of which methods a behavior implements. Example: * - * {{{ + * ``` * [ * 'method' => 'method', * 'aliasedmethod' => 'somethingElse' * ] - * }}} + * ``` * * With the above example, a call to `$Table->method()` will call `$Behavior->method()` * and a call to `$Table->aliasedmethod()` will call `$Behavior->somethingElse()` diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 0458bd0c..92f5fb32 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -28,16 +28,16 @@ * Examples with Post model belonging to User model * * Regular counter cache - * {{{ + * ``` * [ * 'Users' => [ * 'post_count' * ] * ] - * }}} + * ``` * * Counter cache with scope - * {{{ + * ``` * [ * 'Users' => [ * 'posts_published' => [ @@ -47,10 +47,10 @@ * ] * ] * ] - * }}} + * ``` * * Counter cache using custom find - * {{{ + * ``` * [ * 'Users' => [ * 'posts_published' => [ @@ -58,11 +58,11 @@ * ] * ] * ] - * }}} + * ``` * * Counter cache using lambda function returning the count * This is equivalent to example #2 - * {{{ + * ``` * [ * 'Users' => [ * 'posts_published' => function (Event $event, Entity $entity, Table $table) { @@ -74,7 +74,7 @@ * } * ] * ] - * }}} + * ``` * */ class CounterCacheBehavior extends Behavior diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 1734633d..27b0e18e 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -296,10 +296,10 @@ public function locale($locale = null) * * ### Example: * - * {{{ + * ``` * $article = $articles->find('translations', ['locales' => ['eng', 'deu'])->first(); * $englishTranslatedFields = $article->get('_translations')['eng']; - * }}} + * ``` * * If the `locales` array is not passed, it will bring all translations found * for each record. diff --git a/Entity.php b/Entity.php index 6bcbf85a..79d2a67d 100644 --- a/Entity.php +++ b/Entity.php @@ -38,9 +38,9 @@ class Entity implements EntityInterface * * ### Example: * - * {{{ + * ``` * $entity = new Entity(['id' => 1, 'name' => 'Andrew']) - * }}} + * ``` * * @param array $properties hash of properties to set in this entity * @param array $options list of options to use when creating this entity diff --git a/Query.php b/Query.php index 06bdba80..54180827 100644 --- a/Query.php +++ b/Query.php @@ -182,13 +182,13 @@ public function eagerLoader(EagerLoader $instance = null) * * ### Example: * - * {{{ + * ``` * // Bring articles' author information * $query->contain('Author'); * * // Also bring the category and tags associated to each article * $query->contain(['Category', 'Tag']); - * }}} + * ``` * * Associations can be arbitrarily nested using dot notation or nested arrays, * this allows this object to calculate joins or any additional queries that @@ -196,7 +196,7 @@ public function eagerLoader(EagerLoader $instance = null) * * ### Example: * - * {{{ + * ``` * // Eager load the product info, and for each product load other 2 associations * $query->contain(['Product' => ['Manufacturer', 'Distributor']); * @@ -205,14 +205,14 @@ public function eagerLoader(EagerLoader $instance = null) * * // For an author query, load his region, state and country * $query->contain('Regions.States.Countries'); - * }}} + * ``` * * It is possible to control the conditions and fields selected for each of the * contained associations: * * ### Example: * - * {{{ + * ``` * $query->contain(['Tags' => function ($q) { * return $q->where(['Tags.is_popular' => true]); * }]); @@ -220,7 +220,7 @@ public function eagerLoader(EagerLoader $instance = null) * $query->contain(['Products.Manufactures' => function ($q) { * return $q->select(['name'])->where(['Manufactures.active' => true]); * }]); - * }}} + * ``` * * Each association might define special options when eager loaded, the allowed * options that can be set per association are: @@ -233,19 +233,19 @@ public function eagerLoader(EagerLoader $instance = null) * * ### Example: * - * {{{ + * ``` * // Set options for the hasMany articles that will be eagerly loaded for an author * $query->contain([ * 'Articles' => [ * 'fields' => ['title', 'author_id'] * ] * ]); - * }}} + * ``` * * When containing associations, it is important to include foreign key columns. * Failing to do so will trigger exceptions. * - * {{{ + * ``` * // Use special join conditions for getting an Articles's belongsTo 'authors' * $query->contain([ * 'Authors' => [ @@ -255,7 +255,7 @@ public function eagerLoader(EagerLoader $instance = null) * } * ] * ]); - * }}} + * ``` * * If called with no arguments, this function will return an array with * with the list of previously configured associations to be contained in the @@ -294,23 +294,23 @@ public function contain($associations = null, $override = false) * * ### Example: * - * {{{ + * ``` * // Bring only articles that were tagged with 'cake' * $query->matching('Tags', function ($q) { * return $q->where(['name' => 'cake']); * ); - * }}} + * ``` * * It is possible to filter by deep associations by using dot notation: * * ### Example: * - * {{{ + * ``` * // Bring only articles that were commented by 'markstory' * $query->matching('Comments.Users', function ($q) { * return $q->where(['username' => 'markstory']); * ); - * }}} + * ``` * * As this function will create `INNER JOIN`, you might want to consider * calling `distinct` on this query as you might get duplicate rows if @@ -319,13 +319,13 @@ public function contain($associations = null, $override = false) * * ### Example: * - * {{{ + * ``` * // Bring unique articles that were commented by 'markstory' * $query->distinct(['Articles.id']) * ->matching('Comments.Users', function ($q) { * return $q->where(['username' => 'markstory']); * ); - * }}} + * ``` * * Please note that the query passed to the closure will only accept calling * `select`, `where`, `andWhere` and `orWhere` on it. If you wish to @@ -442,7 +442,7 @@ public function aliasFields($fields, $defaultAlias = null) * * ### Example: * - * {{{ + * ``` * $query->applyOptions([ * 'fields' => ['id', 'name'], * 'conditions' => [ @@ -450,16 +450,16 @@ public function aliasFields($fields, $defaultAlias = null) * ], * 'limit' => 10 * ]); - * }}} + * ``` * * Is equivalent to: * - * {{{ + * ``` * $query * ->select(['id', 'name']) * ->where(['created >=' => '2013-01-01']) * ->limit(10) - * }}} + * ``` * * @param array $options list of query clauses to apply new parts to. * @return $this @@ -724,9 +724,9 @@ protected function _addDefaultFields() * * Allows custom find methods to be combined and applied to each other. * - * {{{ + * ``` * $table->find('all')->find('recent'); - * }}} + * ``` * * The above is an example of stacking multiple finder methods onto * a single query. diff --git a/RulesChecker.php b/RulesChecker.php index f0fa71c7..c40255fb 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -290,9 +290,9 @@ public function checkDelete(EntityInterface $entity, array $options = []) * * ### Example: * - * {{{ + * ``` * $rules->add($rules->isUnique('email', 'The email should be unique')); - * }}} + * ``` * * @param array $fields The list of fields to check for uniqueness. * @param string $message The error message to show in case the rule does not pass. @@ -312,11 +312,11 @@ public function isUnique(array $fields, $message = 'This value is already in use * * ### Example: * - * {{{ + * ``` * $rules->add($rules->existsIn('author_id', 'Authors', 'Invalid Author')); * * $rules->add($rules->existsIn('site_id', new SitesTable(), 'Invalid Site')); - * }}} + * ``` * * @param string|array $field The field or list of fields to check for existence by * primary key lookup in the other table. diff --git a/Table.php b/Table.php index 789efe9b..ba71eac5 100644 --- a/Table.php +++ b/Table.php @@ -58,15 +58,15 @@ * finder methods. These methods allow you to easily set basic conditions up. For example * to filter users by username you would call * - * {{{ + * ``` * $query = $users->findByUsername('mark'); - * }}} + * ``` * * You can also combine conditions on multiple fields using either `Or` or `And`: * - * {{{ + * ``` * $query = $users->findByUsernameOrEmail('mark', 'mark@example.org'); - * }}} + * ``` * * ### Bulk updates/deletes * @@ -272,13 +272,13 @@ public static function defaultConnectionName() * You can use this method to define associations, attach behaviors * define validation and do any other initialization logic you need. * - * {{{ + * ``` * public function initialize(array $config) { * $this->belongsTo('Users'); * $this->belongsToMany('Tagging.Tags'); * $this->primaryKey('something_else'); * } - * }}} + * ``` * * @param array $config Configuration options passed to the constructor * @return void @@ -394,12 +394,12 @@ public function schema($schema = null) * * ### Example: * - * {{{ + * ``` * protected function _initializeSchema(\Cake\Database\Schema\Table $table) { * $table->columnType('preferences', 'json'); * return $table; * } - * }}} + * ``` * * @param \Cake\Database\Schema\Table $table The table definition fetched from database. * @return \Cake\Database\Schema\Table the altered schema @@ -521,9 +521,9 @@ public function entityClass($name = null) * * Load a behavior, with some settings. * - * {{{ + * ``` * $this->addBehavior('Tree', ['parent' => 'parentId']); - * }}} + * ``` * * Behaviors are generally loaded during Table::initialize(). * @@ -545,9 +545,9 @@ public function addBehavior($name, array $options = []) * * Remove a behavior from this table. * - * {{{ + * ``` * $this->removeBehavior('Tree'); - * }}} + * ``` * * @param string $name The alias that the behavior was added with. * @return void @@ -606,7 +606,7 @@ public function associations() * It takes an array containing set of table names indexed by association type * as argument: * - * {{{ + * ``` * $this->Posts->addAssociations([ * 'belongsTo' => [ * 'Users' => ['className' => 'App\Model\Table\UsersTable'] @@ -614,7 +614,7 @@ public function associations() * 'hasMany' => ['Comments'], * 'belongsToMany' => ['Tags'] * ]); - * }}} + * ``` * * Each association type accepts multiple associations where the keys * are the aliases, and the values are association config data. If numeric @@ -847,37 +847,37 @@ public function findAll(Query $query, array $options) * * The results of this finder will be in the following form: * - * {{{ + * ``` * [ * 1 => 'value for id 1', * 2 => 'value for id 2', * 4 => 'value for id 4' * ] - * }}} + * ``` * * You can specify which property will be used as the key and which as value * by using the `$options` array, when not specified, it will use the results * of calling `primaryKey` and `displayField` respectively in this table: * - * {{{ + * ``` * $table->find('list', [ * 'idField' => 'name', * 'valueField' => 'age' * ]); - * }}} + * ``` * * Results can be put together in bigger groups when they share a property, you * can customize the property to use for grouping by setting `groupField`: * - * {{{ + * ``` * $table->find('list', [ * 'groupField' => 'category_id', * ]); - * }}} + * ``` * * When using a `groupField` results will be returned in this format: * - * {{{ + * ``` * [ * 'group_1' => [ * 1 => 'value for id 1', @@ -887,7 +887,7 @@ public function findAll(Query $query, array $options) * 4 => 'value for id 4' * ] * ] - * }}} + * ``` * * @param \Cake\ORM\Query $query The query to find with * @param array $options The options for the find @@ -926,12 +926,12 @@ public function findList(Query $query, array $options) * these defaults you need to provide the keys `idField` or `parentField` in * `$options`: * - * {{{ + * ``` * $table->find('threaded', [ * 'idField' => 'id', * 'parentField' => 'ancestor_id' * ]); - * }}} + * ``` * * @param \Cake\ORM\Query $query The query to find with * @param array $options The options to find with @@ -1099,25 +1099,25 @@ public function updateAll($fields, $conditions) * For example, if you wish to create a validation set called 'forSubscription', * you will need to create a method in your Table subclass as follows: * - * {{{ + * ``` * public function validationForSubscription($validator) { * return $validator * ->add('email', 'valid-email', ['rule' => 'email']) * ->add('password', 'valid', ['rule' => 'notEmpty']) * ->requirePresence('username'); * } - * }}} + * ``` * * Otherwise, you can build the object by yourself and store it in the Table object: * - * {{{ + * ``` * $validator = new \Cake\Validation\Validator($table); * $validator * ->add('email', 'valid-email', ['rule' => 'email']) * ->add('password', 'valid', ['rule' => 'notEmpty']) * ->allowEmpty('bio'); * $table->validator('forSubscription', $validator); - * }}} + * ``` * * You can implement the method in `validationDefault` in your Table subclass * should you wish to have a validation set that applies in cases where no other @@ -1243,7 +1243,7 @@ public function exists($conditions) * association in this table. It is possible to control what associations will * be saved and to pass additional option for saving them. * - * {{{ + * ``` * // Only save the comments association * $articles->save($entity, ['associated' => ['Comments']); * @@ -1260,7 +1260,7 @@ public function exists($conditions) * * // Save no associations * $articles->save($entity, ['associated' => false]); - * }}} + * ``` * */ public function save(EntityInterface $entity, $options = []) @@ -1770,45 +1770,45 @@ public function marshaller() * limit which associations are built, or include deeper associations * using the options parameter: * - * {{{ + * ``` * $article = $this->Articles->newEntity( * $this->request->data(), * ['associated' => ['Tags', 'Comments.Users']] * ); - * }}} + * ``` * * You can limit fields that will be present in the constructed entity by * passing the `fieldList` option, which is also accepted for associations: * - * {{{ + * ``` * $article = $this->Articles->newEntity($this->request->data(), [ * 'fieldList' => ['title', 'body'], * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']] * ] * ); - * }}} + * ``` * * The `fieldList` option lets remove or restrict input data from ending up in * the entity. If you'd like to relax the entity's default accessible fields, * you can use the `accessibleFields` option: * - * {{{ + * ``` * $article = $this->Articles->newEntity( * $this->request->data(), * ['accessibleFields' => ['protected_field' => true]] * ); - * }}} + * ``` * * By default, the data is validated before being passed to the new entity. In * the case of invalid fields, those will not be present in the resulting object. * The `validate` option can be used to disable validation on the passed data: * - * {{{ + * ``` * $article = $this->Articles->newEntity( * $this->request->data(), * ['validate' => false] * ); - * }}} + * ``` * * You can also pass the name of the validator to use in the `validate` option. * If `null` is passed to the first param of this function, no validation will @@ -1836,23 +1836,23 @@ public function newEntity($data = null, array $options = []) * limit which associations are built, or include deeper associations * using the options parameter: * - * {{{ + * ``` * $articles = $this->Articles->newEntities( * $this->request->data(), * ['associated' => ['Tags', 'Comments.Users']] * ); - * }}} + * ``` * * You can limit fields that will be present in the constructed entities by * passing the `fieldList` option, which is also accepted for associations: * - * {{{ + * ``` * $articles = $this->Articles->newEntities($this->request->data(), [ * 'fieldList' => ['title', 'body'], * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']] * ] * ); - * }}} + * ``` * */ public function newEntities(array $data, array $options = []) @@ -1874,23 +1874,23 @@ public function newEntities(array $data, array $options = []) * You can limit fields that will be present in the merged entity by * passing the `fieldList` option, which is also accepted for associations: * - * {{{ + * ``` * $article = $this->Articles->patchEntity($article, $this->request->data(), [ * 'fieldList' => ['title', 'body'], * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']] * ] * ); - * }}} + * ``` * * By default, the data is validated before being passed to the entity. In * the case of invalid fields, those will not be assigned to the entity. * The `validate` option can be used to disable validation on the passed data: * - * {{{ + * ``` * $article = $this->patchEntity($article, $this->request->data(),[ * 'validate' => false * ]); - * }}} + * ``` */ public function patchEntity(EntityInterface $entity, array $data, array $options = []) { @@ -1915,13 +1915,13 @@ public function patchEntity(EntityInterface $entity, array $data, array $options * You can limit fields that will be present in the merged entities by * passing the `fieldList` option, which is also accepted for associations: * - * {{{ + * ``` * $articles = $this->Articles->patchEntities($articles, $this->request->data(), [ * 'fieldList' => ['title', 'body'], * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']] * ] * ); - * }}} + * ``` */ public function patchEntities($entities, array $data, array $options = []) { @@ -1939,22 +1939,22 @@ public function patchEntities($entities, array $data, array $options = []) * * ### Example: * - * {{{ + * ``` * $validator->add('email', [ * 'unique' => ['rule' => 'validateUnique', 'provider' => 'table'] * ]) - * }}} + * ``` * * Unique validation can be scoped to the value of another column: * - * {{{ + * ``` * $validator->add('email', [ * 'unique' => [ * 'rule' => ['validateUnique', ['scope' => 'site_id']], * 'provider' => 'table' * ] * ]); - * }}} + * ``` * * In the above example, the email uniqueness will be scoped to only rows having * the same site_id. Scoping will only be used if the scoping field is present in diff --git a/TableRegistry.php b/TableRegistry.php index 585310ea..93d288ff 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -32,9 +32,9 @@ * will be used when creating instances. If you modify configuration after * an instance is made, the instances *will not* be updated. * - * {{{ + * ``` * TableRegistry::config('Users', ['table' => 'my_users']); - * }}} + * ``` * * Configuration data is stored *per alias* if you use the same table with * multiple aliases you will need to set configuration multiple times. @@ -46,9 +46,9 @@ * This is used to make the ORM use less memory and help make cyclic references easier * to solve. * - * {{{ + * ``` * $table = TableRegistry::get('Users', $config); - * }}} + * ``` * */ class TableRegistry From 36658f8951524a2f79070e52a6155846f94ea509 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Fri, 9 Jan 2015 22:14:05 +0100 Subject: [PATCH 0126/2059] Fixed ResultSet when using unbuffered results --- ResultSet.php | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/ResultSet.php b/ResultSet.php index b8befceb..6c6db7f0 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -143,10 +143,10 @@ public function __construct($query, $statement) $this->_hydrate = $this->_query->hydrate(); $this->_entityClass = $repository->entityClass(); $this->_useBuffering = $query->bufferResults(); - $this->count(); if ($this->_useBuffering) { - $this->_results = new SplFixedArray($this->_count); + $count = $this->count(); + $this->_results = new SplFixedArray($count); } } @@ -217,22 +217,26 @@ public function rewind() */ public function valid() { - if ($this->_index >= $this->_count) { + $valid = true; + if ($this->_useBuffering) { + $valid = $this->_index < $this->_count; + if ($valid && $this->_results[$this->_index] !== null) { + $this->_current = $this->_results[$this->_index]; + return true; + } + } + + if (!$valid) { if ($this->_statement !== null) { $this->_statement->closeCursor(); } return false; } - if ($this->_useBuffering && $this->_results[$this->_index] !== null) { - $this->_current = $this->_results[$this->_index]; - return true; - } - $this->_current = $this->_fetchResult(); $valid = $this->_current !== false; - if ($this->_useBuffering) { + if ($valid && $this->_useBuffering) { $this->_results[$this->_index] = $this->_current; } From f96f5bbd7748eb984f7650400b0b6991a204f576 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Fri, 9 Jan 2015 22:31:41 +0100 Subject: [PATCH 0127/2059] Using buffering in TreeBehavior::recover() This totally explains why in the past I could not understand how this worked at all! --- Behavior/TreeBehavior.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index b5c5b8ef..06711ea3 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -665,8 +665,7 @@ protected function _recoverTree($counter = 0, $parentId = null) ->select($pk) ->where([$parent . ' IS' => $parentId]) ->order($pk) - ->hydrate(false) - ->bufferResults(false); + ->hydrate(false); $leftCounter = $counter; foreach ($query as $row) { From 9b5c6905ea903c94ee4fa5a111f92b499286684d Mon Sep 17 00:00:00 2001 From: Bryan Crowe Date: Fri, 9 Jan 2015 19:47:36 -0500 Subject: [PATCH 0128/2059] Fix const docblock indentation --- Association.php | 56 +++++++++++++++++------------------ Association/BelongsToMany.php | 16 +++++----- Query.php | 24 +++++++-------- RulesChecker.php | 24 +++++++-------- 4 files changed, 60 insertions(+), 60 deletions(-) diff --git a/Association.php b/Association.php index 16c7c533..65519ef7 100644 --- a/Association.php +++ b/Association.php @@ -34,52 +34,52 @@ abstract class Association use ConventionsTrait; /** - * Strategy name to use joins for fetching associated records - * - * @var string - */ + * Strategy name to use joins for fetching associated records + * + * @var string + */ const STRATEGY_JOIN = 'join'; /** - * Strategy name to use a subquery for fetching associated records - * - * @var string - */ + * Strategy name to use a subquery for fetching associated records + * + * @var string + */ const STRATEGY_SUBQUERY = 'subquery'; /** - * Strategy name to use a select for fetching associated records - * - * @var string - */ + * Strategy name to use a select for fetching associated records + * + * @var string + */ const STRATEGY_SELECT = 'select'; /** - * Association type for one to one associations. - * - * @var string - */ + * Association type for one to one associations. + * + * @var string + */ const ONE_TO_ONE = 'oneToOne'; /** - * Association type for one to many associations. - * - * @var string - */ + * Association type for one to many associations. + * + * @var string + */ const ONE_TO_MANY = 'oneToMany'; /** - * Association type for many to many associations. - * - * @var string - */ + * Association type for many to many associations. + * + * @var string + */ const MANY_TO_MANY = 'manyToMany'; /** - * Association type for many to one associations. - * - * @var string - */ + * Association type for many to one associations. + * + * @var string + */ const MANY_TO_ONE = 'manyToOne'; /** diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index f1d36bec..2a5f526d 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -36,17 +36,17 @@ class BelongsToMany extends Association } /** - * Saving strategy that will only append to the links set - * - * @var string - */ + * Saving strategy that will only append to the links set + * + * @var string + */ const SAVE_APPEND = 'append'; /** - * Saving strategy that will replace the links with the provided set - * - * @var string - */ + * Saving strategy that will replace the links with the provided set + * + * @var string + */ const SAVE_REPLACE = 'replace'; /** diff --git a/Query.php b/Query.php index 54180827..7eaac3e8 100644 --- a/Query.php +++ b/Query.php @@ -40,24 +40,24 @@ class Query extends DatabaseQuery implements JsonSerializable } /** - * Indicates that the operation should append to the list - * - * @var int - */ + * Indicates that the operation should append to the list + * + * @var int + */ const APPEND = 0; /** - * Indicates that the operation should prepend to the list - * - * @var int - */ + * Indicates that the operation should prepend to the list + * + * @var int + */ const PREPEND = 1; /** - * Indicates that the operation should overwrite the list - * - * @var bool - */ + * Indicates that the operation should overwrite the list + * + * @var bool + */ const OVERWRITE = true; /** diff --git a/RulesChecker.php b/RulesChecker.php index c40255fb..a00f73e0 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -44,24 +44,24 @@ class RulesChecker { /** - * Indicates that the checking rules to apply are those used for creating entities - * - * @var string - */ + * Indicates that the checking rules to apply are those used for creating entities + * + * @var string + */ const CREATE = 'create'; /** - * Indicates that the checking rules to apply are those used for updating entities - * - * @var string - */ + * Indicates that the checking rules to apply are those used for updating entities + * + * @var string + */ const UPDATE = 'update'; /** - * Indicates that the checking rules to apply are those used for deleting entities - * - * @var string - */ + * Indicates that the checking rules to apply are those used for deleting entities + * + * @var string + */ const DELETE = 'delete'; /** From 07760ba6ba107e92aaff2185e468843afaa91ffe Mon Sep 17 00:00:00 2001 From: Bryan Crowe Date: Mon, 12 Jan 2015 15:14:03 -0500 Subject: [PATCH 0129/2059] Update copyright statements in docblocks --- Exception/MissingTableClassException.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Exception/MissingTableClassException.php b/Exception/MissingTableClassException.php index 7c95db40..cfd3c32b 100644 --- a/Exception/MissingTableClassException.php +++ b/Exception/MissingTableClassException.php @@ -8,7 +8,7 @@ * Licensed under The MIT License * Redistributions of files must retain the above copyright notice. * - * @copyright Copyright 2005-2013, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) * @since 3.0.0 * @license http://www.opensource.org/licenses/mit-license.php MIT License */ From ab3568051a58718f549070de61a37225af2cc334 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 11 Jan 2015 22:05:25 -0500 Subject: [PATCH 0130/2059] Fix fatal errors when cascaded deletes are used with MySQL. We cannot use unbuffered queries to delete has many associations as the nested delete cannot be excuted while the previous cursor is still open. Refs #5637 --- Association/DependentDeleteTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/DependentDeleteTrait.php b/Association/DependentDeleteTrait.php index bd3027de..517cb73c 100644 --- a/Association/DependentDeleteTrait.php +++ b/Association/DependentDeleteTrait.php @@ -44,7 +44,7 @@ public function cascadeDelete(EntityInterface $entity, array $options = []) $conditions = array_combine($foreignKey, $entity->extract($primaryKey)); if ($this->_cascadeCallbacks) { - $query = $this->find('all')->where($conditions)->bufferResults(false); + $query = $this->find('all')->where($conditions); foreach ($query as $related) { $table->delete($related, $options); } From e46d220e23e287b54a99b28c270d8da38be73fa4 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 12 Jan 2015 22:31:11 -0500 Subject: [PATCH 0131/2059] Fix errors when validateUnique is used with scope. Fix validateUnique() causing errors when used as a validator with additional scope. Fixes #5652 --- Table.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index ba71eac5..d6dba2a8 100644 --- a/Table.php +++ b/Table.php @@ -1961,11 +1961,15 @@ public function patchEntities($entities, array $data, array $options = []) * the data to be validated. * * @param mixed $value The value of column to be checked for uniqueness - * @param array $options The options array, optionally containing the 'scope' key + * @param array $context Either the options or validation context. + * @param array|null $options The options array, optionally containing the 'scope' key * @return bool true if the value is unique */ - public function validateUnique($value, array $options) + public function validateUnique($value, array $context, array $options = null) { + if ($options === null) { + $options = $context; + } $entity = new Entity( $options['data'], ['useSetters' => false, 'markNew' => $options['newRecord']] From 5613a8548bf65427960716ab0e46aedfbce33948 Mon Sep 17 00:00:00 2001 From: Patrick Conroy Date: Tue, 13 Jan 2015 11:11:36 -0500 Subject: [PATCH 0132/2059] Fixing existing translations I missed one of these somehow. This fixes an issue with saving data with non-default locales. --- Behavior/TranslateBehavior.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 924ef031..ec1d0159 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -482,8 +482,8 @@ protected function _bundleTranslatedFields($entity) */ protected function _findExistingTranslations($ruleSet) { - $target = TableRegistry::get($this->_config['translationTable']); - $association = $this->_table->association($target->alias()); + $targetAlias = Inflector::slug($this->_config['translationTable']); + $association = $this->_table->association($targetAlias); $query = $association->find() ->select(['id', 'num' => 0]) From 75502e4298bef46a891d1760fec0e50ca858359e Mon Sep 17 00:00:00 2001 From: Patrick Conroy Date: Tue, 13 Jan 2015 21:38:04 -0500 Subject: [PATCH 0133/2059] Inflecting slug with dash instead of underscore --- Behavior/TranslateBehavior.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index ec1d0159..72e458cf 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -118,7 +118,7 @@ public function initialize(array $config) public function setupFieldAssociations($fields, $table, $model, $strategy) { $targetTable = TableRegistry::get($table); - $targetAlias = Inflector::slug($table); + $targetAlias = Inflector::slug($table, '_'); $alias = $this->_table->alias(); $filter = $this->_config['onlyTranslated']; @@ -234,7 +234,7 @@ public function beforeSave(Event $event, Entity $entity, ArrayObject $options) { $locale = $entity->get('_locale') ?: $this->locale(); $table = $this->_config['translationTable']; - $targetAlias = Inflector::slug($table); + $targetAlias = Inflector::slug($table, '_'); $newOptions = [$targetAlias => ['validate' => false]]; $options['associated'] = $newOptions + $options['associated']; @@ -334,7 +334,7 @@ public function findTranslations(Query $query, array $options) { $locales = isset($options['locales']) ? $options['locales'] : []; $table = $this->_config['translationTable']; - $targetAlias = Inflector::slug($table); + $targetAlias = Inflector::slug($table, '_'); return $query ->contain([$targetAlias => function ($q) use ($locales, $targetAlias) { @@ -482,7 +482,7 @@ protected function _bundleTranslatedFields($entity) */ protected function _findExistingTranslations($ruleSet) { - $targetAlias = Inflector::slug($this->_config['translationTable']); + $targetAlias = Inflector::slug($this->_config['translationTable'], '_'); $association = $this->_table->association($targetAlias); $query = $association->find() From 8c64723da66992d38eba1438222ad5281ba01047 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Mon, 12 Jan 2015 22:15:46 +0100 Subject: [PATCH 0134/2059] Starting to use an object for representing an eagerloadable --- EagerLoadable.php | 127 ++++++++++++++++++++++++++++++++++++++++++++++ EagerLoader.php | 116 +++++++++++++++++++++++------------------- 2 files changed, 190 insertions(+), 53 deletions(-) create mode 100644 EagerLoadable.php diff --git a/EagerLoadable.php b/EagerLoadable.php new file mode 100644 index 00000000..b5c3b715 --- /dev/null +++ b/EagerLoadable.php @@ -0,0 +1,127 @@ +_name = $name; + $allowed = [ + 'associations', 'instance', 'config', 'canBeJoined', + 'aliasPath', 'propertyPath', 'forMatching' + ]; + foreach ($allowed as $property) { + if (isset($config[$property])) { + $this->{'_' . $property} = $config[$property]; + } + } + } + + public function addAssociation($name, EagerLoadable $association) + { + $this->_associations[$name] = $association; + } + + public function associations() + { + return $this->_associations; + } + + public function instance($instance = null) + { + if ($instance === null) { + return $this->_instance; + } + $this->_instance = $instance; + } + + public function aliasPath($path = null) + { + if ($path === null) { + return $this->_aliasPath; + } + $this->_aliasPath = $path; + } + + public function propertyPath($path = null) + { + if ($path === null) { + return $this->_propertyPath; + } + $this->_propertyPath = $path; + } + + public function canBeJoined($possible = null) + { + if ($possible === null) { + return $this->_canBeJoined; + } + $this->_canBeJoined = $possible; + } + + public function config(array $config = null) + { + if ($config === null) { + return $this->_config; + } + $this->_config = $config; + } + + public function forMatching($matching = null) + { + if ($matching === null) { + return $this->_forMatching; + } + $this->_forMatching = $matching; + } + + public function asContainArray() + { + $associations = []; + foreach ($this->_associations as $assoc) { + $associations += $assoc->asContainArray(); + } + $config = $this->_config; + if ($this->_forMatching !== null) { + $config = ['matching' => $this->_forMatching] + $config; + } + return [ + $this->_name => [ + 'associations' => $associations, + 'config' => $config + ] + ]; + } +} diff --git a/EagerLoader.php b/EagerLoader.php index 0f063939..6788f387 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -195,7 +195,7 @@ public function normalized(Table $repository) $contain = (array)$this->_containments; break; } - $contain[$alias] =& $this->_normalizeContain( + $contain[$alias] = $this->_normalizeContain( $repository, $alias, $options, @@ -279,13 +279,13 @@ public function attachAssociations(Query $query, Table $repository, $includeFiel return; } - foreach ($this->attachableAssociations($repository) as $options) { - $config = $options['config'] + [ - 'aliasPath' => $options['aliasPath'], - 'propertyPath' => $options['propertyPath'], + foreach ($this->attachableAssociations($repository) as $loadable) { + $config = $loadable->config() + [ + 'aliasPath' => $loadable->aliasPath(), + 'propertyPath' => $loadable->propertyPath(), 'includeFields' => $includeFields ]; - $options['instance']->attachTo($query, $config); + $loadable->instance()->attachTo($query, $config); } } @@ -344,7 +344,7 @@ public function externalAssociations(Table $repository) * @return array normalized associations * @throws \InvalidArgumentException When containments refer to associations that do not exist. */ - protected function &_normalizeContain(Table $parent, $alias, $options, $paths) + protected function _normalizeContain(Table $parent, $alias, $options, $paths) { $defaults = $this->_containOptions; $instance = $parent->association($alias); @@ -369,18 +369,22 @@ protected function &_normalizeContain(Table $parent, $alias, $options, $paths) 'propertyPath' => trim($paths['propertyPath'], '.') ]; $config['canBeJoined'] = $instance->canBeJoined($config['config']); + $eagerLoadable = new EagerLoadable($alias, $config); if ($config['canBeJoined']) { - $this->_aliasList[$paths['root']][$alias][] =& $config; + $this->_aliasList[$paths['root']][$alias][] = $eagerLoadable; } else { $paths['root'] = $config['aliasPath']; } foreach ($extra as $t => $assoc) { - $config['associations'][$t] =& $this->_normalizeContain($table, $t, $assoc, $paths); + $eagerLoadable->addAssociation( + $t, + $this->_normalizeContain($table, $t, $assoc, $paths) + ); } - return $config; + return $eagerLoadable; } /** @@ -394,14 +398,14 @@ protected function &_normalizeContain(Table $parent, $alias, $options, $paths) */ protected function _fixStrategies() { - foreach ($this->_aliasList as &$aliases) { - foreach ($aliases as $alias => &$configs) { + foreach ($this->_aliasList as $aliases) { + foreach ($aliases as $alias => $configs) { if (count($configs) < 2) { continue; } - foreach ($configs as &$config) { - if (strpos($config['aliasPath'], '.')) { - $this->_correctStrategy($config, $alias); + foreach ($configs as $loadable) { + if (strpos($loadable->aliasPath(), '.')) { + $this->_correctStrategy($loadable, $alias); } } } @@ -412,26 +416,23 @@ protected function _fixStrategies() * Changes the association fetching strategy if required because of duplicate * under the same direct associations chain * - * This function modifies the $config variable - * - * @param array &$config The association config + * @param \Cake\ORM\EagerLoader $loadable The association config * @param string $alias the name of the association to evaluate - * @return void|array - * @throws \RuntimeException if a duplicate association in the same chain is detected - * but is not possible to change the strategy due to conflicting settings + * @return void */ - protected function _correctStrategy(&$config, $alias) + protected function _correctStrategy($loadable, $alias) { - $currentStrategy = isset($config['config']['strategy']) ? - $config['config']['strategy'] : + $config = $loadable->config(); + $currentStrategy = isset($config['strategy']) ? + $config['strategy'] : 'join'; - if (!$config['canBeJoined'] || $currentStrategy !== 'join') { - return $config; + if (!$loadable->canBeJoined() || $currentStrategy !== 'join') { + return; } - $config['canBeJoined'] = false; - $config['config']['strategy'] = $config['instance']::STRATEGY_SELECT; + $config = $config['strategy'] = Association::STRATEGY_SELECT; + $loadable->canBeJoined(false); } /** @@ -447,15 +448,18 @@ protected function _resolveJoins($associations, $matching = []) $result = []; foreach ($matching as $table => $options) { $result[$table] = $options; - $result += $this->_resolveJoins($options['associations'], []); + $result += $this->_resolveJoins($options->associations(), []); } foreach ($associations as $table => $options) { $inMatching = isset($matching[$table]); - if (!$inMatching && $options['canBeJoined']) { + if (!$inMatching && $options->canBeJoined()) { $result[$table] = $options; - $result += $this->_resolveJoins($options['associations'], $inMatching ? $mathching[$table] : []); + $result += $this->_resolveJoins( + $options->associations(), + $inMatching ? $mathching[$table] : [] + ); } else { - $options['canBeJoined'] = false; + $options->canBeJoined(false); $this->_loadExternal[] = $options; } } @@ -481,21 +485,23 @@ public function loadExternal($query, $statement) $driver = $query->connection()->driver(); list($collected, $statement) = $this->_collectKeys($external, $query, $statement); foreach ($external as $meta) { - $contain = $meta['associations']; - $alias = $meta['instance']->source()->alias(); + $contain = $meta->associations(); + $instance = $meta->instance(); + $config = $meta->config(); + $alias = $instance->source()->alias(); - $requiresKeys = $meta['instance']->requiresKeys($meta['config']); + $requiresKeys = $instance->requiresKeys($config); if ($requiresKeys && empty($collected[$alias])) { continue; } $keys = isset($collected[$alias]) ? $collected[$alias] : null; - $f = $meta['instance']->eagerLoader( - $meta['config'] + [ + $f = $instance->eagerLoader( + $config + [ 'query' => $query, 'contain' => $contain, 'keys' => $keys, - 'nestKey' => $meta['aliasPath'] + 'nestKey' => $meta->aliasPath() ] ); $statement = new CallbackStatement($statement, $driver, $f); @@ -528,16 +534,20 @@ public function associationsMap($table) $visitor = function ($level, $matching = false) use (&$visitor, &$map) { foreach ($level as $assoc => $meta) { + $canBeJoined = $meta->canBeJoined(); + $instance = $meta->instance(); + $associations = $meta->associations(); + $forMatching = $meta->forMatching(); $map[] = [ 'alias' => $assoc, - 'instance' => $meta['instance'], - 'canBeJoined' => $meta['canBeJoined'], - 'entityClass' => $meta['instance']->target()->entityClass(), - 'nestKey' => $meta['canBeJoined'] ? $assoc : $meta['aliasPath'], - 'matching' => isset($meta['matching']) ? $meta['matching'] : $matching + 'instance' => $instance, + 'canBeJoined' => $canBeJoined, + 'entityClass' => $instance->target()->entityClass(), + 'nestKey' => $canBeJoined ? $assoc : $meta->aliasPath(), + 'matching' => $forMatching !== null ? $forMatching : $matching ]; - if ($meta['canBeJoined'] && !empty($meta['associations'])) { - $visitor($meta['associations'], $matching); + if ($canBeJoined && $associations) { + $visitor($associations, $matching); } } }; @@ -561,13 +571,12 @@ public function associationsMap($table) */ public function addToJoinsMap($alias, Association $assoc, $asMatching = false) { - $this->_joinsMap[$alias] = [ + $this->_joinsMap[$alias] = new EagerLoadable($alias, [ 'aliasPath' => $alias, 'instance' => $assoc, 'canBeJoined' => true, - 'matching' => $asMatching, - 'associations' => [] - ]; + 'forMatching' => $asMatching, + ]); } /** @@ -583,13 +592,14 @@ protected function _collectKeys($external, $query, $statement) { $collectKeys = []; foreach ($external as $meta) { - if (!$meta['instance']->requiresKeys($meta['config'])) { + $instance = $meta->instance(); + if (!$instance->requiresKeys($meta->config())) { continue; } - $source = $meta['instance']->source(); - $keys = $meta['instance']->type() === $meta['instance']::MANY_TO_ONE ? - (array)$meta['instance']->foreignKey() : + $source = $instance->source(); + $keys = $instance->type() === Association::MANY_TO_ONE ? + (array)$instance->foreignKey() : (array)$source->primaryKey(); $alias = $source->alias(); From a45dcb824ea1dd2151f349a70ef49c5aff520936 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Tue, 13 Jan 2015 19:54:59 +0100 Subject: [PATCH 0135/2059] Fixing most of the failing test --- EagerLoader.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/EagerLoader.php b/EagerLoader.php index 6788f387..dd8b4aef 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -228,6 +228,12 @@ protected function _reformatContain($associations, $original) $options = []; } + if ($options instanceof EagerLoadable) { + $options = $options->asContainArray(); + $table = key($options); + $options = current($options); + } + if (isset($this->_containOptions[$table])) { $pointer[$table] = $options; continue; From 4c32b6c4fa4040015bad15b79724daae64e33066 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Wed, 14 Jan 2015 20:27:17 +0100 Subject: [PATCH 0136/2059] Fixed another test by fixing a mistake in previous commits --- EagerLoader.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index dd8b4aef..26e66289 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -428,7 +428,7 @@ protected function _fixStrategies() */ protected function _correctStrategy($loadable, $alias) { - $config = $loadable->config(); + $config = $loadable->config(); $currentStrategy = isset($config['strategy']) ? $config['strategy'] : 'join'; @@ -437,7 +437,8 @@ protected function _correctStrategy($loadable, $alias) return; } - $config = $config['strategy'] = Association::STRATEGY_SELECT; + $config['strategy'] = Association::STRATEGY_SELECT; + $loadable->config($config); $loadable->canBeJoined(false); } From 3f83b9b86a2e5b138d2b18ea3c8ea8786e8c48b4 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Wed, 14 Jan 2015 20:45:03 +0100 Subject: [PATCH 0137/2059] Making sure contain and matching clashes are always resolved, fixes #5584 --- EagerLoader.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index 26e66289..36d2cfac 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -465,10 +465,15 @@ protected function _resolveJoins($associations, $matching = []) $options->associations(), $inMatching ? $mathching[$table] : [] ); - } else { - $options->canBeJoined(false); - $this->_loadExternal[] = $options; + continue; + } + + if ($inMatching) { + $this->_correctStrategy($options, $table); } + + $options->canBeJoined(false); + $this->_loadExternal[] = $options; } return $result; } From 4519f92bfec75af4675f23a8367237cd11ef4560 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Wed, 14 Jan 2015 20:57:43 +0100 Subject: [PATCH 0138/2059] Refactoring code so strategies are only corrected at the end --- EagerLoader.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index 36d2cfac..4b63fe38 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -203,7 +203,6 @@ public function normalized(Table $repository) ); } - $this->_fixStrategies(); return $this->_normalized = $contain; } @@ -311,6 +310,7 @@ public function attachableAssociations(Table $repository) { $contain = $this->normalized($repository); $matching = $this->_matching ? $this->_matching->normalized($repository) : []; + $this->_fixStrategies(); return $this->_resolveJoins($contain, $matching); } @@ -331,8 +331,7 @@ public function externalAssociations(Table $repository) return $this->_loadExternal; } - $contain = $this->normalized($repository); - $this->_resolveJoins($contain, []); + $this->attachableAssociations($repository); return $this->_loadExternal; } From 9e31323404b8eda527ae7383fd7eb59a6404fe50 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Wed, 14 Jan 2015 21:13:40 +0100 Subject: [PATCH 0139/2059] Cleaning up the code and fixing some doc blocks --- EagerLoader.php | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index 4b63fe38..1ce3fd3c 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -17,6 +17,7 @@ use Cake\Database\Statement\BufferedStatement; use Cake\Database\Statement\CallbackStatement; use Cake\ORM\Association; +use Cake\ORM\EagerLoadable; use Cake\ORM\Query; use Cake\ORM\Table; use Closure; @@ -113,6 +114,7 @@ class EagerLoader * - matching: Whether to inform the association class that it should filter the * main query by the results fetched by that class. * - joinType: For joinable associations, the SQL join type to use. + * - strategy: The loading strategy to use (join, select, subquery) * * @param array|string $associations list of table aliases to be queried. * When this method is called multiple times it will merge previous list with @@ -176,6 +178,10 @@ public function matching($assoc = null, callable $builder = null) * loaded for a table. The normalized array will restructure the original array * by sorting all associations under one key and special options under another. * + * Each of the levels of the associations tree will converted to a Cake\ORM\EagerLoadable + * object, that contains all the information required for the association objects + * to load the information from the database. + * * Additionally it will set an 'instance' key per association containing the * association instance from the corresponding source table * @@ -297,10 +303,7 @@ public function attachAssociations(Query $query, Table $repository, $includeFiel /** * Returns an array with the associations that can be fetched using a single query, * the array keys are the association aliases and the values will contain an array - * with the following keys: - * - * - instance: the association object instance - * - config: the options set for fetching such association + * with Cake\ORM\EagerLoadable objects. * * @param \Cake\ORM\Table $repository The table containing the associations to be * attached @@ -316,10 +319,7 @@ public function attachableAssociations(Table $repository) /** * Returns an array with the associations that need to be fetched using a - * separate query, each array value will contain the following keys: - * - * - instance: the association object instance - * - config: the options set for fetching such association + * separate query, each array value will contain a Cake\ORM\EagerLoadable object. * * @param \Cake\ORM\Table $repository The table containing the associations * to be loaded @@ -452,27 +452,24 @@ protected function _correctStrategy($loadable, $alias) protected function _resolveJoins($associations, $matching = []) { $result = []; - foreach ($matching as $table => $options) { - $result[$table] = $options; - $result += $this->_resolveJoins($options->associations(), []); + foreach ($matching as $table => $loadable) { + $result[$table] = $loadable; + $result += $this->_resolveJoins($loadable->associations(), []); } - foreach ($associations as $table => $options) { + foreach ($associations as $table => $loadable) { $inMatching = isset($matching[$table]); - if (!$inMatching && $options->canBeJoined()) { - $result[$table] = $options; - $result += $this->_resolveJoins( - $options->associations(), - $inMatching ? $mathching[$table] : [] - ); + if (!$inMatching && $loadable->canBeJoined()) { + $result[$table] = $loadable; + $result += $this->_resolveJoins($loadable->associations(), []); continue; } if ($inMatching) { - $this->_correctStrategy($options, $table); + $this->_correctStrategy($loadable, $table); } - $options->canBeJoined(false); - $this->_loadExternal[] = $options; + $loadable->canBeJoined(false); + $this->_loadExternal[] = $loadable; } return $result; } From b441a6985116c62630da6d62532f29381ef9a5a4 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Wed, 14 Jan 2015 21:34:32 +0100 Subject: [PATCH 0140/2059] Adding doc blocks to EagerLoadable --- EagerLoadable.php | 141 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) diff --git a/EagerLoadable.php b/EagerLoadable.php index b5c3b715..ad8e8416 100644 --- a/EagerLoadable.php +++ b/EagerLoadable.php @@ -15,26 +15,91 @@ namespace Cake\ORM; /** + * Represents a single level in the associations tree to be eagerly loaded + * for a specific query. This contains all the information required to + * fetch the results from the database from an associations and all its children + * levels. */ class EagerLoadable { + /** + * The name of the association to load. + * + * @var string + */ protected $_name; + /** + * A list of other associations to load from this level. + * + * @var array + */ protected $_associations = []; + /** + * The Association class instance to use for loading the records. + * + * @var \Cake\ORM\Association + */ protected $_instance; + /** + * A list of options to pass to the association object for loading + * the records. + * + * @var array + */ protected $_config = []; + /** + * A dotted separated string representing the path of associations + * that should be followed to fetch this level. + * + * @var string + */ protected $_aliasPath; + /** + * A dotted separated string representing the path of entity properties + * in which results for this level should be placed. + * + * @var string + */ protected $_propertyPath; + /** + * Weather or not this level can be fetched using a join. + * + * @var bool + */ protected $_canBeJoined = false; + /** + * Weather or not this level was meant for a "matching" fetch + * operation + * + * @var bool + */ protected $_forMatching; + /** + * Constructor. The $config parameted accepts the following array + * keys: + * + * - associations + * - instance + * - config + * - canBeJoined + * - aliasPath + * - propertyPath + * - forMatching + * + * The keys maps to the settable properties in this class. + * + * @param string $name The Association name. + * @param array $config The list of properties to set. + */ public function __construct($name, array $config = []) { $this->_name = $name; @@ -49,16 +114,37 @@ public function __construct($name, array $config = []) } } + /** + * Adds a new association to be loaded from this level. + * + * @param string $name The association name. + * @param \Cake\ORM\EagerLoadable $association The association to load. + * @return void + */ public function addAssociation($name, EagerLoadable $association) { $this->_associations[$name] = $association; } + /** + * Returns the Association class instance to use for loading the records. + * + * @return array + */ public function associations() { return $this->_associations; } + /** + * Sets the Association class instance to use for loading the records. + * + * If called with no arguments it returns the current + * value. + * + * @param \Cake\ORM\Association|null $instance The value to set. + * @return \Cake\ORM\Association|null + */ public function instance($instance = null) { if ($instance === null) { @@ -67,6 +153,16 @@ public function instance($instance = null) $this->_instance = $instance; } + /** + * Sets a dotted separated string representing the path of associations + * that should be followed to fetch this level. + * + * If called with no arguments it returns the current + * value. + * + * @param string|null $path The value to set. + * @return string|null + */ public function aliasPath($path = null) { if ($path === null) { @@ -75,6 +171,16 @@ public function aliasPath($path = null) $this->_aliasPath = $path; } + /** + * Sets a dotted separated string representing the path of entity properties + * in which results for this level should be placed. + * + * If called with no arguments it returns the current + * value. + * + * @param string|null $path The value to set. + * @return string|null + */ public function propertyPath($path = null) { if ($path === null) { @@ -83,6 +189,15 @@ public function propertyPath($path = null) $this->_propertyPath = $path; } + /** + * Sets weather or not this level can be fetched using a join. + * + * If called with no arguments it returns the current + * value. + * + * @param bool|null $possible The value to set. + * @return bool|null + */ public function canBeJoined($possible = null) { if ($possible === null) { @@ -91,6 +206,16 @@ public function canBeJoined($possible = null) $this->_canBeJoined = $possible; } + /** + * Sets the list of options to pass to the association object for loading + * the records. + * + * If called with no arguments it returns the current + * value. + * + * @param array|null $config The value to set. + * @return array|null + */ public function config(array $config = null) { if ($config === null) { @@ -99,6 +224,16 @@ public function config(array $config = null) $this->_config = $config; } + /** + * Sets weather or not this level was meant for a + * "matching" fetch operation. + * + * If called with no arguments it returns the current + * value. + * + * @param bool|null $matching The value to set. + * @return bool|null + */ public function forMatching($matching = null) { if ($matching === null) { @@ -107,6 +242,12 @@ public function forMatching($matching = null) $this->_forMatching = $matching; } + /** + * Returns a representation of this object that can be passed to + * Cake\ORM\EagerLoader::contain() + * + * @return array + */ public function asContainArray() { $associations = []; From fb7d42a9c36810098448e852dff8e7d7bb955437 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Wed, 14 Jan 2015 22:14:00 +0100 Subject: [PATCH 0141/2059] Fixing CS errors --- EagerLoader.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index 1ce3fd3c..fdb8919e 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -383,10 +383,10 @@ protected function _normalizeContain(Table $parent, $alias, $options, $paths) } foreach ($extra as $t => $assoc) { - $eagerLoadable->addAssociation( - $t, - $this->_normalizeContain($table, $t, $assoc, $paths) - ); + $eagerLoadable->addAssociation( + $t, + $this->_normalizeContain($table, $t, $assoc, $paths) + ); } return $eagerLoadable; From ef281e57b5651a55895f302cf5ed9b0b60932179 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Wed, 14 Jan 2015 21:21:17 -0500 Subject: [PATCH 0142/2059] Doc block updates. Make Eagerloadable internal. End developers do not directly interact with this class, as it is mostly an implementation detail of the eagerloader. --- EagerLoadable.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/EagerLoadable.php b/EagerLoadable.php index ad8e8416..3b4a5e02 100644 --- a/EagerLoadable.php +++ b/EagerLoadable.php @@ -19,6 +19,8 @@ * for a specific query. This contains all the information required to * fetch the results from the database from an associations and all its children * levels. + * + * @internal */ class EagerLoadable { @@ -69,14 +71,14 @@ class EagerLoadable protected $_propertyPath; /** - * Weather or not this level can be fetched using a join. + * Whether or not this level can be fetched using a join. * * @var bool */ protected $_canBeJoined = false; /** - * Weather or not this level was meant for a "matching" fetch + * Whether or not this level was meant for a "matching" fetch * operation * * @var bool @@ -154,7 +156,7 @@ public function instance($instance = null) } /** - * Sets a dotted separated string representing the path of associations + * Sets a dot separated string representing the path of associations * that should be followed to fetch this level. * * If called with no arguments it returns the current @@ -172,11 +174,10 @@ public function aliasPath($path = null) } /** - * Sets a dotted separated string representing the path of entity properties + * Sets a dot separated string representing the path of entity properties * in which results for this level should be placed. * - * If called with no arguments it returns the current - * value. + * If called with no arguments it returns the current value. * * @param string|null $path The value to set. * @return string|null @@ -190,10 +191,9 @@ public function propertyPath($path = null) } /** - * Sets weather or not this level can be fetched using a join. + * Sets whether or not this level can be fetched using a join. * - * If called with no arguments it returns the current - * value. + * If called with no arguments it returns the current value. * * @param bool|null $possible The value to set. * @return bool|null @@ -225,7 +225,7 @@ public function config(array $config = null) } /** - * Sets weather or not this level was meant for a + * Sets whether or not this level was meant for a * "matching" fetch operation. * * If called with no arguments it returns the current From b104dee2277a1758d99725caceb3332d1fe1628b Mon Sep 17 00:00:00 2001 From: Mark Story Date: Wed, 14 Jan 2015 21:30:37 -0500 Subject: [PATCH 0143/2059] Remove unused argument. --- EagerLoader.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index fdb8919e..3290e162 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -410,7 +410,7 @@ protected function _fixStrategies() } foreach ($configs as $loadable) { if (strpos($loadable->aliasPath(), '.')) { - $this->_correctStrategy($loadable, $alias); + $this->_correctStrategy($loadable); } } } @@ -422,10 +422,9 @@ protected function _fixStrategies() * under the same direct associations chain * * @param \Cake\ORM\EagerLoader $loadable The association config - * @param string $alias the name of the association to evaluate * @return void */ - protected function _correctStrategy($loadable, $alias) + protected function _correctStrategy($loadable) { $config = $loadable->config(); $currentStrategy = isset($config['strategy']) ? @@ -465,7 +464,7 @@ protected function _resolveJoins($associations, $matching = []) } if ($inMatching) { - $this->_correctStrategy($loadable, $table); + $this->_correctStrategy($loadable); } $loadable->canBeJoined(false); From 7afdb669313eba71dc957feb9eda819ee17e69bc Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Thu, 15 Jan 2015 09:14:27 +0100 Subject: [PATCH 0144/2059] Removing setters for prperties in EagerLoadable, fixing indentation Also removed a parameter in a function that is not used anymore --- EagerLoadable.php | 52 +++++++++++------------------------------------ EagerLoader.php | 11 +++++----- 2 files changed, 17 insertions(+), 46 deletions(-) diff --git a/EagerLoadable.php b/EagerLoadable.php index ad8e8416..a62934cf 100644 --- a/EagerLoadable.php +++ b/EagerLoadable.php @@ -137,56 +137,35 @@ public function associations() } /** - * Sets the Association class instance to use for loading the records. + * Gets the Association class instance to use for loading the records. * - * If called with no arguments it returns the current - * value. - * - * @param \Cake\ORM\Association|null $instance The value to set. * @return \Cake\ORM\Association|null */ - public function instance($instance = null) + public function instance() { - if ($instance === null) { - return $this->_instance; - } - $this->_instance = $instance; + return $this->_instance; } /** - * Sets a dotted separated string representing the path of associations + * Gets a dotted separated string representing the path of associations * that should be followed to fetch this level. * - * If called with no arguments it returns the current - * value. - * - * @param string|null $path The value to set. * @return string|null */ - public function aliasPath($path = null) + public function aliasPath() { - if ($path === null) { - return $this->_aliasPath; - } - $this->_aliasPath = $path; + return $this->_aliasPath; } /** - * Sets a dotted separated string representing the path of entity properties + * Gets a dotted separated string representing the path of entity properties * in which results for this level should be placed. * - * If called with no arguments it returns the current - * value. - * - * @param string|null $path The value to set. * @return string|null */ - public function propertyPath($path = null) + public function propertyPath() { - if ($path === null) { - return $this->_propertyPath; - } - $this->_propertyPath = $path; + return $this->_propertyPath; } /** @@ -225,21 +204,14 @@ public function config(array $config = null) } /** - * Sets weather or not this level was meant for a + * Gets weather or not this level was meant for a * "matching" fetch operation. * - * If called with no arguments it returns the current - * value. - * - * @param bool|null $matching The value to set. * @return bool|null */ - public function forMatching($matching = null) + public function forMatching() { - if ($matching === null) { - return $this->_forMatching; - } - $this->_forMatching = $matching; + return $this->_forMatching; } /** diff --git a/EagerLoader.php b/EagerLoader.php index fdb8919e..a41c6d98 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -404,13 +404,13 @@ protected function _normalizeContain(Table $parent, $alias, $options, $paths) protected function _fixStrategies() { foreach ($this->_aliasList as $aliases) { - foreach ($aliases as $alias => $configs) { + foreach ($aliases as $configs) { if (count($configs) < 2) { continue; } foreach ($configs as $loadable) { if (strpos($loadable->aliasPath(), '.')) { - $this->_correctStrategy($loadable, $alias); + $this->_correctStrategy($loadable); } } } @@ -421,11 +421,10 @@ protected function _fixStrategies() * Changes the association fetching strategy if required because of duplicate * under the same direct associations chain * - * @param \Cake\ORM\EagerLoader $loadable The association config - * @param string $alias the name of the association to evaluate + * @param \Cake\ORM\EagerLoadable $loadable The association config * @return void */ - protected function _correctStrategy($loadable, $alias) + protected function _correctStrategy($loadable) { $config = $loadable->config(); $currentStrategy = isset($config['strategy']) ? @@ -465,7 +464,7 @@ protected function _resolveJoins($associations, $matching = []) } if ($inMatching) { - $this->_correctStrategy($loadable, $table); + $this->_correctStrategy($loadable); } $loadable->canBeJoined(false); From c94aac05b3e824dda94eeb9e1817bdc11c02657c Mon Sep 17 00:00:00 2001 From: Patrick Conroy Date: Thu, 15 Jan 2015 16:46:54 -0500 Subject: [PATCH 0145/2059] Setting translationTable on the class to make it easier to work with --- Behavior/TranslateBehavior.php | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 72e458cf..bae186e8 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -23,7 +23,6 @@ use Cake\ORM\Query; use Cake\ORM\Table; use Cake\ORM\TableRegistry; -use Cake\Utility\Inflector; /** * This behavior provides a way to translate dynamic data by keeping translations @@ -55,6 +54,12 @@ class TranslateBehavior extends Behavior */ protected $_locale; + /** + * Instance of Table responsible for translating + * @var \Cake\ORM\Table + */ + protected $_translationTable; + /** * Default config * @@ -93,6 +98,7 @@ public function __construct(Table $table, array $config = []) */ public function initialize(array $config) { + $this->_translationTable = TableRegistry::get($this->_config['translationTable']); $this->setupFieldAssociations( $this->_config['fields'], $this->_config['translationTable'], @@ -117,8 +123,7 @@ public function initialize(array $config) */ public function setupFieldAssociations($fields, $table, $model, $strategy) { - $targetTable = TableRegistry::get($table); - $targetAlias = Inflector::slug($table, '_'); + $targetAlias = $this->_translationTable->alias(); $alias = $this->_table->alias(); $filter = $this->_config['onlyTranslated']; @@ -129,7 +134,7 @@ public function setupFieldAssociations($fields, $table, $model, $strategy) $fieldTable = TableRegistry::get($name, [ 'className' => $table, 'alias' => $name, - 'table' => $targetTable->table() + 'table' => $this->_translationTable->table() ]); } else { $fieldTable = TableRegistry::get($name); @@ -155,6 +160,7 @@ public function setupFieldAssociations($fields, $table, $model, $strategy) 'propertyName' => '_i18n', 'dependent' => true ]); + } /** @@ -233,9 +239,7 @@ public function beforeFind(Event $event, Query $query, $options) public function beforeSave(Event $event, Entity $entity, ArrayObject $options) { $locale = $entity->get('_locale') ?: $this->locale(); - $table = $this->_config['translationTable']; - $targetAlias = Inflector::slug($table, '_'); - $newOptions = [$targetAlias => ['validate' => false]]; + $newOptions = [$this->_translationTable->alias() => ['validate' => false]]; $options['associated'] = $newOptions + $options['associated']; $this->_bundleTranslatedFields($entity); @@ -251,7 +255,7 @@ public function beforeSave(Event $event, Entity $entity, ArrayObject $options) $key = $entity->get(current($primaryKey)); $model = $this->_table->alias(); - $preexistent = TableRegistry::get($table)->find() + $preexistent = $this->_translationTable->find() ->select(['id', 'field']) ->where(['field IN' => $fields, 'locale' => $locale, 'foreign_key' => $key, 'model' => $model]) ->bufferResults(false) @@ -333,9 +337,7 @@ public function locale($locale = null) public function findTranslations(Query $query, array $options) { $locales = isset($options['locales']) ? $options['locales'] : []; - $table = $this->_config['translationTable']; - $targetAlias = Inflector::slug($table, '_'); - + $targetAlias = $this->_translationTable->alias(); return $query ->contain([$targetAlias => function ($q) use ($locales, $targetAlias) { if ($locales) { @@ -482,8 +484,7 @@ protected function _bundleTranslatedFields($entity) */ protected function _findExistingTranslations($ruleSet) { - $targetAlias = Inflector::slug($this->_config['translationTable'], '_'); - $association = $this->_table->association($targetAlias); + $association = $this->_table->association($this->_translationTable->alias()); $query = $association->find() ->select(['id', 'num' => 0]) From 23176f1b1afe22db2133d49080685be780da845d Mon Sep 17 00:00:00 2001 From: Patrick Conroy Date: Thu, 15 Jan 2015 16:51:13 -0500 Subject: [PATCH 0146/2059] Fixing typo --- Behavior/TranslateBehavior.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index bae186e8..f24b6264 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -56,6 +56,7 @@ class TranslateBehavior extends Behavior /** * Instance of Table responsible for translating + * * @var \Cake\ORM\Table */ protected $_translationTable; From 5894706af7ef480a1bb74bd69233e196297f2bae Mon Sep 17 00:00:00 2001 From: Patrick Conroy Date: Thu, 15 Jan 2015 17:11:23 -0500 Subject: [PATCH 0147/2059] One more typo. --- Behavior/TranslateBehavior.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index f24b6264..3083bcde 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -161,7 +161,6 @@ public function setupFieldAssociations($fields, $table, $model, $strategy) 'propertyName' => '_i18n', 'dependent' => true ]); - } /** From 13c2b53036ba2290d02266bcc21dc4b429f3ca20 Mon Sep 17 00:00:00 2001 From: Patrick Conroy Date: Thu, 15 Jan 2015 18:20:29 -0500 Subject: [PATCH 0148/2059] Fixing a typo that I thought I fixed --- Behavior/TranslateBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 3083bcde..3fa6273a 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -56,7 +56,7 @@ class TranslateBehavior extends Behavior /** * Instance of Table responsible for translating - * + * * @var \Cake\ORM\Table */ protected $_translationTable; From b10a4d9cc79f55550202388c7b529eb05387fd86 Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 14 Jan 2015 13:53:11 +0100 Subject: [PATCH 0149/2059] Added Model.beforeMarshal event. --- Marshaller.php | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index ad767cd2..2f53ec4d 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -104,21 +104,18 @@ public function one(array $data, array $options = []) $propertyMap = $this->_buildPropertyMap($options); $schema = $this->_table->schema(); - $tableName = $this->_table->alias(); $entityClass = $this->_table->entityClass(); $entity = new $entityClass(); $entity->source($this->_table->alias()); - if (isset($data[$tableName])) { - $data = $data[$tableName]; - } - if (isset($options['accessibleFields'])) { foreach ((array)$options['accessibleFields'] as $key => $value) { $entity->accessible($key, $value); } } + $data = $this->_prepareData($data, $options); + $errors = $this->_validate($data, $options, true); $primaryKey = $schema->primaryKey(); $properties = []; @@ -185,6 +182,27 @@ protected function _validate($data, $options, $isNew) return $options['validate']->errors($data, $isNew); } + /** + * Returns data prepared to validate and marshall. + * + * @param array $data The data to prepare. + * @param array $options The options passed to this marshaller. + * @return array Prepared data. + */ + protected function _prepareData($data, $options) + { + $tableName = $this->_table->alias(); + + if (isset($data[$tableName])) { + $data = $data[$tableName]; + } + + $dataObject = new \ArrayObject($data); + $this->_table->dispatchEvent('Model.beforeMarshal', compact('dataObject', 'options')); + + return (array)$dataObject; + } + /** * Create a new sub-marshaller and marshal the associated data. * @@ -326,18 +344,15 @@ public function merge(EntityInterface $entity, array $data, array $options = []) { $options += ['validate' => true]; $propertyMap = $this->_buildPropertyMap($options); - $tableName = $this->_table->alias(); $isNew = $entity->isNew(); $keys = []; - if (isset($data[$tableName])) { - $data = $data[$tableName]; - } - if (!$isNew) { $keys = $entity->extract((array)$this->_table->primaryKey()); } + $data = $this->_prepareData($data, $options); + $errors = $this->_validate($data + $keys, $options, $isNew); $schema = $this->_table->schema(); $properties = []; From 207a88e804b02fa548875c991e1a74dc8e5f6b94 Mon Sep 17 00:00:00 2001 From: Patrick Conroy Date: Fri, 16 Jan 2015 15:48:20 -0500 Subject: [PATCH 0150/2059] Fixing i18n when two Tables have the same name Inflector::underscore is the only (Cake) way to ensure uniqueness in the TableRegistry. --- Behavior/TranslateBehavior.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 3fa6273a..374548eb 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -23,6 +23,7 @@ use Cake\ORM\Query; use Cake\ORM\Table; use Cake\ORM\TableRegistry; +use Cake\Utility\Inflector; /** * This behavior provides a way to translate dynamic data by keeping translations @@ -99,7 +100,16 @@ public function __construct(Table $table, array $config = []) */ public function initialize(array $config) { - $this->_translationTable = TableRegistry::get($this->_config['translationTable']); + $translationAlias = Inflector::slug($this->_config['translationTable'], '_'); + + if (!TableRegistry::exists($translationAlias)) { + $this->_translationTable = TableRegistry::get($translationAlias, [ + 'className' => $this->_config['translationTable'] + ]); + } else { + $this->_translationTable = TableRegistry::get($translationAlias); + } + $this->setupFieldAssociations( $this->_config['fields'], $this->_config['translationTable'], From 9e8f24061733ab1681b09489612dd618013d3711 Mon Sep 17 00:00:00 2001 From: ndm2 Date: Fri, 9 Jan 2015 15:05:44 +0100 Subject: [PATCH 0151/2059] Fix table callback argument types Fixes docblocks and `Model.beforeFind` event argument types, and adds a test to ensure that the callbacks receive the expected types of arguments. --- Query.php | 7 ++++++- Table.php | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Query.php b/Query.php index 83c2b06b..77ddc562 100644 --- a/Query.php +++ b/Query.php @@ -14,6 +14,7 @@ */ namespace Cake\ORM; +use ArrayObject; use Cake\Database\Query as DatabaseQuery; use Cake\Database\ValueBinder; use Cake\Datasource\QueryTrait; @@ -603,7 +604,11 @@ public function triggerBeforeFind() { if (!$this->_beforeFindFired && $this->_type === 'select') { $table = $this->repository(); - $table->dispatchEvent('Model.beforeFind', [$this, $this->_options, !$this->eagerLoaded()]); + $table->dispatchEvent('Model.beforeFind', [ + $this, + new ArrayObject($this->_options), + !$this->eagerLoaded() + ]); $this->_beforeFindFired = true; } } diff --git a/Table.php b/Table.php index d6dba2a8..768ffd56 100644 --- a/Table.php +++ b/Table.php @@ -95,7 +95,7 @@ * Fired before an entity is validated using the rules checker. By stopping this event, * you can return the final value of the rules checking operation. * - * - `afterRules(Event $event, Entity $entity,RulesChecker $rules, bool $result)` + * - `afterRules(Event $event, Entity $entity, ArrayObject $options, bool $result, string $operation)` * Fired after the rules have been checked on the entity. By stopping this event, * you can return the final value of the rules checking operation. * @@ -1295,7 +1295,7 @@ public function save(EntityInterface $entity, $options = []) * Performs the actual saving of an entity based on the passed options. * * @param \Cake\Datasource\EntityInterface $entity the entity to be saved - * @param array $options the options to use for the save operation + * @param \ArrayObject $options the options to use for the save operation * @return \Cake\Datasource\EntityInterface|bool * @throws \RuntimeException When an entity is missing some of the primary keys. */ From 24a1bf27100c8bf74166be6d1d9e4910b83b2294 Mon Sep 17 00:00:00 2001 From: ndm2 Date: Sun, 11 Jan 2015 16:38:19 +0100 Subject: [PATCH 0152/2059] Make beforeFind triggered by associations use ArrayObject too --- Association.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association.php b/Association.php index 65519ef7..b1079d55 100644 --- a/Association.php +++ b/Association.php @@ -611,7 +611,7 @@ protected function _dispatchBeforeFind($query) { $table = $this->target(); $options = $query->getOptions(); - $table->dispatchEvent('Model.beforeFind', [$query, $options, false]); + $table->dispatchEvent('Model.beforeFind', [$query, new \ArrayObject($options), false]); } /** From 183efa4da9a5bb3a556a1c9a10f332cb9a43087e Mon Sep 17 00:00:00 2001 From: Adam Taylor Date: Fri, 16 Jan 2015 21:25:17 -0700 Subject: [PATCH 0153/2059] Fix typos --- Association.php | 2 +- EagerLoadable.php | 2 +- EagerLoader.php | 2 +- RulesChecker.php | 4 ++-- Table.php | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Association.php b/Association.php index b1079d55..f312b3f0 100644 --- a/Association.php +++ b/Association.php @@ -588,7 +588,7 @@ public function updateAll($fields, $conditions) * @param mixed $conditions Conditions to be used, accepts anything Query::where() * can take. * @return bool Success Returns true if one or more rows are affected. - * @see \Cake\ORM\Table::delteAll() + * @see \Cake\ORM\Table::deleteAll() */ public function deleteAll($conditions) { diff --git a/EagerLoadable.php b/EagerLoadable.php index c3709a39..a775f00e 100644 --- a/EagerLoadable.php +++ b/EagerLoadable.php @@ -86,7 +86,7 @@ class EagerLoadable protected $_forMatching; /** - * Constructor. The $config parameted accepts the following array + * Constructor. The $config parameter accepts the following array * keys: * * - associations diff --git a/EagerLoader.php b/EagerLoader.php index a41c6d98..433adec9 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -525,7 +525,7 @@ public function loadExternal($query, $statement) * - canBeJoined: Whether or not the association will be loaded using a JOIN * - entityClass: The entity that should be used for hydrating the results * - nestKey: A dotted path that can be used to correctly insert the data into the results. - * - mathcing: Whether or not it is an association loaded through `matching()`. + * - matching: Whether or not it is an association loaded through `matching()`. * * @param \Cake\ORM\Table $table The table containing the association that * will be normalized diff --git a/RulesChecker.php b/RulesChecker.php index a00f73e0..8c25a663 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -23,7 +23,7 @@ * Contains logic for storing and checking rules on entities * * RulesCheckers are used by Table classes to ensure that the - * current entity state satifies the application logic and business rules. + * current entity state satisfies the application logic and business rules. * * RulesCheckers afford different rules to be applied in the create and update * scenario. @@ -31,7 +31,7 @@ * ### Adding rules * * Rules must be callable objects that return true/false depending on whether or - * not the rule has been satisified. You can use RulesChecker::add(), RulesChecker::addCreate(), + * not the rule has been satisfied. You can use RulesChecker::add(), RulesChecker::addCreate(), * RulesChecker::addUpdate() and RulesChecker::addDelete to add rules to a checker. * * ### Running checks diff --git a/Table.php b/Table.php index 768ffd56..9e92302c 100644 --- a/Table.php +++ b/Table.php @@ -922,7 +922,7 @@ public function findList(Query $query, array $options) * recursively nested inside the parent row values using the `children` property * * You can customize what fields are used for nesting results, by default the - * primary key and the `parent_id` fields are used. If you you wish to change + * primary key and the `parent_id` fields are used. If you wish to change * these defaults you need to provide the keys `idField` or `parentField` in * `$options`: * From ef5d981558dabb87985e836eff30137dec45dc3f Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 20 Jan 2015 21:42:28 -0500 Subject: [PATCH 0154/2059] Add documentation and event mapping for beforeMarshal event. Add event hooks and doc blocks for Model.beforeMarshal event. Refs #5704 --- Behavior.php | 1 + Table.php | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/Behavior.php b/Behavior.php index 224aecca..ac47e277 100644 --- a/Behavior.php +++ b/Behavior.php @@ -249,6 +249,7 @@ public function verifyConfig() public function implementedEvents() { $eventMap = [ + 'Model.beforeMarshal' => 'beforeMarshal', 'Model.beforeFind' => 'beforeFind', 'Model.beforeSave' => 'beforeSave', 'Model.afterSave' => 'afterSave', diff --git a/Table.php b/Table.php index 9e92302c..40fb9f65 100644 --- a/Table.php +++ b/Table.php @@ -1813,6 +1813,9 @@ public function marshaller() * You can also pass the name of the validator to use in the `validate` option. * If `null` is passed to the first param of this function, no validation will * be performed. + * + * You can use the `Model.beforeMarshal` event to modify request data + * before it is converted into entities. */ public function newEntity($data = null, array $options = []) { @@ -1854,6 +1857,8 @@ public function newEntity($data = null, array $options = []) * ); * ``` * + * You can use the `Model.beforeMarshal` event to modify request data + * before it is converted into entities. */ public function newEntities(array $data, array $options = []) { @@ -1891,6 +1896,9 @@ public function newEntities(array $data, array $options = []) * 'validate' => false * ]); * ``` + * + * You can use the `Model.beforeMarshal` event to modify request data + * before it is converted into entities. */ public function patchEntity(EntityInterface $entity, array $data, array $options = []) { @@ -1922,6 +1930,9 @@ public function patchEntity(EntityInterface $entity, array $data, array $options * ] * ); * ``` + * + * You can use the `Model.beforeMarshal` event to modify request data + * before it is converted into entities. */ public function patchEntities($entities, array $data, array $options = []) { @@ -2063,6 +2074,7 @@ public function buildRules(RulesChecker $rules) public function implementedEvents() { $eventMap = [ + 'Model.beforeMarshal' => 'beforeMarshal', 'Model.beforeFind' => 'beforeFind', 'Model.beforeSave' => 'beforeSave', 'Model.afterSave' => 'afterSave', From 585b60f7e30fc24f6091667fe0673e0794740543 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Wed, 21 Jan 2015 23:44:07 -0500 Subject: [PATCH 0155/2059] Make beforeMarhal $options an ArrayObject. Use consistent types across the various event methods. Refs #5711 --- Marshaller.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Marshaller.php b/Marshaller.php index 2f53ec4d..3e3d0a9b 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -198,6 +198,7 @@ protected function _prepareData($data, $options) } $dataObject = new \ArrayObject($data); + $options = new \ArrayObject($options); $this->_table->dispatchEvent('Model.beforeMarshal', compact('dataObject', 'options')); return (array)$dataObject; From 04672a6b037b71d3a795360907b01d86b5ee4508 Mon Sep 17 00:00:00 2001 From: antograssiot Date: Thu, 22 Jan 2015 09:17:45 +0100 Subject: [PATCH 0156/2059] fix psr2 cs --- Behavior/TimestampBehavior.php | 3 +-- Marshaller.php | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 401cfe5f..8e830a4b 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -99,8 +99,7 @@ public function handleEvent(Event $event, Entity $entity) sprintf('When should be one of "always", "new" or "existing". The passed value "%s" is invalid', $when) ); } - if ( - $when === 'always' || + if ($when === 'always' || ($when === 'new' && $new) || ($when === 'existing' && !$new) ) { diff --git a/Marshaller.php b/Marshaller.php index 2f53ec4d..2410a40f 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -371,8 +371,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) $converter = Type::build($columnType); $value = $converter->marshal($value); $isObject = is_object($value); - if ( - (!$isObject && $original === $value) || + if ((!$isObject && $original === $value) || ($isObject && $original == $value) ) { continue; From 2a3cb29fa5206e7094bdb269560f4a064109ab50 Mon Sep 17 00:00:00 2001 From: Walther Lalk Date: Fri, 23 Jan 2015 08:29:34 +0200 Subject: [PATCH 0157/2059] Add support for disabling of the existing check on save --- Table.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index 40fb9f65..7b48b6c5 100644 --- a/Table.php +++ b/Table.php @@ -1268,7 +1268,8 @@ public function save(EntityInterface $entity, $options = []) $options = new ArrayObject($options + [ 'atomic' => true, 'associated' => true, - 'checkRules' => true + 'checkRules' => true, + 'checkExisting' => true ]); if ($entity->errors()) { @@ -1303,7 +1304,7 @@ protected function _processSave($entity, $options) { $primaryColumns = (array)$this->primaryKey(); - if ($primaryColumns && $entity->isNew() && $entity->has($primaryColumns)) { + if ($options['checkExisting'] && $primaryColumns && $entity->isNew() && $entity->has($primaryColumns)) { $alias = $this->alias(); $conditions = []; foreach ($entity->extract($primaryColumns) as $k => $v) { From b2ffc816110077035b8a699f7956039786e40615 Mon Sep 17 00:00:00 2001 From: Walther Lalk Date: Fri, 23 Jan 2015 13:28:50 +0200 Subject: [PATCH 0158/2059] Add docblock for checkExisting --- Table.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Table.php b/Table.php index 7b48b6c5..f7a9a5d5 100644 --- a/Table.php +++ b/Table.php @@ -1203,6 +1203,8 @@ public function exists($conditions) * to be saved. It is possible to provide different options for saving on associated * table objects using this key by making the custom options the array value. * If false no associated records will be saved. (default: true) + * - checkExisting: Whether or not to check if the entity already exists, assuming that the + * entity is marked as not new, and the primary key has been set. * * ### Events * From f4c1e26f0444591dbad13846a1843fb9839ef3f5 Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 23 Jan 2015 15:21:41 +0100 Subject: [PATCH 0159/2059] Make use of modifiable $options from beforeMarshal --- Marshaller.php | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 9014270c..8691f82b 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -100,7 +100,8 @@ protected function _buildPropertyMap($options) */ public function one(array $data, array $options = []) { - $options += ['validate' => true]; + list($data, $options) = $this->_prepareDataAndOptions($data, $options); + $propertyMap = $this->_buildPropertyMap($options); $schema = $this->_table->schema(); @@ -114,8 +115,6 @@ public function one(array $data, array $options = []) } } - $data = $this->_prepareData($data, $options); - $errors = $this->_validate($data, $options, true); $primaryKey = $schema->primaryKey(); $properties = []; @@ -183,25 +182,26 @@ protected function _validate($data, $options, $isNew) } /** - * Returns data prepared to validate and marshall. + * Returns data and options prepared to validate and marshall. * * @param array $data The data to prepare. * @param array $options The options passed to this marshaller. - * @return array Prepared data. + * @return array An array containing prepared data and options. */ - protected function _prepareData($data, $options) + protected function _prepareDataAndOptions($data, $options) { - $tableName = $this->_table->alias(); + $options += ['validate' => true]; + $tableName = $this->_table->alias(); if (isset($data[$tableName])) { $data = $data[$tableName]; } - $dataObject = new \ArrayObject($data); + $data = new \ArrayObject($data); $options = new \ArrayObject($options); - $this->_table->dispatchEvent('Model.beforeMarshal', compact('dataObject', 'options')); + $this->_table->dispatchEvent('Model.beforeMarshal', compact('data', 'options')); - return (array)$dataObject; + return [(array)$data, (array)$options]; } /** @@ -343,7 +343,8 @@ protected function _loadBelongsToMany($assoc, $ids) */ public function merge(EntityInterface $entity, array $data, array $options = []) { - $options += ['validate' => true]; + list($data, $options) = $this->_prepareDataAndOptions($data, $options); + $propertyMap = $this->_buildPropertyMap($options); $isNew = $entity->isNew(); $keys = []; @@ -352,8 +353,6 @@ public function merge(EntityInterface $entity, array $data, array $options = []) $keys = $entity->extract((array)$this->_table->primaryKey()); } - $data = $this->_prepareData($data, $options); - $errors = $this->_validate($data + $keys, $options, $isNew); $schema = $this->_table->schema(); $properties = []; @@ -373,7 +372,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) $value = $converter->marshal($value); $isObject = is_object($value); if ((!$isObject && $original === $value) || - ($isObject && $original == $value) + ($isObject && $original == $value) ) { continue; } From 61ea505a12ab0f2cd4192e64124428400b7f0baf Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 23 Jan 2015 15:51:10 +0100 Subject: [PATCH 0160/2059] Fix CS --- Marshaller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index 8691f82b..5079492a 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -372,7 +372,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) $value = $converter->marshal($value); $isObject = is_object($value); if ((!$isObject && $original === $value) || - ($isObject && $original == $value) + ($isObject && $original == $value) ) { continue; } From 913238c5fbc9c7c7175b2a8536c28a6567cddd81 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 23 Jan 2015 22:52:45 -0500 Subject: [PATCH 0161/2059] Fix first() failing on unbuffered queries. Re-add first() to ResultSet. Because of how `LimitIterator` and unbuffered queries interact, valid() was being called multiple times, advancing the iterator position beyond the result set data. By having a specific first() on ResultSet we can ensure that the statement is closed after the first record is read. This of course makes ResultSet forget any additional length they may have. Refs #5720 --- ResultSet.php | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/ResultSet.php b/ResultSet.php index 6c6db7f0..6f675cfb 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -226,23 +226,36 @@ public function valid() } } - if (!$valid) { - if ($this->_statement !== null) { - $this->_statement->closeCursor(); - } - return false; - } - $this->_current = $this->_fetchResult(); $valid = $this->_current !== false; if ($valid && $this->_useBuffering) { $this->_results[$this->_index] = $this->_current; } + if (!$valid && $this->_statement !== null) { + $this->_statement->closeCursor(); + } return $valid; } + /** + * Get the first record from a result set. + * + * This method will also close the underlying statement cursor. + * + * @return array|object + */ + public function first() + { + foreach ($this as $result) { + if ($this->_statement) { + $this->_statement->closeCursor(); + } + return $result; + } + } + /** * Serializes a resultset. * From 889f3b8d92b58c0ae8e1d9fb4089abcf68c1ebf9 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 24 Jan 2015 23:08:13 -0500 Subject: [PATCH 0162/2059] Reduce scope of changes in previous commit. Revert some behavior for buffered queries as we don't want to close off the cursor for bufferred queries. --- ResultSet.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ResultSet.php b/ResultSet.php index 6f675cfb..8bfaf7cf 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -224,6 +224,9 @@ public function valid() $this->_current = $this->_results[$this->_index]; return true; } + if (!$valid) { + return $valid; + } } $this->_current = $this->_fetchResult(); @@ -249,7 +252,7 @@ public function valid() public function first() { foreach ($this as $result) { - if ($this->_statement) { + if ($this->_statement && !$this->_useBuffering) { $this->_statement->closeCursor(); } return $result; From 54338bafc3aabb8ef023a928042eb16a312ad6a6 Mon Sep 17 00:00:00 2001 From: Bryan Crowe Date: Mon, 26 Jan 2015 22:31:21 -0500 Subject: [PATCH 0163/2059] Fix method PSR-2 braces in docblock code examples --- Table.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index 40fb9f65..3247beba 100644 --- a/Table.php +++ b/Table.php @@ -273,7 +273,8 @@ public static function defaultConnectionName() * define validation and do any other initialization logic you need. * * ``` - * public function initialize(array $config) { + * public function initialize(array $config) + * { * $this->belongsTo('Users'); * $this->belongsToMany('Tagging.Tags'); * $this->primaryKey('something_else'); @@ -1100,7 +1101,8 @@ public function updateAll($fields, $conditions) * you will need to create a method in your Table subclass as follows: * * ``` - * public function validationForSubscription($validator) { + * public function validationForSubscription($validator) + * { * return $validator * ->add('email', 'valid-email', ['rule' => 'email']) * ->add('password', 'valid', ['rule' => 'notEmpty']) From 4a9b95a1f5293247c05a2e16630a2602e2f21ab0 Mon Sep 17 00:00:00 2001 From: Maycon Vinicius Date: Wed, 28 Jan 2015 16:00:53 -0200 Subject: [PATCH 0164/2059] Change method existsIn and isUnique, for translate message. --- RulesChecker.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/RulesChecker.php b/RulesChecker.php index 8c25a663..6c97e33e 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -298,8 +298,10 @@ public function checkDelete(EntityInterface $entity, array $options = []) * @param string $message The error message to show in case the rule does not pass. * @return callable */ - public function isUnique(array $fields, $message = 'This value is already in use') + public function isUnique(array $fields, $message = null) { + $message = $message?: __('This value is already in use'); + $errorField = current($fields); return $this->_addError(new IsUnique($fields), compact('errorField', 'message')); } @@ -324,8 +326,10 @@ public function isUnique(array $fields, $message = 'This value is already in use * @param string $message The error message to show in case the rule does not pass. * @return callable */ - public function existsIn($field, $table, $message = 'This value does not exist') + public function existsIn($field, $table, $message = null) { + $message = $message?: __('This value does not exist'); + $errorField = $field; return $this->_addError(new ExistsIn($field, $table), compact('errorField', 'message')); } From e143ee2af0596e28ef4ce9b778ff71b9b9d1ee1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Thu, 29 Jan 2015 01:22:19 +0100 Subject: [PATCH 0165/2059] Adding a method to get the depth of a tree node to the TreeBehavior. See https://github.com/cakephp/cakephp/issues/4553 --- Behavior/TreeBehavior.php | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 06711ea3..e4aef01b 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -61,7 +61,8 @@ class TreeBehavior extends Behavior 'moveUp' => 'moveUp', 'moveDown' => 'moveDown', 'recover' => 'recover', - 'removeFromTree' => 'removeFromTree' + 'removeFromTree' => 'removeFromTree', + 'getLevel' => 'getLevel' ], 'parent' => 'parent_id', 'left' => 'lft', @@ -792,4 +793,29 @@ protected function _getPrimaryKey() } return $this->_primaryKey; } + +/** + * Returns the depth level of a node in the tree. + * + * @param int|string $id + * @return int|bool Integer of the level or false if the node does not exist. + */ + public function getLevel($id) { + $config = $this->config(); + $entity = $this->_table->find('all') + ->select([$config['left'], $config['right']]) + ->where([$this->_getPrimaryKey() => $id]) + ->first(); + + if ($entity === null) { + return false; + } + + $query = $this->_table->find('all')->where([ + $config['left'] . ' <' => $entity[$config['left']], + $config['right'] . ' >'=> $entity[$config['right']] + ]); + + return $this->_scope($query)->count(); + } } From 3996b0f8337174d60bcdf7fe8879cf530ea1fa34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Thu, 29 Jan 2015 01:41:17 +0100 Subject: [PATCH 0166/2059] phpcs fixes. --- Behavior/TreeBehavior.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index e4aef01b..15e8ce4f 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -797,10 +797,11 @@ protected function _getPrimaryKey() /** * Returns the depth level of a node in the tree. * - * @param int|string $id + * @param int|string $id Primary key of the node. * @return int|bool Integer of the level or false if the node does not exist. */ - public function getLevel($id) { + public function getLevel($id) + { $config = $this->config(); $entity = $this->_table->find('all') ->select([$config['left'], $config['right']]) @@ -813,7 +814,7 @@ public function getLevel($id) { $query = $this->_table->find('all')->where([ $config['left'] . ' <' => $entity[$config['left']], - $config['right'] . ' >'=> $entity[$config['right']] + $config['right'] . ' >' => $entity[$config['right']] ]); return $this->_scope($query)->count(); From 97a8d393d5d4a500515b591c5242ff8b1d32b0e7 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Thu, 29 Jan 2015 09:53:32 +0100 Subject: [PATCH 0167/2059] Added fix for the subquery strategy using the wrong alias when comparing. Previously, the subquery strategy was naively assuming that the alias used for comparing could be derived from the original query, but the right thing to do was to use the alias from the association object itself. Fixes #5769 --- Association/SelectableAssociationTrait.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Association/SelectableAssociationTrait.php b/Association/SelectableAssociationTrait.php index d3703a77..c6a3e113 100644 --- a/Association/SelectableAssociationTrait.php +++ b/Association/SelectableAssociationTrait.php @@ -141,7 +141,7 @@ protected function _buildQuery($options) public function _addFilteringJoin($query, $key, $subquery) { $filter = []; - $aliasedTable = $subquery->repository()->alias(); + $aliasedTable = $this->source()->alias(); foreach ($subquery->clause('select') as $aliasedField => $field) { $filter[] = new IdentifierExpression($field); @@ -239,7 +239,7 @@ protected function _buildSubquery($query) $keys = (array)$this->foreignKey(); } - $fields = $query->aliasFields($keys); + $fields = $query->aliasFields($keys, $this->source()->alias()); return $filterQuery->select($fields, true); } From 36bfdd4e30f0c137429518227e2cc5f46712a725 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Thu, 29 Jan 2015 10:07:23 +0100 Subject: [PATCH 0168/2059] Subquery strategy was duplicating results in some cases after recent changes. Adding a distinct() modifier to the query solved this problem and retains the semantincs of the find operation. --- Association/SelectableAssociationTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/SelectableAssociationTrait.php b/Association/SelectableAssociationTrait.php index c6a3e113..f32f6ed4 100644 --- a/Association/SelectableAssociationTrait.php +++ b/Association/SelectableAssociationTrait.php @@ -240,7 +240,7 @@ protected function _buildSubquery($query) } $fields = $query->aliasFields($keys, $this->source()->alias()); - return $filterQuery->select($fields, true); + return $filterQuery->select($fields, true)->distinct(); } /** From 078e53da55d315f8b66f87b2861993ef0df20c54 Mon Sep 17 00:00:00 2001 From: Chris Burke Date: Thu, 29 Jan 2015 16:15:05 +0000 Subject: [PATCH 0169/2059] Allow configuring Table objects with Validator(s) --- Table.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Table.php b/Table.php index f7b05aca..f7657da4 100644 --- a/Table.php +++ b/Table.php @@ -213,6 +213,10 @@ class Table implements RepositoryInterface, EventListenerInterface * - eventManager: An instance of an event manager to use for internal events * - behaviors: A BehaviorRegistry. Generally not used outside of tests. * - associations: An AssociationCollection instance. + * - validator: A Validator instance which is assigned as the "default" + * validation set, or an associative array, where key is the name of the + * validation set and value the Validator instance. If a key is omitted, then + * the "default" validation set is assumed. * * @param array $config List of options for this table */ @@ -243,6 +247,16 @@ public function __construct(array $config = []) if (!empty($config['associations'])) { $associations = $config['associations']; } + if (!empty($config['validator'])) { + if (!is_array($config['validator'])) { + $this->validator(null, $config['validator']); + } else { + foreach ($config['validator'] as $key => $validator) { + $name = (!is_int($key)) ? $key : null; + $this->validator($name, $validator); + } + } + } $this->_eventManager = $eventManager ?: new EventManager(); $this->_behaviors = $behaviors ?: new BehaviorRegistry($this); $this->_associations = $associations ?: new AssociationCollection(); From fdec04393e7d711d6c133c9027f43d0909efafaf Mon Sep 17 00:00:00 2001 From: Maycon Vinicius Date: Thu, 29 Jan 2015 15:47:00 -0200 Subject: [PATCH 0170/2059] Change method for translate string. --- RulesChecker.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RulesChecker.php b/RulesChecker.php index 6c97e33e..91da6e9d 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -300,7 +300,7 @@ public function checkDelete(EntityInterface $entity, array $options = []) */ public function isUnique(array $fields, $message = null) { - $message = $message?: __('This value is already in use'); + $message = $message?: __n('cake', 'This value is already in use'); $errorField = current($fields); return $this->_addError(new IsUnique($fields), compact('errorField', 'message')); @@ -328,7 +328,7 @@ public function isUnique(array $fields, $message = null) */ public function existsIn($field, $table, $message = null) { - $message = $message?: __('This value does not exist'); + $message = $message?: __n('cake', 'This value does not exist'); $errorField = $field; return $this->_addError(new ExistsIn($field, $table), compact('errorField', 'message')); From 933ae1a34a389780f807bfe6bdd5b3c63877a825 Mon Sep 17 00:00:00 2001 From: Maycon Vinicius Date: Thu, 29 Jan 2015 15:47:00 -0200 Subject: [PATCH 0171/2059] Change method for translate string. --- RulesChecker.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RulesChecker.php b/RulesChecker.php index 6c97e33e..16bfa88b 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -300,7 +300,7 @@ public function checkDelete(EntityInterface $entity, array $options = []) */ public function isUnique(array $fields, $message = null) { - $message = $message?: __('This value is already in use'); + $message = $message?: __d('cake', 'This value is already in use'); $errorField = current($fields); return $this->_addError(new IsUnique($fields), compact('errorField', 'message')); @@ -328,7 +328,7 @@ public function isUnique(array $fields, $message = null) */ public function existsIn($field, $table, $message = null) { - $message = $message?: __('This value does not exist'); + $message = $message?: __d('cake', 'This value does not exist'); $errorField = $field; return $this->_addError(new ExistsIn($field, $table), compact('errorField', 'message')); From bcf3da082f8e249f5bbfa4c4d9408ba3b4d88039 Mon Sep 17 00:00:00 2001 From: Chris Burke Date: Thu, 29 Jan 2015 21:01:21 +0000 Subject: [PATCH 0172/2059] Remove numeric index support when configuring Tables with validators --- Table.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Table.php b/Table.php index f7657da4..f07f52d9 100644 --- a/Table.php +++ b/Table.php @@ -215,8 +215,7 @@ class Table implements RepositoryInterface, EventListenerInterface * - associations: An AssociationCollection instance. * - validator: A Validator instance which is assigned as the "default" * validation set, or an associative array, where key is the name of the - * validation set and value the Validator instance. If a key is omitted, then - * the "default" validation set is assumed. + * validation set and value the Validator instance. * * @param array $config List of options for this table */ @@ -251,8 +250,7 @@ public function __construct(array $config = []) if (!is_array($config['validator'])) { $this->validator(null, $config['validator']); } else { - foreach ($config['validator'] as $key => $validator) { - $name = (!is_int($key)) ? $key : null; + foreach ($config['validator'] as $name => $validator) { $this->validator($name, $validator); } } From 549bd957afbf7938961072328acbcf1dd89a1fc8 Mon Sep 17 00:00:00 2001 From: Chris Burke Date: Thu, 29 Jan 2015 21:05:14 +0000 Subject: [PATCH 0173/2059] First argument to Table::validator() should be 'default' --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index f07f52d9..932f6c87 100644 --- a/Table.php +++ b/Table.php @@ -248,7 +248,7 @@ public function __construct(array $config = []) } if (!empty($config['validator'])) { if (!is_array($config['validator'])) { - $this->validator(null, $config['validator']); + $this->validator('default', $config['validator']); } else { foreach ($config['validator'] as $name => $validator) { $this->validator($name, $validator); From 782e10a6f0a0cbc9d346b389e69b95ab85bf5490 Mon Sep 17 00:00:00 2001 From: Chris Burke Date: Thu, 29 Jan 2015 21:25:48 +0000 Subject: [PATCH 0174/2059] Move 'default' validation set name to a constant --- Table.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index 932f6c87..a420f10a 100644 --- a/Table.php +++ b/Table.php @@ -120,6 +120,13 @@ class Table implements RepositoryInterface, EventListenerInterface use EventManagerTrait; + /** + * Name of default validation set. + * + * @var string + */ + const VALIDATOR_DEFAULT = 'default'; + /** * Name of the table as it can be found in the database * @@ -248,7 +255,7 @@ public function __construct(array $config = []) } if (!empty($config['validator'])) { if (!is_array($config['validator'])) { - $this->validator('default', $config['validator']); + $this->validator(self::VALIDATOR_DEFAULT, $config['validator']); } else { foreach ($config['validator'] as $name => $validator) { $this->validator($name, $validator); @@ -1142,7 +1149,7 @@ public function updateAll($fields, $conditions) * use null to get a validator. * @return \Cake\Validation\Validator */ - public function validator($name = 'default', Validator $validator = null) + public function validator($name = self::VALIDATOR_DEFAULT, Validator $validator = null) { if ($validator === null && isset($this->_validators[$name])) { return $this->_validators[$name]; From 4b0fbfdb6948cda21e19595fc8ebf4167e6fd0db Mon Sep 17 00:00:00 2001 From: Chris Burke Date: Thu, 29 Jan 2015 22:21:59 +0000 Subject: [PATCH 0175/2059] Rename default validation constant to read better --- Table.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Table.php b/Table.php index a420f10a..0565c9f3 100644 --- a/Table.php +++ b/Table.php @@ -125,7 +125,7 @@ class Table implements RepositoryInterface, EventListenerInterface * * @var string */ - const VALIDATOR_DEFAULT = 'default'; + const DEFAULT_VALIDATOR = 'default'; /** * Name of the table as it can be found in the database @@ -255,7 +255,7 @@ public function __construct(array $config = []) } if (!empty($config['validator'])) { if (!is_array($config['validator'])) { - $this->validator(self::VALIDATOR_DEFAULT, $config['validator']); + $this->validator(self::DEFAULT_VALIDATOR, $config['validator']); } else { foreach ($config['validator'] as $name => $validator) { $this->validator($name, $validator); @@ -1149,7 +1149,7 @@ public function updateAll($fields, $conditions) * use null to get a validator. * @return \Cake\Validation\Validator */ - public function validator($name = self::VALIDATOR_DEFAULT, Validator $validator = null) + public function validator($name = self::DEFAULT_VALIDATOR, Validator $validator = null) { if ($validator === null && isset($this->_validators[$name])) { return $this->_validators[$name]; From 8c4d85eddfe2021e9384f899f9286fd72466ff7a Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 29 Jan 2015 22:02:08 -0500 Subject: [PATCH 0176/2059] Make getLevel() accept an entity. This makes all the methods in TreeBehavior consistent in that they all accept an entity. --- Behavior/TreeBehavior.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 15e8ce4f..edb3979b 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -797,15 +797,20 @@ protected function _getPrimaryKey() /** * Returns the depth level of a node in the tree. * - * @param int|string $id Primary key of the node. + * @param int|string|\Cake\Datasource\EntityInterface $entity The entity or primary key get the level of. * @return int|bool Integer of the level or false if the node does not exist. */ - public function getLevel($id) + public function getLevel($entity) { + $primaryKey = $this->_getPrimaryKey(); + $id = $entity; + if (!is_scalar($entity)) { + $id = $entity->get($primaryKey); + } $config = $this->config(); $entity = $this->_table->find('all') ->select([$config['left'], $config['right']]) - ->where([$this->_getPrimaryKey() => $id]) + ->where([$primaryKey => $id]) ->first(); if ($entity === null) { From 797c4c3fa8af80169d2aff6120518556164caf5d Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 30 Jan 2015 09:43:01 +0530 Subject: [PATCH 0177/2059] Translate messages only if I18n package is available. --- RulesChecker.php | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/RulesChecker.php b/RulesChecker.php index 16bfa88b..62896bb2 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -99,6 +99,13 @@ class RulesChecker */ protected $_options = []; + /** + * Whether or not to use I18n functions for translating default error messages + * + * @var bool + */ + protected $_useI18n = false; + /** * Constructor. Takes the options to be passed to all rules. * @@ -107,6 +114,7 @@ class RulesChecker public function __construct(array $options) { $this->_options = $options; + $this->_useI18n = function_exists('__d'); } /** @@ -300,7 +308,13 @@ public function checkDelete(EntityInterface $entity, array $options = []) */ public function isUnique(array $fields, $message = null) { - $message = $message?: __d('cake', 'This value is already in use'); + if (!$message) { + if ($this->_useI18n) { + $message = __d('cake', 'This value is already in use'); + } else { + $message = 'This value is already in use'; + } + } $errorField = current($fields); return $this->_addError(new IsUnique($fields), compact('errorField', 'message')); @@ -328,7 +342,13 @@ public function isUnique(array $fields, $message = null) */ public function existsIn($field, $table, $message = null) { - $message = $message?: __d('cake', 'This value does not exist'); + if (!$message) { + if ($this->_useI18n) { + $message = __d('cake', 'This value does not exist'); + } else { + $message = 'This value does not exist'; + } + } $errorField = $field; return $this->_addError(new ExistsIn($field, $table), compact('errorField', 'message')); From c0a2b6e2a0ff6328427c32a5f15567a05284ca21 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 30 Jan 2015 21:12:04 -0500 Subject: [PATCH 0178/2059] Use interface instead of scalar check. Checking the interface is more proper and less likely to go wrong. --- Behavior/TreeBehavior.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index edb3979b..5bdfd0bd 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -14,6 +14,7 @@ */ namespace Cake\ORM\Behavior; +use Cake\Datasource\EntityInterface; use Cake\Datasource\Exception\RecordNotFoundException; use Cake\Event\Event; use Cake\ORM\Behavior; @@ -804,7 +805,7 @@ public function getLevel($entity) { $primaryKey = $this->_getPrimaryKey(); $id = $entity; - if (!is_scalar($entity)) { + if ($entity instanceof EntityInterface) { $id = $entity->get($primaryKey); } $config = $this->config(); From facdd5a2a3e5bfa7a2b1c5594349cb12cdfaf014 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 30 Jan 2015 22:37:17 -0500 Subject: [PATCH 0179/2059] Don't include duplicate keys when eagerly loading When eagerly loading associations with secondary queries, the ORM should only include each value once. This will only work in the simple primary key case, as scanning through composite key results can be pretty expensive. The main goal/advantage of this is that for large applications we can avoid parameter limitations in SQLServer/PDO for more cases. Refs #5778 --- Association.php | 2 +- EagerLoader.php | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Association.php b/Association.php index f312b3f0..12f94729 100644 --- a/Association.php +++ b/Association.php @@ -827,7 +827,7 @@ abstract public function type(); * the source table. * * The required way of passing related source records is controlled by "strategy" - * By default the subquery strategy is used, which requires a query on the source + * When the subquery strategy is used it will require a query on the source table. * When using the select strategy, the list of primary keys will be used. * * Returns a closure that should be run for each record returned in a specific diff --git a/EagerLoader.php b/EagerLoader.php index 433adec9..2e77c256 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -491,6 +491,7 @@ public function loadExternal($query, $statement) $driver = $query->connection()->driver(); list($collected, $statement) = $this->_collectKeys($external, $query, $statement); + foreach ($external as $meta) { $contain = $meta->associations(); $instance = $meta->instance(); @@ -501,8 +502,8 @@ public function loadExternal($query, $statement) if ($requiresKeys && empty($collected[$alias])) { continue; } - $keys = isset($collected[$alias]) ? $collected[$alias] : null; + $f = $instance->eagerLoader( $config + [ 'query' => $query, @@ -642,14 +643,15 @@ protected function _groupKeys($statement, $collectKeys) while ($result = $statement->fetch('assoc')) { foreach ($collectKeys as $parts) { // Missed joins will have null in the results. - if ($parts[2] && !isset($result[$parts[1][0]])) { + if ($parts[2] === true && !isset($result[$parts[1][0]])) { continue; } - if ($parts[2]) { + if ($parts[2] === true) { $keys[$parts[0]][] = $result[$parts[1][0]]; continue; } + // Handle composite keys. $collected = []; foreach ($parts[1] as $key) { $collected[] = $result[$key]; @@ -658,6 +660,12 @@ protected function _groupKeys($statement, $collectKeys) } } + foreach ($collectKeys as $parts) { + if ($parts[2] === true && isset($keys[$parts[0]])) { + $keys[$parts[0]] = array_unique($keys[$parts[0]]); + } + } + $statement->rewind(); return $keys; } From 7eb9b735ea0c595dcbb921325ba9233d2fdd47b6 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sat, 31 Jan 2015 12:33:21 +0100 Subject: [PATCH 0180/2059] Initial fix for conflict between contain and matching. Currently it only works for MySQL --- EagerLoader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EagerLoader.php b/EagerLoader.php index 433adec9..9e705360 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -435,7 +435,7 @@ protected function _correctStrategy($loadable) return; } - $config['strategy'] = Association::STRATEGY_SELECT; + $config['strategy'] = Association::STRATEGY_SUBQUERY; $loadable->config($config); $loadable->canBeJoined(false); } From 089255e8070107d5b909c7ed310d9a73e9c77093 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sat, 31 Jan 2015 13:58:15 +0100 Subject: [PATCH 0181/2059] Postgres and SQL Server require an order clause for subqueries having a distinct --- Association/SelectableAssociationTrait.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Association/SelectableAssociationTrait.php b/Association/SelectableAssociationTrait.php index f32f6ed4..f199fd0d 100644 --- a/Association/SelectableAssociationTrait.php +++ b/Association/SelectableAssociationTrait.php @@ -240,7 +240,17 @@ protected function _buildSubquery($query) } $fields = $query->aliasFields($keys, $this->source()->alias()); - return $filterQuery->select($fields, true)->distinct(); + $filterQuery->select($fields, true)->distinct(); + + $order = $filterQuery->clause('order'); + if ($order) { + $order->iterateParts(function ($dir, $field) use ($filterQuery) { + $filterQuery->select(is_int($field) ? $dir : $field); + return $dir; + }); + } + + return $filterQuery; } /** From 886d84b4a22a3d726cbafd55e3798f96f756b6ee Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sat, 31 Jan 2015 14:02:13 +0100 Subject: [PATCH 0182/2059] Now going back to the SELECT strategy so it can be fixed as well --- EagerLoader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EagerLoader.php b/EagerLoader.php index 9e705360..433adec9 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -435,7 +435,7 @@ protected function _correctStrategy($loadable) return; } - $config['strategy'] = Association::STRATEGY_SUBQUERY; + $config['strategy'] = Association::STRATEGY_SELECT; $loadable->config($config); $loadable->canBeJoined(false); } From b8a58ed0f98578c1f06b8cd12bc6c3dda2fb3489 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sat, 31 Jan 2015 22:14:03 +0100 Subject: [PATCH 0183/2059] Fixed eagerloading with matching and contain involving belongsTo associations --- EagerLoader.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index 433adec9..a34e63df 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -496,13 +496,14 @@ public function loadExternal($query, $statement) $instance = $meta->instance(); $config = $meta->config(); $alias = $instance->source()->alias(); + $path = $meta->aliasPath(); $requiresKeys = $instance->requiresKeys($config); - if ($requiresKeys && empty($collected[$alias])) { + if ($requiresKeys && empty($collected[$path][$alias])) { continue; } - $keys = isset($collected[$alias]) ? $collected[$alias] : null; + $keys = isset($collected[$path][$alias]) ? $collected[$path][$alias] : null; $f = $instance->eagerLoader( $config + [ 'query' => $query, @@ -614,7 +615,7 @@ protected function _collectKeys($external, $query, $statement) foreach ($keys as $key) { $pkFields[] = key($query->aliasField($key, $alias)); } - $collectKeys[$alias] = [$alias, $pkFields, count($pkFields) === 1]; + $collectKeys[$meta->aliasPath()] = [$alias, $pkFields, count($pkFields) === 1]; } if (empty($collectKeys)) { @@ -640,13 +641,13 @@ protected function _groupKeys($statement, $collectKeys) { $keys = []; while ($result = $statement->fetch('assoc')) { - foreach ($collectKeys as $parts) { + foreach ($collectKeys as $nestKey => $parts) { // Missed joins will have null in the results. if ($parts[2] && !isset($result[$parts[1][0]])) { continue; } if ($parts[2]) { - $keys[$parts[0]][] = $result[$parts[1][0]]; + $keys[$nestKey][$parts[0]][] = $result[$parts[1][0]]; continue; } @@ -654,7 +655,7 @@ protected function _groupKeys($statement, $collectKeys) foreach ($parts[1] as $key) { $collected[] = $result[$key]; } - $keys[$parts[0]][] = $collected; + $keys[$nestKey][$parts[0]][] = $collected; } } From c2e4487138b428985ec505430dba560f96f13f5d Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 1 Feb 2015 12:29:29 +0530 Subject: [PATCH 0184/2059] Allow specifying name for application rules. --- RulesChecker.php | 41 ++++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/RulesChecker.php b/RulesChecker.php index 62896bb2..d78e6ba4 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -131,13 +131,14 @@ public function __construct(array $options) * * @param callable $rule A callable function or object that will return whether * the entity is valid or not. + * @param string $name The alias for a rule. * @param array $options List of extra options to pass to the rule callable as * second argument. * @return $this */ - public function add(callable $rule, array $options = []) + public function add(callable $rule, $name = null, array $options = []) { - $this->_rules[] = $this->_addError($rule, $options); + $this->_rules[] = $this->_addError($rule, $name, $options); return $this; } @@ -154,13 +155,14 @@ public function add(callable $rule, array $options = []) * * @param callable $rule A callable function or object that will return whether * the entity is valid or not. + * @param string $name The alias for a rule. * @param array $options List of extra options to pass to the rule callable as * second argument. * @return $this */ - public function addCreate(callable $rule, array $options = []) + public function addCreate(callable $rule, $name = null, array $options = []) { - $this->_createRules[] = $this->_addError($rule, $options); + $this->_createRules[] = $this->_addError($rule, $name, $options); return $this; } @@ -177,13 +179,14 @@ public function addCreate(callable $rule, array $options = []) * * @param callable $rule A callable function or object that will return whether * the entity is valid or not. + * @param string $name The alias for a rule. * @param array $options List of extra options to pass to the rule callable as * second argument. * @return $this */ - public function addUpdate(callable $rule, array $options = []) + public function addUpdate(callable $rule, $name = null, array $options = []) { - $this->_updateRules[] = $this->_addError($rule, $options); + $this->_updateRules[] = $this->_addError($rule, $name, $options); return $this; } @@ -200,13 +203,14 @@ public function addUpdate(callable $rule, array $options = []) * * @param callable $rule A callable function or object that will return whether * the entity is valid or not. + * @param string $name The alias for a rule. * @param array $options List of extra options to pass to the rule callable as * second argument. * @return $this */ - public function addDelete(callable $rule, array $options = []) + public function addDelete(callable $rule, $name = null, array $options = []) { - $this->_deleteRules[] = $this->_addError($rule, $options); + $this->_deleteRules[] = $this->_addError($rule, $name, $options); return $this; } @@ -317,7 +321,7 @@ public function isUnique(array $fields, $message = null) } $errorField = current($fields); - return $this->_addError(new IsUnique($fields), compact('errorField', 'message')); + return $this->_addError(new IsUnique($fields), 'isUnique', compact('errorField', 'message')); } /** @@ -351,7 +355,7 @@ public function existsIn($field, $table, $message = null) } $errorField = $field; - return $this->_addError(new ExistsIn($field, $table), compact('errorField', 'message')); + return $this->_addError(new ExistsIn($field, $table), 'existsIn', compact('errorField', 'message')); } /** @@ -359,12 +363,18 @@ public function existsIn($field, $table, $message = null) * property in the entity is marked as invalid. * * @param callable $rule The rule to decorate + * @param string $name The alias for a rule. * @param array $options The options containing the error message and field * @return callable */ - protected function _addError($rule, $options) + protected function _addError($rule, $name, $options) { - return function ($entity, $scope) use ($rule, $options) { + if (is_array($name)) { + $options = $name; + $name = null; + } + + return function ($entity, $scope) use ($rule, $name, $options) { $pass = $rule($entity, $options + $scope); if ($pass || empty($options['errorField'])) { @@ -372,7 +382,12 @@ protected function _addError($rule, $options) } $message = isset($options['message']) ? $options['message'] : 'invalid'; - $entity->errors($options['errorField'], (array)$message); + if ($name) { + $message = [$name => $message]; + } else { + $message = (array)$message; + } + $entity->errors($options['errorField'], $message); return $pass; }; } From 7fe601c5fac8f853fae8d72e83d3adbf4526dd40 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 1 Feb 2015 13:40:04 +0530 Subject: [PATCH 0185/2059] Fix docblock example. First arg of RulesChecker::isUnique() must be an array. --- RulesChecker.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RulesChecker.php b/RulesChecker.php index 62896bb2..b6d364c8 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -299,7 +299,7 @@ public function checkDelete(EntityInterface $entity, array $options = []) * ### Example: * * ``` - * $rules->add($rules->isUnique('email', 'The email should be unique')); + * $rules->add($rules->isUnique(['email'], 'The email should be unique')); * ``` * * @param array $fields The list of fields to check for uniqueness. From 626ddffd007c12b0161aed483911165baa41cd10 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 1 Feb 2015 23:41:02 +0530 Subject: [PATCH 0186/2059] Prefix name keys used by core with underscore. --- RulesChecker.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RulesChecker.php b/RulesChecker.php index d78e6ba4..f4dd3a59 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -321,7 +321,7 @@ public function isUnique(array $fields, $message = null) } $errorField = current($fields); - return $this->_addError(new IsUnique($fields), 'isUnique', compact('errorField', 'message')); + return $this->_addError(new IsUnique($fields), '_isUnique', compact('errorField', 'message')); } /** @@ -355,7 +355,7 @@ public function existsIn($field, $table, $message = null) } $errorField = $field; - return $this->_addError(new ExistsIn($field, $table), 'existsIn', compact('errorField', 'message')); + return $this->_addError(new ExistsIn($field, $table), '_existsIn', compact('errorField', 'message')); } /** From 469cc6e0af2076de582f4bfe95449b2ecddcba06 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 1 Feb 2015 23:53:08 +0530 Subject: [PATCH 0187/2059] Indent docblock. --- Behavior/TreeBehavior.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 5bdfd0bd..1de5a06e 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -795,12 +795,12 @@ protected function _getPrimaryKey() return $this->_primaryKey; } -/** - * Returns the depth level of a node in the tree. - * - * @param int|string|\Cake\Datasource\EntityInterface $entity The entity or primary key get the level of. - * @return int|bool Integer of the level or false if the node does not exist. - */ + /** + * Returns the depth level of a node in the tree. + * + * @param int|string|\Cake\Datasource\EntityInterface $entity The entity or primary key get the level of. + * @return int|bool Integer of the level or false if the node does not exist. + */ public function getLevel($entity) { $primaryKey = $this->_getPrimaryKey(); From 5173dceac13e458485e639847addfa9e5abc34fb Mon Sep 17 00:00:00 2001 From: Patrick Conroy Date: Mon, 2 Feb 2015 11:58:58 -0500 Subject: [PATCH 0188/2059] Starting to get fieldConditions in place for i18n --- Behavior/TranslateBehavior.php | 36 ++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 374548eb..70c87d70 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -77,7 +77,8 @@ class TranslateBehavior extends Behavior 'defaultLocale' => '', 'model' => '', 'onlyTranslated' => false, - 'strategy' => 'subquery' + 'strategy' => 'subquery', + 'fieldConditions' => ['model' => ''] ]; /** @@ -110,10 +111,12 @@ public function initialize(array $config) $this->_translationTable = TableRegistry::get($translationAlias); } + $this->config('fieldConditions.model', $this->config('model') ?: $this->config('fieldConditions.model') ?: $this->_table->alias()); + $this->setupFieldAssociations( $this->_config['fields'], $this->_config['translationTable'], - $this->_config['model'] ? $this->_config['model'] : $this->_table->alias(), + $this->_config['fieldConditions'], $this->_config['strategy'] ); } @@ -127,12 +130,12 @@ public function initialize(array $config) * * @param array $fields list of fields to create associations for * @param string $table the table name to use for storing each field translation - * @param string $model the model field value + * @param array $fieldConditions conditions for finding fields * @param string $strategy the strategy used in the _i18n association * * @return void */ - public function setupFieldAssociations($fields, $table, $model, $strategy) + public function setupFieldAssociations($fields, $table, $fieldConditions, $strategy) { $targetAlias = $this->_translationTable->alias(); $alias = $this->_table->alias(); @@ -140,7 +143,17 @@ public function setupFieldAssociations($fields, $table, $model, $strategy) foreach ($fields as $field) { $name = $alias . '_' . $field . '_translation'; - + $conditions = [ + $name . '.model' => $fieldConditions['model'], + $name . '.field' => $field, + ]; + foreach ($fieldConditions as $fieldName => $fieldValue) { + if (is_numeric($fieldName)) { + $conditions[] = $name . '.' . $fieldValue; + } else { + $conditions[$name . '.' . $fieldName] = $fieldValue; + } + } if (!TableRegistry::exists($name)) { $fieldTable = TableRegistry::get($name, [ 'className' => $table, @@ -155,10 +168,7 @@ public function setupFieldAssociations($fields, $table, $model, $strategy) 'targetTable' => $fieldTable, 'foreignKey' => 'foreign_key', 'joinType' => $filter ? 'INNER' : 'LEFT', - 'conditions' => [ - $name . '.model' => $model, - $name . '.field' => $field, - ], + 'conditions' => $conditions, 'propertyName' => $field . '_translation' ]); } @@ -167,7 +177,7 @@ public function setupFieldAssociations($fields, $table, $model, $strategy) 'className' => $table, 'foreignKey' => 'foreign_key', 'strategy' => $strategy, - 'conditions' => ["$targetAlias.model" => $model], + 'conditions' => ["$targetAlias.model" => $fieldConditions['model']], 'propertyName' => '_i18n', 'dependent' => true ]); @@ -263,7 +273,7 @@ public function beforeSave(Event $event, Entity $entity, ArrayObject $options) $fields = array_keys($values); $primaryKey = (array)$this->_table->primaryKey(); $key = $entity->get(current($primaryKey)); - $model = $this->_table->alias(); + $model = $this->config('fieldConditions.model'); $preexistent = $this->_translationTable->find() ->select(['id', 'field']) @@ -469,14 +479,14 @@ protected function _bundleTranslatedFields($entity) } $results = $this->_findExistingTranslations($find); - $alias = $this->_table->alias(); + $model = $this->config('fieldConditions.model'); foreach ($find as $i => $translation) { if (!empty($results[$i])) { $contents[$i]->set('id', $results[$i], ['setter' => false]); $contents[$i]->isNew(false); } else { - $translation['model'] = $alias; + $translation['model'] = $model; $contents[$i]->set($translation, ['setter' => false, 'guard' => false]); $contents[$i]->isNew(true); } From 158415592acca7959bdb76f512f8cdafde0ef536 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Mon, 2 Feb 2015 19:12:44 -0430 Subject: [PATCH 0189/2059] An attempt to fix errors in SQL server --- Association/SelectableAssociationTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/SelectableAssociationTrait.php b/Association/SelectableAssociationTrait.php index f199fd0d..91a44d26 100644 --- a/Association/SelectableAssociationTrait.php +++ b/Association/SelectableAssociationTrait.php @@ -245,7 +245,7 @@ protected function _buildSubquery($query) $order = $filterQuery->clause('order'); if ($order) { $order->iterateParts(function ($dir, $field) use ($filterQuery) { - $filterQuery->select(is_int($field) ? $dir : $field); + $filterQuery->select(new IdentifierExpression(is_int($field) ? $dir : $field)); return $dir; }); } From 00e90ae71314fef15b44c46749182cbc37b90335 Mon Sep 17 00:00:00 2001 From: Patrick Conroy Date: Tue, 3 Feb 2015 00:41:13 -0500 Subject: [PATCH 0190/2059] Changing key to conditions + adding test --- Behavior/TranslateBehavior.php | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 70c87d70..8ebaff6d 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -78,7 +78,7 @@ class TranslateBehavior extends Behavior 'model' => '', 'onlyTranslated' => false, 'strategy' => 'subquery', - 'fieldConditions' => ['model' => ''] + 'conditions' => ['model' => ''] ]; /** @@ -111,12 +111,12 @@ public function initialize(array $config) $this->_translationTable = TableRegistry::get($translationAlias); } - $this->config('fieldConditions.model', $this->config('model') ?: $this->config('fieldConditions.model') ?: $this->_table->alias()); + $this->config('conditions.model', $this->config('model') ?: $this->config('conditions.model') ?: $this->_table->alias()); $this->setupFieldAssociations( $this->_config['fields'], $this->_config['translationTable'], - $this->_config['fieldConditions'], + $this->_config['conditions'], $this->_config['strategy'] ); } @@ -130,7 +130,7 @@ public function initialize(array $config) * * @param array $fields list of fields to create associations for * @param string $table the table name to use for storing each field translation - * @param array $fieldConditions conditions for finding fields + * @param array $conditions conditions for finding fields * @param string $strategy the strategy used in the _i18n association * * @return void @@ -148,11 +148,7 @@ public function setupFieldAssociations($fields, $table, $fieldConditions, $strat $name . '.field' => $field, ]; foreach ($fieldConditions as $fieldName => $fieldValue) { - if (is_numeric($fieldName)) { - $conditions[] = $name . '.' . $fieldValue; - } else { - $conditions[$name . '.' . $fieldName] = $fieldValue; - } + $conditions[$name . '.' . $fieldName] = $fieldValue; } if (!TableRegistry::exists($name)) { $fieldTable = TableRegistry::get($name, [ @@ -273,7 +269,7 @@ public function beforeSave(Event $event, Entity $entity, ArrayObject $options) $fields = array_keys($values); $primaryKey = (array)$this->_table->primaryKey(); $key = $entity->get(current($primaryKey)); - $model = $this->config('fieldConditions.model'); + $model = $this->config('conditions.model'); $preexistent = $this->_translationTable->find() ->select(['id', 'field']) @@ -479,7 +475,7 @@ protected function _bundleTranslatedFields($entity) } $results = $this->_findExistingTranslations($find); - $model = $this->config('fieldConditions.model'); + $model = $this->config('conditions.model'); foreach ($find as $i => $translation) { if (!empty($results[$i])) { From 2be997336d82c776e7a662ecc0a3456d88bb604c Mon Sep 17 00:00:00 2001 From: antograssiot Date: Tue, 3 Feb 2015 19:59:33 +0100 Subject: [PATCH 0191/2059] add missing backtick --- Marshaller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index 9014270c..3b03148f 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -399,7 +399,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) } /** - * Merges each of the elements from `$data` into each of the entities in `$entities + * Merges each of the elements from `$data` into each of the entities in `$entities` * and recursively does the same for each of the association names passed in * `$options`. When merging associations, if an entity is not present in the parent * entity for a given association, a new one will be created. From b790ab4cd24319b76c2b48f517739fded45cc567 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 3 Feb 2015 22:18:36 -0500 Subject: [PATCH 0192/2059] Fix errors in SQLServer 2008 Because SQLServer 2008 doesn't have native pagination we don't want to try and select the _cake_row_num_ field which is an expression. Generally if the query is ordered by a subquery or expression object we probably don't want to select that. Refs #5817 --- Association/SelectableAssociationTrait.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Association/SelectableAssociationTrait.php b/Association/SelectableAssociationTrait.php index 91a44d26..ad2656d1 100644 --- a/Association/SelectableAssociationTrait.php +++ b/Association/SelectableAssociationTrait.php @@ -14,6 +14,7 @@ */ namespace Cake\ORM\Association; +use Cake\Database\ExpressionInterface; use Cake\Database\Expression\IdentifierExpression; use Cake\Database\Expression\TupleComparison; @@ -245,7 +246,10 @@ protected function _buildSubquery($query) $order = $filterQuery->clause('order'); if ($order) { $order->iterateParts(function ($dir, $field) use ($filterQuery) { - $filterQuery->select(new IdentifierExpression(is_int($field) ? $dir : $field)); + $col = is_int($field) ? $dir : $field; + if (!($col instanceof ExpressionInterface)) { + $filterQuery->select(new IdentifierExpression($col)); + } return $dir; }); } From 82478c2359445cfb7abd99d21fe2b46988cd9fca Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Wed, 4 Feb 2015 11:19:36 -0430 Subject: [PATCH 0193/2059] Using Group By instead of Distinct as it has less shortcomings --- Association/SelectableAssociationTrait.php | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/Association/SelectableAssociationTrait.php b/Association/SelectableAssociationTrait.php index ad2656d1..4499d315 100644 --- a/Association/SelectableAssociationTrait.php +++ b/Association/SelectableAssociationTrait.php @@ -241,19 +241,7 @@ protected function _buildSubquery($query) } $fields = $query->aliasFields($keys, $this->source()->alias()); - $filterQuery->select($fields, true)->distinct(); - - $order = $filterQuery->clause('order'); - if ($order) { - $order->iterateParts(function ($dir, $field) use ($filterQuery) { - $col = is_int($field) ? $dir : $field; - if (!($col instanceof ExpressionInterface)) { - $filterQuery->select(new IdentifierExpression($col)); - } - return $dir; - }); - } - + $filterQuery->select($fields, true)->group(array_values($fields)); return $filterQuery; } From 83813a1666349364a0192ced882f25cdd0c55312 Mon Sep 17 00:00:00 2001 From: Patrick Conroy Date: Wed, 4 Feb 2015 14:39:37 -0500 Subject: [PATCH 0194/2059] Changing param name --- Behavior/TranslateBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 8ebaff6d..f424ad4d 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -130,7 +130,7 @@ public function initialize(array $config) * * @param array $fields list of fields to create associations for * @param string $table the table name to use for storing each field translation - * @param array $conditions conditions for finding fields + * @param array $fieldConditions conditions for finding fields * @param string $strategy the strategy used in the _i18n association * * @return void From 4fe5705e373e0de43ea0d685bba47f9669939f40 Mon Sep 17 00:00:00 2001 From: Patrick Conroy Date: Wed, 4 Feb 2015 14:54:32 -0500 Subject: [PATCH 0195/2059] Breaking out model for conditions --- Behavior/TranslateBehavior.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index f424ad4d..90cc5de1 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -110,8 +110,15 @@ public function initialize(array $config) } else { $this->_translationTable = TableRegistry::get($translationAlias); } - - $this->config('conditions.model', $this->config('model') ?: $this->config('conditions.model') ?: $this->_table->alias()); + + if ($this->config('model')) { + $model = $this->config('model'); + } elseif ($this->config('conditions.model')) { + $model = $this->config('conditions.model'); + } else { + $model = $this->_table->alias(); + } + $this->config('conditions.model', $model); $this->setupFieldAssociations( $this->_config['fields'], From 7548972464c68adad5b61ab2ed603bc8d8b5f6fc Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Thu, 5 Feb 2015 17:30:34 +0100 Subject: [PATCH 0196/2059] Fix doc blocks --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index f7b05aca..130b8f13 100644 --- a/Table.php +++ b/Table.php @@ -573,7 +573,7 @@ public function behaviors() * Check if a behavior with the given alias has been loaded. * * @param string $name The behavior alias to check. - * @return array + * @return bool */ public function hasBehavior($name) { From 23b4525fbf9cfd00984a40e2d3fa77e5450ead33 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 5 Feb 2015 09:13:00 -0500 Subject: [PATCH 0197/2059] Update events to use new methods. The attach()/detach() methods are deprecated and the internals should use the new methods. --- BehaviorRegistry.php | 2 +- Table.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 006206af..46876a56 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -117,7 +117,7 @@ protected function _create($class, $alias, $config) $instance = new $class($this->_table, $config); $enable = isset($config['enabled']) ? $config['enabled'] : true; if ($enable) { - $this->eventManager()->attach($instance); + $this->eventManager()->on($instance); } $methods = $this->_getMethods($instance, $class, $alias); $this->_methodMap += $methods['methods']; diff --git a/Table.php b/Table.php index f7b05aca..89926698 100644 --- a/Table.php +++ b/Table.php @@ -248,7 +248,7 @@ public function __construct(array $config = []) $this->_associations = $associations ?: new AssociationCollection(); $this->initialize($config); - $this->_eventManager->attach($this); + $this->_eventManager->on($this); $this->dispatchEvent('Model.initialize'); } From 475ebf21b16998deddfa68028fea97cc152a69b3 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 7 Feb 2015 19:43:31 +0530 Subject: [PATCH 0198/2059] Allow setting level (depth) of tree nodes on save. --- Behavior/TreeBehavior.php | 82 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 1de5a06e..c764a730 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -68,7 +68,8 @@ class TreeBehavior extends Behavior 'parent' => 'parent_id', 'left' => 'lft', 'right' => 'rght', - 'scope' => null + 'scope' => null, + 'level' => null ]; /** @@ -88,6 +89,7 @@ public function beforeSave(Event $event, Entity $entity) $parent = $entity->get($config['parent']); $primaryKey = $this->_getPrimaryKey(); $dirty = $entity->dirty($config['parent']); + $level = $config['level']; if ($isNew && $parent) { if ($entity->get($primaryKey[0]) == $parent) { @@ -99,20 +101,92 @@ public function beforeSave(Event $event, Entity $entity) $entity->set($config['left'], $edge); $entity->set($config['right'], $edge + 1); $this->_sync(2, '+', ">= {$edge}"); + + if ($level) { + $entity->set($config[$level], $parentNode[$level] + 1); + } + return; } if ($isNew && !$parent) { $edge = $this->_getMax(); $entity->set($config['left'], $edge + 1); $entity->set($config['right'], $edge + 2); + + if ($level) { + $entity->set($config[$level], 0); + } + return; } if (!$isNew && $dirty && $parent) { $this->_setParent($entity, $parent); + + if ($level) { + $parentNode = $this->_getNode($parent); + $entity->set($config[$level], $parentNode[$level] + 1); + } + return; } if (!$isNew && $dirty && !$parent) { $this->_setAsRoot($entity); + + if ($level) { + $entity->set($config[$level], 0); + } + } + } + + /** + * After save listener. + * + * Manages updating level of descendents of currently saved entity. + * + * @param \Cake\Event\Event $event The beforeSave event that was fired + * @param \Cake\ORM\Entity $entity the entity that is going to be saved + * @return void + */ + public function afterSave(Event $event, Entity $entity) + { + if (!$this->_config['level'] || $entity->isNew()) { + return; + } + + $this->_setChildrenLevel($entity); + } + + /** + * Set level for descendents. + * + * @param \Cake\ORM\Entity $entity + * @return void + */ + protected function _setChildrenLevel(Entity $entity) + { + $config = $this->config(); + + if ($entity->get($config['left']) + 1 === $entity->get($config['right'])) { + return; + } + + $primaryKey = $this->_getPrimaryKey(); + $primaryKeyValue = $entity->get($primaryKey); + $depths = [$primaryKeyValue => $entity->get($config['level'])]; + + $children = $this->_table->find('children', [ + 'for' => $primaryKeyValue, + 'fields' => [$this->_getPrimaryKey(), $config['parent'], $config['level']], + 'order' => $config['left'] + ]); + + foreach ($children as $node) { + $parentIdValue = $node->get($config['parent']); + $depth = $depths[$parentIdValue] + 1; + $depths[$node->get($primaryKey)] = $depth; + + $node->set($config['level'], $depth); + $this->_table->save($node, ['checkRules' => false, 'atomic' => false]); } } @@ -624,9 +698,13 @@ protected function _getNode($id) $config = $this->config(); list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']]; $primaryKey = $this->_getPrimaryKey(); + $fields = [$parent, $left, $right]; + if ($config['level']) { + $fields[] = $config['level']; + } $node = $this->_scope($this->_table->find()) - ->select([$parent, $left, $right]) + ->select($fields) ->where([$this->_table->alias() . '.' . $primaryKey => $id]) ->first(); From 8a5b25f921625921c1e1a46c26330cd626a39e9d Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 8 Feb 2015 09:10:04 +0530 Subject: [PATCH 0199/2059] Fix CS error --- Behavior/TreeBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index c764a730..7ce20088 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -159,7 +159,7 @@ public function afterSave(Event $event, Entity $entity) /** * Set level for descendents. * - * @param \Cake\ORM\Entity $entity + * @param \Cake\ORM\Entity $entity The entity whose descendents need to be updated. * @return void */ protected function _setChildrenLevel(Entity $entity) From bae7eee0d6d7ed16442cc8cf92baae3f5a6247fc Mon Sep 17 00:00:00 2001 From: AD7six Date: Sun, 8 Feb 2015 17:16:35 +0000 Subject: [PATCH 0200/2059] Accurately set the lft and rght fields as you go Needs similar updates to moveUp --- Behavior/TreeBehavior.php | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 7ce20088..b993d82f 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -651,6 +651,8 @@ protected function _moveDown($node, $number) } $edge = $this->_getMax(); + $width = $node->{$right} - $node->{$left}; + while ($number-- > 0) { list($nodeLeft, $nodeRight) = array_values($node->extract([$left, $right])); @@ -671,18 +673,15 @@ protected function _moveDown($node, $number) $this->_sync($nextNode->{$left} - $nodeLeft, '-', "BETWEEN {$nextNode->{$left}} AND {$nextNode->{$right}}"); $this->_sync($edge - $nodeLeft - ($nextNode->{$right} - $nextNode->{$left}), '-', "> {$edge}"); - $newLeft = $edge + 1; - if ($newLeft >= $nextNode->{$left} || $newLeft <= $nextNode->{$right}) { - $newLeft -= $nextNode->{$left} - $nodeLeft; - } - $newLeft -= $nextNode->{$right} - $nextNode->{$left} - 1; + $move = $nextNode->{$right} - $node->{$right}; - $node->set($left, $newLeft); - $node->set($right, $newLeft + ($nodeRight - $nodeLeft)); + $node->set($right, $node->{$left} + $width + $move); + $node->set($left, $node->{$left} + $move); } $node->dirty($left, false); $node->dirty($right, false); + return $node; } From 960be7aa19d008d7127933a6853684ea87cc25f6 Mon Sep 17 00:00:00 2001 From: AD7six Date: Sun, 8 Feb 2015 18:22:08 +0000 Subject: [PATCH 0201/2059] Update moveUp too --- Behavior/TreeBehavior.php | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index b993d82f..e402d758 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -570,6 +570,8 @@ protected function _moveUp($node, $number) } $edge = $this->_getMax(); + $width = $node->{$right} - $node->{$left}; + while ($number-- > 0) { list($nodeLeft, $nodeRight) = array_values($node->extract([$left, $right])); @@ -590,14 +592,10 @@ protected function _moveUp($node, $number) $this->_sync($nodeLeft - $nextNode->{$left}, '-', "BETWEEN {$nodeLeft} AND {$nodeRight}"); $this->_sync($edge - $nextNode->{$left} - ($nodeRight - $nodeLeft), '-', "> {$edge}"); - $newLeft = $nodeLeft; - if ($nodeLeft >= $nextNode->{$left} || $nodeLeft <= $nextNode->{$right}) { - $newLeft -= $edge - $nextNode->{$left} + 1; - } - $newLeft = $nodeLeft - ($nodeLeft - $nextNode->{$left}); + $shift = $node->{$right} - $nextNode->{$right}; - $node->set($left, $newLeft); - $node->set($right, $newLeft + ($nodeRight - $nodeLeft)); + $node->set($right, $node->{$left} + $width - $shift); + $node->set($left, $node->{$left} - $shift); } $node->dirty($left, false); @@ -673,10 +671,10 @@ protected function _moveDown($node, $number) $this->_sync($nextNode->{$left} - $nodeLeft, '-', "BETWEEN {$nextNode->{$left}} AND {$nextNode->{$right}}"); $this->_sync($edge - $nodeLeft - ($nextNode->{$right} - $nextNode->{$left}), '-', "> {$edge}"); - $move = $nextNode->{$right} - $node->{$right}; + $shift = $nextNode->{$right} - $node->{$right}; - $node->set($right, $node->{$left} + $width + $move); - $node->set($left, $node->{$left} + $move); + $node->set($right, $node->{$left} + $width + $shift); + $node->set($left, $node->{$left} + $shift); } $node->dirty($left, false); From 93f494572214f8c6ce11f20232088c0d4b8eb507 Mon Sep 17 00:00:00 2001 From: AD7six Date: Mon, 9 Feb 2015 14:10:05 +0000 Subject: [PATCH 0202/2059] Actually, that can be a lot simpler --- Behavior/TreeBehavior.php | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index e402d758..fd3f86cc 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -592,10 +592,8 @@ protected function _moveUp($node, $number) $this->_sync($nodeLeft - $nextNode->{$left}, '-', "BETWEEN {$nodeLeft} AND {$nodeRight}"); $this->_sync($edge - $nextNode->{$left} - ($nodeRight - $nodeLeft), '-', "> {$edge}"); - $shift = $node->{$right} - $nextNode->{$right}; - - $node->set($right, $node->{$left} + $width - $shift); - $node->set($left, $node->{$left} - $shift); + $node->set($left, $nextNode->{$left}); + $node->set($right, $nextNode->{$left} + $width); } $node->dirty($left, false); @@ -671,10 +669,8 @@ protected function _moveDown($node, $number) $this->_sync($nextNode->{$left} - $nodeLeft, '-', "BETWEEN {$nextNode->{$left}} AND {$nextNode->{$right}}"); $this->_sync($edge - $nodeLeft - ($nextNode->{$right} - $nextNode->{$left}), '-', "> {$edge}"); - $shift = $nextNode->{$right} - $node->{$right}; - - $node->set($right, $node->{$left} + $width + $shift); - $node->set($left, $node->{$left} + $shift); + $node->set($left, $nextNode->{$right} - $width); + $node->set($right, $nextNode->{$right}); } $node->dirty($left, false); From b3064c1a629741386c4d41112bed74711a6438bf Mon Sep 17 00:00:00 2001 From: AD7six Date: Tue, 10 Feb 2015 01:10:17 +0000 Subject: [PATCH 0203/2059] Single-step moveDown --- Behavior/TreeBehavior.php | 61 ++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index fd3f86cc..c83eb91a 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -630,48 +630,49 @@ public function moveDown(Entity $node, $number = 1) */ protected function _moveDown($node, $number) { - $config = $this->config(); - list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']]; - if (!$number) { return false; } - $parentRight = 0; - if ($node->get($parent)) { - $parentRight = $this->_getNode($node->get($parent))->get($right); - } - - if ($number === true) { - $number = PHP_INT_MAX; - } - - $edge = $this->_getMax(); - $width = $node->{$right} - $node->{$left}; - - while ($number-- > 0) { - list($nodeLeft, $nodeRight) = array_values($node->extract([$left, $right])); + $config = $this->config(); + list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']]; - if ($parentRight && ($nodeRight + 1 == $parentRight)) { - break; - } + list($nodeParent, $nodeLeft, $nodeRight) = array_values($node->extract([$parent, $left, $right])); - $nextNode = $this->_scope($this->_table->find()) + $targetNode = null; + if ($number !== true) { + $targetNode = $this->_scope($this->_table->find()) + ->select([$left, $right]) + ->where(["$parent IS" => $nodeParent, "$left >" => $nodeRight]) + ->order([$left => 'ASC']) + ->offset($number - 1) + ->limit(1) + ->first(); + } + if (!$targetNode) { + $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) - ->where([$left => $nodeRight + 1]) + ->where(["$parent IS" => $nodeParent, "$left >" => $nodeRight]) + ->order([$left => 'DESC']) + ->limit(1) ->first(); - if (!$nextNode) { - break; + if (!$targetNode) { + return $node; } + } - $this->_sync($edge - $nodeLeft + 1, '+', "BETWEEN {$nodeLeft} AND {$nodeRight}"); - $this->_sync($nextNode->{$left} - $nodeLeft, '-', "BETWEEN {$nextNode->{$left}} AND {$nextNode->{$right}}"); - $this->_sync($edge - $nodeLeft - ($nextNode->{$right} - $nextNode->{$left}), '-', "> {$edge}"); + list($targetLeft, $targetRight) = array_values($targetNode->extract([$left, $right])); + $edge = $this->_getMax(); + $leftBoundary = $nodeRight + 1; + $rightBoundary = $targetRight; - $node->set($left, $nextNode->{$right} - $width); - $node->set($right, $nextNode->{$right}); - } + $this->_sync($edge - $nodeLeft + 1, '+', "BETWEEN {$nodeLeft} AND {$nodeRight}"); + $this->_sync($nodeRight - $nodeLeft + 1, '-', "BETWEEN {$leftBoundary} AND {$rightBoundary}"); + $this->_sync($edge - $nodeLeft - ($rightBoundary - $leftBoundary), '-', "> {$edge}"); + + $node->set($left, $targetRight - ($nodeRight - $nodeLeft)); + $node->set($right, $targetRight); $node->dirty($left, false); $node->dirty($right, false); From 29060130683a2e49fa152eba93a152cf509185f6 Mon Sep 17 00:00:00 2001 From: AD7six Date: Tue, 10 Feb 2015 01:13:05 +0000 Subject: [PATCH 0204/2059] Single-step moveUp --- Behavior/TreeBehavior.php | 59 +++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index c83eb91a..ee0f7efc 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -557,47 +557,52 @@ public function moveUp(Entity $node, $number = 1) */ protected function _moveUp($node, $number) { - $config = $this->config(); - list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']]; - if (!$number) { return false; } - $parentLeft = 0; - if ($node->get($parent)) { - $parentLeft = $this->_getNode($node->get($parent))->get($left); - } - - $edge = $this->_getMax(); - $width = $node->{$right} - $node->{$left}; - - while ($number-- > 0) { - list($nodeLeft, $nodeRight) = array_values($node->extract([$left, $right])); - - if ($parentLeft && ($nodeLeft - 1 == $parentLeft)) { - break; - } + $config = $this->config(); + list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']]; + list($nodeParent, $nodeLeft, $nodeRight) = array_values($node->extract([$parent, $left, $right])); - $nextNode = $this->_scope($this->_table->find()) + $targetNode = null; + if ($number !== true) { + $targetNode = $this->_scope($this->_table->find()) + ->select([$left, $right]) + ->where(["$parent IS" => $nodeParent, "$right <" => $nodeLeft]) + ->order([$left => 'DESC']) + ->offset($number - 1) + ->limit(1) + ->first(); + } + if (!$targetNode) { + $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) - ->where([$right => ($nodeLeft - 1)]) + ->where(["$parent IS" => $nodeParent, "$right <" => $nodeLeft]) + ->order([$left => 'ASC']) + ->limit(1) ->first(); - if (!$nextNode) { - break; + if (!$targetNode) { + return $node; } + } + + list($targetLeft, $targetRight) = array_values($targetNode->extract([$left, $right])); + $edge = $this->_getMax(); + $leftBoundary = $targetLeft; + $rightBoundary = $nodeLeft - 1; - $this->_sync($edge - $nextNode->{$left} + 1, '+', "BETWEEN {$nextNode->{$left}} AND {$nextNode->{$right}}"); - $this->_sync($nodeLeft - $nextNode->{$left}, '-', "BETWEEN {$nodeLeft} AND {$nodeRight}"); - $this->_sync($edge - $nextNode->{$left} - ($nodeRight - $nodeLeft), '-', "> {$edge}"); + $this->_sync($edge - $nodeLeft + 1, '+', "BETWEEN {$nodeLeft} AND {$nodeRight}"); + $this->_sync($nodeRight - $nodeLeft + 1, '+', "BETWEEN {$leftBoundary} AND {$rightBoundary}"); + $this->_sync($edge - $leftBoundary + 1, '-', "> {$edge}"); - $node->set($left, $nextNode->{$left}); - $node->set($right, $nextNode->{$left} + $width); - } + $node->set($left, $targetLeft); + $node->set($right, $targetLeft + ($nodeRight - $nodeLeft)); $node->dirty($left, false); $node->dirty($right, false); + return $node; } From e6f207ac41709774f9078d9b009047f1e77ad965 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 9 Feb 2015 21:41:32 -0500 Subject: [PATCH 0205/2059] Add README and composer.json for the ORM package. Add docs and a composer.json so we can have a subsplit of the ORM package. --- README.md | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++ composer.json | 27 ++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 README.md create mode 100644 composer.json diff --git a/README.md b/README.md new file mode 100644 index 00000000..a191b575 --- /dev/null +++ b/README.md @@ -0,0 +1,112 @@ +# CakePHP ORM + +The CakePHP ORM provides a powerful and flexible way to work with relational +databases. Using a datamapper pattern the ORM allows you to manipulate data as +entities allowing you to create expressive domain layers in your applications. + +## Connecting to the Database + +The CakePHP ORM is compatible with: + +* MySQL 5.1+ +* Postgres 8+ +* SQLite3 +* SQLServer 2008+ + +The first thing you need to do when using this library is register a connection +object. Before performing any operations with the connection, you need to +specify a driver to use: + +```php +use Cake\Datasource\ConnectionManager; + +ConnectionManager::create('default', [ + 'driver' => 'Cake\Database\Driver\Mysql', + 'database' => 'test', + 'login' => 'root', + 'password' => 'secret' +]); +``` + +Once a 'default' connection is registered, it will be used by all the Table +mappers if no explicit connection is defined. + +## Creating Associations + +In your table classes you can define the relations between your tables. CakePHP +supports 4 association types out of the box: + +* belongsTo - E.g. Many articles belong to a user. +* hasOne - E.g. A user has one profile +* hasMany - E.g. A user has many articles +* belongsToMany - E.g. An article belongsToMany tags. + +You define associations in your table's `initialize()` method. See the +[documentation](http://book.cakephp.org/3.0/en/orm/associations.html) for +complete examples. + +## Reading Data + +Once you've defined some table classes you can read existing data in your tables: + +```php +use Cake\ORM\TableRegistry; + +$articles = TableRegistry::get('Articles'); +foreach ($articles->find() as $article) { + echo $article->title; +} +``` + +You can use the [query builder](http://book.cakephp.org/3.0/en/orm/query-builder.html) to create +complex queries, and a [variety of methods](http://book.cakephp.org/3.0/en/orm/retrieving-data-and-resultsets.html) +to access your data. + +## Saving Data + +Table objects provide ways to convert request data into entities, and then persist +those entities to the database: + +```php +use Cake\ORM\TableRegistry; + +$data = [ + 'title' => 'My first article', + 'body' => 'It is a great article', + 'user_id' => 1, + 'tags' => [ + '_ids' => [1, 2, 3] + ], + 'comments' => [ + ['comment' => 'Good job'], + ['comment' => 'Awesome work'], + ] +]; + +$articles = TableRegistry::get('Articles'); +$article = $articles->newEnitity($data, [ + 'associated' => ['Tags', 'Comments'] +]); +$articles->save($article, [ + 'associated' => ['Tags', 'Comments'] +]) +``` + +The above shows how you can easily marshal and save an entity and its +associations in a simple & powerful way. Consult the [ORM documentation](http://book.cakephp.org/3.0/en/orm/saving-data.html) +for more in-depth examples. + +## Deleting Data + +Once you have a reference to an entity, you can use it to delete data: + +```php +$articles = TableRegistry::get('Articles'); +$article = $articles->get(2); +$articles->delete($article); +``` + +## Additional Documentation + +Consult [the CakePHP documentation](http://book.cakephp.org/3.0/en/orm.html) +for more in-depth documentation. diff --git a/composer.json b/composer.json new file mode 100644 index 00000000..b127a160 --- /dev/null +++ b/composer.json @@ -0,0 +1,27 @@ +{ + "name": "cakephp/orm", + "description": "CakePHP ORM - Provides a flexible and powerful ORM implementing a data-mapper pattern.", + "license": "MIT", + "authors": [ + { + "name": "CakePHP Community", + "homepage": "http://cakephp.org" + } + ], + "autoload": { + "psr-4": { + "Cake\\ORM\\": "." + } + }, + "require": { + "cakephp/collection": "dev-master", + "cakephp/core": "dev-master", + "cakephp/datasource": "dev-master", + "cakephp/database": "dev-master", + "cakephp/event": "dev-master", + "cakephp/i18n": "dev-master", + "cakephp/utility": "dev-master", + "cakephp/validation": "dev-master" + }, + "minimum-stability": "beta" +} From 9a03e8bd507222f09f693dd5b050f101694f5f5d Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 10 Feb 2015 08:43:14 +0530 Subject: [PATCH 0206/2059] Use "static" instead of "self" to allow overriding. --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 64c636fe..a14aa9b6 100644 --- a/Table.php +++ b/Table.php @@ -255,7 +255,7 @@ public function __construct(array $config = []) } if (!empty($config['validator'])) { if (!is_array($config['validator'])) { - $this->validator(self::DEFAULT_VALIDATOR, $config['validator']); + $this->validator(static::DEFAULT_VALIDATOR, $config['validator']); } else { foreach ($config['validator'] as $name => $validator) { $this->validator($name, $validator); From 1f2e0d7e31ec4ffb4eada04e0c26f0663142ca10 Mon Sep 17 00:00:00 2001 From: AD7six Date: Tue, 10 Feb 2015 09:23:30 +0000 Subject: [PATCH 0207/2059] Use some better named variables to aide readability --- Behavior/TreeBehavior.php | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index ee0f7efc..969107b4 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -593,9 +593,12 @@ protected function _moveUp($node, $number) $leftBoundary = $targetLeft; $rightBoundary = $nodeLeft - 1; - $this->_sync($edge - $nodeLeft + 1, '+', "BETWEEN {$nodeLeft} AND {$nodeRight}"); - $this->_sync($nodeRight - $nodeLeft + 1, '+', "BETWEEN {$leftBoundary} AND {$rightBoundary}"); - $this->_sync($edge - $leftBoundary + 1, '-', "> {$edge}"); + $nodeToEdge = $edge - $nodeLeft + 1; + $shift = $nodeRight - $nodeLeft + 1; + $nodeToHole = $edge - $leftBoundary + 1; + $this->_sync($nodeToEdge, '+', "BETWEEN {$nodeLeft} AND {$nodeRight}"); + $this->_sync($shift, '+', "BETWEEN {$leftBoundary} AND {$rightBoundary}"); + $this->_sync($nodeToHole, '-', "> {$edge}"); $node->set($left, $targetLeft); $node->set($right, $targetLeft + ($nodeRight - $nodeLeft)); @@ -672,9 +675,12 @@ protected function _moveDown($node, $number) $leftBoundary = $nodeRight + 1; $rightBoundary = $targetRight; - $this->_sync($edge - $nodeLeft + 1, '+', "BETWEEN {$nodeLeft} AND {$nodeRight}"); - $this->_sync($nodeRight - $nodeLeft + 1, '-', "BETWEEN {$leftBoundary} AND {$rightBoundary}"); - $this->_sync($edge - $nodeLeft - ($rightBoundary - $leftBoundary), '-', "> {$edge}"); + $nodeToEdge = $edge - $nodeLeft + 1; + $shift = $nodeRight - $nodeLeft + 1; + $nodeToHole = $edge - $nodeLeft - ($rightBoundary - $leftBoundary); + $this->_sync($nodeToEdge, '+', "BETWEEN {$nodeLeft} AND {$nodeRight}"); + $this->_sync($shift, '-', "BETWEEN {$leftBoundary} AND {$rightBoundary}"); + $this->_sync($nodeToHole, '-', "> {$edge}"); $node->set($left, $targetRight - ($nodeRight - $nodeLeft)); $node->set($right, $targetRight); From 9afbe041abff83a0309f112937a60207d305947a Mon Sep 17 00:00:00 2001 From: AD7six Date: Tue, 10 Feb 2015 10:02:23 +0000 Subject: [PATCH 0208/2059] Better node-to-hole logic More readable at least --- Behavior/TreeBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 969107b4..47d000f5 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -677,7 +677,7 @@ protected function _moveDown($node, $number) $nodeToEdge = $edge - $nodeLeft + 1; $shift = $nodeRight - $nodeLeft + 1; - $nodeToHole = $edge - $nodeLeft - ($rightBoundary - $leftBoundary); + $nodeToHole = $edge - $rightBoundary + $shift; $this->_sync($nodeToEdge, '+', "BETWEEN {$nodeLeft} AND {$nodeRight}"); $this->_sync($shift, '-', "BETWEEN {$leftBoundary} AND {$rightBoundary}"); $this->_sync($nodeToHole, '-', "> {$edge}"); From b766c3d358169760ab336064c722366fd3232fae Mon Sep 17 00:00:00 2001 From: AD7six Date: Tue, 10 Feb 2015 10:16:49 +0000 Subject: [PATCH 0209/2059] Prevent negative offsets Not sure why is this is considered "valid" - there are tests expecting a node returned, rather than false --- Behavior/TreeBehavior.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 47d000f5..27ef12d6 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -560,6 +560,9 @@ protected function _moveUp($node, $number) if (!$number) { return false; } + if ($number < 0) { + return $node; + } $config = $this->config(); list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']]; @@ -641,6 +644,9 @@ protected function _moveDown($node, $number) if (!$number) { return false; } + if ($number < 0) { + return $node; + } $config = $this->config(); list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']]; From 0ce1a4f04519d408204499afac7642c37c834716 Mon Sep 17 00:00:00 2001 From: AD7six Date: Thu, 5 Feb 2015 10:51:26 +0000 Subject: [PATCH 0210/2059] The plugin alias should not be ignored This allows unexpected scenarios such as: $wrongPluginModel = TableRegistry::get('This.Foo'); --- TableRegistry.php | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/TableRegistry.php b/TableRegistry.php index 93d288ff..26d60b97 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -101,7 +101,6 @@ class TableRegistry */ public static function config($alias = null, $options = null) { - list(, $alias) = pluginSplit($alias); if ($alias === null) { return static::$_config; } @@ -154,9 +153,9 @@ public static function config($alias = null, $options = null) * @return \Cake\ORM\Table * @throws RuntimeException When you try to configure an alias that already exists. */ - public static function get($name, array $options = []) + public static function get($alias, array $options = []) { - list(, $alias) = pluginSplit($name); + list(, $classAlias) = pluginSplit($alias); $exists = isset(static::$_instances[$alias]); if ($exists && !empty($options)) { @@ -171,10 +170,10 @@ public static function get($name, array $options = []) return static::$_instances[$alias]; } static::$_options[$alias] = $options; - $options = ['alias' => $alias] + $options; + $options = ['alias' => $classAlias] + $options; if (empty($options['className'])) { - $options['className'] = Inflector::camelize($name); + $options['className'] = Inflector::camelize($alias); } $className = App::className($options['className'], 'Model/Table', 'Table'); $options['className'] = $className ?: 'Cake\ORM\Table'; @@ -199,15 +198,11 @@ public static function get($name, array $options = []) /** * Check to see if an instance exists in the registry. * - * Plugin names will be trimmed off of aliases as instances - * stored in the registry will be without the plugin name as well. - * * @param string $alias The alias to check for. * @return bool */ public static function exists($alias) { - list(, $alias) = pluginSplit($alias); return isset(static::$_instances[$alias]); } @@ -220,7 +215,6 @@ public static function exists($alias) */ public static function set($alias, Table $object) { - list(, $alias) = pluginSplit($alias); return static::$_instances[$alias] = $object; } @@ -252,16 +246,11 @@ public static function genericInstances() /** * Removes an instance from the registry. * - * Plugin name will be trimmed off of aliases as instances - * stored in the registry will be without the plugin name as well. - * * @param string $alias The alias to remove. * @return void */ public static function remove($alias) { - list(, $alias) = pluginSplit($alias); - unset( static::$_instances[$alias], static::$_config[$alias], From fef9c2849f3895ecb5630b07ac2db9e49be157bf Mon Sep 17 00:00:00 2001 From: AD7six Date: Fri, 6 Feb 2015 10:00:03 +0000 Subject: [PATCH 0211/2059] Flatten the code Remove used-once intermediary variables, and move the pluginSplit call to where it's used --- TableRegistry.php | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/TableRegistry.php b/TableRegistry.php index 26d60b97..56b6c51c 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -155,21 +155,18 @@ public static function config($alias = null, $options = null) */ public static function get($alias, array $options = []) { - list(, $classAlias) = pluginSplit($alias); - $exists = isset(static::$_instances[$alias]); - - if ($exists && !empty($options)) { - if (static::$_options[$alias] !== $options) { + if (isset(static::$_instances[$alias])) { + if (!empty($options) && static::$_options[$alias] !== $options) { throw new RuntimeException(sprintf( 'You cannot configure "%s", it already exists in the registry.', $alias )); } - } - if ($exists) { return static::$_instances[$alias]; } + static::$_options[$alias] = $options; + list(, $classAlias) = pluginSplit($alias); $options = ['alias' => $classAlias] + $options; if (empty($options['className'])) { From 575f5a355b53f6963b6002a42691483f23e2f7bf Mon Sep 17 00:00:00 2001 From: AD7six Date: Fri, 6 Feb 2015 11:21:10 +0000 Subject: [PATCH 0212/2059] oops --- TableRegistry.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TableRegistry.php b/TableRegistry.php index 56b6c51c..dbb03a60 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -254,4 +254,6 @@ public static function remove($alias) static::$_fallbacked[$alias] ); } + + public static function keys() { return array_keys(static::$_instances); } } From c3b017727bfb08ad91e9acf3a89c95d67c9aa7a1 Mon Sep 17 00:00:00 2001 From: AD7six Date: Fri, 6 Feb 2015 14:33:00 +0000 Subject: [PATCH 0213/2059] Remove debug code --- TableRegistry.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/TableRegistry.php b/TableRegistry.php index dbb03a60..56b6c51c 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -254,6 +254,4 @@ public static function remove($alias) static::$_fallbacked[$alias] ); } - - public static function keys() { return array_keys(static::$_instances); } } From fb2efac241ea2ba3108ef4a0e728152051e19a28 Mon Sep 17 00:00:00 2001 From: AD7six Date: Fri, 6 Feb 2015 15:05:38 +0000 Subject: [PATCH 0214/2059] Remove nesting The only way the code can arrive there, is if $table is null --- Association.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Association.php b/Association.php index 12f94729..bc6a6bbc 100644 --- a/Association.php +++ b/Association.php @@ -269,13 +269,12 @@ public function target(Table $table = null) return $this->_targetTable = $table; } - if ($table === null) { - $config = []; - if (!TableRegistry::exists($this->_name)) { - $config = ['className' => $this->_className]; - } - $this->_targetTable = TableRegistry::get($this->_name, $config); + $config = []; + if (!TableRegistry::exists($this->_name)) { + $config = ['className' => $this->_className]; } + $this->_targetTable = TableRegistry::get($this->_name, $config); + return $this->_targetTable; } From 788f104a8a1ed176acf6be496e7b68ebe218465a Mon Sep 17 00:00:00 2001 From: AD7six Date: Fri, 6 Feb 2015 15:16:50 +0000 Subject: [PATCH 0215/2059] Use the class name as a table alias --- Association.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Association.php b/Association.php index bc6a6bbc..d2038e4f 100644 --- a/Association.php +++ b/Association.php @@ -269,11 +269,21 @@ public function target(Table $table = null) return $this->_targetTable = $table; } + if ($this->_className) { + if (strpos($this->_className, '\\') !== false) { + $tableAlias = $this->_name; + } else { + $tableAlias = $this->_className; + } + } else { + $tableAlias = $this->_name; + } + $config = []; - if (!TableRegistry::exists($this->_name)) { + if (!TableRegistry::exists($tableAlias)) { $config = ['className' => $this->_className]; } - $this->_targetTable = TableRegistry::get($this->_name, $config); + $this->_targetTable = TableRegistry::get($tableAlias, $config); return $this->_targetTable; } From 63cf9734ac5cf5675b15fc306e8245d4d8d7b767 Mon Sep 17 00:00:00 2001 From: AD7six Date: Fri, 6 Feb 2015 16:31:26 +0000 Subject: [PATCH 0216/2059] Flatten/simplify --- Association.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Association.php b/Association.php index d2038e4f..0273f258 100644 --- a/Association.php +++ b/Association.php @@ -269,12 +269,8 @@ public function target(Table $table = null) return $this->_targetTable = $table; } - if ($this->_className) { - if (strpos($this->_className, '\\') !== false) { - $tableAlias = $this->_name; - } else { - $tableAlias = $this->_className; - } + if ($this->_className && strpos($this->_className, '\\') === false) { + $tableAlias = $this->_className; } else { $tableAlias = $this->_name; } From cb7c783db78baafce82ae8715215842daafa2f2f Mon Sep 17 00:00:00 2001 From: AD7six Date: Tue, 10 Feb 2015 13:30:54 +0000 Subject: [PATCH 0217/2059] Add RepositoryAlias This is an attempt to resolve the problem of entity source being ambiguous. This permits (though does not ensure) that an entity has the means to say which table object it came from. alias and repository alias are not the same thing, repository alias is plugin-prefixed, whereas alias is not. --- Marshaller.php | 2 +- Table.php | 38 ++++++++++++++++++++++++++++++++++---- TableRegistry.php | 1 + 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 476cb401..316313e4 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -107,7 +107,7 @@ public function one(array $data, array $options = []) $schema = $this->_table->schema(); $entityClass = $this->_table->entityClass(); $entity = new $entityClass(); - $entity->source($this->_table->alias()); + $entity->source($this->_table->repositoryAlias()); if (isset($options['accessibleFields'])) { foreach ((array)$options['accessibleFields'] as $key => $value) { diff --git a/Table.php b/Table.php index a14aa9b6..0fee7d42 100644 --- a/Table.php +++ b/Table.php @@ -191,6 +191,13 @@ class Table implements RepositoryInterface, EventListenerInterface */ protected $_entityClass; + /** + * Registry key used to create this table object + * + * @var string + */ + protected $_repositoryAlias; + /** * A list of validation objects indexed by name * @@ -228,6 +235,9 @@ class Table implements RepositoryInterface, EventListenerInterface */ public function __construct(array $config = []) { + if (!empty($config['repositoryAlias'])) { + $this->repositoryAlias($config['repositoryAlias']); + } if (!empty($config['table'])) { $this->table($config['table']); } @@ -348,6 +358,23 @@ public function alias($alias = null) return $this->_alias; } + /** + * Returns the table registry key used to create this table instance + * + * @param string|null $repositoryAlias the key used to access this object + * @return string + */ + public function repositoryAlias($repositoryAlias = null) + { + if ($repositoryAlias !== null) { + $this->_repositoryAlias = $repositoryAlias; + } + if ($this->_repositoryAlias === null) { + $this->_repositoryAlias = Inflector::camelize($this->alias()); + } + return $this->_repositoryAlias; + } + /** * Returns the connection instance or sets a new one * @@ -1379,7 +1406,7 @@ protected function _processSave($entity, $options) $entity->clean(); $this->dispatchEvent('Model.afterSave', compact('entity', 'options')); $entity->isNew(false); - $entity->source($this->alias()); + $entity->source($this->repositoryAlias()); $success = true; } } @@ -1845,8 +1872,7 @@ public function newEntity($data = null, array $options = []) { if ($data === null) { $class = $this->entityClass(); - $entity = new $class; - $entity->source($this->alias()); + $entity = new $class(['source' => $this->repositoryAlias()]); return $entity; } if (!isset($options['associated'])) { @@ -2007,7 +2033,11 @@ public function validateUnique($value, array $context, array $options = null) } $entity = new Entity( $options['data'], - ['useSetters' => false, 'markNew' => $options['newRecord']] + [ + 'useSetters' => false, + 'markNew' => $options['newRecord'], + 'source' => $this->repositoryAlias() + ] ); $fields = array_merge( [$options['field']], diff --git a/TableRegistry.php b/TableRegistry.php index 56b6c51c..7917930a 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -183,6 +183,7 @@ public static function get($alias, array $options = []) $options['connection'] = ConnectionManager::get($connectionName); } + $options['source'] = $alias; static::$_instances[$alias] = new $options['className']($options); if ($options['className'] === 'Cake\ORM\Table') { From 8753b332a00791ea7f3d83badfde1b013e0c0e86 Mon Sep 17 00:00:00 2001 From: AD7six Date: Tue, 10 Feb 2015 18:16:32 +0000 Subject: [PATCH 0218/2059] Don't inflect the alias If it's wrong, it's not likely to be more-right when it's inflected --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 0fee7d42..58e0e1e4 100644 --- a/Table.php +++ b/Table.php @@ -370,7 +370,7 @@ public function repositoryAlias($repositoryAlias = null) $this->_repositoryAlias = $repositoryAlias; } if ($this->_repositoryAlias === null) { - $this->_repositoryAlias = Inflector::camelize($this->alias()); + $this->_repositoryAlias = $this->alias(); } return $this->_repositoryAlias; } From 92e58e8fdaa7ac4afc44e0925cf5381aa0049f20 Mon Sep 17 00:00:00 2001 From: AD7six Date: Wed, 11 Feb 2015 07:32:52 +0000 Subject: [PATCH 0219/2059] Don't accept negative moves And bail early, rather than deal with it later --- Behavior/TreeBehavior.php | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 27ef12d6..3c1b66d9 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -541,6 +541,10 @@ protected function _removeFromTree($node) */ public function moveUp(Entity $node, $number = 1) { + if ($number < 1) { + return false; + } + return $this->_table->connection()->transactional(function () use ($node, $number) { $this->_ensureFields($node); return $this->_moveUp($node, $number); @@ -557,13 +561,6 @@ public function moveUp(Entity $node, $number = 1) */ protected function _moveUp($node, $number) { - if (!$number) { - return false; - } - if ($number < 0) { - return $node; - } - $config = $this->config(); list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']]; list($nodeParent, $nodeLeft, $nodeRight) = array_values($node->extract([$parent, $left, $right])); @@ -625,6 +622,10 @@ protected function _moveUp($node, $number) */ public function moveDown(Entity $node, $number = 1) { + if ($number < 1) { + return false; + } + return $this->_table->connection()->transactional(function () use ($node, $number) { $this->_ensureFields($node); return $this->_moveDown($node, $number); @@ -641,13 +642,6 @@ public function moveDown(Entity $node, $number = 1) */ protected function _moveDown($node, $number) { - if (!$number) { - return false; - } - if ($number < 0) { - return $node; - } - $config = $this->config(); list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']]; From 78c2b812f0ae218511a0420585e4896517114238 Mon Sep 17 00:00:00 2001 From: AD7six Date: Wed, 11 Feb 2015 07:42:27 +0000 Subject: [PATCH 0220/2059] Rename repositoryAlias to registryAlias it refers to the key used in the table _registry_ to access the relevant table object, rather than the alias of the table itself --- Marshaller.php | 2 +- Table.php | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 316313e4..2e1b5ccf 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -107,7 +107,7 @@ public function one(array $data, array $options = []) $schema = $this->_table->schema(); $entityClass = $this->_table->entityClass(); $entity = new $entityClass(); - $entity->source($this->_table->repositoryAlias()); + $entity->source($this->_table->registryAlias()); if (isset($options['accessibleFields'])) { foreach ((array)$options['accessibleFields'] as $key => $value) { diff --git a/Table.php b/Table.php index 58e0e1e4..5da25240 100644 --- a/Table.php +++ b/Table.php @@ -196,7 +196,7 @@ class Table implements RepositoryInterface, EventListenerInterface * * @var string */ - protected $_repositoryAlias; + protected $_registryAlias; /** * A list of validation objects indexed by name @@ -235,8 +235,8 @@ class Table implements RepositoryInterface, EventListenerInterface */ public function __construct(array $config = []) { - if (!empty($config['repositoryAlias'])) { - $this->repositoryAlias($config['repositoryAlias']); + if (!empty($config['registryAlias'])) { + $this->registryAlias($config['registryAlias']); } if (!empty($config['table'])) { $this->table($config['table']); @@ -361,18 +361,18 @@ public function alias($alias = null) /** * Returns the table registry key used to create this table instance * - * @param string|null $repositoryAlias the key used to access this object + * @param string|null $registryAlias the key used to access this object * @return string */ - public function repositoryAlias($repositoryAlias = null) + public function registryAlias($registryAlias = null) { - if ($repositoryAlias !== null) { - $this->_repositoryAlias = $repositoryAlias; + if ($registryAlias !== null) { + $this->_registryAlias = $registryAlias; } - if ($this->_repositoryAlias === null) { - $this->_repositoryAlias = $this->alias(); + if ($this->_registryAlias === null) { + $this->_registryAlias = $this->alias(); } - return $this->_repositoryAlias; + return $this->_registryAlias; } /** @@ -1406,7 +1406,7 @@ protected function _processSave($entity, $options) $entity->clean(); $this->dispatchEvent('Model.afterSave', compact('entity', 'options')); $entity->isNew(false); - $entity->source($this->repositoryAlias()); + $entity->source($this->registryAlias()); $success = true; } } @@ -1872,7 +1872,7 @@ public function newEntity($data = null, array $options = []) { if ($data === null) { $class = $this->entityClass(); - $entity = new $class(['source' => $this->repositoryAlias()]); + $entity = new $class(['source' => $this->registryAlias()]); return $entity; } if (!isset($options['associated'])) { @@ -2036,7 +2036,7 @@ public function validateUnique($value, array $context, array $options = null) [ 'useSetters' => false, 'markNew' => $options['newRecord'], - 'source' => $this->repositoryAlias() + 'source' => $this->registryAlias() ] ); $fields = array_merge( From 07b6e2083a41fc9960c688bdcc567c87dbfd089a Mon Sep 17 00:00:00 2001 From: AD7six Date: Wed, 11 Feb 2015 07:52:41 +0000 Subject: [PATCH 0221/2059] The table registry needs to pass the same option Otherwise the table will ignore it. --- TableRegistry.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TableRegistry.php b/TableRegistry.php index 7917930a..4fd30c04 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -183,7 +183,7 @@ public static function get($alias, array $options = []) $options['connection'] = ConnectionManager::get($connectionName); } - $options['source'] = $alias; + $options['registryAlias'] = $alias; static::$_instances[$alias] = new $options['className']($options); if ($options['className'] === 'Cake\ORM\Table') { From 89d176c81df019eee9be4cf81f9c9c6035180c34 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Wed, 11 Feb 2015 22:26:03 -0500 Subject: [PATCH 0222/2059] i18n is a suggest, not requirement for the ORM. --- composer.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index b127a160..d1b8124c 100644 --- a/composer.json +++ b/composer.json @@ -19,9 +19,11 @@ "cakephp/datasource": "dev-master", "cakephp/database": "dev-master", "cakephp/event": "dev-master", - "cakephp/i18n": "dev-master", "cakephp/utility": "dev-master", "cakephp/validation": "dev-master" }, + "suggest": { + "cakephp/i18n": "dev-master" + }, "minimum-stability": "beta" } From 21ff60732e75ba98688bece12423a2b3b45dddb8 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Wed, 11 Feb 2015 22:59:07 -0500 Subject: [PATCH 0223/2059] Remove dead code in ORM. These classes were undocumented, and aren't part of the current ORM implementation. Refs #5755 --- EntityValidator.php | 189 --------------------------------------- EntityValidatorTrait.php | 41 --------- 2 files changed, 230 deletions(-) delete mode 100644 EntityValidator.php delete mode 100644 EntityValidatorTrait.php diff --git a/EntityValidator.php b/EntityValidator.php deleted file mode 100644 index ef6df65f..00000000 --- a/EntityValidator.php +++ /dev/null @@ -1,189 +0,0 @@ -_table = $table; - } - - /** - * Build the map of property => association names. - * - * @param array $include The array of included associations. - * @return array - */ - protected function _buildPropertyMap($include) - { - if (empty($include['associated'])) { - return []; - } - - $map = []; - foreach ($include['associated'] as $key => $options) { - if (is_int($key) && is_scalar($options)) { - $key = $options; - $options = []; - } - - $options += ['validate' => true, 'associated' => []]; - $assoc = $this->_table->association($key); - if ($assoc) { - $map[$assoc->property()] = [ - 'association' => $assoc, - 'options' => $options - ]; - } - } - return $map; - } - - /** - * Validates a single entity by getting the correct validator object from - * the table and traverses associations passed in $options to validate them - * as well. - * - * @param \Cake\Datasource\EntityInterface $entity The entity to be validated - * @param array|\ArrayObject $options options for validation, including an optional key of - * associations to also be validated. This argument should use the same format as the $options - * argument to \Cake\ORM\Table::save(). - * @return bool true if all validations passed, false otherwise - */ - public function one(EntityInterface $entity, $options = []) - { - $valid = true; - $types = [Association::ONE_TO_ONE, Association::MANY_TO_ONE]; - $propertyMap = $this->_buildPropertyMap($options); - $options = new ArrayObject($options); - - foreach ($propertyMap as $key => $assoc) { - $value = $entity->get($key); - $association = $assoc['association']; - - if (!$value) { - continue; - } - $isOne = in_array($association->type(), $types); - if ($isOne && !($value instanceof EntityInterface)) { - $valid = false; - continue; - } - - $validator = new self($association->target()); - if ($isOne) { - $valid = $validator->one($value, $assoc['options']) && $valid; - } else { - $valid = $validator->many($value, $assoc['options']) && $valid; - } - } - - if (!isset($options['validate'])) { - $options['validate'] = true; - } - - if (!($entity instanceof ValidatableInterface)) { - return $valid; - } - - return $this->_processValidation($entity, $options) && $valid; - } - - /** - * Validates a list of entities by getting the correct validator for the related - * table and traverses associations passed in $include to validate them as well. - * - * If any of the entities in `$entities` does not implement `Cake\Datasource\EntityInterface`, - * it will be treated as an invalid result. - * - * @param array $entities List of entities to be validated - * @param array|\ArrayObject $options options for validation, including an optional key of - * associations to also be validated. This argument should use the same format as the $options - * argument to \Cake\ORM\Table::save(). - * @return bool true if all validations passed, false otherwise - */ - public function many(array $entities, $options = []) - { - $valid = true; - foreach ($entities as $entity) { - if (!($entity instanceof EntityInterface)) { - return false; - } - $valid = $this->one($entity, $options) && $valid; - } - return $valid; - } - - /** - * Validates the $entity if the 'validate' key is not set to false in $options - * If not empty it will construct a default validation object or get one with - * the name passed in the key - * - * @param \Cake\ORM\Entity $entity The entity to validate - * @param \ArrayObject $options The option for processing validation - * @return bool true if the entity is valid, false otherwise - */ - protected function _processValidation($entity, $options) - { - $type = is_string($options['validate']) ? $options['validate'] : 'default'; - $validator = $this->_table->validator($type); - $pass = compact('entity', 'options', 'validator'); - $event = $this->_table->dispatchEvent('Model.beforeValidate', $pass); - - if ($event->isStopped()) { - return (bool)$event->result; - } - - if (!count($validator)) { - return true; - } - - $success = !$entity->validate($validator); - - $event = $this->_table->dispatchEvent('Model.afterValidate', $pass); - if ($event->isStopped()) { - $success = (bool)$event->result; - } - - return $success; - } -} diff --git a/EntityValidatorTrait.php b/EntityValidatorTrait.php deleted file mode 100644 index 9bbdb815..00000000 --- a/EntityValidatorTrait.php +++ /dev/null @@ -1,41 +0,0 @@ -_properties; - $new = $this->isNew(); - $validator->provider('entity', $this); - $this->errors($validator->errors($data, $new === null ? true : $new)); - return $this->_errors; - } -} From 5a3971ae2b153dfb6ee333899bf493b83feb38e0 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Thu, 12 Feb 2015 15:03:15 -0430 Subject: [PATCH 0224/2059] Fixes ambiguous column error in ExistsIn, fixes #5877 --- Rule/ExistsIn.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index e7f81fdd..8c59bd7f 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -78,8 +78,15 @@ public function __invoke(EntityInterface $entity, array $options) return true; } + $alias = $this->_repository->alias(); + $primary = array_map( + function ($key) use ($alias) { + return "$alias.$key"; + }, + (array)$this->_repository->primaryKey() + ); $conditions = array_combine( - (array)$this->_repository->primaryKey(), + $primary, $entity->extract($this->_fields) ); return $this->_repository->exists($conditions); From 81897ed90fa831f346f3e1c7fecfad8eca0932e5 Mon Sep 17 00:00:00 2001 From: Patrick Conroy Date: Fri, 13 Feb 2015 11:57:16 -0500 Subject: [PATCH 0225/2059] Simplifying how the translation table is handled Since the change happened for respecting the plugin prefix in table definitions, the hack in i18n for allowing multiple `Whatever.I18n` classes can go away. --- Behavior/TranslateBehavior.php | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 90cc5de1..2f996c58 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -23,7 +23,6 @@ use Cake\ORM\Query; use Cake\ORM\Table; use Cake\ORM\TableRegistry; -use Cake\Utility\Inflector; /** * This behavior provides a way to translate dynamic data by keeping translations @@ -101,16 +100,8 @@ public function __construct(Table $table, array $config = []) */ public function initialize(array $config) { - $translationAlias = Inflector::slug($this->_config['translationTable'], '_'); + $this->_translationTable = TableRegistry::get($this->_config['translationTable']); - if (!TableRegistry::exists($translationAlias)) { - $this->_translationTable = TableRegistry::get($translationAlias, [ - 'className' => $this->_config['translationTable'] - ]); - } else { - $this->_translationTable = TableRegistry::get($translationAlias); - } - if ($this->config('model')) { $model = $this->config('model'); } elseif ($this->config('conditions.model')) { From f38487f52ded87fa0077f06b10b5d08375462e21 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Fri, 13 Feb 2015 13:03:55 -0430 Subject: [PATCH 0226/2059] Fixing entity source after recent changes --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 5da25240..0e24eaf2 100644 --- a/Table.php +++ b/Table.php @@ -1872,7 +1872,7 @@ public function newEntity($data = null, array $options = []) { if ($data === null) { $class = $this->entityClass(); - $entity = new $class(['source' => $this->registryAlias()]); + $entity = new $class([], ['source' => $this->registryAlias()]); return $entity; } if (!isset($options['associated'])) { From 110bb906626f190619b56b5728ca3249d8be1870 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 13 Feb 2015 23:42:54 +0530 Subject: [PATCH 0227/2059] Avoid triggering callbacks. --- Behavior/TreeBehavior.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 3c1b66d9..f5e5b392 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -185,8 +185,10 @@ protected function _setChildrenLevel(Entity $entity) $depth = $depths[$parentIdValue] + 1; $depths[$node->get($primaryKey)] = $depth; - $node->set($config['level'], $depth); - $this->_table->save($node, ['checkRules' => false, 'atomic' => false]); + $this->_table->updateAll( + [$config['level'] => $depth], + [$primaryKey => $node->get($primaryKey)] + ); } } From d72ff6ec897418b38a088d646fa08cd20ff4f933 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 13 Feb 2015 22:36:30 -0500 Subject: [PATCH 0228/2059] Make ExistsIn rule work with nullable columns. If a column is nullable, ExistsIn will allow the foreign key to be null if all the values in the entity are null. Refs #5853 --- Rule/ExistsIn.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index e7f81fdd..b1bded37 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -78,6 +78,17 @@ public function __invoke(EntityInterface $entity, array $options) return true; } + $nulls = 0; + $schema = $this->_repository->schema(); + foreach ($this->_fields as $field) { + if ($schema->isNullable($field) && $entity->get($field) === null) { + $nulls++; + } + } + if ($nulls === count($this->_fields)) { + return true; + } + $conditions = array_combine( (array)$this->_repository->primaryKey(), $entity->extract($this->_fields) From bfa6b6e6998fe8895805f24aea42d8f3977d6533 Mon Sep 17 00:00:00 2001 From: AD7six Date: Fri, 13 Feb 2015 11:20:02 +0000 Subject: [PATCH 0229/2059] Ensure the stored name is not plugin prefixed Fixes the failing test in the previous commit --- Association.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Association.php b/Association.php index 0273f258..515498ea 100644 --- a/Association.php +++ b/Association.php @@ -176,10 +176,10 @@ abstract class Association * Constructor. Subclasses can override _options function to get the original * list of passed options if expecting any other special key * - * @param string $name The name given to the association + * @param string $alias The name given to the association * @param array $options A list of properties to be set on this object */ - public function __construct($name, array $options = []) + public function __construct($alias, array $options = []) { $defaults = [ 'cascadeCallbacks', @@ -199,6 +199,7 @@ public function __construct($name, array $options = []) } } + list(,$name) = pluginSplit($alias); $this->_name = $name; $this->_options($options); From 3db85bb0f8671b1e7e4c76ebc71d4574cedcfb94 Mon Sep 17 00:00:00 2001 From: AD7six Date: Fri, 13 Feb 2015 14:29:08 +0000 Subject: [PATCH 0230/2059] Set the class name if not already set --- Association.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Association.php b/Association.php index 515498ea..91de5ef2 100644 --- a/Association.php +++ b/Association.php @@ -199,8 +199,13 @@ public function __construct($alias, array $options = []) } } + if(!$this->_className) { + $this->_className = $alias; + } + list(,$name) = pluginSplit($alias); $this->_name = $name; + $this->_options($options); if (!empty($options['strategy'])) { From 68307290da19449feb7e7248fd7c6dcda91fa6c3 Mon Sep 17 00:00:00 2001 From: AD7six Date: Fri, 13 Feb 2015 14:34:36 +0000 Subject: [PATCH 0231/2059] class name is never false now It's always set in the constructor --- Association.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association.php b/Association.php index 91de5ef2..71c420ec 100644 --- a/Association.php +++ b/Association.php @@ -275,7 +275,7 @@ public function target(Table $table = null) return $this->_targetTable = $table; } - if ($this->_className && strpos($this->_className, '\\') === false) { + if (strpos($this->_className, '\\') === false) { $tableAlias = $this->_className; } else { $tableAlias = $this->_name; From b7d5001f105deda485acdd7347571e1d0f61bec0 Mon Sep 17 00:00:00 2001 From: AD7six Date: Sat, 14 Feb 2015 18:28:47 +0000 Subject: [PATCH 0232/2059] phpcs fixes --- Association.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Association.php b/Association.php index 71c420ec..af8281b6 100644 --- a/Association.php +++ b/Association.php @@ -199,11 +199,11 @@ public function __construct($alias, array $options = []) } } - if(!$this->_className) { + if (!$this->_className) { $this->_className = $alias; } - list(,$name) = pluginSplit($alias); + list(, $name) = pluginSplit($alias); $this->_name = $name; $this->_options($options); From 9bb5a151e0b5595893b85ce55d8d86e410ea5c9b Mon Sep 17 00:00:00 2001 From: AD7six Date: Mon, 16 Feb 2015 10:35:58 +0000 Subject: [PATCH 0233/2059] Ensure auto-models have the right table --- TableRegistry.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/TableRegistry.php b/TableRegistry.php index 4fd30c04..1577c717 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -173,7 +173,15 @@ public static function get($alias, array $options = []) $options['className'] = Inflector::camelize($alias); } $className = App::className($options['className'], 'Model/Table', 'Table'); - $options['className'] = $className ?: 'Cake\ORM\Table'; + if ($className) { + $options['className'] = $className; + } else { + if (!isset($options['table'])) { + list(, $table) = pluginSplit($options['className']); + $options['table'] = Inflector::underscore($table); + } + $options['className'] = 'Cake\ORM\Table'; + } if (isset(static::$_config[$alias])) { $options += static::$_config[$alias]; From f2b6e1bfe3d8a153e8b97c4b954bf0c1affd8b4d Mon Sep 17 00:00:00 2001 From: AD7six Date: Mon, 16 Feb 2015 11:10:51 +0000 Subject: [PATCH 0234/2059] Ensure table in options not overriden And don't try to set table at all if it's not possible to accurately determine what table the user meant to use --- TableRegistry.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TableRegistry.php b/TableRegistry.php index 1577c717..7f78e370 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -176,7 +176,7 @@ public static function get($alias, array $options = []) if ($className) { $options['className'] = $className; } else { - if (!isset($options['table'])) { + if (!isset($options['table']) && strpos($options['className'], '\\') === false) { list(, $table) = pluginSplit($options['className']); $options['table'] = Inflector::underscore($table); } From 249705eb67a0ed93d8c109933e5de13dd44b0ea1 Mon Sep 17 00:00:00 2001 From: AD7six Date: Mon, 16 Feb 2015 11:20:49 +0000 Subject: [PATCH 0235/2059] Always use the association alias as a registry key And plugin-prefix it if appropriate --- Association.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Association.php b/Association.php index af8281b6..d790e237 100644 --- a/Association.php +++ b/Association.php @@ -275,17 +275,18 @@ public function target(Table $table = null) return $this->_targetTable = $table; } - if (strpos($this->_className, '\\') === false) { - $tableAlias = $this->_className; + if (strpos($this->_className, '.')) { + list($plugin) = pluginSplit($this->_className, true); + $registryAlias = $plugin . $this->_name; } else { - $tableAlias = $this->_name; + $registryAlias = $this->_name; } $config = []; - if (!TableRegistry::exists($tableAlias)) { + if (!TableRegistry::exists($registryAlias)) { $config = ['className' => $this->_className]; } - $this->_targetTable = TableRegistry::get($tableAlias, $config); + $this->_targetTable = TableRegistry::get($registryAlias, $config); return $this->_targetTable; } From aca8b43fb856d185f3a2fb91105c1ca7dc65aa4e Mon Sep 17 00:00:00 2001 From: AD7six Date: Tue, 17 Feb 2015 08:31:11 +0000 Subject: [PATCH 0236/2059] Add the registry alias to debug table debug info --- Table.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Table.php b/Table.php index 0e24eaf2..72ef86df 100644 --- a/Table.php +++ b/Table.php @@ -2158,6 +2158,7 @@ public function __debugInfo() { $conn = $this->connection(); return [ + 'registryAlias' => $this->registryAlias(), 'table' => $this->table(), 'alias' => $this->alias(), 'entityClass' => $this->entityClass(), From c37751d1945ce724cd599b769d15b42a6af169e4 Mon Sep 17 00:00:00 2001 From: AD7six Date: Tue, 17 Feb 2015 08:39:48 +0000 Subject: [PATCH 0237/2059] Source is not alias Or rather, it isn't when it's a plugin model - set to the registry alias so that hydrated results know from which table class they were created. --- ResultSet.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ResultSet.php b/ResultSet.php index 8bfaf7cf..790eb1cf 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -472,7 +472,7 @@ protected function _groupResult($row) $results[$defaultAlias]['_matchingData'] = $results['_matchingData']; } - $options['source'] = $defaultAlias; + $options['source'] = $this->_defaultTable->registryAlias(); $results = $results[$defaultAlias]; if ($this->_hydrate && !($results instanceof Entity)) { $results = new $this->_entityClass($results, $options); From 9c60eca119a3fcc1f96df336ffb419d35e2d182b Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 17 Feb 2015 21:14:31 -0500 Subject: [PATCH 0238/2059] Cast values before iterating. Force any scalar association values into an array. This prevents warnings when someone flubs `'associated' => 'Something'`. --- AssociationsNormalizerTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AssociationsNormalizerTrait.php b/AssociationsNormalizerTrait.php index a8e1c7e4..1c593fdf 100644 --- a/AssociationsNormalizerTrait.php +++ b/AssociationsNormalizerTrait.php @@ -31,7 +31,7 @@ trait AssociationsNormalizerTrait protected function _normalizeAssociations($associations) { $result = []; - foreach ($associations as $table => $options) { + foreach ((array)$associations as $table => $options) { $pointer =& $result; if (is_int($table)) { From 916fd661fa1e4ae9813cd0c59b5ce1861579db09 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 17 Feb 2015 21:25:55 -0500 Subject: [PATCH 0239/2059] Correctly marshall _joinData when it is not accessible When _joinData is not an accessible field it should still be marshaled into an entity, or have the existing entity patched correctly. The previous code was incorrectly marshalling the _joinData property twice, and the removed array cast was masking the source of the issue. Also ensure that scalar _joinData properties are not marshaled as they are always invalid. Refs #5542 --- Marshaller.php | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 2e1b5ccf..e1efef27 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -451,7 +451,6 @@ public function mergeMany($entities, array $data, array $options = []) } $key = implode(';', $entity->extract($primary)); - if ($key === null || !isset($indexed[$key])) { continue; } @@ -521,7 +520,7 @@ protected function _mergeAssociation($original, $assoc, $value, $options) * @param \Cake\ORM\Association $assoc The association to marshall * @param array $value The data to hydrate * @param array $options List of options. - * @return mixed + * @return array */ protected function _mergeBelongsToMany($original, $assoc, $value, $options) { @@ -538,8 +537,26 @@ protected function _mergeBelongsToMany($original, $assoc, $value, $options) return $this->mergeMany($original, $value, $options); } + return $this->_mergeJoinData($original, $assoc, $value, $options); + } + + /** + * Merge the special _joinData property into the entity set. + * + * @param \Cake\Datasource\EntityInterface $original The original entity + * @param \Cake\ORM\Association $assoc The association to marshall + * @param array $value The data to hydrate + * @param array $options List of options. + * @return array An array of entities + */ + protected function _mergeJoinData($original, $assoc, $value, $options) + { + $associated = isset($options['associated']) ? $options['associated'] : []; $extra = []; foreach ($original as $entity) { + // Mark joinData as accessible so we can marshal it properly. + $entity->accessible('_joinData', true); + $joinData = $entity->get('_joinData'); if ($joinData && $joinData instanceof EntityInterface) { $extra[spl_object_hash($entity)] = $joinData; @@ -558,8 +575,14 @@ protected function _mergeBelongsToMany($original, $assoc, $value, $options) foreach ($records as $record) { $hash = spl_object_hash($record); $value = $record->get('_joinData'); + + if (!is_array($value)) { + $record->unsetProperty('_joinData'); + continue; + } + if (isset($extra[$hash])) { - $record->set('_joinData', $marshaller->merge($extra[$hash], (array)$value, $nested)); + $record->set('_joinData', $marshaller->merge($extra[$hash], $value, $nested)); } else { $joinData = $marshaller->one($value, $nested); $record->set('_joinData', $joinData); From e8841ebb2edb507b4fd535cbbd0e45d395c832c4 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 19 Feb 2015 20:10:40 +0530 Subject: [PATCH 0240/2059] Rename option "idField" to "keyField". The latter is more intuitive. --- Table.php | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/Table.php b/Table.php index 72ef86df..f13ed039 100644 --- a/Table.php +++ b/Table.php @@ -908,7 +908,7 @@ public function findAll(Query $query, array $options) * * ``` * $table->find('list', [ - * 'idField' => 'name', + * 'keyField' => 'name', * 'valueField' => 'age' * ]); * ``` @@ -943,18 +943,25 @@ public function findAll(Query $query, array $options) public function findList(Query $query, array $options) { $options += [ - 'idField' => $this->primaryKey(), + 'keyField' => $this->primaryKey(), 'valueField' => $this->displayField(), 'groupField' => null ]; + + if (isset($options['idField'])) { + $options['keyField'] = $options['idField']; + unset($options['idField']); + trigger_error('Option "idField" is deprecated, use "keyField" instead.', E_USER_WARNING); + } + $options = $this->_setFieldMatchers( $options, - ['idField', 'valueField', 'groupField'] + ['keyField', 'valueField', 'groupField'] ); return $query->formatResults(function ($results) use ($options) { return $results->combine( - $options['idField'], + $options['keyField'], $options['valueField'], $options['groupField'] ); @@ -970,12 +977,12 @@ public function findList(Query $query, array $options) * * You can customize what fields are used for nesting results, by default the * primary key and the `parent_id` fields are used. If you wish to change - * these defaults you need to provide the keys `idField` or `parentField` in + * these defaults you need to provide the keys `keyField` or `parentField` in * `$options`: * * ``` * $table->find('threaded', [ - * 'idField' => 'id', + * 'keyField' => 'id', * 'parentField' => 'ancestor_id' * ]); * ``` @@ -987,13 +994,20 @@ public function findList(Query $query, array $options) public function findThreaded(Query $query, array $options) { $options += [ - 'idField' => $this->primaryKey(), + 'keyField' => $this->primaryKey(), 'parentField' => 'parent_id', ]; - $options = $this->_setFieldMatchers($options, ['idField', 'parentField']); + + if (isset($options['idField'])) { + $options['keyField'] = $options['idField']; + unset($options['idField']); + trigger_error('Option "idField" is deprecated, use "keyField" instead.', E_USER_WARNING); + } + + $options = $this->_setFieldMatchers($options, ['keyField', 'parentField']); return $query->formatResults(function ($results) use ($options) { - return $results->nest($options['idField'], $options['parentField']); + return $results->nest($options['keyField'], $options['parentField']); }); } From 99bdcc2a773e17732867ca9530b5c5efbbd3490e Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 19 Feb 2015 10:48:26 -0500 Subject: [PATCH 0241/2059] Guard against missing new entities. If a find operation has been modified via an event listener or overloaded find method we should avoid trying to marshal data that doesn't exist. Refs #5922 --- Marshaller.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 2e1b5ccf..28d829bd 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -471,11 +471,13 @@ public function mergeMany($entities, array $data, array $options = []) return $query->orWhere($query->newExpr()->and_(array_combine($primary, $keys))); }, $this->_table->find()); - if (count($maybeExistentQuery->clause('where'))) { + if (!empty($indexed) && count($maybeExistentQuery->clause('where'))) { foreach ($maybeExistentQuery as $entity) { $key = implode(';', $entity->extract($primary)); - $output[] = $this->merge($entity, $indexed[$key], $options); - unset($indexed[$key]); + if (isset($indexed[$key])) { + $output[] = $this->merge($entity, $indexed[$key], $options); + unset($indexed[$key]); + } } } From 96ea516503fa21464f6a8a91d62684793440ff76 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 22 Feb 2015 21:03:56 -0500 Subject: [PATCH 0242/2059] Fix scope option not working in validateUnique. The scope option exists in $context not $options. Add a previously failing test as well. --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index f13ed039..03b48fb9 100644 --- a/Table.php +++ b/Table.php @@ -2055,7 +2055,7 @@ public function validateUnique($value, array $context, array $options = null) ); $fields = array_merge( [$options['field']], - isset($options['scope']) ? (array)$options['scope'] : [] + isset($context['scope']) ? (array)$context['scope'] : [] ); $rule = new IsUnique($fields); return $rule($entity, ['repository' => $this]); From 1c75a602f3f7d052dcee7d3b6c97fdf6dafdd49b Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 22 Feb 2015 21:08:11 -0500 Subject: [PATCH 0243/2059] Rename parameters. Validation $context is the last parameter when methods are used as validators. --- Table.php | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Table.php b/Table.php index 03b48fb9..0324a210 100644 --- a/Table.php +++ b/Table.php @@ -2036,26 +2036,27 @@ public function patchEntities($entities, array $data, array $options = []) * the data to be validated. * * @param mixed $value The value of column to be checked for uniqueness - * @param array $context Either the options or validation context. - * @param array|null $options The options array, optionally containing the 'scope' key + * @param array $options The options array, optionally containing the 'scope' key. + * May also be the validation context if there are no options. + * @param array|null $context Either the validation context or null. * @return bool true if the value is unique */ - public function validateUnique($value, array $context, array $options = null) + public function validateUnique($value, array $options, array $context = null) { - if ($options === null) { - $options = $context; + if ($context === null) { + $context = $options; } $entity = new Entity( - $options['data'], + $context['data'], [ 'useSetters' => false, - 'markNew' => $options['newRecord'], + 'markNew' => $context['newRecord'], 'source' => $this->registryAlias() ] ); $fields = array_merge( - [$options['field']], - isset($context['scope']) ? (array)$context['scope'] : [] + [$context['field']], + isset($options['scope']) ? (array)$options['scope'] : [] ); $rule = new IsUnique($fields); return $rule($entity, ['repository' => $this]); From b5db279600ac7b73eca937100fc19ee3d0a2b985 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 23 Feb 2015 22:02:17 -0500 Subject: [PATCH 0244/2059] Fix duplicate column errors in IsUnique rules. Refs #5877 --- Rule/IsUnique.php | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index 36c241fa..596168b3 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -53,10 +53,11 @@ public function __invoke(EntityInterface $entity, array $options) return true; } - $conditions = $entity->extract($this->_fields); + $alias = $options['repository']->alias(); + $conditions = $this->_alias($alias, $entity->extract($this->_fields)); if ($entity->isNew() === false) { $keys = (array)$options['repository']->primaryKey(); - $keys = $entity->extract($keys); + $keys = $this->_alias($alias, $entity->extract($keys)); if (array_filter($keys, 'strlen')) { $conditions['NOT'] = $keys; } @@ -64,4 +65,20 @@ public function __invoke(EntityInterface $entity, array $options) return !$options['repository']->exists($conditions); } + + /** + * Add a model alias to all the keys in a set of conditions. + * + * @param string $alias The alias to add. + * @param array $conditions The conditions to alias. + * @return array + */ + protected function _alias($alias, $conditions) + { + $aliased = []; + foreach ($conditions as $key => $value) { + $aliased["$alias.$key"] = $value; + } + return $aliased; + } } From 5e612be449c4961c56315f9da4a9fb3407fdec36 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 24 Feb 2015 22:43:36 -0500 Subject: [PATCH 0245/2059] Remove fields from count() queries when associations are contained. Strip out any fields defined in contained associations as fetching fields and throwing away the results is wasteful and can lead to slow downs in larger datasets. Refs #5955 --- Query.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Query.php b/Query.php index 77ddc562..54a1fc36 100644 --- a/Query.php +++ b/Query.php @@ -507,8 +507,16 @@ public function count() $complex = $complex || count($query->clause('union')); if (!$complex) { + $cleanContain = []; + foreach ($query->contain() as $alias => $contain) { + unset($contain['fields']); + $cleanContain[$alias] = $contain; + } + $query->contain(null, true); + $statement = $query ->select($count, true) + ->contain($cleanContain) ->autoFields(false) ->execute(); } else { From 5200bab77154e3201165399c606c5c696e579cc7 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 24 Feb 2015 18:25:12 +0530 Subject: [PATCH 0246/2059] Add Model.afterSaveCommit event. --- Table.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index f13ed039..ae0fa7f9 100644 --- a/Table.php +++ b/Table.php @@ -1349,6 +1349,11 @@ public function save(EntityInterface $entity, $options = []) $success = $connection->transactional(function () use ($entity, $options) { return $this->_processSave($entity, $options); }); + if ($success) { + $this->dispatchEvent('Model.afterSaveCommit', compact('entity', 'options')); + $entity->isNew(false); + $entity->source($this->registryAlias()); + } } else { $success = $this->_processSave($entity, $options); } @@ -1419,8 +1424,10 @@ protected function _processSave($entity, $options) if ($success || !$options['atomic']) { $entity->clean(); $this->dispatchEvent('Model.afterSave', compact('entity', 'options')); - $entity->isNew(false); - $entity->source($this->registryAlias()); + if (!$options['atomic']) { + $entity->isNew(false); + $entity->source($this->registryAlias()); + } $success = true; } } @@ -2146,6 +2153,7 @@ public function implementedEvents() 'Model.beforeFind' => 'beforeFind', 'Model.beforeSave' => 'beforeSave', 'Model.afterSave' => 'afterSave', + 'Model.afterSaveCommit' => 'afterSaveCommit', 'Model.beforeDelete' => 'beforeDelete', 'Model.afterDelete' => 'afterDelete', 'Model.beforeRules' => 'beforeRules', From a083b920e9f278707b1835aebf6bcbeea929fbf4 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 26 Feb 2015 13:55:50 +0530 Subject: [PATCH 0247/2059] Add '_primary' flag to save options. It's used to ensure 'afterSaveCommit' is not triggered for nested transactional saves. --- Table.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Table.php b/Table.php index ae0fa7f9..55364e52 100644 --- a/Table.php +++ b/Table.php @@ -1333,7 +1333,8 @@ public function save(EntityInterface $entity, $options = []) 'atomic' => true, 'associated' => true, 'checkRules' => true, - 'checkExisting' => true + 'checkExisting' => true, + '_primary' => true ]); if ($entity->errors()) { @@ -1350,7 +1351,9 @@ public function save(EntityInterface $entity, $options = []) return $this->_processSave($entity, $options); }); if ($success) { - $this->dispatchEvent('Model.afterSaveCommit', compact('entity', 'options')); + if ($options['_primary']) { + $this->dispatchEvent('Model.afterSaveCommit', compact('entity', 'options')); + } $entity->isNew(false); $entity->source($this->registryAlias()); } @@ -1398,7 +1401,7 @@ protected function _processSave($entity, $options) $this, $entity, $options['associated'], - $options->getArrayCopy() + ['_primary' => false] + $options->getArrayCopy() ); if (!$saved && $options['atomic']) { @@ -1419,7 +1422,7 @@ protected function _processSave($entity, $options) $this, $entity, $options['associated'], - $options->getArrayCopy() + ['_primary' => false] + $options->getArrayCopy() ); if ($success || !$options['atomic']) { $entity->clean(); From 53d2c28df505a88f1ecbe0ee2f5f1e0814549451 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 26 Feb 2015 23:04:21 +0530 Subject: [PATCH 0248/2059] Use Connection::isTransaction() instead of _primary flag. The former is a more robust way to check if transaction is running and even accounts for transactions started outside Table::save(). --- Table.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Table.php b/Table.php index 55364e52..97f31d40 100644 --- a/Table.php +++ b/Table.php @@ -1334,7 +1334,6 @@ public function save(EntityInterface $entity, $options = []) 'associated' => true, 'checkRules' => true, 'checkExisting' => true, - '_primary' => true ]); if ($entity->errors()) { @@ -1351,7 +1350,7 @@ public function save(EntityInterface $entity, $options = []) return $this->_processSave($entity, $options); }); if ($success) { - if ($options['_primary']) { + if (!$connection->inTransaction()) { $this->dispatchEvent('Model.afterSaveCommit', compact('entity', 'options')); } $entity->isNew(false); @@ -1401,7 +1400,7 @@ protected function _processSave($entity, $options) $this, $entity, $options['associated'], - ['_primary' => false] + $options->getArrayCopy() + $options->getArrayCopy() ); if (!$saved && $options['atomic']) { @@ -1422,7 +1421,7 @@ protected function _processSave($entity, $options) $this, $entity, $options['associated'], - ['_primary' => false] + $options->getArrayCopy() + $options->getArrayCopy() ); if ($success || !$options['atomic']) { $entity->clean(); From 694633b02e4107e84ba51223605cbc9611af60fc Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 26 Feb 2015 22:49:52 -0500 Subject: [PATCH 0249/2059] Exclude features from count queries with eagerloader features. Instead of rewriting contain() options we should use eagerloader features to exclude associated fields from count queries. --- Association.php | 5 +++++ EagerLoader.php | 25 ++++++++++++++++++++++++- Query.php | 9 +-------- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/Association.php b/Association.php index d790e237..e9fcacf2 100644 --- a/Association.php +++ b/Association.php @@ -640,6 +640,11 @@ protected function _appendFields($query, $surrogate, $options) $fields = $surrogate->clause('select') ?: $options['fields']; $target = $this->_targetTable; $autoFields = $surrogate->autoFields(); + + if ($query->eagerLoader()->autoFields() === false) { + return false; + } + if (empty($fields) && !$autoFields) { if ($options['includeFields'] && ($fields === null || $fields !== false)) { $fields = $target->schema()->columns(); diff --git a/EagerLoader.php b/EagerLoader.php index aeb9684c..6780c2be 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -95,6 +95,15 @@ class EagerLoader */ protected $_joinsMap = []; + /** + * Controls whether or not fields from associated tables + * will be eagerly loaded. When set to false, no fields will + * be loaded from associations. + * + * @var bool + */ + protected $_autoFields = true; + /** * Sets the list of associations that should be eagerly loaded along for a * specific table using when a query is provided. The list of associated tables @@ -134,6 +143,20 @@ public function contain($associations = []) return $this->_containments = $associations; } + /** + * Set whether or not contained associations will load fields automatically. + * + * @param bool $value The value to set. + * @return bool The current value. + */ + public function autoFields($value = null) + { + if ($value !== null) { + $this->_autoFields = (bool)$value; + } + return $this->_autoFields; + } + /** * Adds a new association to the list that will be used to filter the results of * any given query based on the results of finding records for that association. @@ -294,7 +317,7 @@ public function attachAssociations(Query $query, Table $repository, $includeFiel $config = $loadable->config() + [ 'aliasPath' => $loadable->aliasPath(), 'propertyPath' => $loadable->propertyPath(), - 'includeFields' => $includeFields + 'includeFields' => $includeFields, ]; $loadable->instance()->attachTo($query, $config); } diff --git a/Query.php b/Query.php index 54a1fc36..83bcb046 100644 --- a/Query.php +++ b/Query.php @@ -507,16 +507,9 @@ public function count() $complex = $complex || count($query->clause('union')); if (!$complex) { - $cleanContain = []; - foreach ($query->contain() as $alias => $contain) { - unset($contain['fields']); - $cleanContain[$alias] = $contain; - } - $query->contain(null, true); - + $query->eagerLoader()->autoFields(false); $statement = $query ->select($count, true) - ->contain($cleanContain) ->autoFields(false) ->execute(); } else { From 4f27687433ed7b26d9c7846d3dd9764afaa2e48e Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 27 Feb 2015 12:03:25 +0530 Subject: [PATCH 0250/2059] Add afterDeleteCommit event. --- Table.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 97f31d40..484c9eed 100644 --- a/Table.php +++ b/Table.php @@ -1598,7 +1598,15 @@ public function delete(EntityInterface $entity, $options = []) }; if ($options['atomic']) { - return $this->connection()->transactional($process); + $connection = $this->connection(); + $success = $connection->transactional($process); + if ($success && !$connection->inTransaction()) { + $this->dispatchEvent('Model.afterDeleteCommit', [ + 'entity' => $entity, + 'options' => $options + ]); + } + return $success; } return $process(); } @@ -2158,6 +2166,7 @@ public function implementedEvents() 'Model.afterSaveCommit' => 'afterSaveCommit', 'Model.beforeDelete' => 'beforeDelete', 'Model.afterDelete' => 'afterDelete', + 'Model.afterDeleteCommit' => 'afterDeleteCommit', 'Model.beforeRules' => 'beforeRules', 'Model.afterRules' => 'afterRules', ]; From a117be5ffa9104d82039ff8227d833748c6eee7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Lorenzo=20Rodr=C3=ADguez?= Date: Fri, 27 Feb 2015 08:57:49 +0100 Subject: [PATCH 0251/2059] Not returning false in function that is meant to return void --- Association.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association.php b/Association.php index e9fcacf2..9c4eda35 100644 --- a/Association.php +++ b/Association.php @@ -642,7 +642,7 @@ protected function _appendFields($query, $surrogate, $options) $autoFields = $surrogate->autoFields(); if ($query->eagerLoader()->autoFields() === false) { - return false; + return; } if (empty($fields) && !$autoFields) { From 3d124e5198f17ca8d8b7b257f11cae2c94ea414f Mon Sep 17 00:00:00 2001 From: AD7six Date: Sat, 28 Feb 2015 13:00:34 +0000 Subject: [PATCH 0252/2059] Revert "Merge pull request #5822 from patrickconroy/3.0-translate-fieldconditions" This only reverts changes made to the behavior, keeping changes made to tests. This reverts commit e8d88a7efadafc16414e50b573530075df48bc07, reversing changes made to 902757ff6b0bc5d20815fd66f73785e7a7fe2568. Conflicts: src/ORM/Behavior/TranslateBehavior.php --- Behavior/TranslateBehavior.php | 39 ++++++++++++---------------------- 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 2f996c58..d66e1b46 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -76,8 +76,7 @@ class TranslateBehavior extends Behavior 'defaultLocale' => '', 'model' => '', 'onlyTranslated' => false, - 'strategy' => 'subquery', - 'conditions' => ['model' => ''] + 'strategy' => 'subquery' ]; /** @@ -102,19 +101,10 @@ public function initialize(array $config) { $this->_translationTable = TableRegistry::get($this->_config['translationTable']); - if ($this->config('model')) { - $model = $this->config('model'); - } elseif ($this->config('conditions.model')) { - $model = $this->config('conditions.model'); - } else { - $model = $this->_table->alias(); - } - $this->config('conditions.model', $model); - $this->setupFieldAssociations( $this->_config['fields'], $this->_config['translationTable'], - $this->_config['conditions'], + $this->_config['model'] ? $this->_config['model'] : $this->_table->alias(), $this->_config['strategy'] ); } @@ -128,12 +118,12 @@ public function initialize(array $config) * * @param array $fields list of fields to create associations for * @param string $table the table name to use for storing each field translation - * @param array $fieldConditions conditions for finding fields + * @param string $model the model field value * @param string $strategy the strategy used in the _i18n association * * @return void */ - public function setupFieldAssociations($fields, $table, $fieldConditions, $strategy) + public function setupFieldAssociations($fields, $table, $model, $strategy) { $targetAlias = $this->_translationTable->alias(); $alias = $this->_table->alias(); @@ -141,13 +131,7 @@ public function setupFieldAssociations($fields, $table, $fieldConditions, $strat foreach ($fields as $field) { $name = $alias . '_' . $field . '_translation'; - $conditions = [ - $name . '.model' => $fieldConditions['model'], - $name . '.field' => $field, - ]; - foreach ($fieldConditions as $fieldName => $fieldValue) { - $conditions[$name . '.' . $fieldName] = $fieldValue; - } + if (!TableRegistry::exists($name)) { $fieldTable = TableRegistry::get($name, [ 'className' => $table, @@ -162,7 +146,10 @@ public function setupFieldAssociations($fields, $table, $fieldConditions, $strat 'targetTable' => $fieldTable, 'foreignKey' => 'foreign_key', 'joinType' => $filter ? 'INNER' : 'LEFT', - 'conditions' => $conditions, + 'conditions' => [ + $name . '.model' => $model, + $name . '.field' => $field, + ], 'propertyName' => $field . '_translation' ]); } @@ -171,7 +158,7 @@ public function setupFieldAssociations($fields, $table, $fieldConditions, $strat 'className' => $table, 'foreignKey' => 'foreign_key', 'strategy' => $strategy, - 'conditions' => ["$targetAlias.model" => $fieldConditions['model']], + 'conditions' => ["$targetAlias.model" => $model], 'propertyName' => '_i18n', 'dependent' => true ]); @@ -267,7 +254,7 @@ public function beforeSave(Event $event, Entity $entity, ArrayObject $options) $fields = array_keys($values); $primaryKey = (array)$this->_table->primaryKey(); $key = $entity->get(current($primaryKey)); - $model = $this->config('conditions.model'); + $model = $this->_table->alias(); $preexistent = $this->_translationTable->find() ->select(['id', 'field']) @@ -473,14 +460,14 @@ protected function _bundleTranslatedFields($entity) } $results = $this->_findExistingTranslations($find); - $model = $this->config('conditions.model'); + $alias = $this->_table->alias(); foreach ($find as $i => $translation) { if (!empty($results[$i])) { $contents[$i]->set('id', $results[$i], ['setter' => false]); $contents[$i]->isNew(false); } else { - $translation['model'] = $model; + $translation['model'] = $alias; $contents[$i]->set($translation, ['setter' => false, 'guard' => false]); $contents[$i]->isNew(true); } From d0cab6836c44da413a0357332383f1f704e29eec Mon Sep 17 00:00:00 2001 From: AD7six Date: Sat, 28 Feb 2015 14:10:40 +0000 Subject: [PATCH 0253/2059] Add allowEmptyTranslations This re-implements conditions. --- Behavior/TranslateBehavior.php | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index d66e1b46..79c33f10 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -75,6 +75,7 @@ class TranslateBehavior extends Behavior 'translationTable' => 'I18n', 'defaultLocale' => '', 'model' => '', + 'allowEmptyTranslations' => true, 'onlyTranslated' => false, 'strategy' => 'subquery' ]; @@ -142,23 +143,33 @@ public function setupFieldAssociations($fields, $table, $model, $strategy) $fieldTable = TableRegistry::get($name); } + $conditions = [ + $name . '.model' => $model, + $name . '.field' => $field, + ]; + if (!$this->_config['allowEmptyTranslations']) { + $conditions[$name . '.content !='] = ''; + } + $this->_table->hasOne($name, [ 'targetTable' => $fieldTable, 'foreignKey' => 'foreign_key', 'joinType' => $filter ? 'INNER' : 'LEFT', - 'conditions' => [ - $name . '.model' => $model, - $name . '.field' => $field, - ], + 'conditions' => $conditions, 'propertyName' => $field . '_translation' ]); } + $conditions = ["$targetAlias.model" => $model]; + if (!$this->_config['allowEmptyTranslations']) { + $conditions["$targetAlias.content !="] = ''; + } + $this->_table->hasMany($targetAlias, [ 'className' => $table, 'foreignKey' => 'foreign_key', 'strategy' => $strategy, - 'conditions' => ["$targetAlias.model" => $model], + 'conditions' => $conditions, 'propertyName' => '_i18n', 'dependent' => true ]); From 2159130d916597558ffa209af7e1e0aff46f0fcb Mon Sep 17 00:00:00 2001 From: AD7six Date: Sat, 28 Feb 2015 20:42:16 +0000 Subject: [PATCH 0254/2059] Use the model key if set --- Behavior/TranslateBehavior.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 79c33f10..10cbdf34 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -105,7 +105,7 @@ public function initialize(array $config) $this->setupFieldAssociations( $this->_config['fields'], $this->_config['translationTable'], - $this->_config['model'] ? $this->_config['model'] : $this->_table->alias(), + $this->_config['model'] ?: $this->_table->alias(), $this->_config['strategy'] ); } @@ -471,14 +471,14 @@ protected function _bundleTranslatedFields($entity) } $results = $this->_findExistingTranslations($find); - $alias = $this->_table->alias(); + $model = $this->_config['model'] ?: $this->_table->alias(); foreach ($find as $i => $translation) { if (!empty($results[$i])) { $contents[$i]->set('id', $results[$i], ['setter' => false]); $contents[$i]->isNew(false); } else { - $translation['model'] = $alias; + $translation['model'] = $model; $contents[$i]->set($translation, ['setter' => false, 'guard' => false]); $contents[$i]->isNew(true); } From 0ff8ede8af3d14aa8b194fdafda21a578a462cf3 Mon Sep 17 00:00:00 2001 From: AD7six Date: Sat, 28 Feb 2015 21:44:51 +0000 Subject: [PATCH 0255/2059] Another missed 'model' use --- Behavior/TranslateBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 10cbdf34..3486b75a 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -265,7 +265,7 @@ public function beforeSave(Event $event, Entity $entity, ArrayObject $options) $fields = array_keys($values); $primaryKey = (array)$this->_table->primaryKey(); $key = $entity->get(current($primaryKey)); - $model = $this->_table->alias(); + $model = $this->_config['model'] ?: $this->_table->alias(); $preexistent = $this->_translationTable->find() ->select(['id', 'field']) From 040ee8f3d202a892768ded86de94baf910085fe1 Mon Sep 17 00:00:00 2001 From: AD7six Date: Sun, 1 Mar 2015 10:15:13 +0000 Subject: [PATCH 0256/2059] Consolidate model/alias shuffling So that if we can make it cleaner - we do so in one place only --- Behavior/TranslateBehavior.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 3486b75a..78a18e48 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -88,7 +88,10 @@ class TranslateBehavior extends Behavior */ public function __construct(Table $table, array $config = []) { - $config += ['defaultLocale' => I18n::defaultLocale()]; + $config += [ + 'defaultLocale' => I18n::defaultLocale(), + 'model' => $table->alias() + ]; parent::__construct($table, $config); } @@ -105,7 +108,7 @@ public function initialize(array $config) $this->setupFieldAssociations( $this->_config['fields'], $this->_config['translationTable'], - $this->_config['model'] ?: $this->_table->alias(), + $this->_config['model'], $this->_config['strategy'] ); } @@ -265,7 +268,7 @@ public function beforeSave(Event $event, Entity $entity, ArrayObject $options) $fields = array_keys($values); $primaryKey = (array)$this->_table->primaryKey(); $key = $entity->get(current($primaryKey)); - $model = $this->_config['model'] ?: $this->_table->alias(); + $model = $this->_config['model']; $preexistent = $this->_translationTable->find() ->select(['id', 'field']) @@ -471,14 +474,13 @@ protected function _bundleTranslatedFields($entity) } $results = $this->_findExistingTranslations($find); - $model = $this->_config['model'] ?: $this->_table->alias(); foreach ($find as $i => $translation) { if (!empty($results[$i])) { $contents[$i]->set('id', $results[$i], ['setter' => false]); $contents[$i]->isNew(false); } else { - $translation['model'] = $model; + $translation['model'] = $this->_config['model']; $contents[$i]->set($translation, ['setter' => false, 'guard' => false]); $contents[$i]->isNew(true); } From de13f3a16a63689d0de5b24b8b1383293282bce3 Mon Sep 17 00:00:00 2001 From: AD7six Date: Sun, 1 Mar 2015 21:52:40 +0000 Subject: [PATCH 0257/2059] Rename model to referenceName While 'model' is the field name, as a config key it is quite meaningless without looking how it's used. Changed to reference name as, while not perfect, it's more descriptive than "model". --- Behavior/TranslateBehavior.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 78a18e48..9950d04c 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -74,7 +74,7 @@ class TranslateBehavior extends Behavior 'fields' => [], 'translationTable' => 'I18n', 'defaultLocale' => '', - 'model' => '', + 'referenceName' => '', 'allowEmptyTranslations' => true, 'onlyTranslated' => false, 'strategy' => 'subquery' @@ -90,7 +90,7 @@ public function __construct(Table $table, array $config = []) { $config += [ 'defaultLocale' => I18n::defaultLocale(), - 'model' => $table->alias() + 'referenceName' => $table->alias() ]; parent::__construct($table, $config); } @@ -108,7 +108,7 @@ public function initialize(array $config) $this->setupFieldAssociations( $this->_config['fields'], $this->_config['translationTable'], - $this->_config['model'], + $this->_config['referenceName'], $this->_config['strategy'] ); } @@ -268,7 +268,7 @@ public function beforeSave(Event $event, Entity $entity, ArrayObject $options) $fields = array_keys($values); $primaryKey = (array)$this->_table->primaryKey(); $key = $entity->get(current($primaryKey)); - $model = $this->_config['model']; + $model = $this->_config['referenceName']; $preexistent = $this->_translationTable->find() ->select(['id', 'field']) @@ -480,7 +480,7 @@ protected function _bundleTranslatedFields($entity) $contents[$i]->set('id', $results[$i], ['setter' => false]); $contents[$i]->isNew(false); } else { - $translation['model'] = $this->_config['model']; + $translation['model'] = $this->_config['referenceName']; $contents[$i]->set($translation, ['setter' => false, 'guard' => false]); $contents[$i]->isNew(true); } From 81b0d9de19daa77cad7db449571466b30411d8c2 Mon Sep 17 00:00:00 2001 From: AD7six Date: Sun, 1 Mar 2015 22:02:19 +0000 Subject: [PATCH 0258/2059] Accurately derive the reference name So that only in none-conventional/exceptional circumstances is it necessary for developers to specify it at all --- Behavior/TranslateBehavior.php | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 9950d04c..326eb85e 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -23,6 +23,7 @@ use Cake\ORM\Query; use Cake\ORM\Table; use Cake\ORM\TableRegistry; +use Cake\Utility\Inflector; /** * This behavior provides a way to translate dynamic data by keeping translations @@ -90,7 +91,7 @@ public function __construct(Table $table, array $config = []) { $config += [ 'defaultLocale' => I18n::defaultLocale(), - 'referenceName' => $table->alias() + 'referenceName' => $this->_referenceName($table) ]; parent::__construct($table, $config); } @@ -363,6 +364,29 @@ public function findTranslations(Query $query, array $options) ->formatResults([$this, 'groupTranslations'], $query::PREPEND); } + /** + * Determine the reference name to use for a given table + * + * The reference name is usually derived from the class name of the table object + * (PostsTable -> Posts), however for autotable instances it is derived from + * the database table the object points at - or as a last resort, the alias + * of the autotable instance. + * + * @param Table $table + * @return string + */ + protected function _referenceName(Table $table) + { + $name = namespaceSplit(get_class($table)); + $name = substr(end($name), 0, -5); + if (empty($name)) { + $name = $table->table() ?: $table->alias(); + $name = Inflector::camelize($name); + } + + return $name; + } + /** * Modifies the results from a table find in order to merge the translated fields * into each entity for a given locale. From 4a243db37e8e172b2a630279ffaa0b6b688632ab Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 28 Feb 2015 08:16:41 +0530 Subject: [PATCH 0259/2059] Add info about new events to docblock --- Table.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index 484c9eed..e210e481 100644 --- a/Table.php +++ b/Table.php @@ -1294,6 +1294,9 @@ public function exists($conditions) * listeners will receive the entity and the options array as arguments. The type * of operation performed (insert or update) can be determined by checking the * entity's method `isNew`, true meaning an insert and false an update. + * - Model.afterSaveCommit: Will be triggered after the transaction is commited + * for atomic save, listeners will receive the entity and the options array + * as arguments. * * This method will determine whether the passed entity needs to be * inserted or updated in the database. It does that by checking the `isNew` @@ -1579,10 +1582,14 @@ protected function _update($entity, $data) * * ### Events * - * - `beforeDelete` Fired before the delete occurs. If stopped the delete + * - `Model.beforeDelete` Fired before the delete occurs. If stopped the delete * will be aborted. Receives the event, entity, and options. - * - `afterDelete` Fired after the delete has been successful. Receives + * - `Model.afterDelete` Fired after the delete has been successful. Receives * the event, entity, and options. + * - `Model.afterDelete` Fired after the delete has been successful. Receives + * the event, entity, and options. + * - `Model.afterDeleteCommit` Fired after the transaction is committed for + * an atomic delete. Receives the event, entity, and options. * * The options argument will be converted into an \ArrayObject instance * for the duration of the callbacks, this allows listeners to modify From 73ee6f877fb9e0446029775a2f9c5a4939bade27 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 3 Mar 2015 19:21:57 +0530 Subject: [PATCH 0260/2059] Trigger afterSaveCommit for non-atomic saves too for primary table. --- Table.php | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/Table.php b/Table.php index e210e481..b3b68223 100644 --- a/Table.php +++ b/Table.php @@ -1337,6 +1337,7 @@ public function save(EntityInterface $entity, $options = []) 'associated' => true, 'checkRules' => true, 'checkExisting' => true, + '_primary' => true ]); if ($entity->errors()) { @@ -1347,20 +1348,25 @@ public function save(EntityInterface $entity, $options = []) return $entity; } + $connection = $this->connection(); if ($options['atomic']) { - $connection = $this->connection(); $success = $connection->transactional(function () use ($entity, $options) { return $this->_processSave($entity, $options); }); - if ($success) { - if (!$connection->inTransaction()) { - $this->dispatchEvent('Model.afterSaveCommit', compact('entity', 'options')); - } + } else { + $success = $this->_processSave($entity, $options); + } + + if ($success) { + if (!$connection->inTransaction() && + ($options['atomic'] || (!$options['atomic'] && $options['_primary'])) + ) { + $this->dispatchEvent('Model.afterSaveCommit', compact('entity', 'options')); + } + if ($options['atomic'] || $options['_primary']) { $entity->isNew(false); $entity->source($this->registryAlias()); } - } else { - $success = $this->_processSave($entity, $options); } return $success; @@ -1403,7 +1409,7 @@ protected function _processSave($entity, $options) $this, $entity, $options['associated'], - $options->getArrayCopy() + ['_primary' => false] + $options->getArrayCopy() ); if (!$saved && $options['atomic']) { @@ -1424,12 +1430,12 @@ protected function _processSave($entity, $options) $this, $entity, $options['associated'], - $options->getArrayCopy() + ['_primary' => false] + $options->getArrayCopy() ); if ($success || !$options['atomic']) { $entity->clean(); $this->dispatchEvent('Model.afterSave', compact('entity', 'options')); - if (!$options['atomic']) { + if (!$options['atomic'] && !$options['_primary']) { $entity->isNew(false); $entity->source($this->registryAlias()); } From 0b0ac1ac6f0af02298e9c7af7b60be6ca05e5384 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 3 Mar 2015 19:29:53 +0530 Subject: [PATCH 0261/2059] Trigger afterDeleteCommit for non-atomic deletes too for primary table. --- Table.php | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/Table.php b/Table.php index b3b68223..3e093f8d 100644 --- a/Table.php +++ b/Table.php @@ -1604,24 +1604,33 @@ protected function _update($entity, $data) */ public function delete(EntityInterface $entity, $options = []) { - $options = new ArrayObject($options + ['atomic' => true, 'checkRules' => true]); + $options = new ArrayObject($options + [ + 'atomic' => true, + 'checkRules' => true, + '_primary' => true, + ]); $process = function () use ($entity, $options) { return $this->_processDelete($entity, $options); }; + $connection = $this->connection(); if ($options['atomic']) { - $connection = $this->connection(); $success = $connection->transactional($process); - if ($success && !$connection->inTransaction()) { - $this->dispatchEvent('Model.afterDeleteCommit', [ - 'entity' => $entity, - 'options' => $options - ]); - } - return $success; + } else { + $success = $process(); + } + + if ($success && + !$connection->inTransaction() && + ($options['atomic'] || (!$options['atomic'] && $options['_primary'])) + ) { + $this->dispatchEvent('Model.afterDeleteCommit', [ + 'entity' => $entity, + 'options' => $options + ]); } - return $process(); + return $success; } /** @@ -1661,7 +1670,10 @@ protected function _processDelete($entity, $options) return $event->result; } - $this->_associations->cascadeDelete($entity, $options->getArrayCopy()); + $this->_associations->cascadeDelete( + $entity, + ['_primary' => false] + $options->getArrayCopy() + ); $query = $this->query(); $conditions = (array)$entity->extract($primaryKey); From 5fbafe705e050e56f10997481d42e560ccc3c73a Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 5 Mar 2015 01:52:00 +0530 Subject: [PATCH 0262/2059] Fix incorrect level field name usage. Fixes #6010 --- Behavior/TreeBehavior.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index f5e5b392..d4347847 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -103,7 +103,7 @@ public function beforeSave(Event $event, Entity $entity) $this->_sync(2, '+', ">= {$edge}"); if ($level) { - $entity->set($config[$level], $parentNode[$level] + 1); + $entity->set($level, $parentNode[$level] + 1); } return; } @@ -114,7 +114,7 @@ public function beforeSave(Event $event, Entity $entity) $entity->set($config['right'], $edge + 2); if ($level) { - $entity->set($config[$level], 0); + $entity->set($level, 0); } return; } @@ -133,7 +133,7 @@ public function beforeSave(Event $event, Entity $entity) $this->_setAsRoot($entity); if ($level) { - $entity->set($config[$level], 0); + $entity->set($level, 0); } } } From b58dbd1d5fa951bd5f912c79f1164c574115bbf0 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Wed, 4 Mar 2015 21:18:50 -0500 Subject: [PATCH 0263/2059] Fix PHPCS errors. --- Behavior/TranslateBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 326eb85e..cc12407d 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -372,7 +372,7 @@ public function findTranslations(Query $query, array $options) * the database table the object points at - or as a last resort, the alias * of the autotable instance. * - * @param Table $table + * @param Table $table The table class to get a reference name for. * @return string */ protected function _referenceName(Table $table) From a5acdecd12b24eafe4072f0b6c0dbbfc26a95598 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 5 Mar 2015 08:36:23 +0530 Subject: [PATCH 0264/2059] Update docblock --- Behavior/TranslateBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index cc12407d..603d653c 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -372,7 +372,7 @@ public function findTranslations(Query $query, array $options) * the database table the object points at - or as a last resort, the alias * of the autotable instance. * - * @param Table $table The table class to get a reference name for. + * @param \Cake\ORM\Table $table The table class to get a reference name for. * @return string */ protected function _referenceName(Table $table) From 47f2a73c31743843875050ac9f0b8e031b50c87b Mon Sep 17 00:00:00 2001 From: DnSu Date: Thu, 5 Mar 2015 21:14:11 -0500 Subject: [PATCH 0265/2059] Update TreeBehavior.php extra space cause api to not generate it as a new line --- Behavior/TreeBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index d4347847..7558af1e 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -449,7 +449,7 @@ function ($field) use ($alias) { * return the key out of the provided row. * - valuePath: A dot separated path to fetch the field to use for the array value, or a closure to * return the value out of the provided row. - * - spacer: A string to be used as prefix for denoting the depth in the tree for each item + * - spacer: A string to be used as prefix for denoting the depth in the tree for each item * * @param \Cake\ORM\Query $query Query. * @param array $options Array of options as described above From 66d9838597257f3989bb634ce124a9b1d447cdf8 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 5 Mar 2015 21:42:43 -0500 Subject: [PATCH 0266/2059] Reset EagerLoader when cloning a query. When a 'clean' copy is made of a query we need to reset the eager loader so changes to associations are separate. Make sure to keep the contained associations around though. Refs #5961 --- Query.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Query.php b/Query.php index 83bcb046..73fb5bbf 100644 --- a/Query.php +++ b/Query.php @@ -479,6 +479,7 @@ public function cleanCopy() $query = clone $this; $query->triggerBeforeFind(); $query->autoFields(false); + $query->eagerLoader(clone $this->eagerLoader()); $query->limit(null); $query->order([], true); $query->offset(null); From e7d7db445c0bfb06e198f3570223bd7ec61acbde Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 2 Mar 2015 21:25:10 -0500 Subject: [PATCH 0267/2059] Fix composite primary key support when inserting data. When inserting data into tables that have composite keys that involve an autoincrement column we should not require the autoincrement column to be populated as the database server will populate it on insert. --- Table.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 51acc916..46c9283f 100644 --- a/Table.php +++ b/Table.php @@ -1479,8 +1479,9 @@ protected function _insert($entity, $data) $data = $filteredKeys + $data; if (count($primary) > 1) { + $schema = $this->schema(); foreach ($primary as $k => $v) { - if (!isset($data[$k])) { + if (!isset($data[$k]) && empty($schema->column($k)['autoIncrement'])) { $msg = 'Cannot insert row, some of the primary key values are missing. '; $msg .= sprintf( 'Got (%s), expecting (%s)', From b7c4bdc5311f9a21014a0df8599fe49db60c8e76 Mon Sep 17 00:00:00 2001 From: Patrick Conroy Date: Fri, 6 Mar 2015 17:16:30 -0500 Subject: [PATCH 0268/2059] Adding ability for subclasses of Association to lock down possible strategies --- Association.php | 24 ++++++++++++++++++++++-- Association/BelongsTo.php | 7 +++++++ Association/BelongsToMany.php | 7 +++++++ Association/HasMany.php | 7 +++++++ Association/HasOne.php | 7 +++++++ 5 files changed, 50 insertions(+), 2 deletions(-) diff --git a/Association.php b/Association.php index 9c4eda35..57125730 100644 --- a/Association.php +++ b/Association.php @@ -172,6 +172,13 @@ abstract class Association */ protected $_finder = 'all'; + /** + * Valid strategies for this association. Subclasses can narrow this down. + * + * @var array + */ + protected $_validStrategies = [self::STRATEGY_JOIN, self::STRATEGY_SELECT, self::STRATEGY_SUBQUERY]; + /** * Constructor. Subclasses can override _options function to get the original * list of passed options if expecting any other special key @@ -401,8 +408,7 @@ public function property($name = null) public function strategy($name = null) { if ($name !== null) { - $valid = [self::STRATEGY_JOIN, self::STRATEGY_SELECT, self::STRATEGY_SUBQUERY]; - if (!in_array($name, $valid)) { + if (!$this->validStrategy($name)) { throw new \InvalidArgumentException( sprintf('Invalid strategy "%s" was provided', $name) ); @@ -412,6 +418,20 @@ public function strategy($name = null) return $this->_strategy; } + /** + * Checks if a given strategy is valid for the association type. + * + * @param string $name The strategy type. + * @return boolean + */ + public function validStrategy($name) + { + if (in_array($name, $this->_validStrategies)) { + return true; + } + return false; + } + /** * Sets the default finder to use for fetching rows from the target table. * If no parameters are passed, it will return the currently configured diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index bedcda68..fdeffdfc 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -32,6 +32,13 @@ class BelongsTo extends Association use SelectableAssociationTrait; + /** + * Valid strategies for this type of association + * + * @var array + */ + protected $_validStrategies = [parent::STRATEGY_JOIN, parent::STRATEGY_SELECT]; + /** * Sets the name of the field representing the foreign key to the target table. * If no parameters are passed current field is returned diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 2a5f526d..1d2a420f 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -114,6 +114,13 @@ class BelongsToMany extends Association */ protected $_through; + /** + * Valid strategies for this type of association + * + * @var array + */ + protected $_validStrategies = [parent::STRATEGY_SELECT, parent::STRATEGY_SUBQUERY]; + /** * Sets the name of the field representing the foreign key to the target table. * If no parameters are passed current field is returned diff --git a/Association/HasMany.php b/Association/HasMany.php index d70e3147..0f260aa0 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -47,6 +47,13 @@ class HasMany extends Association */ protected $_strategy = parent::STRATEGY_SELECT; + /** + * Valid strategies for this type of association + * + * @var array + */ + protected $_validStrategies = [parent::STRATEGY_SELECT, parent::STRATEGY_SUBQUERY]; + /** * Returns whether or not the passed table is the owning side for this * association. This means that rows in the 'target' table would miss important diff --git a/Association/HasOne.php b/Association/HasOne.php index 5eef0ff4..f6e64924 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -33,6 +33,13 @@ class HasOne extends Association use DependentDeleteTrait; use SelectableAssociationTrait; + /** + * Valid strategies for this type of association + * + * @var array + */ + protected $_validStrategies = [parent::STRATEGY_JOIN]; + /** * Sets the name of the field representing the foreign key to the target table. * If no parameters are passed current field is returned From 0b8e90ea63b4ce9b39cf2e14a28b92065eecc2e9 Mon Sep 17 00:00:00 2001 From: Patrick Conroy Date: Fri, 6 Mar 2015 17:25:11 -0500 Subject: [PATCH 0269/2059] Fixing BelongsTo strategies / test --- Association/BelongsTo.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index fdeffdfc..78e4abf8 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -37,7 +37,7 @@ class BelongsTo extends Association * * @var array */ - protected $_validStrategies = [parent::STRATEGY_JOIN, parent::STRATEGY_SELECT]; + protected $_validStrategies = [parent::STRATEGY_JOIN]; /** * Sets the name of the field representing the foreign key to the target table. From 09d87461e3741e9f06ce772e554ab5887f942983 Mon Sep 17 00:00:00 2001 From: Patrick Conroy Date: Fri, 6 Mar 2015 19:21:33 -0500 Subject: [PATCH 0270/2059] Fixing cs errors --- Association.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association.php b/Association.php index 57125730..0ce4aa05 100644 --- a/Association.php +++ b/Association.php @@ -422,7 +422,7 @@ public function strategy($name = null) * Checks if a given strategy is valid for the association type. * * @param string $name The strategy type. - * @return boolean + * @return bool */ public function validStrategy($name) { From ef301bf2ebd21b92cf4ecb0877e3faa2fd4ef7a5 Mon Sep 17 00:00:00 2001 From: Patrick Conroy Date: Sat, 7 Mar 2015 01:36:36 -0500 Subject: [PATCH 0271/2059] Adding 'select' as a strategy for HasOne / BelongsTo --- Association.php | 5 +---- Association/BelongsTo.php | 2 +- Association/HasOne.php | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Association.php b/Association.php index 0ce4aa05..7c1a5fa6 100644 --- a/Association.php +++ b/Association.php @@ -426,10 +426,7 @@ public function strategy($name = null) */ public function validStrategy($name) { - if (in_array($name, $this->_validStrategies)) { - return true; - } - return false; + return in_array($name, $this->_validStrategies); } /** diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 78e4abf8..fdeffdfc 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -37,7 +37,7 @@ class BelongsTo extends Association * * @var array */ - protected $_validStrategies = [parent::STRATEGY_JOIN]; + protected $_validStrategies = [parent::STRATEGY_JOIN, parent::STRATEGY_SELECT]; /** * Sets the name of the field representing the foreign key to the target table. diff --git a/Association/HasOne.php b/Association/HasOne.php index f6e64924..32ff0775 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -38,7 +38,7 @@ class HasOne extends Association * * @var array */ - protected $_validStrategies = [parent::STRATEGY_JOIN]; + protected $_validStrategies = [parent::STRATEGY_JOIN, parent::STRATEGY_SELECT]; /** * Sets the name of the field representing the foreign key to the target table. From 01eb1250f70dcb9a808c1ee63ca700d1110e9a43 Mon Sep 17 00:00:00 2001 From: Patrick Conroy Date: Sat, 7 Mar 2015 02:08:11 -0500 Subject: [PATCH 0272/2059] Removing method for validStategy, cleaning up QueryTest --- Association.php | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/Association.php b/Association.php index 7c1a5fa6..ca584cb2 100644 --- a/Association.php +++ b/Association.php @@ -408,7 +408,7 @@ public function property($name = null) public function strategy($name = null) { if ($name !== null) { - if (!$this->validStrategy($name)) { + if (!in_array($name, $this->_validStrategies)) { throw new \InvalidArgumentException( sprintf('Invalid strategy "%s" was provided', $name) ); @@ -418,17 +418,6 @@ public function strategy($name = null) return $this->_strategy; } - /** - * Checks if a given strategy is valid for the association type. - * - * @param string $name The strategy type. - * @return bool - */ - public function validStrategy($name) - { - return in_array($name, $this->_validStrategies); - } - /** * Sets the default finder to use for fetching rows from the target table. * If no parameters are passed, it will return the currently configured From 2df65615b35dfaa56686c9d1cd150f99335c6d5b Mon Sep 17 00:00:00 2001 From: Patrick Conroy Date: Sat, 7 Mar 2015 09:58:35 -0500 Subject: [PATCH 0273/2059] Updating reference of strategies to self --- Association/BelongsTo.php | 2 +- Association/BelongsToMany.php | 4 ++-- Association/HasMany.php | 4 ++-- Association/HasOne.php | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index fdeffdfc..6a0c22de 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -37,7 +37,7 @@ class BelongsTo extends Association * * @var array */ - protected $_validStrategies = [parent::STRATEGY_JOIN, parent::STRATEGY_SELECT]; + protected $_validStrategies = [self::STRATEGY_JOIN, self::STRATEGY_SELECT]; /** * Sets the name of the field representing the foreign key to the target table. diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 1d2a420f..f32b94a2 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -61,7 +61,7 @@ class BelongsToMany extends Association * * @var string */ - protected $_strategy = parent::STRATEGY_SELECT; + protected $_strategy = self::STRATEGY_SELECT; /** * Junction table instance @@ -119,7 +119,7 @@ class BelongsToMany extends Association * * @var array */ - protected $_validStrategies = [parent::STRATEGY_SELECT, parent::STRATEGY_SUBQUERY]; + protected $_validStrategies = [self::STRATEGY_SELECT, self::STRATEGY_SUBQUERY]; /** * Sets the name of the field representing the foreign key to the target table. diff --git a/Association/HasMany.php b/Association/HasMany.php index 0f260aa0..09ea480e 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -45,14 +45,14 @@ class HasMany extends Association * * @var string */ - protected $_strategy = parent::STRATEGY_SELECT; + protected $_strategy = self::STRATEGY_SELECT; /** * Valid strategies for this type of association * * @var array */ - protected $_validStrategies = [parent::STRATEGY_SELECT, parent::STRATEGY_SUBQUERY]; + protected $_validStrategies = [self::STRATEGY_SELECT, self::STRATEGY_SUBQUERY]; /** * Returns whether or not the passed table is the owning side for this diff --git a/Association/HasOne.php b/Association/HasOne.php index 32ff0775..af6dad73 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -38,7 +38,7 @@ class HasOne extends Association * * @var array */ - protected $_validStrategies = [parent::STRATEGY_JOIN, parent::STRATEGY_SELECT]; + protected $_validStrategies = [self::STRATEGY_JOIN, self::STRATEGY_SELECT]; /** * Sets the name of the field representing the foreign key to the target table. From e3aabbe19e2a6674661ae580b0ac47fcaa428c48 Mon Sep 17 00:00:00 2001 From: Ceeram Date: Wed, 11 Mar 2015 17:43:43 +0100 Subject: [PATCH 0274/2059] Fix case sensitivity errors on associations --- Association.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association.php b/Association.php index ca584cb2..8bea7abf 100644 --- a/Association.php +++ b/Association.php @@ -206,7 +206,7 @@ public function __construct($alias, array $options = []) } } - if (!$this->_className) { + if (empty($this->_className) && strpos($alias, '.')) { $this->_className = $alias; } From d078ec7888d7c68143568b6853a72c67755d2187 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 14 Mar 2015 01:15:40 +0530 Subject: [PATCH 0275/2059] Fix CS errors in Console, Controller, Core, Database, Event, ORM packages. --- Query.php | 2 +- TableRegistry.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Query.php b/Query.php index 73fb5bbf..d4a6e106 100644 --- a/Query.php +++ b/Query.php @@ -653,7 +653,7 @@ protected function _execute() * using `contain` * * @see \Cake\Database\Query::execute() - * @return $this + * @return void */ protected function _transformQuery() { diff --git a/TableRegistry.php b/TableRegistry.php index 7f78e370..a67193b3 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -147,11 +147,11 @@ public static function config($alias = null, $options = null) * If no `connection` option is passed the table's defaultConnectionName() method * will be called to get the default connection name to use. * - * @param string $name The alias name you want to get. + * @param string $alias The alias name you want to get. * @param array $options The options you want to build the table with. * If a table has already been loaded the options will be ignored. * @return \Cake\ORM\Table - * @throws RuntimeException When you try to configure an alias that already exists. + * @throws \RuntimeException When you try to configure an alias that already exists. */ public static function get($alias, array $options = []) { From ab5a55d053bcbd1d110cdab1a6d44b1c64ad1059 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 13 Mar 2015 21:37:25 -0400 Subject: [PATCH 0276/2059] Allow joint table records to be not deleted. Re-use the dependent option to allow people to disable cascaded deletes onto the joint table. This is useful when you have constraints with `cascade delete` Refs #6063 --- Association/BelongsToMany.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index f32b94a2..0b0784fe 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -121,6 +121,16 @@ class BelongsToMany extends Association */ protected $_validStrategies = [self::STRATEGY_SELECT, self::STRATEGY_SUBQUERY]; + /** + * Whether the records on the joint table should be removed when a record + * on the source table is deleted. + * + * Defaults to true for backwards compatibility. + * + * @var bool + */ + protected $_dependent = true; + /** * Sets the name of the field representing the foreign key to the target table. * If no parameters are passed current field is returned @@ -332,6 +342,9 @@ protected function _buildResultMap($fetchQuery, $options) */ public function cascadeDelete(EntityInterface $entity, array $options = []) { + if (!$this->dependent()) { + return true; + } $foreignKey = (array)$this->foreignKey(); $primaryKey = (array)$this->source()->primaryKey(); $conditions = []; From 8a840a4aaba2d967e70b47c24d565d787a34ead8 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sat, 14 Mar 2015 22:10:03 +0100 Subject: [PATCH 0277/2059] Only marking an association property as dirty if it changed Fixes #6050 --- Marshaller.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index c832145f..6a32f5e5 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -355,7 +355,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) $errors = $this->_validate($data + $keys, $options, $isNew); $schema = $this->_table->schema(); - $properties = []; + $properties = $marshalledAssocs = []; foreach ($data as $key => $value) { if (!empty($errors[$key])) { continue; @@ -367,6 +367,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) if (isset($propertyMap[$key])) { $assoc = $propertyMap[$key]['association']; $value = $this->_mergeAssociation($original, $assoc, $value, $propertyMap[$key]); + $marshalledAssocs[$key] = true; } elseif ($columnType) { $converter = Type::build($columnType); $value = $converter->marshal($value); @@ -384,12 +385,22 @@ public function merge(EntityInterface $entity, array $data, array $options = []) if (!isset($options['fieldList'])) { $entity->set($properties); $entity->errors($errors); + + foreach (array_keys($marshalledAssocs) as $field) { + if ($properties[$field] instanceof EntityInterface) { + $entity->dirty($field, $properties[$field]->dirty()); + } + } return $entity; } foreach ((array)$options['fieldList'] as $field) { if (array_key_exists($field, $properties)) { $entity->set($field, $properties[$field]); + if ($properties[$field] instanceof EntityInterface && + isset($marshalledAssocs[$field])) { + $entity->dirty($assoc, $properties[$field]->dirty()); + } } } From 9c9fc68d409d3ff117ce8753534336aa23c49092 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 15 Mar 2015 15:02:38 +0100 Subject: [PATCH 0278/2059] Quick solution for casting result primary keys to the correct type Fixes #5881 --- Table.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 46c9283f..977f4a56 100644 --- a/Table.php +++ b/Table.php @@ -1505,10 +1505,13 @@ protected function _insert($entity, $data) if ($statement->rowCount() !== 0) { $success = $entity; $entity->set($filteredKeys, ['guard' => false]); + $schema = $this->schema(); + $driver = $this->connection()->driver(); foreach ($primary as $key => $v) { if (!isset($data[$key])) { $id = $statement->lastInsertId($this->table(), $key); - $entity->set($key, $id); + $type = $schema->columnType($key); + $entity->set($key, Type::build($type)->toPHP($id, $driver)); break; } } From 4a421e6c592c023f37da42590cff24f5fe7336d7 Mon Sep 17 00:00:00 2001 From: Luan Hospodarsky Date: Sun, 15 Mar 2015 21:09:24 -0300 Subject: [PATCH 0279/2059] Fixed a bug which was not possible to pass id and _joinData when marshalling a belongsToMany relation --- Marshaller.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index c832145f..9a7eb06f 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -105,6 +105,18 @@ public function one(array $data, array $options = []) $propertyMap = $this->_buildPropertyMap($options); $schema = $this->_table->schema(); + $primaryKey = $schema->primaryKey(); + + if ( array_intersect($primaryKey, array_keys($data)) == $primaryKey ) { + $record=$this->_table->find('all'); + foreach ( $primaryKey as $pkey ) { + $record->where(["$pkey"=>$data[$pkey] ]); + } + if ( $record->count() > 0 ) { + return $record->first(); + } + } + $entityClass = $this->_table->entityClass(); $entity = new $entityClass(); $entity->source($this->_table->registryAlias()); @@ -116,7 +128,6 @@ public function one(array $data, array $options = []) } $errors = $this->_validate($data, $options, true); - $primaryKey = $schema->primaryKey(); $properties = []; foreach ($data as $key => $value) { if (!empty($errors[$key])) { From a254f4e6ac0fdb3ea367ec9061d56b1dbcc01908 Mon Sep 17 00:00:00 2001 From: Luan Hospodarsky Date: Sun, 15 Mar 2015 23:11:00 -0300 Subject: [PATCH 0280/2059] Fixed PHPCS issues --- Marshaller.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 9a7eb06f..314a1d77 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -107,12 +107,12 @@ public function one(array $data, array $options = []) $schema = $this->_table->schema(); $primaryKey = $schema->primaryKey(); - if ( array_intersect($primaryKey, array_keys($data)) == $primaryKey ) { - $record=$this->_table->find('all'); - foreach ( $primaryKey as $pkey ) { - $record->where(["$pkey"=>$data[$pkey] ]); + if (array_intersect($primaryKey, array_keys($data)) == $primaryKey) { + $record = $this->_table->find('all'); + foreach ($primaryKey as $pkey) { + $record->where(["$pkey" => $data[$pkey] ]); } - if ( $record->count() > 0 ) { + if ($record->count() > 0) { return $record->first(); } } From 5b200cd0d9d456b6d95e3714d849b62e1e69638f Mon Sep 17 00:00:00 2001 From: Luan Hospodarsky Date: Mon, 16 Mar 2015 21:59:01 -0300 Subject: [PATCH 0281/2059] Optimizing code for better performance. --- Marshaller.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 314a1d77..374e4afa 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -112,8 +112,11 @@ public function one(array $data, array $options = []) foreach ($primaryKey as $pkey) { $record->where(["$pkey" => $data[$pkey] ]); } - if ($record->count() > 0) { - return $record->first(); + + $record = $record->first(); + + if ($record) { + return $record; } } From ec9a61ca42f2589ca8d4e1171a851decc9db71be Mon Sep 17 00:00:00 2001 From: Luan Hospodarsky Date: Mon, 16 Mar 2015 23:26:51 -0300 Subject: [PATCH 0282/2059] Avoiding table field name collisions while searching for associated data by primary key --- Marshaller.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index bc424ac7..9cf2f3fc 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -108,9 +108,10 @@ public function one(array $data, array $options = []) $primaryKey = $schema->primaryKey(); if (array_intersect($primaryKey, array_keys($data)) == $primaryKey) { + $tableName=$this->_table->table(); $record = $this->_table->find('all'); foreach ($primaryKey as $pkey) { - $record->where(["$pkey" => $data[$pkey] ]); + $record->where(["$tableName.$pkey" => $data[$pkey] ]); } $record = $record->first(); From 9dade0a206cfc792108a019598f71fe07d1eaf6c Mon Sep 17 00:00:00 2001 From: Luan Hospodarsky Date: Mon, 16 Mar 2015 23:38:21 -0300 Subject: [PATCH 0283/2059] Fixing some more PHPCS issues in last commit --- Marshaller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index 9cf2f3fc..8cfb3bac 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -108,7 +108,7 @@ public function one(array $data, array $options = []) $primaryKey = $schema->primaryKey(); if (array_intersect($primaryKey, array_keys($data)) == $primaryKey) { - $tableName=$this->_table->table(); + $tableName = $this->_table->table(); $record = $this->_table->find('all'); foreach ($primaryKey as $pkey) { $record->where(["$tableName.$pkey" => $data[$pkey] ]); From 340f2df326e7fb20e390e7d473a476bdfc8c88c9 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 17 Mar 2015 08:45:25 +0530 Subject: [PATCH 0284/2059] Convert indentation to spaces for composer.json files --- composer.json | 54 +++++++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/composer.json b/composer.json index d1b8124c..aa19e229 100644 --- a/composer.json +++ b/composer.json @@ -1,29 +1,29 @@ { - "name": "cakephp/orm", - "description": "CakePHP ORM - Provides a flexible and powerful ORM implementing a data-mapper pattern.", - "license": "MIT", - "authors": [ - { - "name": "CakePHP Community", - "homepage": "http://cakephp.org" - } - ], - "autoload": { - "psr-4": { - "Cake\\ORM\\": "." - } - }, - "require": { - "cakephp/collection": "dev-master", - "cakephp/core": "dev-master", - "cakephp/datasource": "dev-master", - "cakephp/database": "dev-master", - "cakephp/event": "dev-master", - "cakephp/utility": "dev-master", - "cakephp/validation": "dev-master" - }, - "suggest": { - "cakephp/i18n": "dev-master" - }, - "minimum-stability": "beta" + "name": "cakephp/orm", + "description": "CakePHP ORM - Provides a flexible and powerful ORM implementing a data-mapper pattern.", + "license": "MIT", + "authors": [ + { + "name": "CakePHP Community", + "homepage": "http://cakephp.org" + } + ], + "autoload": { + "psr-4": { + "Cake\\ORM\\": "." + } + }, + "require": { + "cakephp/collection": "dev-master", + "cakephp/core": "dev-master", + "cakephp/datasource": "dev-master", + "cakephp/database": "dev-master", + "cakephp/event": "dev-master", + "cakephp/utility": "dev-master", + "cakephp/validation": "dev-master" + }, + "suggest": { + "cakephp/i18n": "dev-master" + }, + "minimum-stability": "beta" } From e3e967075f153afa28dba8c9e38db043302f5461 Mon Sep 17 00:00:00 2001 From: Luan Hospodarsky Date: Tue, 17 Mar 2015 21:39:34 -0300 Subject: [PATCH 0285/2059] Correcting marshaling using table alias when searching for associated data by id --- Marshaller.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 8cfb3bac..d6142e89 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -108,10 +108,10 @@ public function one(array $data, array $options = []) $primaryKey = $schema->primaryKey(); if (array_intersect($primaryKey, array_keys($data)) == $primaryKey) { - $tableName = $this->_table->table(); + $tableName = $this->_table->alias(); $record = $this->_table->find('all'); foreach ($primaryKey as $pkey) { - $record->where(["$tableName.$pkey" => $data[$pkey] ]); + $record->where(["$tableName.$pkey" => $data[$pkey]]); } $record = $record->first(); From 0920c176f2ad1a2d34b08754b4b1e4613510652f Mon Sep 17 00:00:00 2001 From: Mark Story Date: Wed, 18 Mar 2015 22:14:52 -0400 Subject: [PATCH 0286/2059] Allow newEntity() to update existing entities. This fixes a regression introduced in #6086 where calling newEntity() with a primary key should still patch properties and related entities. Refs #6097 --- Marshaller.php | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index d6142e89..198f3f14 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -107,23 +107,22 @@ public function one(array $data, array $options = []) $schema = $this->_table->schema(); $primaryKey = $schema->primaryKey(); + $entity = null; if (array_intersect($primaryKey, array_keys($data)) == $primaryKey) { $tableName = $this->_table->alias(); - $record = $this->_table->find('all'); + $query = $this->_table->find('all'); foreach ($primaryKey as $pkey) { - $record->where(["$tableName.$pkey" => $data[$pkey]]); + $query->where(["$tableName.$pkey" => $data[$pkey]]); } + $entity = $query->first(); + } - $record = $record->first(); - - if ($record) { - return $record; - } + if (!isset($entity)) { + $entityClass = $this->_table->entityClass(); + $entity = new $entityClass(); + $entity->source($this->_table->registryAlias()); } - $entityClass = $this->_table->entityClass(); - $entity = new $entityClass(); - $entity->source($this->_table->registryAlias()); if (isset($options['accessibleFields'])) { foreach ((array)$options['accessibleFields'] as $key => $value) { From 7ed9e400be050ac8a9c032391f8bd35262f66195 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Wed, 18 Mar 2015 22:30:19 -0400 Subject: [PATCH 0287/2059] Add and use Table::aliasField() where it makes sense. Adding this method came up in #6086 and it was simple enough to implement. --- Behavior/TranslateBehavior.php | 5 ++--- Behavior/TreeBehavior.php | 18 +++++++++--------- Table.php | 11 +++++++++++ 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 603d653c..ad9fe203 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -199,12 +199,11 @@ public function beforeFind(Event $event, Query $query, $options) $conditions = function ($field, $locale, $query, $select) { return function ($q) use ($field, $locale, $query, $select) { - $q->where([$q->repository()->alias() . '.locale' => $locale]); - $alias = $this->_table->alias(); + $q->where([$q->repository()->aliasField('locale') => $locale]); if ($query->autoFields() || in_array($field, $select, true) || - in_array("$alias.$field", $select, true) + in_array($this->_table->aliasField($field), $select, true) ) { $q->select(['id', 'content']); } diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 7558af1e..82ee6c97 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -347,10 +347,9 @@ public function findPath(Query $query, array $options) } $config = $this->config(); - $alias = $this->_table->alias(); list($left, $right) = array_map( - function ($field) use ($alias) { - return "$alias.$field"; + function ($field) { + return $this->_table->aliasField($field); }, [$config['left'], $config['right']] ); @@ -375,8 +374,7 @@ function ($field) use ($alias) { public function childCount(Entity $node, $direct = false) { $config = $this->config(); - $alias = $this->_table->alias(); - $parent = $alias . '.' . $config['parent']; + $parent = $this->_table->aliasField($config['parent']); if ($direct) { return $this->_scope($this->_table->find()) @@ -407,11 +405,10 @@ public function childCount(Entity $node, $direct = false) public function findChildren(Query $query, array $options) { $config = $this->config(); - $alias = $this->_table->alias(); $options += ['for' => null, 'direct' => false]; list($parent, $left, $right) = array_map( - function ($field) use ($alias) { - return "$alias.$field"; + function ($field) { + return $this->_table->aliasField($field); }, [$config['parent'], $config['left'], $config['right']] ); @@ -458,7 +455,10 @@ function ($field) use ($alias) { public function findTreeList(Query $query, array $options) { return $this->_scope($query) - ->find('threaded', ['parentField' => $this->config()['parent'], 'order' => [$this->config()['left'] => 'ASC']]) + ->find('threaded', [ + 'parentField' => $this->config('parent'), + 'order' => [$this->config('left') => 'ASC'] + ]) ->formatResults(function ($results) use ($options) { $options += [ 'keyPath' => $this->_getPrimaryKey(), diff --git a/Table.php b/Table.php index 977f4a56..ecf1d3a6 100644 --- a/Table.php +++ b/Table.php @@ -358,6 +358,17 @@ public function alias($alias = null) return $this->_alias; } + /** + * Alias a field with the table's current alias. + * + * @param string $field The field to alias. + * @return string The field prefixed with the table alias. + */ + public function aliasField($field) + { + return $this->alias() . '.' . $field; + } + /** * Returns the table registry key used to create this table instance * From 1c0e3f0f99f4f79276ec49024f568a755dc5b75a Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 19 Mar 2015 22:48:27 -0400 Subject: [PATCH 0288/2059] Don't allow allow one() to do queries. The belongsToMany special casing should only apply to belongsToMany. Having it in one() makes it too broad and potentially breaks the promise that one() makes - which is to create new entities. This change also fixes the N query issue the old code had. --- Marshaller.php | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 198f3f14..308ce2f1 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -106,23 +106,9 @@ public function one(array $data, array $options = []) $schema = $this->_table->schema(); $primaryKey = $schema->primaryKey(); - - $entity = null; - if (array_intersect($primaryKey, array_keys($data)) == $primaryKey) { - $tableName = $this->_table->alias(); - $query = $this->_table->find('all'); - foreach ($primaryKey as $pkey) { - $query->where(["$tableName.$pkey" => $data[$pkey]]); - } - $entity = $query->first(); - } - - if (!isset($entity)) { - $entityClass = $this->_table->entityClass(); - $entity = new $entityClass(); - $entity->source($this->_table->registryAlias()); - } - + $entityClass = $this->_table->entityClass(); + $entity = new $entityClass(); + $entity->source($this->_table->registryAlias()); if (isset($options['accessibleFields'])) { foreach ((array)$options['accessibleFields'] as $key => $value) { @@ -276,6 +262,7 @@ public function many(array $data, array $options = []) */ protected function _belongsToMany(Association $assoc, array $data, $options = []) { + // Accept _ids = [1, 2] $associated = isset($options['associated']) ? $options['associated'] : []; $hasIds = array_key_exists('_ids', $data); if ($hasIds && is_array($data['_ids'])) { @@ -285,7 +272,22 @@ protected function _belongsToMany(Association $assoc, array $data, $options = [] return []; } - $records = $this->many($data, $options); + // Accept [ [id => 1], [id = 2] ] style. + $primaryKey = array_flip($assoc->target()->schema()->primaryKey()); + if (array_intersect_key($primaryKey, current($data)) === $primaryKey) { + $primaryCount = count($primaryKey); + $query = $assoc->find(); + foreach ($data as $row) { + $keys = array_intersect_key($row, $primaryKey); + if (count($keys) === $primaryCount) { + $query->orWhere($keys); + } + } + $records = $query->toArray(); + } else { + $records = $this->many($data, $options); + } + $joint = $assoc->junction(); $jointMarshaller = $joint->marshaller(); From 62b4945aa1bd191e61f42ef25c7b18a7f9f7dcfa Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 21 Mar 2015 19:57:30 -0400 Subject: [PATCH 0289/2059] Fix missing _joinData for sparse belongsToMany data. When belongsToMany data is sparse the _joinData properties would not be marshalled correctly. By re-indexing the array of data before we marshal it we can ensure the indexes will match up. Refs #6100 --- Marshaller.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Marshaller.php b/Marshaller.php index 308ce2f1..282cbf16 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -271,6 +271,7 @@ protected function _belongsToMany(Association $assoc, array $data, $options = [] if ($hasIds) { return []; } + $data = array_values($data); // Accept [ [id => 1], [id = 2] ] style. $primaryKey = array_flip($assoc->target()->schema()->primaryKey()); From 4f657721814a1da8daced413e976394549d703e5 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 22 Mar 2015 17:39:39 +0100 Subject: [PATCH 0290/2059] Adding new required versions to split packages --- composer.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index aa19e229..54a99515 100644 --- a/composer.json +++ b/composer.json @@ -14,16 +14,16 @@ } }, "require": { - "cakephp/collection": "dev-master", - "cakephp/core": "dev-master", - "cakephp/datasource": "dev-master", - "cakephp/database": "dev-master", - "cakephp/event": "dev-master", - "cakephp/utility": "dev-master", - "cakephp/validation": "dev-master" + "cakephp/collection": "~3.0", + "cakephp/core": "~3.0", + "cakephp/datasource": "~3.0", + "cakephp/database": "~3.0", + "cakephp/event": "~3.0", + "cakephp/utility": "~3.0", + "cakephp/validation": "~3.0" }, "suggest": { - "cakephp/i18n": "dev-master" + "cakephp/i18n": "~3.0" }, - "minimum-stability": "beta" + "minimum-stability": "dev" } From 7949fb9a76b0fd5b7ef9cbeb0f3189fbd4af1b29 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 24 Mar 2015 22:17:44 -0400 Subject: [PATCH 0291/2059] Make types declared and protected I think we missed this in the development phase. --- ResultSet.php | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/ResultSet.php b/ResultSet.php index 790eb1cf..92e8ae99 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -127,6 +127,15 @@ class ResultSet implements ResultSetInterface */ protected $_count; + /** + * Type cache for type converters. + * + * Converters are indexed by alias and column name. + * + * @var array + */ + protected $_types = []; + /** * Constructor * @@ -493,18 +502,18 @@ protected function _castValues($table, $values) { $alias = $table->alias(); $driver = $this->_query->connection()->driver(); - if (empty($this->types[$alias])) { + if (empty($this->_types[$alias])) { $schema = $table->schema(); foreach ($schema->columns() as $col) { - $this->types[$alias][$col] = Type::build($schema->columnType($col)); + $this->_types[$alias][$col] = Type::build($schema->columnType($col)); } } foreach ($values as $field => $value) { - if (!isset($this->types[$alias][$field])) { + if (!isset($this->_types[$alias][$field])) { continue; } - $values[$field] = $this->types[$alias][$field]->toPHP($value, $driver); + $values[$field] = $this->_types[$alias][$field]->toPHP($value, $driver); } return $values; From e879f6f93ff974f41926f34a333113c59bf6cdea Mon Sep 17 00:00:00 2001 From: Graeme Tait Date: Wed, 25 Mar 2015 10:25:04 +0000 Subject: [PATCH 0292/2059] Fix database setup instructions config() seems to be the correct method to use, not create() which doesn't exist. Also had to add 'className' and change 'login' to 'username'. --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a191b575..342b3d38 100644 --- a/README.md +++ b/README.md @@ -20,10 +20,11 @@ specify a driver to use: ```php use Cake\Datasource\ConnectionManager; -ConnectionManager::create('default', [ +ConnectionManager::config('default', [ + 'className' => 'Cake\Database\Connection', 'driver' => 'Cake\Database\Driver\Mysql', 'database' => 'test', - 'login' => 'root', + 'username' => 'root', 'password' => 'secret' ]); ``` From b3d692e653d27304c8cb140b19e3f8502351afaf Mon Sep 17 00:00:00 2001 From: antograssiot Date: Fri, 27 Mar 2015 06:27:38 +0100 Subject: [PATCH 0293/2059] update exception namespaces for API --- Behavior/TreeBehavior.php | 10 +++++----- Table.php | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 82ee6c97..d8748cb0 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -538,7 +538,7 @@ protected function _removeFromTree($node) * * @param \Cake\ORM\Entity $node The node to move * @param int|bool $number How many places to move the node, or true to move to first position - * @throws \Cake\ORM\Exception\RecordNotFoundException When node was not found + * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found * @return \Cake\ORM\Entity|bool $node The node after being moved or false on failure */ public function moveUp(Entity $node, $number = 1) @@ -558,7 +558,7 @@ public function moveUp(Entity $node, $number = 1) * * @param \Cake\ORM\Entity $node The node to move * @param int|bool $number How many places to move the node, or true to move to first position - * @throws \Cake\ORM\Exception\RecordNotFoundException When node was not found + * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found * @return \Cake\ORM\Entity|bool $node The node after being moved or false on failure */ protected function _moveUp($node, $number) @@ -619,7 +619,7 @@ protected function _moveUp($node, $number) * * @param \Cake\ORM\Entity $node The node to move * @param int|bool $number How many places to move the node or true to move to last position - * @throws \Cake\ORM\Exception\RecordNotFoundException When node was not found + * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found * @return \Cake\ORM\Entity|bool the entity after being moved or false on failure */ public function moveDown(Entity $node, $number = 1) @@ -639,7 +639,7 @@ public function moveDown(Entity $node, $number = 1) * * @param \Cake\ORM\Entity $node The node to move * @param int|bool $number How many places to move the node, or true to move to last position - * @throws \Cake\ORM\Exception\RecordNotFoundException When node was not found + * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found * @return \Cake\ORM\Entity|bool $node The node after being moved or false on failure */ protected function _moveDown($node, $number) @@ -698,7 +698,7 @@ protected function _moveDown($node, $number) * * @param mixed $id Record id. * @return \Cake\ORM\Entity - * @throws \Cake\ORM\Exception\RecordNotFoundException When node was not found + * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found */ protected function _getNode($id) { diff --git a/Table.php b/Table.php index ecf1d3a6..2e463235 100644 --- a/Table.php +++ b/Table.php @@ -1063,7 +1063,7 @@ protected function _setFieldMatchers($options, $keys) /** * {@inheritDoc} * - * @throws Cake\Datasource\Exception\InvalidPrimaryKeyException When $primaryKey has an + * @throws \Cake\Datasource\Exception\InvalidPrimaryKeyException When $primaryKey has an * incorrect number of elements. */ public function get($primaryKey, $options = []) From a6bf05a5988cbed67f7ac502593dc12d8fd35e50 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Fri, 27 Mar 2015 23:19:31 +0100 Subject: [PATCH 0294/2059] Correctly setting the error message for existsIn() when an array is used --- RulesChecker.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RulesChecker.php b/RulesChecker.php index c9187d34..09da6ad1 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -354,7 +354,7 @@ public function existsIn($field, $table, $message = null) } } - $errorField = $field; + $errorField = is_string($field) ? $field : current($field); return $this->_addError(new ExistsIn($field, $table), '_existsIn', compact('errorField', 'message')); } From 638a42899e7988901e2d0e0ca053bc6c5dcb8de2 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 27 Mar 2015 22:24:02 -0400 Subject: [PATCH 0295/2059] Allow primary key values to be set when creating records. When using UUIDs it is often helpful to allow external systems to define your UUID values. This change lets entity data replace generated values. Refs #6185 --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 2e463235..9a8806a6 100644 --- a/Table.php +++ b/Table.php @@ -1487,7 +1487,7 @@ protected function _insert($entity, $data) $id = (array)$this->_newId($primary) + $keys; $primary = array_combine($primary, $id); $filteredKeys = array_filter($primary, 'strlen'); - $data = $filteredKeys + $data; + $data = $data + $filteredKeys; if (count($primary) > 1) { $schema = $this->schema(); From ddf569fafa5cc772e0fd2248cbbf04b14f78eb68 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sat, 28 Mar 2015 15:23:43 +0100 Subject: [PATCH 0296/2059] Optimizing ResultSet by having less loops. After this, I could trim 500ms off a loop having 1000 records. There is still work to do, though :) --- ResultSet.php | 111 +++++++++++++++++++++++++++++--------------------- 1 file changed, 64 insertions(+), 47 deletions(-) diff --git a/ResultSet.php b/ResultSet.php index 92e8ae99..bec870a0 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -69,6 +69,13 @@ class ResultSet implements ResultSetInterface */ protected $_defaultTable; + /** + * The default table alias + * + * @var string + */ + protected $_defaultAlias; + /** * List of associations that should be placed under the `_matchingData` * result key. @@ -88,9 +95,17 @@ class ResultSet implements ResultSetInterface * Map of fields that are fetched from the statement with * their type and the table they belong to * - * @var string + * @var array + */ + protected $_map = []; + + /** + * List of matching associations and the column keys to expect + * from each of them. + * + * @var array */ - protected $_map; + protected $_matchingMapColumns = []; /** * Results that have been fetched or hydrated into the results. @@ -152,6 +167,8 @@ public function __construct($query, $statement) $this->_hydrate = $this->_query->hydrate(); $this->_entityClass = $repository->entityClass(); $this->_useBuffering = $query->bufferResults(); + $this->_defaultAlias = $this->_defaultTable->alias(); + $this->_calculateColumnMap(); if ($this->_useBuffering) { $count = $this->count(); @@ -327,6 +344,7 @@ protected function _calculateAssociationMap() $map = $this->_query->eagerLoader()->associationsMap($this->_defaultTable); $this->_matchingMap = (new Collection($map)) ->match(['matching' => true]) + ->indexBy('alias') ->toArray(); $this->_containMap = (new Collection(array_reverse($map))) @@ -335,6 +353,32 @@ protected function _calculateAssociationMap() ->toArray(); } + /** + * Creates a map of row keys out of the query select clasuse that can be + * used to quickly hydrate correctly nested result sets. + * + * @return void + */ + protected function _calculateColumnMap() + { + $map = []; + foreach ($this->_query->clause('select') as $key => $field) { + if (strpos($key, '__') > 0) { + $parts = explode('__', $key, 2); + $map[$parts[0]][$key] = $parts[1]; + } else { + $map[$this->_defaultAlias][$key] = $key; + } + } + + foreach ($this->_matchingMap as $alias => $assoc) { + $this->_matchingMapColumns[$alias] = $map[$alias]; + unset($map[$alias]); + } + + $this->_map = $map; + } + /** * Helper function to fetch the next result from the statement or * seeded results. @@ -362,7 +406,7 @@ protected function _fetchResult() */ protected function _groupResult($row) { - $defaultAlias = $this->_defaultTable->alias(); + $defaultAlias = $this->_defaultAlias; $results = $presentAliases = []; $options = [ 'useSetters' => false, @@ -371,57 +415,26 @@ protected function _groupResult($row) 'guard' => false ]; - foreach ($this->_matchingMap as $matching) { - foreach ($row as $key => $value) { - if (strpos($key, $matching['alias'] . '__') !== 0) { - continue; - } - list($table, $field) = explode('__', $key); - $results['_matchingData'][$table][$field] = $value; - - if (!isset($this->_containMap[$table])) { - unset($row[$key]); - } - } - if (empty($results['_matchingData'][$matching['alias']])) { - continue; - } - - $results['_matchingData'][$matching['alias']] = $this->_castValues( + foreach ($this->_matchingMapColumns as $alias => $keys) { + $matching = $this->_matchingMap[$alias]; + $results['_matchingData'][$alias] = $this->_castValues( $matching['instance']->target(), - $results['_matchingData'][$matching['alias']] + array_combine( + $keys, + array_intersect_key($row, $keys) + ) ); - if ($this->_hydrate) { - $options['source'] = $matching['alias']; - $entity = new $matching['entityClass']($results['_matchingData'][$matching['alias']], $options); + $options['source'] = $alias; + $entity = new $matching['entityClass']($results['_matchingData'][$alias], $options); $entity->clean(); - $results['_matchingData'][$matching['alias']] = $entity; + $results['_matchingData'][$alias] = $entity; } } - foreach ($row as $key => $value) { - $table = $defaultAlias; - $field = $key; - - if ($value !== null && !is_scalar($value)) { - $results[$key] = $value; - continue; - } - - if (empty($this->_map[$key])) { - $parts = explode('__', $key); - if (count($parts) > 1) { - $this->_map[$key] = $parts; - } - } - - if (!empty($this->_map[$key])) { - list($table, $field) = $this->_map[$key]; - } - + foreach ($this->_map as $table => $keys) { + $results[$table] = array_combine($keys, array_intersect_key($row, $keys)); $presentAliases[$table] = true; - $results[$table][$field] = $value; } if (isset($presentAliases[$defaultAlias])) { @@ -436,11 +449,15 @@ protected function _groupResult($row) $alias = $assoc['nestKey']; $instance = $assoc['instance']; - if (!isset($results[$alias])) { + if (!$assoc['canBeJoined'] && !isset($row[$alias])) { $results = $instance->defaultRowValue($results, $assoc['canBeJoined']); continue; } + if (!$assoc['canBeJoined']) { + $results[$alias] = $row[$alias]; + } + $target = $instance->target(); $options['source'] = $target->alias(); unset($presentAliases[$alias]); From 22f677d038454477481fbabfbeb9d57114ef0f92 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sat, 28 Mar 2015 15:55:27 +0100 Subject: [PATCH 0297/2059] Fixing failing tests when using identifier quoting --- ResultSet.php | 1 + 1 file changed, 1 insertion(+) diff --git a/ResultSet.php b/ResultSet.php index bec870a0..e929afc4 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -363,6 +363,7 @@ protected function _calculateColumnMap() { $map = []; foreach ($this->_query->clause('select') as $key => $field) { + $key = trim($key, '"`[]'); if (strpos($key, '__') > 0) { $parts = explode('__', $key, 2); $map[$parts[0]][$key] = $parts[1]; From c85c655a25db3a99ebadde67555c37a5dba51386 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 28 Mar 2015 13:07:02 -0400 Subject: [PATCH 0298/2059] Fix typo. --- ResultSet.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ResultSet.php b/ResultSet.php index e929afc4..355e3fe7 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -354,8 +354,8 @@ protected function _calculateAssociationMap() } /** - * Creates a map of row keys out of the query select clasuse that can be - * used to quickly hydrate correctly nested result sets. + * Creates a map of row keys out of the query select clause that can be + * used to hydrate nested result sets more quickly. * * @return void */ From 14a7d9da95d668978c191d8cacb8a261f7650c2d Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 29 Mar 2015 12:58:59 +0200 Subject: [PATCH 0299/2059] Optimizing the Entity constructor by not looping over properties. In the case of hydrating an entity for the first time, there is no actual need for calling the genering set() method. This change makes hydrating entities twice as fast. --- Entity.php | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/Entity.php b/Entity.php index 79d2a67d..93e4b2c3 100644 --- a/Entity.php +++ b/Entity.php @@ -56,6 +56,19 @@ public function __construct(array $properties = [], array $options = []) ]; $this->_className = get_class($this); + if (!empty($options['source'])) { + $this->source($options['source']); + } + + if ($options['markNew'] !== null) { + $this->isNew($options['markNew']); + } + + if (!empty($properties) && $options['markClean'] && !$options['useSetters']) { + $this->_properties = $properties; + return; + } + if (!empty($properties)) { $this->set($properties, [ 'setter' => $options['useSetters'], @@ -66,13 +79,5 @@ public function __construct(array $properties = [], array $options = []) if ($options['markClean']) { $this->clean(); } - - if ($options['markNew'] !== null) { - $this->isNew($options['markNew']); - } - - if (!empty($options['source'])) { - $this->source($options['source']); - } } } From 10cdad762c4ea1428125146c1a73d6435be85373 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 29 Mar 2015 19:02:09 +0200 Subject: [PATCH 0300/2059] Optimizing type casting in ResultSet. This is still the heaviest part of the hydration process, but these changes help reduce in half the amount of method calls. --- ResultSet.php | 105 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 86 insertions(+), 19 deletions(-) diff --git a/ResultSet.php b/ResultSet.php index 355e3fe7..6b665093 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -151,6 +151,15 @@ class ResultSet implements ResultSetInterface */ protected $_types = []; + /** + * The Database driver object. + * + * Cached in a property to avoid multiple calls to the same function. + * + * @var \Cake\Database\Driver + */ + protected $_driver; + /** * Constructor * @@ -162,6 +171,7 @@ public function __construct($query, $statement) $repository = $query->repository(); $this->_query = $query; $this->_statement = $statement; + $this->_driver = $driver = $this->_query->connection()->driver(); $this->_defaultTable = $this->_query->repository(); $this->_calculateAssociationMap(); $this->_hydrate = $this->_query->hydrate(); @@ -169,6 +179,7 @@ public function __construct($query, $statement) $this->_useBuffering = $query->bufferResults(); $this->_defaultAlias = $this->_defaultTable->alias(); $this->_calculateColumnMap(); + $this->_calculateTypeMap(); if ($this->_useBuffering) { $count = $this->count(); @@ -380,6 +391,74 @@ protected function _calculateColumnMap() $this->_map = $map; } + /** + * Creates a map of Type converter classes for each of the columns that should + * be fetched by this object. + * + * @return void + */ + protected function _calculateTypeMap() + { + if (isset($this->_map[$this->_defaultAlias])) { + $this->_types[$this->_defaultAlias] = $this->_getTypes( + $this->_defaultTable, + $this->_map[$this->_defaultAlias] + ); + } + + foreach ($this->_matchingMapColumns as $alias => $keys) { + $this->_types[$alias] = $this->_getTypes( + $this->_matchingMap[$alias]['instance']->target(), + $keys + ); + } + + foreach ($this->_containMap as $assoc) { + $alias = $assoc['alias']; + if (isset($this->_types[$alias]) || !$assoc['canBeJoined'] || !isset($this->_map[$alias])) { + continue; + } + $this->_types[$alias] = $this->_getTypes( + $assoc['instance']->target(), + $this->_map[$alias] + ); + } + } + + /** + * Returns the Type classes for each of the passed fields belonging to the + * table. + * + * @param \Cake\ORM\Table $table The table from which to get the schema + * @param array $fields The fields whitelist to use for fields in the schema. + * @return array + */ + protected function _getTypes($table, $fields) + { + $types = []; + $schema = $table->schema(); + $map = array_keys(Type::map() + ['string' => 1, 'text' => 1, 'boolean' => 1]); + $typeMap = array_combine( + $map, + array_map(['Cake\Database\Type', 'build'], $map) + ); + + foreach (['string', 'text'] as $t) { + if (get_class($typeMap[$t]) === 'Cake\Database\Type') { + unset($typeMap[$t]); + } + } + + foreach (array_intersect($fields, $schema->columns()) as $col) { + $typeName = $schema->columnType($col); + if (isset($typeMap[$typeName])) { + $types[$col] = $typeMap[$typeName]; + } + } + + return $types; + } + /** * Helper function to fetch the next result from the statement or * seeded results. @@ -419,7 +498,7 @@ protected function _groupResult($row) foreach ($this->_matchingMapColumns as $alias => $keys) { $matching = $this->_matchingMap[$alias]; $results['_matchingData'][$alias] = $this->_castValues( - $matching['instance']->target(), + $alias, array_combine( $keys, array_intersect_key($row, $keys) @@ -440,7 +519,7 @@ protected function _groupResult($row) if (isset($presentAliases[$defaultAlias])) { $results[$defaultAlias] = $this->_castValues( - $this->_defaultTable, + $defaultAlias, $results[$defaultAlias] ); } @@ -464,7 +543,7 @@ protected function _groupResult($row) unset($presentAliases[$alias]); if ($assoc['canBeJoined']) { - $results[$alias] = $this->_castValues($target, $results[$alias]); + $results[$alias] = $this->_castValues($assoc['alias'], $results[$alias]); $hasData = false; foreach ($results[$alias] as $v) { @@ -512,26 +591,14 @@ protected function _groupResult($row) * Casts all values from a row brought from a table to the correct * PHP type. * - * @param Table $table The table object + * @param string $alias The table object alias * @param array $values The values to cast * @return array */ - protected function _castValues($table, $values) + protected function _castValues($alias, $values) { - $alias = $table->alias(); - $driver = $this->_query->connection()->driver(); - if (empty($this->_types[$alias])) { - $schema = $table->schema(); - foreach ($schema->columns() as $col) { - $this->_types[$alias][$col] = Type::build($schema->columnType($col)); - } - } - - foreach ($values as $field => $value) { - if (!isset($this->_types[$alias][$field])) { - continue; - } - $values[$field] = $this->_types[$alias][$field]->toPHP($value, $driver); + foreach ($this->_types[$alias] as $field => $type) { + $values[$field] = $type->toPHP($values[$field], $this->_driver); } return $values; From 123bb99f8464717e161a22cf9cbd4735767026c7 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 30 Mar 2015 02:40:17 +0530 Subject: [PATCH 0301/2059] Throw exception if argument passed to Table::connection() is of incorrect type Refs #6199 --- Table.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Table.php b/Table.php index 2e463235..366a23a2 100644 --- a/Table.php +++ b/Table.php @@ -17,6 +17,7 @@ use ArrayObject; use BadMethodCallException; use Cake\Core\App; +use Cake\Database\Connection; use Cake\Database\Schema\Table as Schema; use Cake\Database\Type; use Cake\Datasource\EntityInterface; @@ -397,6 +398,11 @@ public function connection($conn = null) if ($conn === null) { return $this->_connection; } + + if (!($conn instanceof Connection)) { + throw new RuntimeException('$conn must be an instance of \Cake\Database\Connection'); + } + return $this->_connection = $conn; } From 14b5a2110159b4a8e55355845f425c46fd251b0a Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 30 Mar 2015 13:22:12 +0530 Subject: [PATCH 0302/2059] Use typehinting instead of throwing exception. --- Table.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Table.php b/Table.php index dea50d1e..89dee5f6 100644 --- a/Table.php +++ b/Table.php @@ -393,16 +393,12 @@ public function registryAlias($registryAlias = null) * @param \Cake\Database\Connection|null $conn The new connection instance * @return \Cake\Database\Connection */ - public function connection($conn = null) + public function connection(Connection $conn = null) { if ($conn === null) { return $this->_connection; } - if (!($conn instanceof Connection)) { - throw new RuntimeException('$conn must be an instance of \Cake\Database\Connection'); - } - return $this->_connection = $conn; } From 6ce02bf05d9588eb0de4047062b4e2908c3f170b Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Mon, 30 Mar 2015 21:04:14 +0200 Subject: [PATCH 0303/2059] Fixes #6214 --- ResultSet.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ResultSet.php b/ResultSet.php index 6b665093..9508f7a4 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -527,6 +527,11 @@ protected function _groupResult($row) foreach ($this->_containMap as $assoc) { $alias = $assoc['nestKey']; + + if ($assoc['canBeJoined'] && empty($this->_map[$alias])) { + continue; + } + $instance = $assoc['instance']; if (!$assoc['canBeJoined'] && !isset($row[$alias])) { From 3b5132f00b1d316e27ac6f580014bbec379ff48f Mon Sep 17 00:00:00 2001 From: pirouet Date: Tue, 31 Mar 2015 00:07:16 +0100 Subject: [PATCH 0304/2059] Code Cleanup Remove unused use statements from CakePHP core --- Association/SelectableAssociationTrait.php | 1 - Behavior/CounterCacheBehavior.php | 1 - Behavior/TimestampBehavior.php | 1 - Behavior/TreeBehavior.php | 1 - ResultSet.php | 1 - Table.php | 1 - 6 files changed, 6 deletions(-) diff --git a/Association/SelectableAssociationTrait.php b/Association/SelectableAssociationTrait.php index 4499d315..e9bb2675 100644 --- a/Association/SelectableAssociationTrait.php +++ b/Association/SelectableAssociationTrait.php @@ -14,7 +14,6 @@ */ namespace Cake\ORM\Association; -use Cake\Database\ExpressionInterface; use Cake\Database\Expression\IdentifierExpression; use Cake\Database\Expression\TupleComparison; diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 92f5fb32..ba0f4805 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -18,7 +18,6 @@ use Cake\ORM\Association; use Cake\ORM\Behavior; use Cake\ORM\Entity; -use Cake\ORM\Table; /** * CounterCache behavior diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 8e830a4b..4e17ec03 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -18,7 +18,6 @@ use Cake\I18n\Time; use Cake\ORM\Behavior; use Cake\ORM\Entity; -use Cake\ORM\Table; class TimestampBehavior extends Behavior { diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index d8748cb0..a0be5361 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -20,7 +20,6 @@ use Cake\ORM\Behavior; use Cake\ORM\Entity; use Cake\ORM\Query; -use Cake\ORM\Table; /** * Makes the table to which this is attached to behave like a nested set and diff --git a/ResultSet.php b/ResultSet.php index 6b665093..19901b1f 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -19,7 +19,6 @@ use Cake\Database\Exception; use Cake\Database\Type; use Cake\Datasource\ResultSetInterface; -use JsonSerializable; use SplFixedArray; /** diff --git a/Table.php b/Table.php index 89dee5f6..889d4911 100644 --- a/Table.php +++ b/Table.php @@ -38,7 +38,6 @@ use Cake\ORM\Rule\IsUnique; use Cake\Utility\Inflector; use Cake\Validation\Validator; -use RuntimeException; /** * Represents a single database table. From 9c00ab46cff7c349a50465a1d565a7fd97d6f665 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 30 Mar 2015 22:09:23 -0400 Subject: [PATCH 0305/2059] String return values from rules should trigger errors. This makes rules and validation more consistent. Returning a string from a rule always triggers a validation failure, but only when the errorField is defined does an error message get populated. Refs #6181 --- RulesChecker.php | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/RulesChecker.php b/RulesChecker.php index 09da6ad1..77835a98 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -376,19 +376,24 @@ protected function _addError($rule, $name, $options) return function ($entity, $scope) use ($rule, $name, $options) { $pass = $rule($entity, $options + $scope); - - if ($pass || empty($options['errorField'])) { - return $pass; + if ($pass === true || empty($options['errorField'])) { + return $pass === true; } - $message = isset($options['message']) ? $options['message'] : 'invalid'; + $message = 'invalid'; + if (isset($options['message'])) { + $message = $options['message']; + } + if (is_string($pass)) { + $message = $pass; + } if ($name) { $message = [$name => $message]; } else { - $message = (array)$message; + $message = [$message]; } $entity->errors($options['errorField'], $message); - return $pass; + return $pass === true; }; } } From b6d62160adf9ba8ac6f2d610d29da4147423b542 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Tue, 31 Mar 2015 19:47:54 +0200 Subject: [PATCH 0306/2059] Fixes #6223 --- ResultSet.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ResultSet.php b/ResultSet.php index ff1e795a..68254a8b 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -383,6 +383,9 @@ protected function _calculateColumnMap() } foreach ($this->_matchingMap as $alias => $assoc) { + if (!isset($map[$alias])) { + continue; + } $this->_matchingMapColumns[$alias] = $map[$alias]; unset($map[$alias]); } From cbb978c6e1c02ed39b3cf8b43e79624f2254f8c4 Mon Sep 17 00:00:00 2001 From: Luan Hospodarsky Date: Sun, 29 Mar 2015 23:56:18 -0300 Subject: [PATCH 0307/2059] Adding the possibility of having _ids field in a Hasmany association. Signed-off-by: Luan Hospodarsky --- Marshaller.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 282cbf16..8cccf7ca 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -223,6 +223,11 @@ protected function _marshalAssociation($assoc, $value, $options) if ($assoc->type() === Association::MANY_TO_MANY) { return $marshaller->_belongsToMany($assoc, $value, (array)$options); } + if ($assoc->type() === Association::ONE_TO_MANY && array_key_exists('_ids', $value)) { + if (is_array($value['_ids'])) { + return $this->_loadAssociatedByIds($assoc, $value['_ids']); + } + } return $marshaller->many($value, (array)$options); } @@ -266,7 +271,7 @@ protected function _belongsToMany(Association $assoc, array $data, $options = [] $associated = isset($options['associated']) ? $options['associated'] : []; $hasIds = array_key_exists('_ids', $data); if ($hasIds && is_array($data['_ids'])) { - return $this->_loadBelongsToMany($assoc, $data['_ids']); + return $this->_loadAssociatedByIds($assoc, $data['_ids']); } if ($hasIds) { return []; @@ -313,7 +318,7 @@ protected function _belongsToMany(Association $assoc, array $data, $options = [] * @param array $ids The list of ids to load. * @return array An array of entities. */ - protected function _loadBelongsToMany($assoc, $ids) + protected function _loadAssociatedByIds($assoc, $ids) { $target = $assoc->target(); $primaryKey = (array)$target->primaryKey(); @@ -557,7 +562,7 @@ protected function _mergeBelongsToMany($original, $assoc, $value, $options) $hasIds = array_key_exists('_ids', $value); $associated = isset($options['associated']) ? $options['associated'] : []; if ($hasIds && is_array($value['_ids'])) { - return $this->_loadBelongsToMany($assoc, $value['_ids']); + return $this->_loadAssociatedByIds($assoc, $value['_ids']); } if ($hasIds) { return []; From 119f6499c94718237779d90783755971047d4315 Mon Sep 17 00:00:00 2001 From: Luan Hospodarsky Date: Mon, 30 Mar 2015 23:50:44 -0300 Subject: [PATCH 0308/2059] Adding a new test in TableTest.php to check if the user_id in the comments have been set properly after saving a hasMany with _ids attribute. Signed-off-by: Luan Hospodarsky --- Marshaller.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 8cccf7ca..a5c7055d 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -223,10 +223,8 @@ protected function _marshalAssociation($assoc, $value, $options) if ($assoc->type() === Association::MANY_TO_MANY) { return $marshaller->_belongsToMany($assoc, $value, (array)$options); } - if ($assoc->type() === Association::ONE_TO_MANY && array_key_exists('_ids', $value)) { - if (is_array($value['_ids'])) { - return $this->_loadAssociatedByIds($assoc, $value['_ids']); - } + if ($assoc->type() === Association::ONE_TO_MANY && array_key_exists('_ids', $value) && is_array($value['_ids'])) { + return $this->_loadAssociatedByIds($assoc, $value['_ids']); } return $marshaller->many($value, (array)$options); } From be5da33ae521328c4390f79b102445f0bd2ca638 Mon Sep 17 00:00:00 2001 From: Rachman Chavik Date: Thu, 2 Apr 2015 12:42:47 +0700 Subject: [PATCH 0309/2059] Add method for backward compatibility --- Marshaller.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Marshaller.php b/Marshaller.php index a5c7055d..4d4ca047 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -337,6 +337,19 @@ protected function _loadAssociatedByIds($assoc, $ids) return $target->find()->where($filter)->toArray(); } + /** + * Loads a list of belongs to many from ids. + * + * @param Association $assoc The association class for the belongsToMany association. + * @param array $ids The list of ids to load. + * @return array An array of entities. + * @deprecated Use _loadAssociatedByIds() + */ + protected function _loadBelongsToMany($assoc, $ids) + { + return $this->_loadAssociatedByIds($assoc, $ids); + } + /** * Merges `$data` into `$entity` and recursively does the same for each one of * the association names passed in `$options`. When merging associations, if an From b58c6cb1d7efe7f513f52a62bfaf03eba6b23239 Mon Sep 17 00:00:00 2001 From: cjquinn Date: Thu, 2 Apr 2015 17:15:56 +0100 Subject: [PATCH 0310/2059] Added saving belongs to many associations where the data array contains new and existing associated data --- Marshaller.php | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 4d4ca047..fba601f8 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -276,20 +276,26 @@ protected function _belongsToMany(Association $assoc, array $data, $options = [] } $data = array_values($data); - // Accept [ [id => 1], [id = 2] ] style. $primaryKey = array_flip($assoc->target()->schema()->primaryKey()); - if (array_intersect_key($primaryKey, current($data)) === $primaryKey) { - $primaryCount = count($primaryKey); - $query = $assoc->find(); - foreach ($data as $row) { + $records = []; + + foreach ($data as $row) { + if (array_intersect_key($primaryKey, $row) === $primaryKey) { + if (!isset($query)) { + $primaryCount = count($primaryKey); + $query = $assoc->find(); + } $keys = array_intersect_key($row, $primaryKey); if (count($keys) === $primaryCount) { $query->orWhere($keys); } + } else { + $records = array_merge($records, $this->many([$row], $options)); } - $records = $query->toArray(); - } else { - $records = $this->many($data, $options); + } + + if (isset($query)) { + $records = array_merge($records, $query->toArray()); } $joint = $assoc->junction(); From fde49a2e568cf84d4aeddb8aa26953acbd69df4e Mon Sep 17 00:00:00 2001 From: pirouet Date: Thu, 2 Apr 2015 21:55:22 +0100 Subject: [PATCH 0311/2059] Code Cleanup - RuntimeException Ensure files that use RuntimeException have the appropriate use statement --- Association.php | 11 ++++++----- Association/BelongsTo.php | 5 +++-- Association/BelongsToMany.php | 5 +++-- Association/HasMany.php | 3 ++- Behavior/TreeBehavior.php | 9 +++++---- Query.php | 9 +++++---- Table.php | 15 ++++++++------- 7 files changed, 32 insertions(+), 25 deletions(-) diff --git a/Association.php b/Association.php index 8bea7abf..d24b5b04 100644 --- a/Association.php +++ b/Association.php @@ -22,6 +22,7 @@ use Cake\ORM\Table; use Cake\ORM\TableRegistry; use Cake\Utility\Inflector; +use RuntimeException; /** * An Association is a relationship established between two tables and is used @@ -470,7 +471,7 @@ protected function _options(array $options) * @param Query $query the query to be altered to include the target table data * @param array $options Any extra options or overrides to be taken in account * @return void - * @throws \RuntimeException if the query builder passed does not return a query + * @throws RuntimeException if the query builder passed does not return a query * object */ public function attachTo(Query $query, array $options = []) @@ -501,7 +502,7 @@ public function attachTo(Query $query, array $options = []) if (!empty($options['queryBuilder'])) { $dummy = $options['queryBuilder']($dummy); if (!($dummy instanceof Query)) { - throw new \RuntimeException(sprintf( + throw new RuntimeException(sprintf( 'Query builder for association "%s" did not return a query', $this->name() )); @@ -740,7 +741,7 @@ protected function _bindNewAssociations($query, $surrogate, $options) * * @param array $options list of options passed to attachTo method * @return array - * @throws \RuntimeException if the number of columns in the foreignKey do not + * @throws RuntimeException if the number of columns in the foreignKey do not * match the number of columns in the source table primaryKey */ protected function _joinCondition($options) @@ -753,7 +754,7 @@ protected function _joinCondition($options) if (count($foreignKey) !== count($primaryKey)) { $msg = 'Cannot match provided foreignKey for "%s", got "(%s)" but expected foreign key for "(%s)"'; - throw new \RuntimeException(sprintf( + throw new RuntimeException(sprintf( $msg, $this->_name, implode(', ', $foreignKey), @@ -803,7 +804,7 @@ protected function _extractFinder($finderData) * * @param string $property the property name * @return \Cake\ORM\Association - * @throws \RuntimeException if no association with such name exists + * @throws RuntimeException if no association with such name exists */ public function __get($property) { diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 6a0c22de..1711b0eb 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -20,6 +20,7 @@ use Cake\ORM\Association\SelectableAssociationTrait; use Cake\ORM\Table; use Cake\Utility\Inflector; +use RuntimeException; /** * Represents an 1 - N relationship where the source side of the relation is @@ -154,7 +155,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) * * @param array $options list of options passed to attachTo method * @return array - * @throws \RuntimeException if the number of columns in the foreignKey do not + * @throws RuntimeException if the number of columns in the foreignKey do not * match the number of columns in the target table primaryKey */ protected function _joinCondition($options) @@ -167,7 +168,7 @@ protected function _joinCondition($options) if (count($foreignKey) !== count($primaryKey)) { $msg = 'Cannot match provided foreignKey for "%s", got "(%s)" but expected foreign key for "(%s)"'; - throw new \RuntimeException(sprintf( + throw new RuntimeException(sprintf( $msg, $this->_name, implode(', ', $foreignKey), diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 0b0784fe..75341927 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -20,6 +20,7 @@ use Cake\ORM\Table; use Cake\ORM\TableRegistry; use Cake\Utility\Inflector; +use RuntimeException; /** * Represents an M - N relationship where there exists a junction - or join - table @@ -301,7 +302,7 @@ protected function _joinCondition($options) * @param \Cake\ORM\Query $fetchQuery The query to get results from * @param array $options The options passed to the eager loader * @return array - * @throws \RuntimeException when the association property is not part of the results set. + * @throws RuntimeException when the association property is not part of the results set. */ protected function _buildResultMap($fetchQuery, $options) { @@ -312,7 +313,7 @@ protected function _buildResultMap($fetchQuery, $options) foreach ($fetchQuery->all() as $result) { if (!isset($result[$property])) { - throw new \RuntimeException(sprintf( + throw new RuntimeException(sprintf( '"%s" is missing from the belongsToMany results. Results cannot be created.', $property )); diff --git a/Association/HasMany.php b/Association/HasMany.php index 09ea480e..b93b5111 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -20,6 +20,7 @@ use Cake\ORM\Association\DependentDeleteTrait; use Cake\ORM\Association\ExternalAssociationTrait; use Cake\ORM\Table; +use RuntimeException; /** * Represents an N - 1 relationship where the target side of the relationship @@ -138,7 +139,7 @@ protected function _linkField($options) if ($options['foreignKey'] === false) { $msg = 'Cannot have foreignKey = false for hasMany associations. ' . 'You must provide a foreignKey column.'; - throw new \RuntimeException($msg); + throw new RuntimeException($msg); } foreach ((array)$options['foreignKey'] as $key) { diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index a0be5361..aa686ec9 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -20,6 +20,7 @@ use Cake\ORM\Behavior; use Cake\ORM\Entity; use Cake\ORM\Query; +use RuntimeException; /** * Makes the table to which this is attached to behave like a nested set and @@ -79,7 +80,7 @@ class TreeBehavior extends Behavior * @param \Cake\Event\Event $event The beforeSave event that was fired * @param \Cake\ORM\Entity $entity the entity that is going to be saved * @return void - * @throws \RuntimeException if the parent to set for the node is invalid + * @throws RuntimeException if the parent to set for the node is invalid */ public function beforeSave(Event $event, Entity $entity) { @@ -92,7 +93,7 @@ public function beforeSave(Event $event, Entity $entity) if ($isNew && $parent) { if ($entity->get($primaryKey[0]) == $parent) { - throw new \RuntimeException("Cannot set a node's parent as itself"); + throw new RuntimeException("Cannot set a node's parent as itself"); } $parentNode = $this->_getNode($parent); @@ -224,7 +225,7 @@ public function beforeDelete(Event $event, Entity $entity) * @param \Cake\ORM\Entity $entity The entity to re-parent * @param mixed $parent the id of the parent to set * @return void - * @throws \RuntimeException if the parent to set to the entity is not valid + * @throws RuntimeException if the parent to set to the entity is not valid */ protected function _setParent($entity, $parent) { @@ -237,7 +238,7 @@ protected function _setParent($entity, $parent) $left = $entity->get($config['left']); if ($parentLeft > $left && $parentLeft < $right) { - throw new \RuntimeException(sprintf( + throw new RuntimeException(sprintf( 'Cannot use node "%s" as parent for entity "%s"', $parent, $entity->get($this->_getPrimaryKey()) diff --git a/Query.php b/Query.php index d4a6e106..c25eff52 100644 --- a/Query.php +++ b/Query.php @@ -22,6 +22,7 @@ use Cake\ORM\ResultSet; use Cake\ORM\Table; use JsonSerializable; +use RuntimeException; /** * Extends the base Query class to provide new methods related to association @@ -570,12 +571,12 @@ public function hydrate($enable = null) * {@inheritDoc} * * @return $this - * @throws \RuntimeException When you attempt to cache a non-select query. + * @throws RuntimeException When you attempt to cache a non-select query. */ public function cache($key, $config = 'default') { if ($this->_type !== 'select' && $this->_type !== null) { - throw new \RuntimeException('You cannot cache the results of non-select queries.'); + throw new RuntimeException('You cannot cache the results of non-select queries.'); } return $this->_cache($key, $config); } @@ -583,12 +584,12 @@ public function cache($key, $config = 'default') /** * {@inheritDoc} * - * @throws \RuntimeException if this method is called on a non-select Query. + * @throws RuntimeException if this method is called on a non-select Query. */ public function all() { if ($this->_type !== 'select' && $this->_type !== null) { - throw new \RuntimeException( + throw new RuntimeException( 'You cannot call all() on a non-select query. Use execute() instead.' ); } diff --git a/Table.php b/Table.php index 889d4911..82bdc0a3 100644 --- a/Table.php +++ b/Table.php @@ -38,6 +38,7 @@ use Cake\ORM\Rule\IsUnique; use Cake\Utility\Inflector; use Cake\Validation\Validator; +use RuntimeException; /** * Represents a single database table. @@ -589,7 +590,7 @@ public function entityClass($name = null) * @param string $name The name of the behavior. Can be a short class reference. * @param array $options The options for the behavior to use. * @return void - * @throws \RuntimeException If a behavior is being reloaded. + * @throws RuntimeException If a behavior is being reloaded. * @see \Cake\ORM\Behavior */ public function addBehavior($name, array $options = []) @@ -1390,7 +1391,7 @@ public function save(EntityInterface $entity, $options = []) * @param \Cake\Datasource\EntityInterface $entity the entity to be saved * @param \ArrayObject $options the options to use for the save operation * @return \Cake\Datasource\EntityInterface|bool - * @throws \RuntimeException When an entity is missing some of the primary keys. + * @throws RuntimeException When an entity is missing some of the primary keys. */ protected function _processSave($entity, $options) { @@ -1471,7 +1472,7 @@ protected function _processSave($entity, $options) * @param \Cake\Datasource\EntityInterface $entity the subject entity from were $data was extracted * @param array $data The actual data that needs to be saved * @return \Cake\Datasource\EntityInterface|bool - * @throws \RuntimeException if not all the primary keys where supplied or could + * @throws RuntimeException if not all the primary keys where supplied or could * be generated when the table has composite primary keys. Or when the table has no primary key. */ protected function _insert($entity, $data) @@ -1482,7 +1483,7 @@ protected function _insert($entity, $data) 'Cannot insert row in "%s" table, it has no primary key.', $this->table() ); - throw new \RuntimeException($msg); + throw new RuntimeException($msg); } $keys = array_fill(0, count($primary), null); $id = (array)$this->_newId($primary) + $keys; @@ -1500,7 +1501,7 @@ protected function _insert($entity, $data) implode(', ', $filteredKeys + $entity->extract(array_keys($primary))), implode(', ', array_keys($primary)) ); - throw new \RuntimeException($msg); + throw new RuntimeException($msg); } } } @@ -1845,13 +1846,13 @@ public function __call($method, $args) * * @param string $property the association name * @return \Cake\ORM\Association - * @throws \RuntimeException if no association with such name exists + * @throws RuntimeException if no association with such name exists */ public function __get($property) { $association = $this->_associations->get($property); if (!$association) { - throw new \RuntimeException(sprintf( + throw new RuntimeException(sprintf( 'Table "%s" is not associated with "%s"', get_class($this), $property From ff9389ed6fbbeeb5ef8db2afb3ec43971caf872e Mon Sep 17 00:00:00 2001 From: pirouet Date: Fri, 3 Apr 2015 08:59:54 +0100 Subject: [PATCH 0312/2059] Docblock Absolute Path Updating docblock to use absolute path for RuntimeException --- Association.php | 6 +++--- Association/BelongsTo.php | 2 +- Association/BelongsToMany.php | 2 +- Behavior/TreeBehavior.php | 4 ++-- Query.php | 4 ++-- Table.php | 8 ++++---- TableRegistry.php | 2 +- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Association.php b/Association.php index d24b5b04..534c6133 100644 --- a/Association.php +++ b/Association.php @@ -471,7 +471,7 @@ protected function _options(array $options) * @param Query $query the query to be altered to include the target table data * @param array $options Any extra options or overrides to be taken in account * @return void - * @throws RuntimeException if the query builder passed does not return a query + * @throws \RuntimeException if the query builder passed does not return a query * object */ public function attachTo(Query $query, array $options = []) @@ -741,7 +741,7 @@ protected function _bindNewAssociations($query, $surrogate, $options) * * @param array $options list of options passed to attachTo method * @return array - * @throws RuntimeException if the number of columns in the foreignKey do not + * @throws \RuntimeException if the number of columns in the foreignKey do not * match the number of columns in the source table primaryKey */ protected function _joinCondition($options) @@ -804,7 +804,7 @@ protected function _extractFinder($finderData) * * @param string $property the property name * @return \Cake\ORM\Association - * @throws RuntimeException if no association with such name exists + * @throws \RuntimeException if no association with such name exists */ public function __get($property) { diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 1711b0eb..e8653157 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -155,7 +155,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) * * @param array $options list of options passed to attachTo method * @return array - * @throws RuntimeException if the number of columns in the foreignKey do not + * @throws \RuntimeException if the number of columns in the foreignKey do not * match the number of columns in the target table primaryKey */ protected function _joinCondition($options) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 75341927..d5bf44bf 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -302,7 +302,7 @@ protected function _joinCondition($options) * @param \Cake\ORM\Query $fetchQuery The query to get results from * @param array $options The options passed to the eager loader * @return array - * @throws RuntimeException when the association property is not part of the results set. + * @throws \RuntimeException when the association property is not part of the results set. */ protected function _buildResultMap($fetchQuery, $options) { diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index aa686ec9..3cda25fe 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -80,7 +80,7 @@ class TreeBehavior extends Behavior * @param \Cake\Event\Event $event The beforeSave event that was fired * @param \Cake\ORM\Entity $entity the entity that is going to be saved * @return void - * @throws RuntimeException if the parent to set for the node is invalid + * @throws \RuntimeException if the parent to set for the node is invalid */ public function beforeSave(Event $event, Entity $entity) { @@ -225,7 +225,7 @@ public function beforeDelete(Event $event, Entity $entity) * @param \Cake\ORM\Entity $entity The entity to re-parent * @param mixed $parent the id of the parent to set * @return void - * @throws RuntimeException if the parent to set to the entity is not valid + * @throws \RuntimeException if the parent to set to the entity is not valid */ protected function _setParent($entity, $parent) { diff --git a/Query.php b/Query.php index c25eff52..4af5d340 100644 --- a/Query.php +++ b/Query.php @@ -571,7 +571,7 @@ public function hydrate($enable = null) * {@inheritDoc} * * @return $this - * @throws RuntimeException When you attempt to cache a non-select query. + * @throws \RuntimeException When you attempt to cache a non-select query. */ public function cache($key, $config = 'default') { @@ -584,7 +584,7 @@ public function cache($key, $config = 'default') /** * {@inheritDoc} * - * @throws RuntimeException if this method is called on a non-select Query. + * @throws \RuntimeException if this method is called on a non-select Query. */ public function all() { diff --git a/Table.php b/Table.php index 82bdc0a3..0250c8e7 100644 --- a/Table.php +++ b/Table.php @@ -590,7 +590,7 @@ public function entityClass($name = null) * @param string $name The name of the behavior. Can be a short class reference. * @param array $options The options for the behavior to use. * @return void - * @throws RuntimeException If a behavior is being reloaded. + * @throws \RuntimeException If a behavior is being reloaded. * @see \Cake\ORM\Behavior */ public function addBehavior($name, array $options = []) @@ -1391,7 +1391,7 @@ public function save(EntityInterface $entity, $options = []) * @param \Cake\Datasource\EntityInterface $entity the entity to be saved * @param \ArrayObject $options the options to use for the save operation * @return \Cake\Datasource\EntityInterface|bool - * @throws RuntimeException When an entity is missing some of the primary keys. + * @throws \RuntimeException When an entity is missing some of the primary keys. */ protected function _processSave($entity, $options) { @@ -1472,7 +1472,7 @@ protected function _processSave($entity, $options) * @param \Cake\Datasource\EntityInterface $entity the subject entity from were $data was extracted * @param array $data The actual data that needs to be saved * @return \Cake\Datasource\EntityInterface|bool - * @throws RuntimeException if not all the primary keys where supplied or could + * @throws \RuntimeException if not all the primary keys where supplied or could * be generated when the table has composite primary keys. Or when the table has no primary key. */ protected function _insert($entity, $data) @@ -1846,7 +1846,7 @@ public function __call($method, $args) * * @param string $property the association name * @return \Cake\ORM\Association - * @throws RuntimeException if no association with such name exists + * @throws \RuntimeException if no association with such name exists */ public function __get($property) { diff --git a/TableRegistry.php b/TableRegistry.php index a67193b3..ad75ce9f 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -97,7 +97,7 @@ class TableRegistry * @param string|null $alias Name of the alias * @param array|null $options list of options for the alias * @return array The config data. - * @throws RuntimeException When you attempt to configure an existing table instance. + * @throws \RuntimeException When you attempt to configure an existing table instance. */ public static function config($alias = null, $options = null) { From de969086449bfca5ae3a7b55631811af2bf7bfa1 Mon Sep 17 00:00:00 2001 From: pirouet Date: Fri, 3 Apr 2015 19:18:59 +0100 Subject: [PATCH 0313/2059] Code Cleanup - InvalidArgumentException Ensure files that use InvalidArgumentException have the appropriate use statement --- Association.php | 3 ++- Association/BelongsToMany.php | 11 ++++++----- Association/HasMany.php | 3 ++- Association/SelectableAssociationTrait.php | 3 ++- AssociationCollection.php | 3 ++- Behavior/TreeBehavior.php | 5 +++-- EagerLoader.php | 3 ++- Table.php | 5 +++-- 8 files changed, 22 insertions(+), 14 deletions(-) diff --git a/Association.php b/Association.php index 534c6133..fc03d2a0 100644 --- a/Association.php +++ b/Association.php @@ -22,6 +22,7 @@ use Cake\ORM\Table; use Cake\ORM\TableRegistry; use Cake\Utility\Inflector; +use InvalidArgumentException; use RuntimeException; /** @@ -410,7 +411,7 @@ public function strategy($name = null) { if ($name !== null) { if (!in_array($name, $this->_validStrategies)) { - throw new \InvalidArgumentException( + throw new InvalidArgumentException( sprintf('Invalid strategy "%s" was provided', $name) ); } diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index d5bf44bf..3cfd11e3 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -20,6 +20,7 @@ use Cake\ORM\Table; use Cake\ORM\TableRegistry; use Cake\Utility\Inflector; +use InvalidArgumentException; use RuntimeException; /** @@ -394,7 +395,7 @@ public function saveStrategy($strategy = null) } if (!in_array($strategy, [self::SAVE_APPEND, self::SAVE_REPLACE])) { $msg = sprintf('Invalid save strategy "%s"', $strategy); - throw new \InvalidArgumentException($msg); + throw new InvalidArgumentException($msg); } return $this->_saveStrategy = $strategy; } @@ -473,7 +474,7 @@ protected function _saveTarget(EntityInterface $parentEntity, $entities, $option if (!(is_array($entities) || $entities instanceof \Traversable)) { $name = $this->property(); $message = sprintf('Could not save %s, it cannot be traversed', $name); - throw new \InvalidArgumentException($message); + throw new InvalidArgumentException($message); } $table = $this->target(); @@ -722,7 +723,7 @@ public function replaceLinks(EntityInterface $sourceEntity, array $targetEntitie if (count(array_filter($primaryValue, 'strlen')) !== count($primaryKey)) { $message = 'Could not find primary key value for source entity'; - throw new \InvalidArgumentException($message); + throw new InvalidArgumentException($message); } return $this->junction()->connection()->transactional( @@ -835,13 +836,13 @@ protected function _checkPersistenceStatus($sourceEntity, array $targetEntities) { if ($sourceEntity->isNew()) { $error = 'Source entity needs to be persisted before proceeding'; - throw new \InvalidArgumentException($error); + throw new InvalidArgumentException($error); } foreach ($targetEntities as $entity) { if ($entity->isNew()) { $error = 'Cannot link not persisted entities'; - throw new \InvalidArgumentException($error); + throw new InvalidArgumentException($error); } } diff --git a/Association/HasMany.php b/Association/HasMany.php index b93b5111..cc6f33b4 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -20,6 +20,7 @@ use Cake\ORM\Association\DependentDeleteTrait; use Cake\ORM\Association\ExternalAssociationTrait; use Cake\ORM\Table; +use InvalidArgumentException; use RuntimeException; /** @@ -92,7 +93,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) if (!is_array($targetEntities) && !($targetEntities instanceof \Traversable)) { $name = $this->property(); $message = sprintf('Could not save %s, it cannot be traversed', $name); - throw new \InvalidArgumentException($message); + throw new InvalidArgumentException($message); } $properties = array_combine( diff --git a/Association/SelectableAssociationTrait.php b/Association/SelectableAssociationTrait.php index e9bb2675..187a9230 100644 --- a/Association/SelectableAssociationTrait.php +++ b/Association/SelectableAssociationTrait.php @@ -16,6 +16,7 @@ use Cake\Database\Expression\IdentifierExpression; use Cake\Database\Expression\TupleComparison; +use InvalidArgumentException; /** * Represents a type of association that that can be fetched using another query @@ -106,7 +107,7 @@ protected function _buildQuery($options) if (!empty($options['fields'])) { $fields = $fetchQuery->aliasFields($options['fields'], $alias); if (!in_array($key, $fields)) { - throw new \InvalidArgumentException( + throw new InvalidArgumentException( sprintf('You are required to select the "%s" field', $key) ); } diff --git a/AssociationCollection.php b/AssociationCollection.php index 56f5d42b..a4342dd3 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -18,6 +18,7 @@ use Cake\ORM\AssociationsNormalizerTrait; use Cake\ORM\Entity; use Cake\ORM\Table; +use InvalidArgumentException; /** * A container/collection for association classes. @@ -216,7 +217,7 @@ protected function _saveAssociations($table, $entity, $associations, $options, $ $alias, $table->alias() ); - throw new \InvalidArgumentException($msg); + throw new InvalidArgumentException($msg); } if ($relation->isOwningSide($table) !== $owningSide) { continue; diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 3cda25fe..bf105db8 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -20,6 +20,7 @@ use Cake\ORM\Behavior; use Cake\ORM\Entity; use Cake\ORM\Query; +use InvalidArgumentException; use RuntimeException; /** @@ -343,7 +344,7 @@ protected function _unmarkInternalTree() public function findPath(Query $query, array $options) { if (empty($options['for'])) { - throw new \InvalidArgumentException("The 'for' key is required for find('path')"); + throw new InvalidArgumentException("The 'for' key is required for find('path')"); } $config = $this->config(); @@ -416,7 +417,7 @@ function ($field) { list($for, $direct) = [$options['for'], $options['direct']]; if (empty($for)) { - throw new \InvalidArgumentException("The 'for' key is required for find('children')"); + throw new InvalidArgumentException("The 'for' key is required for find('children')"); } if ($query->clause('order') === null) { diff --git a/EagerLoader.php b/EagerLoader.php index 6780c2be..f2e53989 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -21,6 +21,7 @@ use Cake\ORM\Query; use Cake\ORM\Table; use Closure; +use InvalidArgumentException; /** * Exposes the methods for storing the associations that should be eager loaded @@ -377,7 +378,7 @@ protected function _normalizeContain(Table $parent, $alias, $options, $paths) $defaults = $this->_containOptions; $instance = $parent->association($alias); if (!$instance) { - throw new \InvalidArgumentException( + throw new InvalidArgumentException( sprintf('%s is not associated with %s', $parent->alias(), $alias) ); } diff --git a/Table.php b/Table.php index 0250c8e7..b2dca381 100644 --- a/Table.php +++ b/Table.php @@ -38,6 +38,7 @@ use Cake\ORM\Rule\IsUnique; use Cake\Utility\Inflector; use Cake\Validation\Validator; +use InvalidArgumentException; use RuntimeException; /** @@ -1573,7 +1574,7 @@ protected function _update($entity, $data) if (!$entity->has($primaryColumns)) { $message = 'All primary key value(s) are needed for updating'; - throw new \InvalidArgumentException($message); + throw new InvalidArgumentException($message); } $query = $this->query(); @@ -1671,7 +1672,7 @@ protected function _processDelete($entity, $options) $primaryKey = (array)$this->primaryKey(); if (!$entity->has($primaryKey)) { $msg = 'Deleting requires all primary key values.'; - throw new \InvalidArgumentException($msg); + throw new InvalidArgumentException($msg); } if ($options['checkRules'] && !$this->checkRules($entity, RulesChecker::DELETE, $options)) { From cdd42acfbcdbce3b1299761daba6a50343c1c06c Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Sat, 4 Apr 2015 17:18:20 -0400 Subject: [PATCH 0314/2059] Removed duplicated option on find docblock --- Table.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Table.php b/Table.php index 0250c8e7..dfedb13e 100644 --- a/Table.php +++ b/Table.php @@ -870,7 +870,6 @@ public function belongsToMany($associated, array $options = []) * - limit * - offset * - page - * - order * - group * - having * - contain From 472c35ae7309f49dc9f13a3998e6fb49d69b6067 Mon Sep 17 00:00:00 2001 From: cjquinn Date: Thu, 2 Apr 2015 17:15:56 +0100 Subject: [PATCH 0315/2059] Added saving belongs to many associations where the data array contains new and existing associated data --- Marshaller.php | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 4d4ca047..fba601f8 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -276,20 +276,26 @@ protected function _belongsToMany(Association $assoc, array $data, $options = [] } $data = array_values($data); - // Accept [ [id => 1], [id = 2] ] style. $primaryKey = array_flip($assoc->target()->schema()->primaryKey()); - if (array_intersect_key($primaryKey, current($data)) === $primaryKey) { - $primaryCount = count($primaryKey); - $query = $assoc->find(); - foreach ($data as $row) { + $records = []; + + foreach ($data as $row) { + if (array_intersect_key($primaryKey, $row) === $primaryKey) { + if (!isset($query)) { + $primaryCount = count($primaryKey); + $query = $assoc->find(); + } $keys = array_intersect_key($row, $primaryKey); if (count($keys) === $primaryCount) { $query->orWhere($keys); } + } else { + $records = array_merge($records, $this->many([$row], $options)); } - $records = $query->toArray(); - } else { - $records = $this->many($data, $options); + } + + if (isset($query)) { + $records = array_merge($records, $query->toArray()); } $joint = $assoc->junction(); From ef52c34909aece2c7acdc3c9fb7c886dc71dcd67 Mon Sep 17 00:00:00 2001 From: cjquinn Date: Sun, 5 Apr 2015 19:26:10 +0100 Subject: [PATCH 0316/2059] Using one instead of many to create record, fixed PHPCS errors --- Marshaller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index fba601f8..a02aa351 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -290,7 +290,7 @@ protected function _belongsToMany(Association $assoc, array $data, $options = [] $query->orWhere($keys); } } else { - $records = array_merge($records, $this->many([$row], $options)); + $records[] = $this->one($row, $options); } } From eb108525817b8a32496713404bd78488deeae66e Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 6 Apr 2015 14:12:55 -0400 Subject: [PATCH 0317/2059] Add missing option. --- Marshaller.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Marshaller.php b/Marshaller.php index 4d4ca047..7173db42 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -237,6 +237,7 @@ protected function _marshalAssociation($assoc, $value, $options) * * associated: Associations listed here will be marshalled as well. * * fieldList: A whitelist of fields to be assigned to the entity. If not present, * the accessible fields list in the entity will be used. + * * accessibleFields: A list of fields to allow or deny in entity accessible fields. * * @param array $data The data to hydrate. * @param array $options List of options From 20fb53cb3ffae6dca36c51e9828d416c726b7b01 Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 1 Apr 2015 09:49:34 +0200 Subject: [PATCH 0318/2059] TableRegistry split into the facade and registry. --- Registry.php | 277 ++++++++++++++++++++++++++++++++++++++++++++++ TableRegistry.php | 219 ++++-------------------------------- 2 files changed, 300 insertions(+), 196 deletions(-) create mode 100644 Registry.php diff --git a/Registry.php b/Registry.php new file mode 100644 index 00000000..d02d5d33 --- /dev/null +++ b/Registry.php @@ -0,0 +1,277 @@ + 'my_users']); + * ``` + * + * Configuration data is stored *per alias* if you use the same table with + * multiple aliases you will need to set configuration multiple times. + * + * ### Getting instances + * + * You can fetch instances out of the registry using get(). One instance is stored + * per alias. Once an alias is populated the same instance will always be returned. + * This is used to make the ORM use less memory and help make cyclic references easier + * to solve. + * + * ``` + * $table = TableRegistry::get('Users', $config); + * ``` + * + */ +class Registry +{ + + /** + * Configuration for aliases. + * + * @var array + */ + protected $_config = []; + + /** + * Instances that belong to the registry. + * + * @var array + */ + protected $_instances = []; + + /** + * Contains a list of Table objects that were created out of the + * built-in Table class. The list is indexed by table alias + * + * @var array + */ + protected $_fallbacked = []; + + /** + * Contains a list of options that were passed to get() method. + * + * @var array + */ + protected $_options = []; + + /** + * Stores a list of options to be used when instantiating an object + * with a matching alias. + * + * The options that can be stored are those that are recognized by `get()` + * If second argument is omitted, it will return the current settings + * for $alias. + * + * If no arguments are passed it will return the full configuration array for + * all aliases + * + * @param string|null $alias Name of the alias + * @param array|null $options list of options for the alias + * @return array The config data. + * @throws RuntimeException When you attempt to configure an existing table instance. + */ + public function config($alias = null, $options = null) + { + if ($alias === null) { + return $this->_config; + } + if (!is_string($alias)) { + return $this->_config = $alias; + } + if ($options === null) { + return isset($this->_config[$alias]) ? $this->_config[$alias] : []; + } + if (isset($this->_instances[$alias])) { + throw new RuntimeException(sprintf( + 'You cannot configure "%s", it has already been constructed.', $alias + )); + } + return $this->_config[$alias] = $options; + } + + /** + * Get a table instance from the registry. + * + * Tables are only created once until the registry is flushed. + * This means that aliases must be unique across your application. + * This is important because table associations are resolved at runtime + * and cyclic references need to be handled correctly. + * + * The options that can be passed are the same as in `Table::__construct()`, but the + * key `className` is also recognized. + * + * If $options does not contain `className` CakePHP will attempt to construct the + * class name based on the alias. For example 'Users' would result in + * `App\Model\Table\UsersTable` being attempted. If this class does not exist, + * then the default `Cake\ORM\Table` class will be used. By setting the `className` + * option you can define the specific class to use. This className can + * use a plugin short class reference. + * + * If you use a `$name` that uses plugin syntax only the name part will be used as + * key in the registry. This means that if two plugins, or a plugin and app provide + * the same alias, the registry will only store the first instance. + * + * If no `table` option is passed, the table name will be the underscored version + * of the provided $alias. + * + * If no `connection` option is passed the table's defaultConnectionName() method + * will be called to get the default connection name to use. + * + * @param string $alias The alias name you want to get. + * @param array $options The options you want to build the table with. + * If a table has already been loaded the options will be ignored. + * @return \Cake\ORM\Table + * @throws \RuntimeException When you try to configure an alias that already exists. + */ + public function get($alias, array $options = []) + { + if (isset($this->_instances[$alias])) { + if (!empty($options) && $this->_options[$alias] !== $options) { + throw new RuntimeException(sprintf( + 'You cannot configure "%s", it already exists in the registry.', $alias + )); + } + return $this->_instances[$alias]; + } + + $this->_options[$alias] = $options; + list(, $classAlias) = pluginSplit($alias); + $options = ['alias' => $classAlias] + $options; + + if (empty($options['className'])) { + $options['className'] = Inflector::camelize($alias); + } + $className = App::className($options['className'], 'Model/Table', 'Table'); + if ($className) { + $options['className'] = $className; + } else { + if (!isset($options['table']) && strpos($options['className'], '\\') === false) { + list(, $table) = pluginSplit($options['className']); + $options['table'] = Inflector::underscore($table); + } + $options['className'] = 'Cake\ORM\Table'; + } + + if (isset($this->_config[$alias])) { + $options += $this->_config[$alias]; + } + if (empty($options['connection'])) { + $connectionName = $options['className']::defaultConnectionName(); + $options['connection'] = ConnectionManager::get($connectionName); + } + + $options['registryAlias'] = $alias; + $this->_instances[$alias] = $this->_create($options); + + if ($options['className'] === 'Cake\ORM\Table') { + $this->_fallbacked[$alias] = $this->_instances[$alias]; + } + + return $this->_instances[$alias]; + } + + /** + * Wrapper for creating table instances + * + * @param array $options The alias to check for. + * @return \Cake\ORM\Table + */ + protected function _create(array $options) + { + return new $options['className']($options); + } + + /** + * Check to see if an instance exists in the registry. + * + * @param string $alias The alias to check for. + * @return bool + */ + public function exists($alias) + { + return isset($this->_instances[$alias]); + } + + /** + * Set an instance. + * + * @param string $alias The alias to set. + * @param \Cake\ORM\Table $object The table to set. + * @return \Cake\ORM\Table + */ + public function set($alias, Table $object) + { + return $this->_instances[$alias] = $object; + } + + /** + * Clears the registry of configuration and instances. + * + * @return void + */ + public function clear() + { + $this->_instances = []; + $this->_config = []; + $this->_fallbacked = []; + } + + /** + * Returns the list of tables that were created by this registry that could + * not be instantiated from a specific subclass. This method is useful for + * debugging common mistakes when setting up associations or created new table + * classes. + * + * @return array + */ + public function genericInstances() + { + return $this->_fallbacked; + } + + /** + * Removes an instance from the registry. + * + * @param string $alias The alias to remove. + * @return void + */ + public function remove($alias) + { + unset( + $this->_instances[$alias], + $this->_config[$alias], + $this->_fallbacked[$alias] + ); + } +} diff --git a/TableRegistry.php b/TableRegistry.php index ad75ce9f..9b912a40 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -1,4 +1,5 @@ $classAlias] + $options; - - if (empty($options['className'])) { - $options['className'] = Inflector::camelize($alias); - } - $className = App::className($options['className'], 'Model/Table', 'Table'); - if ($className) { - $options['className'] = $className; - } else { - if (!isset($options['table']) && strpos($options['className'], '\\') === false) { - list(, $table) = pluginSplit($options['className']); - $options['table'] = Inflector::underscore($table); - } - $options['className'] = 'Cake\ORM\Table'; - } - - if (isset(static::$_config[$alias])) { - $options += static::$_config[$alias]; - } - if (empty($options['connection'])) { - $connectionName = $options['className']::defaultConnectionName(); - $options['connection'] = ConnectionManager::get($connectionName); + if (!static::$_instance) { + static::$_instance = new Registry; } - $options['registryAlias'] = $alias; - static::$_instances[$alias] = new $options['className']($options); - - if ($options['className'] === 'Cake\ORM\Table') { - static::$_fallbacked[$alias] = static::$_instances[$alias]; - } - - return static::$_instances[$alias]; + return static::$_instance; } /** - * Check to see if an instance exists in the registry. - * - * @param string $alias The alias to check for. - * @return bool - */ - public static function exists($alias) - { - return isset(static::$_instances[$alias]); - } - - /** - * Set an instance. - * - * @param string $alias The alias to set. - * @param \Cake\ORM\Table $object The table to set. - * @return \Cake\ORM\Table - */ - public static function set($alias, Table $object) - { - return static::$_instances[$alias] = $object; - } - - /** - * Clears the registry of configuration and instances. - * - * @return void - */ - public static function clear() - { - static::$_instances = []; - static::$_config = []; - static::$_fallbacked = []; - } - - /** - * Returns the list of tables that were created by this registry that could - * not be instantiated from a specific subclass. This method is useful for - * debugging common mistakes when setting up associations or created new table - * classes. - * - * @return array - */ - public static function genericInstances() - { - return static::$_fallbacked; - } - - /** - * Removes an instance from the registry. - * - * @param string $alias The alias to remove. - * @return void + * Proxy for static calls on a singleton. + * + * @param string $name + * @param array $arguments + * @return mixed */ - public static function remove($alias) + public static function __callStatic($name, $arguments) { - unset( - static::$_instances[$alias], - static::$_config[$alias], - static::$_fallbacked[$alias] - ); + return call_user_func_array([static::instance(), $name], $arguments); } } From a8a8aa1b0bcb652a286897a0ee4b0ef2ecf30ead Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 1 Apr 2015 12:08:34 +0200 Subject: [PATCH 0319/2059] Table registries must implement RegistryInterface --- Registry.php => Registry/DefaultRegistry.php | 69 ++++-------------- Registry/RegistryInterface.php | 77 ++++++++++++++++++++ TableRegistry.php | 23 ++++-- 3 files changed, 107 insertions(+), 62 deletions(-) rename Registry.php => Registry/DefaultRegistry.php (78%) create mode 100644 Registry/RegistryInterface.php diff --git a/Registry.php b/Registry/DefaultRegistry.php similarity index 78% rename from Registry.php rename to Registry/DefaultRegistry.php index d02d5d33..d5e8764b 100644 --- a/Registry.php +++ b/Registry/DefaultRegistry.php @@ -10,50 +10,23 @@ * * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) * @link http://cakephp.org CakePHP(tm) Project - * @since 3.0.0 + * @since 3.1.0 * @license http://www.opensource.org/licenses/mit-license.php MIT License */ -namespace Cake\ORM; +namespace Cake\ORM\Registry; use Cake\Core\App; use Cake\Datasource\ConnectionManager; +use Cake\ORM\Registry\RegistryInterface; +use Cake\ORM\Table; use Cake\Utility\Inflector; use RuntimeException; /** - * Provides a registry/factory for Table objects. - * - * This registry allows you to centralize the configuration for tables - * their connections and other meta-data. - * - * ### Configuring instances - * - * You may need to configure your table objects, using TableRegistry you can - * centralize configuration. Any configuration set before instances are created - * will be used when creating instances. If you modify configuration after - * an instance is made, the instances *will not* be updated. - * - * ``` - * TableRegistry::config('Users', ['table' => 'my_users']); - * ``` - * - * Configuration data is stored *per alias* if you use the same table with - * multiple aliases you will need to set configuration multiple times. - * - * ### Getting instances - * - * You can fetch instances out of the registry using get(). One instance is stored - * per alias. Once an alias is populated the same instance will always be returned. - * This is used to make the ORM use less memory and help make cyclic references easier - * to solve. - * - * ``` - * $table = TableRegistry::get('Users', $config); - * ``` - * + * Provides a default registry/factory for Table objects. */ -class Registry +class DefaultRegistry implements RegistryInterface { /** @@ -114,7 +87,7 @@ public function config($alias = null, $options = null) } if (isset($this->_instances[$alias])) { throw new RuntimeException(sprintf( - 'You cannot configure "%s", it has already been constructed.', $alias + 'You cannot configure "%s", it has already been constructed.', $alias )); } return $this->_config[$alias] = $options; @@ -159,7 +132,7 @@ public function get($alias, array $options = []) if (isset($this->_instances[$alias])) { if (!empty($options) && $this->_options[$alias] !== $options) { throw new RuntimeException(sprintf( - 'You cannot configure "%s", it already exists in the registry.', $alias + 'You cannot configure "%s", it already exists in the registry.', $alias )); } return $this->_instances[$alias]; @@ -197,7 +170,7 @@ public function get($alias, array $options = []) if ($options['className'] === 'Cake\ORM\Table') { $this->_fallbacked[$alias] = $this->_instances[$alias]; } - + return $this->_instances[$alias]; } @@ -213,10 +186,7 @@ protected function _create(array $options) } /** - * Check to see if an instance exists in the registry. - * - * @param string $alias The alias to check for. - * @return bool + * @inheritDoc */ public function exists($alias) { @@ -224,11 +194,7 @@ public function exists($alias) } /** - * Set an instance. - * - * @param string $alias The alias to set. - * @param \Cake\ORM\Table $object The table to set. - * @return \Cake\ORM\Table + * @inheritDoc */ public function set($alias, Table $object) { @@ -236,9 +202,7 @@ public function set($alias, Table $object) } /** - * Clears the registry of configuration and instances. - * - * @return void + * @inheritDoc */ public function clear() { @@ -261,16 +225,13 @@ public function genericInstances() } /** - * Removes an instance from the registry. - * - * @param string $alias The alias to remove. - * @return void + * @inheritDoc */ public function remove($alias) { unset( - $this->_instances[$alias], - $this->_config[$alias], + $this->_instances[$alias], + $this->_config[$alias], $this->_fallbacked[$alias] ); } diff --git a/Registry/RegistryInterface.php b/Registry/RegistryInterface.php new file mode 100644 index 00000000..3d252bf0 --- /dev/null +++ b/Registry/RegistryInterface.php @@ -0,0 +1,77 @@ + Date: Wed, 1 Apr 2015 12:37:16 +0200 Subject: [PATCH 0320/2059] CS fixes --- Registry/DefaultRegistry.php | 18 ++++++++++-------- TableRegistry.php | 6 +++--- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Registry/DefaultRegistry.php b/Registry/DefaultRegistry.php index d5e8764b..602c1d27 100644 --- a/Registry/DefaultRegistry.php +++ b/Registry/DefaultRegistry.php @@ -87,7 +87,8 @@ public function config($alias = null, $options = null) } if (isset($this->_instances[$alias])) { throw new RuntimeException(sprintf( - 'You cannot configure "%s", it has already been constructed.', $alias + 'You cannot configure "%s", it has already been constructed.', + $alias )); } return $this->_config[$alias] = $options; @@ -132,7 +133,8 @@ public function get($alias, array $options = []) if (isset($this->_instances[$alias])) { if (!empty($options) && $this->_options[$alias] !== $options) { throw new RuntimeException(sprintf( - 'You cannot configure "%s", it already exists in the registry.', $alias + 'You cannot configure "%s", it already exists in the registry.', + $alias )); } return $this->_instances[$alias]; @@ -186,7 +188,7 @@ protected function _create(array $options) } /** - * @inheritDoc + * {@inheritDoc} */ public function exists($alias) { @@ -194,7 +196,7 @@ public function exists($alias) } /** - * @inheritDoc + * {@inheritDoc} */ public function set($alias, Table $object) { @@ -202,7 +204,7 @@ public function set($alias, Table $object) } /** - * @inheritDoc + * {@inheritDoc} */ public function clear() { @@ -225,13 +227,13 @@ public function genericInstances() } /** - * @inheritDoc + * {@inheritDoc} */ public function remove($alias) { unset( - $this->_instances[$alias], - $this->_config[$alias], + $this->_instances[$alias], + $this->_config[$alias], $this->_fallbacked[$alias] ); } diff --git a/TableRegistry.php b/TableRegistry.php index a027d9b8..4d8c5946 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -70,7 +70,7 @@ class TableRegistry /** * Sets and returns singleton instance of Registry. * - * @param \Cake\ORM\Registry\RegistryInterface $instance + * @param \Cake\ORM\Registry\RegistryInterface $instance Instance of registry to set. * @return \Cake\ORM\Registry\RegistryInterface */ public static function instance(RegistryInterface $instance = null) @@ -89,8 +89,8 @@ public static function instance(RegistryInterface $instance = null) /** * Proxy for static calls on a singleton. * - * @param string $name - * @param array $arguments + * @param string $name Method name. + * @param array $arguments Method arguments. * @return mixed */ public static function __callStatic($name, $arguments) From 32cff903b816cdc831d2192a6f69379e5c707ba5 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 2 Apr 2015 09:44:21 +0200 Subject: [PATCH 0321/2059] Refactored Registry to Locator. --- .../LocatorInterface.php | 4 ++-- .../TableLocator.php | 6 +++--- TableRegistry.php | 18 +++++++++--------- 3 files changed, 14 insertions(+), 14 deletions(-) rename Registry/RegistryInterface.php => Locator/LocatorInterface.php (97%) rename Registry/DefaultRegistry.php => Locator/TableLocator.php (98%) diff --git a/Registry/RegistryInterface.php b/Locator/LocatorInterface.php similarity index 97% rename from Registry/RegistryInterface.php rename to Locator/LocatorInterface.php index 3d252bf0..ea034741 100644 --- a/Registry/RegistryInterface.php +++ b/Locator/LocatorInterface.php @@ -14,14 +14,14 @@ * @license http://www.opensource.org/licenses/mit-license.php MIT License */ -namespace Cake\ORM\Registry; +namespace Cake\ORM\Locator; use Cake\ORM\Table; /** * Registries for Table objects should implement this interface. */ -interface RegistryInterface +interface LocatorInterface { /** diff --git a/Registry/DefaultRegistry.php b/Locator/TableLocator.php similarity index 98% rename from Registry/DefaultRegistry.php rename to Locator/TableLocator.php index 602c1d27..759b0a99 100644 --- a/Registry/DefaultRegistry.php +++ b/Locator/TableLocator.php @@ -14,11 +14,11 @@ * @license http://www.opensource.org/licenses/mit-license.php MIT License */ -namespace Cake\ORM\Registry; +namespace Cake\ORM\Locator; use Cake\Core\App; use Cake\Datasource\ConnectionManager; -use Cake\ORM\Registry\RegistryInterface; +use Cake\ORM\Locator\LocatorInterface; use Cake\ORM\Table; use Cake\Utility\Inflector; use RuntimeException; @@ -26,7 +26,7 @@ /** * Provides a default registry/factory for Table objects. */ -class DefaultRegistry implements RegistryInterface +class TableLocator implements LocatorInterface { /** diff --git a/TableRegistry.php b/TableRegistry.php index 4d8c5946..1a0c1716 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -16,7 +16,7 @@ namespace Cake\ORM; -use Cake\ORM\Registry\RegistryInterface; +use Cake\ORM\Locator\LocatorInterface; /** * Provides a registry/factory for Table objects. @@ -56,31 +56,31 @@ class TableRegistry /** * Singleton for static calls. * - * @var \Cake\ORM\Registry\RegistryInterface + * @var \Cake\ORM\Locator\LocatorInterface */ protected static $_instance; /** - * Default RegistryInterface implementation class. + * Default LocatorInterface implementation class. * * @var string */ - protected static $_defaultRegistryClass = 'Cake\ORM\Registry\DefaultRegistry'; + protected static $_defaultLocatorClass = 'Cake\ORM\Locator\TableLocator'; /** - * Sets and returns singleton instance of Registry. + * Sets and returns singleton instance of a LocatorInterface. * - * @param \Cake\ORM\Registry\RegistryInterface $instance Instance of registry to set. - * @return \Cake\ORM\Registry\RegistryInterface + * @param \Cake\ORM\Locator\LocatorInterface $instance Instance of registry to set. + * @return \Cake\ORM\Locator\LocatorInterface */ - public static function instance(RegistryInterface $instance = null) + public static function instance(LocatorInterface $instance = null) { if ($instance) { static::$_instance = $instance; } if (!static::$_instance) { - static::$_instance = new static::$_defaultRegistryClass; + static::$_instance = new static::$_defaultLocatorClass; } return static::$_instance; From 703f11574e1c75d1929145584124977cf986d46d Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 2 Apr 2015 09:45:55 +0200 Subject: [PATCH 0322/2059] Added direct static proxy for interface methods. --- TableRegistry.php | 69 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/TableRegistry.php b/TableRegistry.php index 1a0c1716..4189a0ba 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -86,6 +86,75 @@ public static function instance(LocatorInterface $instance = null) return static::$_instance; } + /** + * Stores a list of options to be used when instantiating an object + * with a matching alias. + * + * @param string|null $alias Name of the alias + * @param array|null $options list of options for the alias + * @return array The config data. + */ + public static function config($alias = null, $options = null) + { + return static::instance()->config($alias, $options); + } + + /** + * Get a table instance from the registry. + * + * @param string $alias The alias name you want to get. + * @param array $options The options you want to build the table with. + * @return \Cake\ORM\Table + */ + public static function get($alias, array $options = []) + { + return static::instance()->get($alias, $options); + } + + /** + * Check to see if an instance exists in the registry. + * + * @param string $alias The alias to check for. + * @return bool + */ + public static function exists($alias) + { + return static::instance()->exists($alias); + } + + /** + * Set an instance. + * + * @param string $alias The alias to set. + * @param \Cake\ORM\Table $object The table to set. + * @return \Cake\ORM\Table + */ + public static function set($alias, Table $object) + { + return static::instance()->set($alias, $object); + } + + /** + * Removes an instance from the registry. + * + * @param string $alias The alias to remove. + * @return void + */ + public static function remove($alias) + { + static::instance()->remove($alias); + } + + /** + * Clears the registry of configuration and instances. + * + * @return void + */ + public static function clear() + { + static::instance()->clear(); + } + /** * Proxy for static calls on a singleton. * From e54f6d760b6bcfa6940ab3d3f226970002b14808 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 2 Apr 2015 09:49:55 +0200 Subject: [PATCH 0323/2059] Renamed instance() to locator() to avoid confusion. --- TableRegistry.php | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/TableRegistry.php b/TableRegistry.php index 4189a0ba..b183626f 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -54,11 +54,11 @@ class TableRegistry { /** - * Singleton for static calls. + * LocatorInterface implementation instance. * * @var \Cake\ORM\Locator\LocatorInterface */ - protected static $_instance; + protected static $_locator; /** * Default LocatorInterface implementation class. @@ -68,22 +68,22 @@ class TableRegistry protected static $_defaultLocatorClass = 'Cake\ORM\Locator\TableLocator'; /** - * Sets and returns singleton instance of a LocatorInterface. + * Sets and returns a singleton instance of LocatorInterface implementation. * - * @param \Cake\ORM\Locator\LocatorInterface $instance Instance of registry to set. + * @param \Cake\ORM\Locator\LocatorInterface $locator Instance of a locator to use. * @return \Cake\ORM\Locator\LocatorInterface */ - public static function instance(LocatorInterface $instance = null) + public static function locator(LocatorInterface $locator = null) { - if ($instance) { - static::$_instance = $instance; + if ($locator) { + static::$_locator = $locator; } - if (!static::$_instance) { - static::$_instance = new static::$_defaultLocatorClass; + if (!static::$_locator) { + static::$_locator = new static::$_defaultLocatorClass; } - return static::$_instance; + return static::$_locator; } /** @@ -96,7 +96,7 @@ public static function instance(LocatorInterface $instance = null) */ public static function config($alias = null, $options = null) { - return static::instance()->config($alias, $options); + return static::locator()->config($alias, $options); } /** @@ -108,7 +108,7 @@ public static function config($alias = null, $options = null) */ public static function get($alias, array $options = []) { - return static::instance()->get($alias, $options); + return static::locator()->get($alias, $options); } /** @@ -119,7 +119,7 @@ public static function get($alias, array $options = []) */ public static function exists($alias) { - return static::instance()->exists($alias); + return static::locator()->exists($alias); } /** @@ -131,7 +131,7 @@ public static function exists($alias) */ public static function set($alias, Table $object) { - return static::instance()->set($alias, $object); + return static::locator()->set($alias, $object); } /** @@ -142,7 +142,7 @@ public static function set($alias, Table $object) */ public static function remove($alias) { - static::instance()->remove($alias); + static::locator()->remove($alias); } /** @@ -152,11 +152,11 @@ public static function remove($alias) */ public static function clear() { - static::instance()->clear(); + static::locator()->clear(); } /** - * Proxy for static calls on a singleton. + * Proxy for static calls on a locator. * * @param string $name Method name. * @param array $arguments Method arguments. @@ -164,6 +164,6 @@ public static function clear() */ public static function __callStatic($name, $arguments) { - return call_user_func_array([static::instance(), $name], $arguments); + return call_user_func_array([static::locator(), $name], $arguments); } } From 9ef063cf77a3930d27b2a8b2636db6d84c23c985 Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 8 Apr 2015 14:06:11 +0200 Subject: [PATCH 0324/2059] Removed extra lines --- Locator/LocatorInterface.php | 2 -- Locator/TableLocator.php | 2 -- TableRegistry.php | 2 -- 3 files changed, 6 deletions(-) diff --git a/Locator/LocatorInterface.php b/Locator/LocatorInterface.php index ea034741..a1658d65 100644 --- a/Locator/LocatorInterface.php +++ b/Locator/LocatorInterface.php @@ -1,5 +1,4 @@ Date: Thu, 9 Apr 2015 21:06:58 -0400 Subject: [PATCH 0325/2059] Skip non-entity entries in BelongsToMany associations When saving entities with accessible belongs to many association properties that were not marshalled, fatal errors would be encountered. Skipping the obviously bad data is a resonable course of action as the ORM should not marshall it, as it was either explicitly or implicitly not marshalled. Refs #6301 --- Association/BelongsToMany.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 3cfd11e3..1d28068b 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -804,6 +804,9 @@ protected function _diffLinks($existing, $jointEntities, $targetEntities) $primary = (array)$target->primaryKey(); $jointProperty = $this->_junctionProperty; foreach ($targetEntities as $k => $entity) { + if (!($entity instanceof EntityInterface)) { + continue; + } $key = array_values($entity->extract($primary)); foreach ($present as $i => $data) { if ($key === $data && !$entity->get($jointProperty)) { @@ -873,6 +876,9 @@ protected function _collectJointEntities($sourceEntity, $targetEntities) $missing = []; foreach ($targetEntities as $entity) { + if (!($entity instanceof EntityInterface)) { + continue; + } $joint = $entity->get($jointProperty); if (!$joint || !($joint instanceof EntityInterface)) { From 47eac9a89633209f1b9e8b012edb500b9e995fd9 Mon Sep 17 00:00:00 2001 From: PGBarrow Date: Mon, 13 Apr 2015 11:14:06 -0700 Subject: [PATCH 0326/2059] Fix issue #6295 --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 03250855..d937e951 100644 --- a/Table.php +++ b/Table.php @@ -1787,7 +1787,7 @@ protected function _dynamicFinder($method, $args) )); } foreach ($fields as $field) { - $conditions[$field] = array_shift($args); + $conditions[$this->alias() . '.' . $field] = array_shift($args); } return $conditions; }; From 5e2889422e4afd686a6554efa39b0d1a5c3d98f8 Mon Sep 17 00:00:00 2001 From: PGBarrow Date: Mon, 13 Apr 2015 11:30:44 -0700 Subject: [PATCH 0327/2059] Aliasing fields with built-in method --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index d937e951..04981f42 100644 --- a/Table.php +++ b/Table.php @@ -1787,7 +1787,7 @@ protected function _dynamicFinder($method, $args) )); } foreach ($fields as $field) { - $conditions[$this->alias() . '.' . $field] = array_shift($args); + $conditions[$this->aliasField($field)] = array_shift($args); } return $conditions; }; From 217386e10d3c265f35e594b8fe1fa08815caf29e Mon Sep 17 00:00:00 2001 From: PGBarrow Date: Mon, 13 Apr 2015 18:37:19 -0700 Subject: [PATCH 0328/2059] Prioritizing cascade delete to associations for which cascadeCallbacks is true --- AssociationCollection.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/AssociationCollection.php b/AssociationCollection.php index a4342dd3..df228482 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -259,7 +259,14 @@ protected function _save($association, $entity, $nested, $options) public function cascadeDelete(Entity $entity, array $options) { foreach ($this->_items as $assoc) { - $assoc->cascadeDelete($entity, $options); + if ($assoc->cascadeCallbacks()) { + $assoc->cascadeDelete($entity, $options); + } + } + foreach ($this->_items as $assoc) { + if (!$assoc->cascadeCallbacks()) { + $assoc->cascadeDelete($entity, $options); + } } } From 7e71412ea99a4d73d2b59ae848a8f8b126093221 Mon Sep 17 00:00:00 2001 From: PGBarrow Date: Tue, 14 Apr 2015 14:16:05 -0700 Subject: [PATCH 0329/2059] Improved looping across associations --- AssociationCollection.php | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/AssociationCollection.php b/AssociationCollection.php index df228482..e8a8dbdc 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -18,6 +18,7 @@ use Cake\ORM\AssociationsNormalizerTrait; use Cake\ORM\Entity; use Cake\ORM\Table; +use Cake\Collection\Collection; use InvalidArgumentException; /** @@ -251,6 +252,7 @@ protected function _save($association, $entity, $nested, $options) /** * Cascade a delete across the various associations. + * Cascade first across associations for which cascadeCallbacks is true. * * @param \Cake\ORM\Entity $entity The entity to delete associations for. * @param array $options The options used in the delete operation. @@ -258,15 +260,16 @@ protected function _save($association, $entity, $nested, $options) */ public function cascadeDelete(Entity $entity, array $options) { - foreach ($this->_items as $assoc) { + $assocs = new Collection($this->_items); + $assocs = $assocs->filter(function ($assoc) use ($entity, $options) { if ($assoc->cascadeCallbacks()) { $assoc->cascadeDelete($entity, $options); + return false; } - } - foreach ($this->_items as $assoc) { - if (!$assoc->cascadeCallbacks()) { - $assoc->cascadeDelete($entity, $options); - } + return true; + })->toArray(); + foreach ($assocs as $assoc) { + $assoc->cascadeDelete($entity, $options); } } From 24a31baa26c1dac0fc7059530068efb8c38bed88 Mon Sep 17 00:00:00 2001 From: PGBarrow Date: Tue, 14 Apr 2015 15:19:36 -0700 Subject: [PATCH 0330/2059] Coding standards applied --- AssociationCollection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AssociationCollection.php b/AssociationCollection.php index e8a8dbdc..450826e3 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -14,11 +14,11 @@ */ namespace Cake\ORM; +use Cake\Collection\Collection; use Cake\ORM\Association; use Cake\ORM\AssociationsNormalizerTrait; use Cake\ORM\Entity; use Cake\ORM\Table; -use Cake\Collection\Collection; use InvalidArgumentException; /** From f384c064598fc8218e0bccc8a20e8afaa807ecf6 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 14 Apr 2015 22:31:22 -0400 Subject: [PATCH 0331/2059] Don't assume that queries always contain fields Not all queries will have fields from the original table. In the cases they do not, there shouldn't be any notice errors. Refs #6326 --- ResultSet.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ResultSet.php b/ResultSet.php index 68254a8b..dff7ca9a 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -586,7 +586,9 @@ protected function _groupResult($row) } $options['source'] = $this->_defaultTable->registryAlias(); - $results = $results[$defaultAlias]; + if (isset($results[$defaultAlias])) { + $results = $results[$defaultAlias]; + } if ($this->_hydrate && !($results instanceof Entity)) { $results = new $this->_entityClass($results, $options); } From 6149440b5ac927a4d53be632cd717816ffb131f2 Mon Sep 17 00:00:00 2001 From: PGBarrow Date: Wed, 15 Apr 2015 11:45:54 -0700 Subject: [PATCH 0332/2059] cascade callback bug for aliased associations --- Association/BelongsToMany.php | 10 +++++----- Association/ExternalAssociationTrait.php | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 1d28068b..8bce84c0 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1002,12 +1002,12 @@ protected function _junctionTableName($name = null) { if ($name === null) { if (empty($this->_junctionTableName)) { - $aliases = array_map('\Cake\Utility\Inflector::underscore', [ - $this->source()->alias(), - $this->target()->alias() + $tablesNames = array_map('\Cake\Utility\Inflector::underscore', [ + $this->source()->table(), + $this->target()->table() ]); - sort($aliases); - $this->_junctionTableName = implode('_', $aliases); + sort($tablesNames); + $this->_junctionTableName = implode('_', $tablesNames); } return $this->_junctionTableName; } diff --git a/Association/ExternalAssociationTrait.php b/Association/ExternalAssociationTrait.php index 466b20cc..d25649ae 100644 --- a/Association/ExternalAssociationTrait.php +++ b/Association/ExternalAssociationTrait.php @@ -57,7 +57,7 @@ public function foreignKey($key = null) { if ($key === null) { if ($this->_foreignKey === null) { - $this->_foreignKey = $this->_modelKey($this->source()->alias()); + $this->_foreignKey = $this->_modelKey($this->source()->table()); } return $this->_foreignKey; } From bf810ba59026e0c2a701138069788873169e813c Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 17 Apr 2015 20:34:41 -0400 Subject: [PATCH 0333/2059] Remove additional test stubs that are not required. Remove code that can easily be replicated in the test method. Fix doc blocks in fixtures. --- AssociationCollection.php | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/AssociationCollection.php b/AssociationCollection.php index 450826e3..ff400e3d 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -14,7 +14,6 @@ */ namespace Cake\ORM; -use Cake\Collection\Collection; use Cake\ORM\Association; use Cake\ORM\AssociationsNormalizerTrait; use Cake\ORM\Entity; @@ -260,15 +259,15 @@ protected function _save($association, $entity, $nested, $options) */ public function cascadeDelete(Entity $entity, array $options) { - $assocs = new Collection($this->_items); - $assocs = $assocs->filter(function ($assoc) use ($entity, $options) { - if ($assoc->cascadeCallbacks()) { - $assoc->cascadeDelete($entity, $options); - return false; + $noCascade = []; + foreach ($this->_items as $assoc) { + if (!$assoc->cascadeCallbacks()) { + $noCascade[] = $assoc; + continue; } - return true; - })->toArray(); - foreach ($assocs as $assoc) { + $assoc->cascadeDelete($entity, $options); + } + foreach ($noCascade as $assoc) { $assoc->cascadeDelete($entity, $options); } } From e5fff6a1f8651f6e7c1d7da6285d945343e99009 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 17 Apr 2015 21:58:59 -0400 Subject: [PATCH 0334/2059] Extract validator logic into a trait. Having these methods in a trait will let us not copy + paste code into the elastic search plugin. It could also be useful for other datasource plugins that need validation features. --- Table.php | 81 ++----------------------------------------------------- 1 file changed, 2 insertions(+), 79 deletions(-) diff --git a/Table.php b/Table.php index 04981f42..2cd68c7f 100644 --- a/Table.php +++ b/Table.php @@ -37,7 +37,7 @@ use Cake\ORM\RulesChecker; use Cake\ORM\Rule\IsUnique; use Cake\Utility\Inflector; -use Cake\Validation\Validator; +use Cake\Validation\ValidatorAwareTrait; use InvalidArgumentException; use RuntimeException; @@ -121,6 +121,7 @@ class Table implements RepositoryInterface, EventListenerInterface { use EventManagerTrait; + use ValidatorAwareTrait; /** * Name of default validation set. @@ -200,13 +201,6 @@ class Table implements RepositoryInterface, EventListenerInterface */ protected $_registryAlias; - /** - * A list of validation objects indexed by name - * - * @var array - */ - protected $_validators = []; - /** * The domain rules to be applied to entities saved by this table * @@ -1161,77 +1155,6 @@ public function updateAll($fields, $conditions) return $statement->rowCount(); } - /** - * Returns the validation rules tagged with $name. It is possible to have - * multiple different named validation sets, this is useful when you need - * to use varying rules when saving from different routines in your system. - * - * There are two different ways of creating and naming validation sets: by - * creating a new method inside your own Table subclass, or by building - * the validator object yourself and storing it using this method. - * - * For example, if you wish to create a validation set called 'forSubscription', - * you will need to create a method in your Table subclass as follows: - * - * ``` - * public function validationForSubscription($validator) - * { - * return $validator - * ->add('email', 'valid-email', ['rule' => 'email']) - * ->add('password', 'valid', ['rule' => 'notEmpty']) - * ->requirePresence('username'); - * } - * ``` - * - * Otherwise, you can build the object by yourself and store it in the Table object: - * - * ``` - * $validator = new \Cake\Validation\Validator($table); - * $validator - * ->add('email', 'valid-email', ['rule' => 'email']) - * ->add('password', 'valid', ['rule' => 'notEmpty']) - * ->allowEmpty('bio'); - * $table->validator('forSubscription', $validator); - * ``` - * - * You can implement the method in `validationDefault` in your Table subclass - * should you wish to have a validation set that applies in cases where no other - * set is specified. - * - * @param string $name the name of the validation set to return - * @param \Cake\Validation\Validator|null $validator The validator instance to store, - * use null to get a validator. - * @return \Cake\Validation\Validator - */ - public function validator($name = self::DEFAULT_VALIDATOR, Validator $validator = null) - { - if ($validator === null && isset($this->_validators[$name])) { - return $this->_validators[$name]; - } - - if ($validator === null) { - $validator = new Validator(); - $validator = $this->{'validation' . ucfirst($name)}($validator); - $this->dispatchEvent('Model.buildValidator', compact('validator', 'name')); - } - - $validator->provider('table', $this); - return $this->_validators[$name] = $validator; - } - - /** - * Returns the default validator object. Subclasses can override this function - * to add a default validation set to the validator object. - * - * @param \Cake\Validation\Validator $validator The validator that can be modified to - * add some rules to it. - * @return \Cake\Validation\Validator - */ - public function validationDefault(Validator $validator) - { - return $validator; - } - /** * {@inheritDoc} */ From 9d4cac6c97292fa42cf818c370835585353bba3d Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 19 Apr 2015 21:54:39 -0400 Subject: [PATCH 0335/2059] Add docs and constant for defining validator hookups. Document how the trait interacts with the including class, and define a constant so that not all validator aware things get assigned as 'table' to the validators. --- Table.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Table.php b/Table.php index 2cd68c7f..f9a01e16 100644 --- a/Table.php +++ b/Table.php @@ -130,6 +130,13 @@ class Table implements RepositoryInterface, EventListenerInterface */ const DEFAULT_VALIDATOR = 'default'; + /** + * The alias this object is assigned to validators as. + * + * @var string + */ + const VALIDATOR_PROVIDER_NAME = 'table'; + /** * Name of the table as it can be found in the database * From c901959907a1d5e16e6950db67d4579a11d4d036 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 21 Apr 2015 22:04:01 -0400 Subject: [PATCH 0336/2059] Fix error when marshalling an existing association When using a fieldList that includes an association name no errors should be emitted. Refs #6387 --- Marshaller.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index ea9b2793..37db8348 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -437,9 +437,8 @@ public function merge(EntityInterface $entity, array $data, array $options = []) foreach ((array)$options['fieldList'] as $field) { if (array_key_exists($field, $properties)) { $entity->set($field, $properties[$field]); - if ($properties[$field] instanceof EntityInterface && - isset($marshalledAssocs[$field])) { - $entity->dirty($assoc, $properties[$field]->dirty()); + if ($properties[$field] instanceof EntityInterface && isset($marshalledAssocs[$field])) { + $entity->dirty($field, $properties[$field]->dirty()); } } } From 827ab77387cee120a3d254d26925bb18a2145252 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 23 Apr 2015 21:59:01 -0400 Subject: [PATCH 0337/2059] Don't marshall non-array data. Non array data will never turn into entities, so we can skip it. Refs #6271 --- Marshaller.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Marshaller.php b/Marshaller.php index 37db8348..e92f30fd 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -531,6 +531,9 @@ public function mergeMany($entities, array $data, array $options = []) } foreach ((new Collection($indexed))->append($new) as $value) { + if (!is_array($value)) { + continue; + } $output[] = $this->one($value, $options); } From 6ca59234699cf80a2239cd23b28843564ce1eec9 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 23 Apr 2015 22:00:45 -0400 Subject: [PATCH 0338/2059] Skip non array data in many() as well. Refs #6271 --- Marshaller.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Marshaller.php b/Marshaller.php index e92f30fd..e5b50555 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -248,6 +248,9 @@ public function many(array $data, array $options = []) { $output = []; foreach ($data as $record) { + if (!is_array($record)) { + continue; + } $output[] = $this->one($record, $options); } return $output; From 772211749a7e3990174c1c3087d5d50365c700ba Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 24 Apr 2015 21:57:05 -0400 Subject: [PATCH 0339/2059] Fix bound values being dropped when a clone is made. Use the clone hook to clear out a query's state. This also moves most of the logic into `__clone`. I'm not sure why I didn't do this earlier, but it makes sense to try and have `clone` be safe. Refs #6384 --- Query.php | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/Query.php b/Query.php index 4af5d340..8fa1825a 100644 --- a/Query.php +++ b/Query.php @@ -477,16 +477,28 @@ public function applyOptions(array $options) */ public function cleanCopy() { - $query = clone $this; - $query->triggerBeforeFind(); - $query->autoFields(false); - $query->eagerLoader(clone $this->eagerLoader()); - $query->limit(null); - $query->order([], true); - $query->offset(null); - $query->mapReduce(null, null, true); - $query->formatResults(null, true); - return $query; + return clone $this; + } + + /** + * Object clone hook. + * + * Destroys the clones inner iterator and clones the value binder, and eagerloader instances. + * + * @return void + */ + public function __clone() + { + $this->_iterator = null; + $this->triggerBeforeFind(); + $this->eagerLoader(clone $this->eagerLoader()); + $this->valueBinder(clone $this->valueBinder()); + $this->autoFields(false); + $this->limit(null); + $this->order([], true); + $this->offset(null); + $this->mapReduce(null, null, true); + $this->formatResults(null, true); } /** From f760e011e6aca0422bb5e8efe4746f52fca2bfd9 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 28 Apr 2015 00:42:14 +0530 Subject: [PATCH 0340/2059] Avoid selecting all fields if possible when using find('list'). Closes #6186. --- Table.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Table.php b/Table.php index f9a01e16..ec867333 100644 --- a/Table.php +++ b/Table.php @@ -968,6 +968,14 @@ public function findList(Query $query, array $options) trigger_error('Option "idField" is deprecated, use "keyField" instead.', E_USER_WARNING); } + if (!$query->clause('select')) { + $fields = array_merge((array)$options['keyField'], (array)$options['valueField']); + $columns = $this->schema()->columns(); + if (count($fields) === count(array_intersect($fields, $columns))) { + $query->select($fields); + } + } + $options = $this->_setFieldMatchers( $options, ['keyField', 'valueField', 'groupField'] From adf0ef208e6bbd0de38df802dd53ce2aa7e34789 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 28 Apr 2015 02:28:14 +0530 Subject: [PATCH 0341/2059] Account for 'groupField' when auto selecting fields --- Table.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Table.php b/Table.php index ec867333..298aca0a 100644 --- a/Table.php +++ b/Table.php @@ -969,7 +969,11 @@ public function findList(Query $query, array $options) } if (!$query->clause('select')) { - $fields = array_merge((array)$options['keyField'], (array)$options['valueField']); + $fields = array_merge( + (array)$options['keyField'], + (array)$options['valueField'], + (array)$options['groupField'] + ); $columns = $this->schema()->columns(); if (count($fields) === count(array_intersect($fields, $columns))) { $query->select($fields); From e81d1ea1022a5621e2ba21652eedd42d88804e8b Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 28 Apr 2015 02:58:50 +0530 Subject: [PATCH 0342/2059] Don't auto select fields if one of the options is a closure --- Table.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 298aca0a..8886181b 100644 --- a/Table.php +++ b/Table.php @@ -968,7 +968,11 @@ public function findList(Query $query, array $options) trigger_error('Option "idField" is deprecated, use "keyField" instead.', E_USER_WARNING); } - if (!$query->clause('select')) { + if (!$query->clause('select') && + !is_object($options['keyField']) && + !is_object($options['valueField']) && + !is_object($options['groupField']) + ) { $fields = array_merge( (array)$options['keyField'], (array)$options['valueField'], From 684bc56988b60f91b0aaed30b3781a863c3ffaaf Mon Sep 17 00:00:00 2001 From: David Yell Date: Tue, 28 Apr 2015 10:11:44 +0100 Subject: [PATCH 0343/2059] Allow associations to be iterated Added an iterator to the class so that associations can be looped through --- AssociationCollection.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/AssociationCollection.php b/AssociationCollection.php index ff400e3d..36a61905 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -14,11 +14,13 @@ */ namespace Cake\ORM; +use ArrayIterator; use Cake\ORM\Association; use Cake\ORM\AssociationsNormalizerTrait; use Cake\ORM\Entity; use Cake\ORM\Table; use InvalidArgumentException; +use IteratorAggregate; /** * A container/collection for association classes. @@ -26,7 +28,7 @@ * Contains methods for managing associations, and * ordering operations around saving and deleting. */ -class AssociationCollection +class AssociationCollection implements IteratorAggregate { use AssociationsNormalizerTrait; @@ -292,4 +294,14 @@ public function normalizeKeys($keys) return $this->_normalizeAssociations($keys); } + + /** + * Allow looping through the associations + * + * @return \Cake\ORM\Association + */ + public function getIterator() + { + return new ArrayIterator($this->_items); + } } From 1b79183ceb951268c410d664dc6af953cbfc431b Mon Sep 17 00:00:00 2001 From: David Yell Date: Tue, 28 Apr 2015 11:29:51 +0100 Subject: [PATCH 0344/2059] Corrected the return type in the docblock and added a test --- AssociationCollection.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/AssociationCollection.php b/AssociationCollection.php index 36a61905..f793cd12 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -294,11 +294,11 @@ public function normalizeKeys($keys) return $this->_normalizeAssociations($keys); } - + /** * Allow looping through the associations - * - * @return \Cake\ORM\Association + * + * @return ArrayIterator */ public function getIterator() { From ffbdeb459f6f886d5b646c1a4e66c1e2bd32fcf9 Mon Sep 17 00:00:00 2001 From: Stew Eucen Date: Fri, 1 May 2015 15:04:31 +0900 Subject: [PATCH 0345/2059] Bugfix about unused param in Query::update() --- Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Query.php b/Query.php index 8fa1825a..c830e655 100644 --- a/Query.php +++ b/Query.php @@ -749,7 +749,7 @@ protected function _dirty() */ public function update($table = null) { - $table = $this->repository()->table(); + $table = $table ?: $this->repository()->table(); return parent::update($table); } From 526582a57a3170bdd99287df5889a1d177d4ec0b Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 2 May 2015 23:20:09 -0400 Subject: [PATCH 0346/2059] Make extractOriginal() consistent with getOriginal(). Make the two methods consistent. Introduce a new method for getting the original value of properties that have been modified. Refs #6467 --- Behavior/CounterCacheBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index ba0f4805..4c952307 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -138,7 +138,7 @@ protected function _processAssociation(Event $event, Entity $entity, Association $countConditions = $entity->extract($foreignKeys); $updateConditions = array_combine($primaryKeys, $countConditions); - $countOriginalConditions = $entity->extractOriginal($foreignKeys); + $countOriginalConditions = $entity->extractOriginalDirty($foreignKeys); if ($countOriginalConditions !== []) { $updateOriginalConditions = array_combine($primaryKeys, $countOriginalConditions); } From d7d0e8697dc03de525d77fb8c7457efeabffa40d Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 27 Apr 2015 10:22:39 +0200 Subject: [PATCH 0347/2059] Make Associations aware of table locators. --- Association.php | 32 ++++++++++++++++++++++++++++++-- Association/BelongsToMany.php | 7 +++---- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/Association.php b/Association.php index fc03d2a0..d09eddfe 100644 --- a/Association.php +++ b/Association.php @@ -18,6 +18,7 @@ use Cake\Database\Expression\IdentifierExpression; use Cake\Datasource\EntityInterface; use Cake\Datasource\ResultSetDecorator; +use Cake\ORM\Locator\LocatorInterface; use Cake\ORM\Query; use Cake\ORM\Table; use Cake\ORM\TableRegistry; @@ -144,6 +145,13 @@ abstract class Association */ protected $_targetTable; + /** + * Table locator instance + * + * @var \Cake\ORM\Locator\LocatorInterface + */ + protected $_locator; + /** * The type of join to be used when adding the association to a query * @@ -198,6 +206,7 @@ public function __construct($alias, array $options = []) 'finder', 'foreignKey', 'joinType', + 'locator', 'propertyName', 'sourceTable', 'targetTable' @@ -215,6 +224,10 @@ public function __construct($alias, array $options = []) list(, $name) = pluginSplit($alias); $this->_name = $name; + if (!$this->_locator) { + $this->_locator =& TableRegistry::locator(); + } + $this->_options($options); if (!empty($options['strategy'])) { @@ -292,10 +305,10 @@ public function target(Table $table = null) } $config = []; - if (!TableRegistry::exists($registryAlias)) { + if (!$this->_locator->exists($registryAlias)) { $config = ['className' => $this->_className]; } - $this->_targetTable = TableRegistry::get($registryAlias, $config); + $this->_targetTable = $this->_locator->get($registryAlias, $config); return $this->_targetTable; } @@ -436,6 +449,21 @@ public function finder($finder = null) return $this->_finder; } + /** + * Sets the table locator for this association. + * If no parameters are passed, it will return the currently used locator. + * + * @param LocatorInterface|null $locator LocatorInterface instance. + * @return LocatorInterface + */ + public function locator(LocatorInterface $locator = null) + { + if ($locator !== null) { + $this->_locator = $locator; + } + return $this->_locator; + } + /** * Override this function to initialize any concrete association class, it will * get passed the original list of options used in the constructor diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 1d28068b..5bbcdf94 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -18,7 +18,6 @@ use Cake\ORM\Association; use Cake\ORM\Query; use Cake\ORM\Table; -use Cake\ORM\TableRegistry; use Cake\Utility\Inflector; use InvalidArgumentException; use RuntimeException; @@ -177,15 +176,15 @@ public function junction($table = null) $tableAlias = Inflector::camelize($tableName); $config = []; - if (!TableRegistry::exists($tableAlias)) { + if (!$this->_locator->exists($tableAlias)) { $config = ['table' => $tableName]; } - $table = TableRegistry::get($tableAlias, $config); + $table = $this->_locator->get($tableAlias, $config); } } if (is_string($table)) { - $table = TableRegistry::get($table); + $table = $this->_locator->get($table); } $junctionAlias = $table->alias(); From f332e55eee6499865e61981064fe0a23cbd87a44 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 27 Apr 2015 10:48:11 +0200 Subject: [PATCH 0348/2059] Move LocatorInterface accessor/mutator to LocatorAwareTrait and use it in Association. --- Association.php | 33 +++-------------------- Association/BelongsToMany.php | 6 ++--- Locator/LocatorAwareTrait.php | 50 +++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 32 deletions(-) create mode 100644 Locator/LocatorAwareTrait.php diff --git a/Association.php b/Association.php index d09eddfe..9dd85a4a 100644 --- a/Association.php +++ b/Association.php @@ -18,7 +18,7 @@ use Cake\Database\Expression\IdentifierExpression; use Cake\Datasource\EntityInterface; use Cake\Datasource\ResultSetDecorator; -use Cake\ORM\Locator\LocatorInterface; +use Cake\ORM\Locator\LocatorAwareTrait; use Cake\ORM\Query; use Cake\ORM\Table; use Cake\ORM\TableRegistry; @@ -35,6 +35,7 @@ abstract class Association { use ConventionsTrait; + use LocatorAwareTrait; /** * Strategy name to use joins for fetching associated records @@ -145,13 +146,6 @@ abstract class Association */ protected $_targetTable; - /** - * Table locator instance - * - * @var \Cake\ORM\Locator\LocatorInterface - */ - protected $_locator; - /** * The type of join to be used when adding the association to a query * @@ -224,10 +218,6 @@ public function __construct($alias, array $options = []) list(, $name) = pluginSplit($alias); $this->_name = $name; - if (!$this->_locator) { - $this->_locator =& TableRegistry::locator(); - } - $this->_options($options); if (!empty($options['strategy'])) { @@ -305,10 +295,10 @@ public function target(Table $table = null) } $config = []; - if (!$this->_locator->exists($registryAlias)) { + if (!$this->locator()->exists($registryAlias)) { $config = ['className' => $this->_className]; } - $this->_targetTable = $this->_locator->get($registryAlias, $config); + $this->_targetTable = $this->locator()->get($registryAlias, $config); return $this->_targetTable; } @@ -449,21 +439,6 @@ public function finder($finder = null) return $this->_finder; } - /** - * Sets the table locator for this association. - * If no parameters are passed, it will return the currently used locator. - * - * @param LocatorInterface|null $locator LocatorInterface instance. - * @return LocatorInterface - */ - public function locator(LocatorInterface $locator = null) - { - if ($locator !== null) { - $this->_locator = $locator; - } - return $this->_locator; - } - /** * Override this function to initialize any concrete association class, it will * get passed the original list of options used in the constructor diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 5bbcdf94..46b57456 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -176,15 +176,15 @@ public function junction($table = null) $tableAlias = Inflector::camelize($tableName); $config = []; - if (!$this->_locator->exists($tableAlias)) { + if (!$this->locator()->exists($tableAlias)) { $config = ['table' => $tableName]; } - $table = $this->_locator->get($tableAlias, $config); + $table = $this->locator()->get($tableAlias, $config); } } if (is_string($table)) { - $table = $this->_locator->get($table); + $table = $this->locator()->get($table); } $junctionAlias = $table->alias(); diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php new file mode 100644 index 00000000..791070b7 --- /dev/null +++ b/Locator/LocatorAwareTrait.php @@ -0,0 +1,50 @@ +_locator = $locator; + } + if (!$this->_locator) { + $this->_locator = TableRegistry::locator(); + } + return $this->_locator; + } +} From 2c1f73dc28faf9312b4152221d0b43c810d26b86 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 27 Apr 2015 11:09:14 +0200 Subject: [PATCH 0349/2059] Use LocatorAwareTrait in TranslateBehavior --- Behavior/TranslateBehavior.php | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index ad9fe203..ac9d204b 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -20,9 +20,9 @@ use Cake\I18n\I18n; use Cake\ORM\Behavior; use Cake\ORM\Entity; +use Cake\ORM\Locator\LocatorAwareTrait; use Cake\ORM\Query; use Cake\ORM\Table; -use Cake\ORM\TableRegistry; use Cake\Utility\Inflector; /** @@ -40,6 +40,8 @@ class TranslateBehavior extends Behavior { + use LocatorAwareTrait; + /** * Table instance * @@ -78,7 +80,8 @@ class TranslateBehavior extends Behavior 'referenceName' => '', 'allowEmptyTranslations' => true, 'onlyTranslated' => false, - 'strategy' => 'subquery' + 'strategy' => 'subquery', + 'locator' => null ]; /** @@ -93,6 +96,11 @@ public function __construct(Table $table, array $config = []) 'defaultLocale' => I18n::defaultLocale(), 'referenceName' => $this->_referenceName($table) ]; + + if (isset($config['locator'])) { + $this->_locator = $config['locator']; + } + parent::__construct($table, $config); } @@ -104,7 +112,7 @@ public function __construct(Table $table, array $config = []) */ public function initialize(array $config) { - $this->_translationTable = TableRegistry::get($this->_config['translationTable']); + $this->_translationTable = $this->locator()->get($this->_config['translationTable']); $this->setupFieldAssociations( $this->_config['fields'], @@ -137,14 +145,14 @@ public function setupFieldAssociations($fields, $table, $model, $strategy) foreach ($fields as $field) { $name = $alias . '_' . $field . '_translation'; - if (!TableRegistry::exists($name)) { - $fieldTable = TableRegistry::get($name, [ + if (!$this->locator()->exists($name)) { + $fieldTable = $this->locator()->get($name, [ 'className' => $table, 'alias' => $name, 'table' => $this->_translationTable->table() ]); } else { - $fieldTable = TableRegistry::get($name); + $fieldTable = $this->locator()->get($name); } $conditions = [ From 10ab8830aae7c6e89acc630663491067b83f3332 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 27 Apr 2015 11:18:34 +0200 Subject: [PATCH 0350/2059] Assign default locator by reference so it could follow instances swapped in TableRegistry facade. --- Locator/LocatorAwareTrait.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index 791070b7..de130a9e 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -43,7 +43,8 @@ public function locator(LocatorInterface $locator = null) $this->_locator = $locator; } if (!$this->_locator) { - $this->_locator = TableRegistry::locator(); + $locator = TableRegistry::locator(); + $this->_locator =& $locator; } return $this->_locator; } From 45e53ce60b2a361050a0cdab26df5b2b6a314e82 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 27 Apr 2015 11:49:31 +0200 Subject: [PATCH 0351/2059] Remove reference. --- Locator/LocatorAwareTrait.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index de130a9e..791070b7 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -43,8 +43,7 @@ public function locator(LocatorInterface $locator = null) $this->_locator = $locator; } if (!$this->_locator) { - $locator = TableRegistry::locator(); - $this->_locator =& $locator; + $this->_locator = TableRegistry::locator(); } return $this->_locator; } From e017aa7141736712bbd19a6bea9730ff3c56e047 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 27 Apr 2015 13:37:07 +0200 Subject: [PATCH 0352/2059] Use fully namespaced params --- Locator/LocatorAwareTrait.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index 791070b7..88b8764a 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -34,8 +34,8 @@ trait LocatorAwareTrait * Sets the table locator. * If no parameters are passed, it will return the currently used locator. * - * @param LocatorInterface|null $locator LocatorInterface instance. - * @return LocatorInterface + * @param \Cake\ORM\Locator\LocatorInterface|null $locator LocatorInterface instance. + * @return \Cake\ORM\Locator\LocatorInterface */ public function locator(LocatorInterface $locator = null) { From c3d3af199cdf6a86818ead68d6fd597ba2118e6d Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 27 Apr 2015 13:37:56 +0200 Subject: [PATCH 0353/2059] Remove use statement. --- Locator/LocatorAwareTrait.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index 88b8764a..c3a0d3b0 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -14,7 +14,6 @@ */ namespace Cake\ORM\Locator; -use Cake\ORM\Locator\LocatorInterface; use Cake\ORM\TableRegistry; /** From 963dd97e2d34026f9fc6464fb6f8ca647e062f34 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 27 Apr 2015 14:40:58 +0200 Subject: [PATCH 0354/2059] Rename locator() to tableLocator(). --- Association.php | 6 +++--- Association/BelongsToMany.php | 6 +++--- Behavior/TranslateBehavior.php | 14 +++++++------- Locator/LocatorAwareTrait.php | 16 ++++++++-------- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Association.php b/Association.php index 9dd85a4a..3b958c97 100644 --- a/Association.php +++ b/Association.php @@ -200,7 +200,7 @@ public function __construct($alias, array $options = []) 'finder', 'foreignKey', 'joinType', - 'locator', + 'tableLocator', 'propertyName', 'sourceTable', 'targetTable' @@ -295,10 +295,10 @@ public function target(Table $table = null) } $config = []; - if (!$this->locator()->exists($registryAlias)) { + if (!$this->tableLocator()->exists($registryAlias)) { $config = ['className' => $this->_className]; } - $this->_targetTable = $this->locator()->get($registryAlias, $config); + $this->_targetTable = $this->tableLocator()->get($registryAlias, $config); return $this->_targetTable; } diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 46b57456..b89d9e74 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -176,15 +176,15 @@ public function junction($table = null) $tableAlias = Inflector::camelize($tableName); $config = []; - if (!$this->locator()->exists($tableAlias)) { + if (!$this->tableLocator()->exists($tableAlias)) { $config = ['table' => $tableName]; } - $table = $this->locator()->get($tableAlias, $config); + $table = $this->tableLocator()->get($tableAlias, $config); } } if (is_string($table)) { - $table = $this->locator()->get($table); + $table = $this->tableLocator()->get($table); } $junctionAlias = $table->alias(); diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index ac9d204b..36f4560a 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -81,7 +81,7 @@ class TranslateBehavior extends Behavior 'allowEmptyTranslations' => true, 'onlyTranslated' => false, 'strategy' => 'subquery', - 'locator' => null + 'tableLocator' => null ]; /** @@ -97,8 +97,8 @@ public function __construct(Table $table, array $config = []) 'referenceName' => $this->_referenceName($table) ]; - if (isset($config['locator'])) { - $this->_locator = $config['locator']; + if (isset($config['tableLocator'])) { + $this->_tableLocator = $config['tableLocator']; } parent::__construct($table, $config); @@ -112,7 +112,7 @@ public function __construct(Table $table, array $config = []) */ public function initialize(array $config) { - $this->_translationTable = $this->locator()->get($this->_config['translationTable']); + $this->_translationTable = $this->tableLocator()->get($this->_config['translationTable']); $this->setupFieldAssociations( $this->_config['fields'], @@ -145,14 +145,14 @@ public function setupFieldAssociations($fields, $table, $model, $strategy) foreach ($fields as $field) { $name = $alias . '_' . $field . '_translation'; - if (!$this->locator()->exists($name)) { - $fieldTable = $this->locator()->get($name, [ + if (!$this->tableLocator()->exists($name)) { + $fieldTable = $this->tableLocator()->get($name, [ 'className' => $table, 'alias' => $name, 'table' => $this->_translationTable->table() ]); } else { - $fieldTable = $this->locator()->get($name); + $fieldTable = $this->tableLocator()->get($name); } $conditions = [ diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index c3a0d3b0..7a2fdd03 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -27,23 +27,23 @@ trait LocatorAwareTrait * * @var \Cake\ORM\Locator\LocatorInterface */ - protected $_locator; + protected $_tableLocator; /** * Sets the table locator. * If no parameters are passed, it will return the currently used locator. * - * @param \Cake\ORM\Locator\LocatorInterface|null $locator LocatorInterface instance. + * @param \Cake\ORM\Locator\LocatorInterface|null $tableLocator LocatorInterface instance. * @return \Cake\ORM\Locator\LocatorInterface */ - public function locator(LocatorInterface $locator = null) + public function tableLocator(LocatorInterface $tableLocator = null) { - if ($locator !== null) { - $this->_locator = $locator; + if ($tableLocator !== null) { + $this->_tableLocator = $tableLocator; } - if (!$this->_locator) { - $this->_locator = TableRegistry::locator(); + if (!$this->_tableLocator) { + $this->_tableLocator = TableRegistry::locator(); } - return $this->_locator; + return $this->_tableLocator; } } From 50148fe39628a5aa065bf5b1b5c5b48cd6b72e65 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 27 Apr 2015 14:41:22 +0200 Subject: [PATCH 0355/2059] Remove unnessesary TableRegistry use statement. --- Association.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Association.php b/Association.php index 3b958c97..17e1040e 100644 --- a/Association.php +++ b/Association.php @@ -21,7 +21,6 @@ use Cake\ORM\Locator\LocatorAwareTrait; use Cake\ORM\Query; use Cake\ORM\Table; -use Cake\ORM\TableRegistry; use Cake\Utility\Inflector; use InvalidArgumentException; use RuntimeException; From 1c35247e224bd81ac50ade111e26b6eb4d238111 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 5 May 2015 09:04:40 +0200 Subject: [PATCH 0356/2059] Copy tableLocator() to local variable. --- Association.php | 6 ++++-- Association/BelongsToMany.php | 7 ++++--- Behavior/TranslateBehavior.php | 7 ++++--- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Association.php b/Association.php index 17e1040e..6362a975 100644 --- a/Association.php +++ b/Association.php @@ -293,11 +293,13 @@ public function target(Table $table = null) $registryAlias = $this->_name; } + $tableLocator = $this->tableLocator(); + $config = []; - if (!$this->tableLocator()->exists($registryAlias)) { + if (!$tableLocator->exists($registryAlias)) { $config = ['className' => $this->_className]; } - $this->_targetTable = $this->tableLocator()->get($registryAlias, $config); + $this->_targetTable = $tableLocator->get($registryAlias, $config); return $this->_targetTable; } diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index b89d9e74..d147cb6e 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -163,6 +163,7 @@ public function junction($table = null) $source = $this->source(); $sAlias = $source->alias(); $tAlias = $target->alias(); + $tableLocator = $this->tableLocator(); if ($table === null) { if (!empty($this->_junctionTable)) { @@ -176,15 +177,15 @@ public function junction($table = null) $tableAlias = Inflector::camelize($tableName); $config = []; - if (!$this->tableLocator()->exists($tableAlias)) { + if (!$tableLocator->exists($tableAlias)) { $config = ['table' => $tableName]; } - $table = $this->tableLocator()->get($tableAlias, $config); + $table = $tableLocator->get($tableAlias, $config); } } if (is_string($table)) { - $table = $this->tableLocator()->get($table); + $table = $tableLocator->get($table); } $junctionAlias = $table->alias(); diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 36f4560a..a9629dae 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -141,18 +141,19 @@ public function setupFieldAssociations($fields, $table, $model, $strategy) $targetAlias = $this->_translationTable->alias(); $alias = $this->_table->alias(); $filter = $this->_config['onlyTranslated']; + $tableLocator = $this->tableLocator(); foreach ($fields as $field) { $name = $alias . '_' . $field . '_translation'; - if (!$this->tableLocator()->exists($name)) { - $fieldTable = $this->tableLocator()->get($name, [ + if (!$tableLocator->exists($name)) { + $fieldTable = $tableLocator->get($name, [ 'className' => $table, 'alias' => $name, 'table' => $this->_translationTable->table() ]); } else { - $fieldTable = $this->tableLocator()->get($name); + $fieldTable = $tableLocator->get($name); } $conditions = [ From 5e250d5bc5ea191f5f498e9617091411eb3bbe83 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 5 May 2015 22:03:42 -0400 Subject: [PATCH 0357/2059] Rename method. I think extractOriginalChanged() makes more sense than OriginalDirty did. I considered calling the method extractUntouched() at Lorenzo's suggestion, but the method gets properties that have been touched. Refs #6467 --- Behavior/CounterCacheBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 4c952307..0d1c4b10 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -138,7 +138,7 @@ protected function _processAssociation(Event $event, Entity $entity, Association $countConditions = $entity->extract($foreignKeys); $updateConditions = array_combine($primaryKeys, $countConditions); - $countOriginalConditions = $entity->extractOriginalDirty($foreignKeys); + $countOriginalConditions = $entity->extractOriginalChanged($foreignKeys); if ($countOriginalConditions !== []) { $updateOriginalConditions = array_combine($primaryKeys, $countOriginalConditions); } From cca64908313a8f29e35e5d6c16dfe5b6fbfc9d10 Mon Sep 17 00:00:00 2001 From: Cees Vogel Date: Fri, 8 May 2015 14:49:48 +0200 Subject: [PATCH 0358/2059] patch marshaller belongsToMany with mixed _joinData --- Marshaller.php | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index e5b50555..78d4f3cc 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -283,7 +283,7 @@ protected function _belongsToMany(Association $assoc, array $data, $options = [] $primaryKey = array_flip($assoc->target()->schema()->primaryKey()); $records = []; - foreach ($data as $row) { + foreach ($data as $i => $row) { if (array_intersect_key($primaryKey, $row) === $primaryKey) { if (!isset($query)) { $primaryCount = count($primaryKey); @@ -294,12 +294,26 @@ protected function _belongsToMany(Association $assoc, array $data, $options = [] $query->orWhere($keys); } } else { - $records[] = $this->one($row, $options); + $records[$i] = $this->one($row, $options); } } if (isset($query)) { - $records = array_merge($records, $query->toArray()); + $queryData = $query->toArray(); + foreach ($queryData as $queryItem) { + foreach ($data as $i => $row) { + $matchCount = 0; + foreach ($primaryKey as $key => $value) { + if (isset($row[$key]) && $row[$key] == $queryItem->$key) { + $matchCount++; + } + if ($matchCount === count($primaryKey)) { + $records[$i] = $queryItem; + break 2; + } + } + } + } } $joint = $assoc->junction(); From b4272201e8114b4b29636aa03d2a8023b7251687 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 8 May 2015 23:04:39 -0400 Subject: [PATCH 0359/2059] Simplify loops by indexing data in separate loops. By unrolling the nested loops we can have simpler code. I also added another test case to help provide additional case coverage around belongsToMany marshalling. Refs #6512 --- Marshaller.php | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 78d4f3cc..da0684de 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -299,25 +299,29 @@ protected function _belongsToMany(Association $assoc, array $data, $options = [] } if (isset($query)) { - $queryData = $query->toArray(); - foreach ($queryData as $queryItem) { - foreach ($data as $i => $row) { - $matchCount = 0; - foreach ($primaryKey as $key => $value) { - if (isset($row[$key]) && $row[$key] == $queryItem->$key) { - $matchCount++; - } - if ($matchCount === count($primaryKey)) { - $records[$i] = $queryItem; - break 2; - } + $keyFields = array_keys($primaryKey); + + $existing = []; + foreach ($query as $row) { + $k = implode(';', $row->extract($keyFields)); + $existing[$k] = $row; + } + + foreach ($data as $i => $row) { + $key = []; + foreach ($keyFields as $k) { + if (isset($row[$k])) { + $key[] = $row[$k]; } } + $key = implode(';', $key); + if (isset($existing[$key])) { + $records[$i] = $existing[$key]; + } } } - $joint = $assoc->junction(); - $jointMarshaller = $joint->marshaller(); + $jointMarshaller = $assoc->junction()->marshaller(); $nested = []; if (isset($associated['_joinData'])) { From b312488245294a8e55f808c218611e4e77406188 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Mon, 11 May 2015 19:10:09 +0200 Subject: [PATCH 0360/2059] Fixing issue with extra association conditions being applied on the marshaller closes #6534 --- Marshaller.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index da0684de..a663207b 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -282,22 +282,27 @@ protected function _belongsToMany(Association $assoc, array $data, $options = [] $primaryKey = array_flip($assoc->target()->schema()->primaryKey()); $records = []; + $conditions = []; + $primaryCount = count($primaryKey); foreach ($data as $i => $row) { if (array_intersect_key($primaryKey, $row) === $primaryKey) { - if (!isset($query)) { - $primaryCount = count($primaryKey); - $query = $assoc->find(); - } $keys = array_intersect_key($row, $primaryKey); if (count($keys) === $primaryCount) { - $query->orWhere($keys); + $conditions[] = $keys; } } else { $records[$i] = $this->one($row, $options); } } + if (!empty($conditions)) { + $query = $assoc->target()->find(); + $query->andWhere(function ($exp) use ($conditions) { + return $exp->or_($conditions); + }); + } + if (isset($query)) { $keyFields = array_keys($primaryKey); From 805f5a4b0b2192af5ddbbdc70ca04d20b07450f6 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 15 May 2015 21:45:33 -0400 Subject: [PATCH 0361/2059] Skip scalar data in belongsToMany relations. Scalar data shouldn't get converted into an entity, as it is likely to be a mistake/naughty user. Add coverage for other scalar values in both hasMany and belongsToMany associations. Refs #6562 --- Marshaller.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Marshaller.php b/Marshaller.php index a663207b..bf6cc702 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -214,6 +214,9 @@ protected function _prepareDataAndOptions($data, $options) */ protected function _marshalAssociation($assoc, $value, $options) { + if (!is_array($value)) { + return; + } $targetTable = $assoc->target(); $marshaller = $targetTable->marshaller(); $types = [Association::ONE_TO_ONE, Association::MANY_TO_ONE]; @@ -286,6 +289,9 @@ protected function _belongsToMany(Association $assoc, array $data, $options = [] $primaryCount = count($primaryKey); foreach ($data as $i => $row) { + if (!is_array($row)) { + continue; + } if (array_intersect_key($primaryKey, $row) === $primaryKey) { $keys = array_intersect_key($row, $primaryKey); if (count($keys) === $primaryCount) { From 0d6711cd48eee1092a49a8c4baab405b469be64b Mon Sep 17 00:00:00 2001 From: PGBarrow Date: Mon, 18 May 2015 17:13:54 -0700 Subject: [PATCH 0362/2059] Made code DRYER --- Association.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Association.php b/Association.php index fc03d2a0..63267975 100644 --- a/Association.php +++ b/Association.php @@ -629,9 +629,7 @@ public function deleteAll($conditions) */ protected function _dispatchBeforeFind($query) { - $table = $this->target(); - $options = $query->getOptions(); - $table->dispatchEvent('Model.beforeFind', [$query, new \ArrayObject($options), false]); + $query->triggerBeforeFind(); } /** From 0a0cda67fbfb74c2d0d3081e0685e2ba708a3900 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 20 May 2015 01:17:57 +0530 Subject: [PATCH 0363/2059] Reset node level when recovering tree. Closes #6590 --- Behavior/TreeBehavior.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index bf105db8..712d5115 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -741,9 +741,10 @@ public function recover() * * @param int $counter The Last left column value that was assigned * @param mixed $parentId the parent id of the level to be recovered + * @param int $level Node level * @return int The next value to use for the left column */ - protected function _recoverTree($counter = 0, $parentId = null) + protected function _recoverTree($counter = 0, $parentId = null, $level = -1) { $config = $this->config(); list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']]; @@ -756,17 +757,23 @@ protected function _recoverTree($counter = 0, $parentId = null) ->hydrate(false); $leftCounter = $counter; + $nextLevel = $level + 1; foreach ($query as $row) { $counter++; - $counter = $this->_recoverTree($counter, $row[$pk[0]]); + $counter = $this->_recoverTree($counter, $row[$pk[0]], $nextLevel); } if ($parentId === null) { return $counter; } + $fields = [$left => $leftCounter, $right => $counter + 1]; + if ($config['level']) { + $fields[$config['level']] = $level; + } + $this->_table->updateAll( - [$left => $leftCounter, $right => $counter + 1], + $fields, [$pk[0] => $parentId] ); From 18a30c733edf095f5c373100383f2b17623f09b1 Mon Sep 17 00:00:00 2001 From: David Yell Date: Thu, 28 May 2015 10:04:16 +0100 Subject: [PATCH 0364/2059] Updated docBlock Removed the comment about not retaining an instance of the table class, as it is assigned to `$this->_table` --- Behavior.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/Behavior.php b/Behavior.php index ac47e277..6137f599 100644 --- a/Behavior.php +++ b/Behavior.php @@ -144,9 +144,6 @@ class Behavior implements EventListenerInterface * * Merges config with the default and store in the config property * - * Does not retain a reference to the Table object. If you need this - * you should override the constructor. - * * @param \Cake\ORM\Table $table The table this behavior is attached to. * @param array $config The config for this behavior. */ From 1c8807a003129980c07fbd76d5d0df6a9454ca3f Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 29 May 2015 14:14:02 -0400 Subject: [PATCH 0365/2059] Split rules up into ORM and non-ORM flavours. Having a RulesChecker independent of the ORM will be very helpful when implementing rules on the elastic search ODM. This should be fully backwards compatible as the old classes and methods are still around. --- RulesChecker.php | 278 +---------------------------------------------- Table.php | 83 ++------------ 2 files changed, 14 insertions(+), 347 deletions(-) diff --git a/RulesChecker.php b/RulesChecker.php index 77835a98..c84172ba 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -14,288 +14,20 @@ */ namespace Cake\ORM; -use Cake\Datasource\EntityInterface; +use Cake\Datasource\RulesChecker as BaseRulesChecker; use Cake\ORM\Rule\ExistsIn; use Cake\ORM\Rule\IsUnique; -use InvalidArgumentException; /** - * Contains logic for storing and checking rules on entities + * ORM flavoured rules checker. * - * RulesCheckers are used by Table classes to ensure that the - * current entity state satisfies the application logic and business rules. + * Adds ORM related features to the RulesChecker class. * - * RulesCheckers afford different rules to be applied in the create and update - * scenario. - * - * ### Adding rules - * - * Rules must be callable objects that return true/false depending on whether or - * not the rule has been satisfied. You can use RulesChecker::add(), RulesChecker::addCreate(), - * RulesChecker::addUpdate() and RulesChecker::addDelete to add rules to a checker. - * - * ### Running checks - * - * Generally a Table object will invoke the rules objects, but you can manually - * invoke the checks by calling RulesChecker::checkCreate(), RulesChecker::checkUpdate() or - * RulesChecker::checkDelete(). + * @see Cake\Datasource\RulesChecker */ -class RulesChecker +class RulesChecker extends BaseRulesChecker { - /** - * Indicates that the checking rules to apply are those used for creating entities - * - * @var string - */ - const CREATE = 'create'; - - /** - * Indicates that the checking rules to apply are those used for updating entities - * - * @var string - */ - const UPDATE = 'update'; - - /** - * Indicates that the checking rules to apply are those used for deleting entities - * - * @var string - */ - const DELETE = 'delete'; - - /** - * The list of rules to be checked on both create and update operations - * - * @var array - */ - protected $_rules = []; - - /** - * The list of rules to check during create operations - * - * @var array - */ - protected $_createRules = []; - - /** - * The list of rules to check during update operations - * - * @var array - */ - protected $_updateRules = []; - - /** - * The list of rules to check during delete operations - * - * @var array - */ - protected $_deleteRules = []; - - /** - * List of options to pass to every callable rule - * - * @var array - */ - protected $_options = []; - - /** - * Whether or not to use I18n functions for translating default error messages - * - * @var bool - */ - protected $_useI18n = false; - - /** - * Constructor. Takes the options to be passed to all rules. - * - * @param array $options The options to pass to every rule - */ - public function __construct(array $options) - { - $this->_options = $options; - $this->_useI18n = function_exists('__d'); - } - - /** - * Adds a rule that will be applied to the entity both on create and update - * operations. - * - * ### Options - * - * The options array accept the following special keys: - * - * - `errorField`: The name of the entity field that will be marked as invalid - * if the rule does not pass. - * - `message`: The error message to set to `errorField` if the rule does not pass. - * - * @param callable $rule A callable function or object that will return whether - * the entity is valid or not. - * @param string $name The alias for a rule. - * @param array $options List of extra options to pass to the rule callable as - * second argument. - * @return $this - */ - public function add(callable $rule, $name = null, array $options = []) - { - $this->_rules[] = $this->_addError($rule, $name, $options); - return $this; - } - - /** - * Adds a rule that will be applied to the entity on create operations. - * - * ### Options - * - * The options array accept the following special keys: - * - * - `errorField`: The name of the entity field that will be marked as invalid - * if the rule does not pass. - * - `message`: The error message to set to `errorField` if the rule does not pass. - * - * @param callable $rule A callable function or object that will return whether - * the entity is valid or not. - * @param string $name The alias for a rule. - * @param array $options List of extra options to pass to the rule callable as - * second argument. - * @return $this - */ - public function addCreate(callable $rule, $name = null, array $options = []) - { - $this->_createRules[] = $this->_addError($rule, $name, $options); - return $this; - } - - /** - * Adds a rule that will be applied to the entity on update operations. - * - * ### Options - * - * The options array accept the following special keys: - * - * - `errorField`: The name of the entity field that will be marked as invalid - * if the rule does not pass. - * - `message`: The error message to set to `errorField` if the rule does not pass. - * - * @param callable $rule A callable function or object that will return whether - * the entity is valid or not. - * @param string $name The alias for a rule. - * @param array $options List of extra options to pass to the rule callable as - * second argument. - * @return $this - */ - public function addUpdate(callable $rule, $name = null, array $options = []) - { - $this->_updateRules[] = $this->_addError($rule, $name, $options); - return $this; - } - - /** - * Adds a rule that will be applied to the entity on delete operations. - * - * ### Options - * - * The options array accept the following special keys: - * - * - `errorField`: The name of the entity field that will be marked as invalid - * if the rule does not pass. - * - `message`: The error message to set to `errorField` if the rule does not pass. - * - * @param callable $rule A callable function or object that will return whether - * the entity is valid or not. - * @param string $name The alias for a rule. - * @param array $options List of extra options to pass to the rule callable as - * second argument. - * @return $this - */ - public function addDelete(callable $rule, $name = null, array $options = []) - { - $this->_deleteRules[] = $this->_addError($rule, $name, $options); - return $this; - } - - /** - * Runs each of the rules by passing the provided entity and returns true if all - * of them pass. The rules to be applied are depended on the $mode parameter which - * can only be RulesChecker::CREATE, RulesChecker::UPDATE or RulesChecker::DELETE - * - * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. - * @param string $mode Either 'create, 'update' or 'delete'. - * @param array $options Extra options to pass to checker functions. - * @return bool - * @throws \InvalidArgumentException if an invalid mode is passed. - */ - public function check(EntityInterface $entity, $mode, array $options = []) - { - if ($mode === self::CREATE) { - return $this->checkCreate($entity, $options); - } - - if ($mode === self::UPDATE) { - return $this->checkUpdate($entity, $options); - } - - if ($mode === self::DELETE) { - return $this->checkDelete($entity, $options); - } - - throw new InvalidArgumentException('Wrong checking mode: ' . $mode); - } - - /** - * Runs each of the rules by passing the provided entity and returns true if all - * of them pass. The rules selected will be only those specified to be run on 'create' - * - * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. - * @param array $options Extra options to pass to checker functions. - * @return bool - */ - public function checkCreate(EntityInterface $entity, array $options = []) - { - $success = true; - $options = $options + $this->_options; - foreach (array_merge($this->_rules, $this->_createRules) as $rule) { - $success = $rule($entity, $options) && $success; - } - return $success; - } - - /** - * Runs each of the rules by passing the provided entity and returns true if all - * of them pass. The rules selected will be only those specified to be run on 'update' - * - * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. - * @param array $options Extra options to pass to checker functions. - * @return bool - */ - public function checkUpdate(EntityInterface $entity, array $options = []) - { - $success = true; - $options = $options + $this->_options; - foreach (array_merge($this->_rules, $this->_updateRules) as $rule) { - $success = $rule($entity, $options) && $success; - } - return $success; - } - - /** - * Runs each of the rules by passing the provided entity and returns true if all - * of them pass. The rules selected will be only those specified to be run on 'delete' - * - * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. - * @param array $options Extra options to pass to checker functions. - * @return bool - */ - public function checkDelete(EntityInterface $entity, array $options = []) - { - $success = true; - $options = $options + $this->_options; - foreach ($this->_deleteRules as $rule) { - $success = $rule($entity, $options) && $success; - } - return $success; - } - /** * Returns a callable that can be used as a rule for checking the uniqueness of a value * in the table. diff --git a/Table.php b/Table.php index 8886181b..08506783 100644 --- a/Table.php +++ b/Table.php @@ -23,6 +23,7 @@ use Cake\Datasource\EntityInterface; use Cake\Datasource\Exception\InvalidPrimaryKeyException; use Cake\Datasource\RepositoryInterface; +use Cake\Datasource\RulesAwareTrait; use Cake\Event\EventListenerInterface; use Cake\Event\EventManager; use Cake\Event\EventManagerTrait; @@ -121,6 +122,7 @@ class Table implements RepositoryInterface, EventListenerInterface { use EventManagerTrait; + use RulesAwareTrait; use ValidatorAwareTrait; /** @@ -137,6 +139,13 @@ class Table implements RepositoryInterface, EventListenerInterface */ const VALIDATOR_PROVIDER_NAME = 'table'; + /** + * The rules class name that is used. + * + * @var string + */ + const RULES_CLASS = 'Cake\ORM\RulesChecker'; + /** * Name of the table as it can be found in the database * @@ -208,13 +217,6 @@ class Table implements RepositoryInterface, EventListenerInterface */ protected $_registryAlias; - /** - * The domain rules to be applied to entities saved by this table - * - * @var \Cake\ORM\RulesChecker - */ - protected $_rulesChecker; - /** * Initializes a new instance * @@ -2067,73 +2069,6 @@ public function validateUnique($value, array $options, array $context = null) return $rule($entity, ['repository' => $this]); } - /** - * Returns whether or not the passed entity complies with all the rules stored in - * the rules checker. - * - * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. - * @param string $operation The operation being run. Either 'create', 'update' or 'delete'. - * @param \ArrayObject|array $options The options To be passed to the rules. - * @return bool - */ - public function checkRules(EntityInterface $entity, $operation = RulesChecker::CREATE, $options = null) - { - $rules = $this->rulesChecker(); - $options = $options ?: new ArrayObject; - $options = is_array($options) ? new ArrayObject($options) : $options; - - $event = $this->dispatchEvent( - 'Model.beforeRules', - compact('entity', 'options', 'operation') - ); - - if ($event->isStopped()) { - return $event->result; - } - - $result = $rules->check($entity, $operation, $options->getArrayCopy()); - $event = $this->dispatchEvent( - 'Model.afterRules', - compact('entity', 'options', 'result', 'operation') - ); - - if ($event->isStopped()) { - return $event->result; - } - - return $result; - } - - /** - * Returns the rule checker for this table. A rules checker object is used to - * test an entity for validity on rules that may involve complex logic or data that - * needs to be fetched from the database or other sources. - * - * @return \Cake\ORM\RulesChecker - */ - public function rulesChecker() - { - if ($this->_rulesChecker !== null) { - return $this->_rulesChecker; - } - $this->_rulesChecker = $this->buildRules(new RulesChecker(['repository' => $this])); - $this->dispatchEvent('Model.buildRules', ['rules' => $this->_rulesChecker]); - return $this->_rulesChecker; - } - - /** - * Returns rules checker object after modifying the one that was passed. Subclasses - * can override this method in order to initialize the rules to be applied to - * entities saved by this table. - * - * @param \Cake\ORM\RulesChecker $rules The rules object to be modified. - * @return \Cake\ORM\RulesChecker - */ - public function buildRules(RulesChecker $rules) - { - return $rules; - } - /** * Get the Model callbacks this table is interested in. * From 9bc640462b3947dfc5caac972ae42736cecee72e Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 29 May 2015 15:06:40 -0400 Subject: [PATCH 0366/2059] Split out the RulesChecker into a working object. Add some additional tests for the rules checkers as well. --- RulesChecker.php | 39 --------------------------------------- 1 file changed, 39 deletions(-) diff --git a/RulesChecker.php b/RulesChecker.php index c84172ba..09eab09f 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -89,43 +89,4 @@ public function existsIn($field, $table, $message = null) $errorField = is_string($field) ? $field : current($field); return $this->_addError(new ExistsIn($field, $table), '_existsIn', compact('errorField', 'message')); } - - /** - * Utility method for decorating any callable so that if it returns false, the correct - * property in the entity is marked as invalid. - * - * @param callable $rule The rule to decorate - * @param string $name The alias for a rule. - * @param array $options The options containing the error message and field - * @return callable - */ - protected function _addError($rule, $name, $options) - { - if (is_array($name)) { - $options = $name; - $name = null; - } - - return function ($entity, $scope) use ($rule, $name, $options) { - $pass = $rule($entity, $options + $scope); - if ($pass === true || empty($options['errorField'])) { - return $pass === true; - } - - $message = 'invalid'; - if (isset($options['message'])) { - $message = $options['message']; - } - if (is_string($pass)) { - $message = $pass; - } - if ($name) { - $message = [$name => $message]; - } else { - $message = [$message]; - } - $entity->errors($options['errorField'], $message); - return $pass === true; - }; - } } From 3ee18be919c465e4be1775025f2afe9a29f21eb5 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 30 May 2015 17:09:04 -0400 Subject: [PATCH 0367/2059] Add an interface for EventDispatcher. Having an interface will let us not duck type on the method and instead use `instanceof`. It also lets us make typehints elsewhere in CakePHP if we need to. Refs #6689 --- Table.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 08506783..8aa9f4eb 100644 --- a/Table.php +++ b/Table.php @@ -24,6 +24,7 @@ use Cake\Datasource\Exception\InvalidPrimaryKeyException; use Cake\Datasource\RepositoryInterface; use Cake\Datasource\RulesAwareTrait; +use Cake\Event\EventDispatcherInterface; use Cake\Event\EventListenerInterface; use Cake\Event\EventManager; use Cake\Event\EventManagerTrait; @@ -118,7 +119,7 @@ * * @see \Cake\Event\EventManager for reference on the events system. */ -class Table implements RepositoryInterface, EventListenerInterface +class Table implements RepositoryInterface, EventListenerInterface, EventDispatcherInterface { use EventManagerTrait; From dc66405c0aa2cf9014cb6e577d9d4c25229fd407 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 31 May 2015 09:29:25 -0400 Subject: [PATCH 0368/2059] Fix backwards compatibility for typehints. In refactoring the rules checker trait I broke typehints around buildRules(). This adds a compatible implementation that won't trigger strict errors. --- Table.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Table.php b/Table.php index 8aa9f4eb..6339e113 100644 --- a/Table.php +++ b/Table.php @@ -2106,6 +2106,17 @@ public function implementedEvents() return $events; } + /** + * {@inheritDoc} + * + * @param \Cake\ORM\RulesChecker $rules The rules object to be modified. + * @return \Cake\ORM\RulesChecker + */ + public function buildRules(RulesChecker $rules) + { + return $rules; + } + /** * Returns an array that can be used to describe the internal state of this * object. From c07a272fa0b3760118b025aee5cb4c510c663d2f Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 1 Jun 2015 14:05:02 -0400 Subject: [PATCH 0369/2059] Improve API docs for a few table methods. I read through the API docs for ORM\Table. I felt that these methods needed a bit more detail than they currrently had. --- Table.php | 96 ++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 81 insertions(+), 15 deletions(-) diff --git a/Table.php b/Table.php index 6339e113..a1cbc861 100644 --- a/Table.php +++ b/Table.php @@ -626,7 +626,7 @@ public function removeBehavior($name) /** * Returns the behavior registry for this table. * - * @return \Cake\ORM\BehaviorRegistry + * @return \Cake\ORM\BehaviorRegistry The BehaviorRegistry instance. */ public function behaviors() { @@ -637,7 +637,7 @@ public function behaviors() * Check if a behavior with the given alias has been loaded. * * @param string $name The behavior alias to check. - * @return bool + * @return bool Whether or not the behavior exists. */ public function hasBehavior($name) { @@ -647,8 +647,8 @@ public function hasBehavior($name) /** * Returns an association object configured for the specified alias if any * - * @param string $name the alias used for the association - * @return \Cake\ORM\Association + * @param string $name the alias used for the association. + * @return \Cake\ORM\Association|null Either the association or null. */ public function association($name) { @@ -658,7 +658,7 @@ public function association($name) /** * Get the associations collection for this table. * - * @return \Cake\ORM\AssociationCollection + * @return \Cake\ORM\AssociationCollection The collection of association objects. */ public function associations() { @@ -867,6 +867,11 @@ public function belongsToMany($associated, array $options = []) /** * {@inheritDoc} * + * ### Model.beforeFind event + * + * Each find() will trigger a `Model.beforeFind` event for all attached + * listeners. Any listener can set a valid result set using $query + * * By default, `$options` will recognize the following keys: * * - fields @@ -879,7 +884,40 @@ public function belongsToMany($associated, array $options = []) * - having * - contain * - join - * @return \Cake\ORM\Query + * + * ### Usage + * + * Using the options array: + * + * ``` + * $query = $articles->find('all', [ + * 'conditions' => ['published' => 1], + * 'limit' => 10, + * 'contain' => ['Users', 'Comments'] + * ]); + * ``` + * + * Using the builder interface: + * + * ``` + * $query = $articles->find() + * ->where(['published' => 1]) + * ->limit(10) + * ->contain(['Users', 'Comments']); + * ``` + * + * ### Calling finders + * + * The find() method is the entry point for custom finder methods. + * You can invoke a finder by specifying the type: + * + * ``` + * $query = $articles->find('published'); + * ``` + * + * Would invoke the `findPublished` method. + * + * @return \Cake\ORM\Query The query builder */ public function find($type = 'all', $options = []) { @@ -889,11 +927,14 @@ public function find($type = 'all', $options = []) } /** - * Returns the query as passed + * Returns the query as passed. + * + * By default findAll() applies no conditions, you + * can override this method in subclasses to modify how `find('all')` works. * * @param \Cake\ORM\Query $query The query to find with * @param array $options The options to use for the find - * @return \Cake\ORM\Query + * @return \Cake\ORM\Query The query builder */ public function findAll(Query $query, array $options) { @@ -955,7 +996,7 @@ public function findAll(Query $query, array $options) * * @param \Cake\ORM\Query $query The query to find with * @param array $options The options for the find - * @return \Cake\ORM\Query + * @return \Cake\ORM\Query The query builder */ public function findList(Query $query, array $options) { @@ -1022,7 +1063,7 @@ public function findList(Query $query, array $options) * * @param \Cake\ORM\Query $query The query to find with * @param array $options The options to find with - * @return \Cake\ORM\Query + * @return \Cake\ORM\Query The qury builder */ public function findThreaded(Query $query, array $options) { @@ -1085,6 +1126,14 @@ protected function _setFieldMatchers($options, $keys) /** * {@inheritDoc} * + * ### Usage + * + * Get an article and some relationships: + * + * ``` + * $article = $articles->get(1, ['contain' => ['Users', 'Comments']]); + * ``` + * * @throws \Cake\Datasource\Exception\InvalidPrimaryKeyException When $primaryKey has an * incorrect number of elements. */ @@ -1134,9 +1183,13 @@ public function get($primaryKey, $options = []) * Finds an existing record or creates a new one. * * Using the attributes defined in $search a find() will be done to locate - * an existing record. If that record exists it will be returned. If it does - * not exist, a new entity will be created with the $search properties, and - * the $defaults. When a new entity is created, it will be saved. + * an existing record. If records matches the conditions, the first record + * will be returned. + * + * If no record can be found, a new entity will be created + * with the $search properties. If a callback is provided, it will be + * called allowing you to define additional default values. The new + * entity will be saved and returned. * * @param array $search The criteria to find existing records by. * @param callable|null $callback A callback that will be invoked for newly @@ -1214,7 +1267,7 @@ public function exists($conditions) * * ### Options * - * The options array can receive the following keys: + * The options array accepts the following keys: * * - atomic: Whether to execute the save and callbacks inside a database * transaction (default: true) @@ -1237,7 +1290,7 @@ public function exists($conditions) * - Model.beforeRules: Will be triggered right before any rule checking is done * for the passed entity if the `checkRules` key in $options is not set to false. * Listeners will receive as arguments the entity, options array and the operation type. - * If the event is stopped the checking result will be set to the result of the event itself. + * If the event is stopped the rules check result will be set to the result of the event itself. * - Model.afterRules: Will be triggered right after the `checkRules()` method is * called for the entity. Listeners will receive as arguments the entity, * options array, the result of checking the rules and the operation type. @@ -2079,6 +2132,19 @@ public function validateUnique($value, array $options, array $context = null) * Override this method if you need to add non-conventional event listeners. * Or if you want you table to listen to non-standard events. * + * The conventional method map is: + * + * - Model.beforeMarshal => beforeMarshal + * - Model.beforeFind => beforeFind + * - Model.beforeSave => beforeSave + * - Model.afterSave => afterSave + * - Model.afterSaveCommit => afterSaveCommit + * - Model.beforeDelete => beforeDelete + * - Model.afterDelete => afterDelete + * - Model.afterDeleteCommit => afterDeleteCommit + * - Model.beforeRules => beforeRules + * - Model.afterRules => afterRules + * * @return array */ public function implementedEvents() From 678fa923308511904430ac4cbd6fb63367eb7545 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Mon, 1 Jun 2015 22:06:35 +0200 Subject: [PATCH 0370/2059] Extract formatTreeList() from findTreeList() --- Behavior/TreeBehavior.php | 46 +++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 712d5115..ecba71c6 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -455,21 +455,43 @@ function ($field) { */ public function findTreeList(Query $query, array $options) { - return $this->_scope($query) + $results = $this->_scope($query) ->find('threaded', [ 'parentField' => $this->config('parent'), 'order' => [$this->config('left') => 'ASC'] - ]) - ->formatResults(function ($results) use ($options) { - $options += [ - 'keyPath' => $this->_getPrimaryKey(), - 'valuePath' => $this->_table->displayField(), - 'spacer' => '_' - ]; - return $results - ->listNested() - ->printer($options['valuePath'], $options['keyPath'], $options['spacer']); - }); + ]); + return $this->formatTreeList($results, $options); + } + + /** + * Formats query as a flat list where the keys are + * the primary key for the table and the values are the display field for the table. + * Values are prefixed to visually indicate relative depth in the tree. + * + * Available options are: + * + * - keyPath: A dot separated path to fetch the field to use for the array key, or a closure to + * return the key out of the provided row. + * - valuePath: A dot separated path to fetch the field to use for the array value, or a closure to + * return the value out of the provided row. + * - spacer: A string to be used as prefix for denoting the depth in the tree for each item + * + * @param \Cake\ORM\Query $query + * @param array $options Array of options as described above + * @return \Cake\ORM\Query + */ + public function formatTreeList(Query $query, array $options = []) + { + return $query->formatResults(function ($results) use ($options) { + $options += [ + 'keyPath' => $this->_getPrimaryKey(), + 'valuePath' => $this->_table->displayField(), + 'spacer' => '_' + ]; + return $results + ->listNested() + ->printer($options['valuePath'], $options['keyPath'], $options['spacer']); + }); } /** From 054d3e9fe5041f9a8fe243e165d1a221ade4e01d Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Mon, 1 Jun 2015 22:42:45 +0200 Subject: [PATCH 0371/2059] Add test case for formatTreeList(). --- Behavior/TreeBehavior.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index ecba71c6..2971bb21 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -64,7 +64,8 @@ class TreeBehavior extends Behavior 'moveDown' => 'moveDown', 'recover' => 'recover', 'removeFromTree' => 'removeFromTree', - 'getLevel' => 'getLevel' + 'getLevel' => 'getLevel', + 'formatTreeList' => 'formatTreeList' ], 'parent' => 'parent_id', 'left' => 'lft', From c4c49774b06378078a884f9a2e16ae5a26b400ed Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Mon, 1 Jun 2015 22:44:17 +0200 Subject: [PATCH 0372/2059] phpcs --- Behavior/TreeBehavior.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 2971bb21..57699a7e 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -451,7 +451,7 @@ function ($field) { * - spacer: A string to be used as prefix for denoting the depth in the tree for each item * * @param \Cake\ORM\Query $query Query. - * @param array $options Array of options as described above + * @param array $options Array of options as described above. * @return \Cake\ORM\Query */ public function findTreeList(Query $query, array $options) @@ -477,8 +477,8 @@ public function findTreeList(Query $query, array $options) * return the value out of the provided row. * - spacer: A string to be used as prefix for denoting the depth in the tree for each item * - * @param \Cake\ORM\Query $query - * @param array $options Array of options as described above + * @param \Cake\ORM\Query $query Query. + * @param array $options Array of options as described above. * @return \Cake\ORM\Query */ public function formatTreeList(Query $query, array $options = []) From 74ee477e8937778ce432d611d09ce0c31605f2d0 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 1 Jun 2015 21:37:00 -0400 Subject: [PATCH 0373/2059] Tweak doc block wording. --- Behavior/TreeBehavior.php | 43 ++++++++------------------------------- 1 file changed, 8 insertions(+), 35 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 57699a7e..b243c451 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -442,44 +442,17 @@ function ($field) { * the primary key for the table and the values are the display field for the table. * Values are prefixed to visually indicate relative depth in the tree. * - * Available options are: - * - * - keyPath: A dot separated path to fetch the field to use for the array key, or a closure to - * return the key out of the provided row. - * - valuePath: A dot separated path to fetch the field to use for the array value, or a closure to - * return the value out of the provided row. - * - spacer: A string to be used as prefix for denoting the depth in the tree for each item - * - * @param \Cake\ORM\Query $query Query. - * @param array $options Array of options as described above. - * @return \Cake\ORM\Query - */ - public function findTreeList(Query $query, array $options) - { - $results = $this->_scope($query) - ->find('threaded', [ - 'parentField' => $this->config('parent'), - 'order' => [$this->config('left') => 'ASC'] - ]); - return $this->formatTreeList($results, $options); - } - - /** - * Formats query as a flat list where the keys are - * the primary key for the table and the values are the display field for the table. - * Values are prefixed to visually indicate relative depth in the tree. + * ### Options * - * Available options are: - * - * - keyPath: A dot separated path to fetch the field to use for the array key, or a closure to - * return the key out of the provided row. - * - valuePath: A dot separated path to fetch the field to use for the array value, or a closure to - * return the value out of the provided row. - * - spacer: A string to be used as prefix for denoting the depth in the tree for each item + * - keyPath: A dot separated path to the field that will be the result array key, or a closure to + * return the key from the provided row. + * - valuePath: A dot separated path to the field that is the array's value, or a closure to + * return the value from the provided row. + * - spacer: A string to be used as prefix for denoting the depth in the tree for each item. * - * @param \Cake\ORM\Query $query Query. + * @param \Cake\ORM\Query $query The query object to format. * @param array $options Array of options as described above. - * @return \Cake\ORM\Query + * @return \Cake\ORM\Query Augmented query. */ public function formatTreeList(Query $query, array $options = []) { From 82d808e2088ef3a9e499105ca76e91011556e44e Mon Sep 17 00:00:00 2001 From: antograssiot Date: Tue, 2 Jun 2015 08:37:33 +0200 Subject: [PATCH 0374/2059] correct a typo and CS --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index a1cbc861..c81f8461 100644 --- a/Table.php +++ b/Table.php @@ -1063,7 +1063,7 @@ public function findList(Query $query, array $options) * * @param \Cake\ORM\Query $query The query to find with * @param array $options The options to find with - * @return \Cake\ORM\Query The qury builder + * @return \Cake\ORM\Query The query builder */ public function findThreaded(Query $query, array $options) { From 14989d00f0b96e90b2d991feab5b10a076612e8b Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Wed, 3 Jun 2015 23:45:03 +0200 Subject: [PATCH 0375/2059] Implemented Query::leftJoin() --- Association.php | 5 ++-- EagerLoader.php | 9 ++++--- Query.php | 63 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 6 deletions(-) diff --git a/Association.php b/Association.php index c9157cd3..d99c2d68 100644 --- a/Association.php +++ b/Association.php @@ -464,8 +464,7 @@ protected function _options(array $options) * will be merged with any conditions originally configured for this association * - fields: a list of fields in the target table to include in the result * - type: The type of join to be used (e.g. INNER) - * - matching: Indicates whether the query records should be filtered based on - * the records found on this association. This will force a 'INNER JOIN' + * the records found on this association * - aliasPath: A dot separated string representing the path of association names * followed from the passed query main table to this association. * - propertyPath: A dot separated string representing the path of association @@ -488,7 +487,7 @@ public function attachTo(Query $query, array $options = []) 'foreignKey' => $this->foreignKey(), 'conditions' => [], 'fields' => [], - 'type' => empty($options['matching']) ? $joinType : 'INNER', + 'type' => $joinType, 'table' => $target->table(), 'finder' => $this->finder() ]; diff --git a/EagerLoader.php b/EagerLoader.php index f2e53989..a3b2d8f7 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -171,9 +171,11 @@ public function autoFields($value = null) * @param string|null $assoc A single association or a dot separated path of associations. * @param callable|null $builder the callback function to be used for setting extra * options to the filtering query + * @param array $options Extra options for the association matching, such as 'joinType' + * and 'fields' * @return array The resulting containments array */ - public function matching($assoc = null, callable $builder = null) + public function matching($assoc = null, callable $builder = null, $options = []) { if ($this->_matching === null) { $this->_matching = new self(); @@ -187,13 +189,14 @@ public function matching($assoc = null, callable $builder = null) $last = array_pop($assocs); $containments = []; $pointer =& $containments; + $options += ['joinType' => 'INNER']; foreach ($assocs as $name) { - $pointer[$name] = ['matching' => true]; + $pointer[$name] = ['matching' => true] + $options; $pointer =& $pointer[$name]; } - $pointer[$last] = ['queryBuilder' => $builder, 'matching' => true]; + $pointer[$last] = ['queryBuilder' => $builder, 'matching' => true] + $options; return $this->_matching->contain($containments); } diff --git a/Query.php b/Query.php index c830e655..3f13dc1b 100644 --- a/Query.php +++ b/Query.php @@ -337,6 +337,69 @@ public function matching($assoc, callable $builder = null) return $this; } + /** + * Creates a LEFT JOIN with the passed association table while preserving + * the foreign key matching and the custom conditions that were originally set + * for it. + * + * This function will add entries in the `contain` graph. + * + * ### Example: + * + * ``` + * // Get the count of articles per user + * $usersQuery + * ->select(['total_articles' => $query->func()->count('Articles.id')]) + * ->leftJoin('Articles') + * ->group(['Users.id']) + * ->autoFields(true); + * ``` + * + * You can also customize the conditions passed to the LEFT JOIN: + * + * ``` + * // Get the count of articles per user with at least 5 votes + * $usersQuery + * ->select(['total_articles' => $query->func()->count('Articles.id')]) + * ->leftJoin('Articles', function ($q) { + * return $q->where(['Articles.votes >=' => 5]); + * }) + * ->group(['Users.id']) + * ->autoFields(true); + * ``` + * + * It is possible to left join deep associations by using dot notation + * + * ### Example: + * + * ``` + * // Total comments in articles by 'markstory' + * $query + * ->select(['total_comments' => $query->func()->count('Comments.id')]) + * ->leftJoin('Comments.Users', function ($q) { + * return $q->where(['username' => 'markstory']); + * ) + * ->group(['Users.id']); + * ``` + * + * Please note that the query passed to the closure will only accept calling + * `select`, `where`, `andWhere` and `orWhere` on it. If you wish to + * add more complex clauses you can do it directly in the main query. + * + * @param string $assoc The association to join with + * @param callable $builder a function that will receive a pre-made query object + * that can be used to add custom conditions or selecting some fields + * @return $this + */ + public function leftJoinWith($assoc, callable $builder = null) { + $this->eagerLoader()->matching($assoc, $builder, [ + 'joinType' => 'LEFT', + 'fields' => false + ]); + $this->_dirty(); + return $this; + } + /** * Returns a key => value array representing a single aliased field * that can be passed directly to the select() method. From efb2f0ed62e2426b6e7bab5236fa222804d35c38 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Thu, 4 Jun 2015 21:02:13 +0200 Subject: [PATCH 0376/2059] Adding support for leftJoinWith in BelongsToMany --- Association/BelongsToMany.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 8097dcd6..d057be51 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -254,10 +254,11 @@ public function attachTo(Query $query, array $options = []) } unset($options['queryBuilder']); + $type = array_intersect_key($options, ['joinType' => 1, 'fields' => 1]); $options = ['conditions' => [$cond]] + compact('includeFields'); $options['foreignKey'] = $this->targetForeignKey(); $assoc = $this->_targetTable->association($junction->alias()); - $assoc->attachTo($query, $options); + $assoc->attachTo($query, $options + $type); $query->eagerLoader()->addToJoinsMap($junction->alias(), $assoc, true); } From 221a6c06c2f7d90105e9dfa056af9d48c8ffeb3b Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Thu, 4 Jun 2015 23:11:41 +0200 Subject: [PATCH 0377/2059] Implemented innerJoinWith() --- Query.php | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/Query.php b/Query.php index 3f13dc1b..9e3624cc 100644 --- a/Query.php +++ b/Query.php @@ -400,6 +400,40 @@ public function leftJoinWith($assoc, callable $builder = null) { return $this; } + /** + * Creates an INNER JOIN with the passed association table while preserving + * the foreign key matching and the custom conditions that were originally set + * for it. + * + * This function will add entries in the `contain` graph. + * + * ### Example: + * + * ``` + * // Bring only articles that were tagged with 'cake' + * $query->innerJoinWith('Tags', function ($q) { + * return $q->where(['name' => 'cake']); + * ); + * ``` + * + * This function works the same as `matching()` with the difference that it + * will select no fields from the association. + * + * @param string $assoc The association to join with + * @param callable $builder a function that will receive a pre-made query object + * that can be used to add custom conditions or selecting some fields + * @return $this + * @see \Cake\ORM\Query::matching() + */ + public function innerJoinWith($assoc, callable $builder = null) { + $this->eagerLoader()->matching($assoc, $builder, [ + 'joinType' => 'INNER', + 'fields' => false + ]); + $this->_dirty(); + return $this; + } + /** * Returns a key => value array representing a single aliased field * that can be passed directly to the select() method. From 2ae56625588e56ad49be6c6313e1d99b5c6ce444 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Thu, 4 Jun 2015 23:16:49 +0200 Subject: [PATCH 0378/2059] Making all tests pass --- Association.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Association.php b/Association.php index d99c2d68..11df55cb 100644 --- a/Association.php +++ b/Association.php @@ -730,10 +730,15 @@ protected function _bindNewAssociations($query, $surrogate, $options) $newContain[$options['aliasPath'] . '.' . $alias] = $value; } - $query->contain($newContain); + $eagerLoader = $query->eagerLoader(); + $eagerLoader->contain($newContain); foreach ($matching as $alias => $value) { - $query->matching($options['aliasPath'] . '.' . $alias, $value['queryBuilder']); + $eagerLoader->matching( + $options['aliasPath'] . '.' . $alias, + $value['queryBuilder'], + $value + ); } } From 7d8aff05997446362cb7d418ca33f296772a9319 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Fri, 5 Jun 2015 10:01:39 +0200 Subject: [PATCH 0379/2059] Fixing tests for postgres and improving doc blocks --- Query.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Query.php b/Query.php index 9e3624cc..d848816a 100644 --- a/Query.php +++ b/Query.php @@ -368,6 +368,15 @@ public function matching($assoc, callable $builder = null) * ->autoFields(true); * ``` * + * This will create the following SQL: + * + * ``` + * SELECT COUNT(Articles.id) AS total_articles, Users.* + * FROM users Users + * LEFT JOIN articles Articles ON Articles.user_id = Users.id AND Articles.votes >= 5 + * GROUP BY USers.id + * ``` + * * It is possible to left join deep associations by using dot notation * * ### Example: @@ -416,6 +425,16 @@ public function leftJoinWith($assoc, callable $builder = null) { * ); * ``` * + * This will create the following SQL: + * + * ``` + * SELECT Articles.* + * FROM articles Articles + * INNER JOIN tags Tags ON Tags.name = 'cake' + * INNER JOIN articles_tags ArticlesTags ON ArticlesTags.tag_id = Tags.id + * AND ArticlesTags.articles_id = Articles.id + * ``` + * * This function works the same as `matching()` with the difference that it * will select no fields from the association. * From 25ec6ba3a098c6cb9d11ad14eb64dfb75295f22b Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Fri, 5 Jun 2015 10:12:48 +0200 Subject: [PATCH 0380/2059] Fixing examples in doc blocks --- Query.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Query.php b/Query.php index d848816a..c279d683 100644 --- a/Query.php +++ b/Query.php @@ -350,7 +350,7 @@ public function matching($assoc, callable $builder = null) * // Get the count of articles per user * $usersQuery * ->select(['total_articles' => $query->func()->count('Articles.id')]) - * ->leftJoin('Articles') + * ->leftJoinWith('Articles') * ->group(['Users.id']) * ->autoFields(true); * ``` @@ -361,7 +361,7 @@ public function matching($assoc, callable $builder = null) * // Get the count of articles per user with at least 5 votes * $usersQuery * ->select(['total_articles' => $query->func()->count('Articles.id')]) - * ->leftJoin('Articles', function ($q) { + * ->leftJoinWith('Articles', function ($q) { * return $q->where(['Articles.votes >=' => 5]); * }) * ->group(['Users.id']) @@ -385,7 +385,7 @@ public function matching($assoc, callable $builder = null) * // Total comments in articles by 'markstory' * $query * ->select(['total_comments' => $query->func()->count('Comments.id')]) - * ->leftJoin('Comments.Users', function ($q) { + * ->leftJoinWith('Comments.Users', function ($q) { * return $q->where(['username' => 'markstory']); * ) * ->group(['Users.id']); From 982cb688c7575a2c236925af8848b34ad4212119 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Fri, 5 Jun 2015 10:29:25 +0200 Subject: [PATCH 0381/2059] Fixed cs error --- Query.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Query.php b/Query.php index c279d683..e73460c3 100644 --- a/Query.php +++ b/Query.php @@ -400,7 +400,8 @@ public function matching($assoc, callable $builder = null) * that can be used to add custom conditions or selecting some fields * @return $this */ - public function leftJoinWith($assoc, callable $builder = null) { + public function leftJoinWith($assoc, callable $builder = null) + { $this->eagerLoader()->matching($assoc, $builder, [ 'joinType' => 'LEFT', 'fields' => false @@ -444,7 +445,8 @@ public function leftJoinWith($assoc, callable $builder = null) { * @return $this * @see \Cake\ORM\Query::matching() */ - public function innerJoinWith($assoc, callable $builder = null) { + public function innerJoinWith($assoc, callable $builder = null) + { $this->eagerLoader()->matching($assoc, $builder, [ 'joinType' => 'INNER', 'fields' => false From 6ffd45d73cdb66815d8dc059949e6f45b876f770 Mon Sep 17 00:00:00 2001 From: mark_story Date: Fri, 5 Jun 2015 09:47:10 -0400 Subject: [PATCH 0382/2059] Restore accidentally removed method. --- Behavior/TreeBehavior.php | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index b243c451..33947219 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -444,6 +444,33 @@ function ($field) { * * ### Options * + * - keyPath: A dot separated path to fetch the field to use for the array key, or a closure to + * return the key out of the provided row. + * - valuePath: A dot separated path to fetch the field to use for the array value, or a closure to + * return the value out of the provided row. + * - spacer: A string to be used as prefix for denoting the depth in the tree for each item + * + * @param \Cake\ORM\Query $query Query. + * @param array $options Array of options as described above. + * @return \Cake\ORM\Query + */ + public function findTreeList(Query $query, array $options) + { + $results = $this->_scope($query) + ->find('threaded', [ + 'parentField' => $this->config('parent'), + 'order' => [$this->config('left') => 'ASC'] + ]); + return $this->formatTreeList($results, $options); + } + + /** + * Formats query as a flat list where the keys are the primary key for the table + * and the values are the display field for the table. Values are prefixed to visually + * indicate relative depth in the tree. + * + * ### Options + * * - keyPath: A dot separated path to the field that will be the result array key, or a closure to * return the key from the provided row. * - valuePath: A dot separated path to the field that is the array's value, or a closure to From 260cc5ac872573fbf9a25ed1d6ce286f6c05b0bd Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Fri, 5 Jun 2015 23:32:50 +0200 Subject: [PATCH 0383/2059] Make select() accept a Table or Association instance Closes #6103 --- Query.php | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Query.php b/Query.php index c830e655..76e90398 100644 --- a/Query.php +++ b/Query.php @@ -124,6 +124,30 @@ public function __construct($connection, $table) } } + /** + * {@inheritDocs} + * + * If you pass an instance of a `Cake\ORM\Table` or `Cake\ORM\Association` class, + * all the fields in the schema of the table or the association will be added to + * the select clause. + * + * @param array|ExpressionInterface|string|\Cake\ORM\Table|\Cake\ORM\Association $fields fields + * to be added to the list. + * @param bool $overwrite whether to reset fields with passed list or not + */ + public function select($fields = [], $overwrite = false) + { + if ($fields instanceof Association) { + $fields = $fields->target(); + } + + if ($fields instanceof Table) { + $fields = $this->aliasFields($fields->schema()->columns(), $fields->alias()); + } + + return parent::select($fields, $overwrite); + } + /** * Hints this object to associate the correct types when casting conditions * for the database. This is done by extracting the field types from the schema From 3a26a07c1c8863fd1d14a97959e18219d7cfc7d5 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sat, 6 Jun 2015 00:46:18 +0200 Subject: [PATCH 0384/2059] Fixing test case by using a less annoying expression --- Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Query.php b/Query.php index 76e90398..7b5c432f 100644 --- a/Query.php +++ b/Query.php @@ -125,7 +125,7 @@ public function __construct($connection, $table) } /** - * {@inheritDocs} + * {@inheritDoc} * * If you pass an instance of a `Cake\ORM\Table` or `Cake\ORM\Association` class, * all the fields in the schema of the table or the association will be added to From 0a09c960da8a28eee243793139546d29e6568b9a Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 7 Jun 2015 00:26:08 +0200 Subject: [PATCH 0385/2059] Initial implementation of Query::notMatching() --- Association.php | 10 +++++++++- EagerLoader.php | 3 ++- Query.php | 11 +++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/Association.php b/Association.php index 11df55cb..267ce128 100644 --- a/Association.php +++ b/Association.php @@ -499,6 +499,14 @@ public function attachTo(Query $query, array $options = []) } } + if ($options['negateMatch']) { + $primaryKey = $query->aliasFields((array)$target->primaryKey(), $this->_name); + $query->andWhere(function ($exp) use ($primaryKey) { + array_map([$exp, 'isNull'], $primaryKey); + return $exp; + }); + } + list($finder, $opts) = $this->_extractFinder($options['finder']); $dummy = $this ->find($finder, $opts) @@ -518,7 +526,7 @@ public function attachTo(Query $query, array $options = []) $joinOptions = ['table' => 1, 'conditions' => 1, 'type' => 1]; $options['conditions'] = $dummy->clause('where'); - $query->join([$target->alias() => array_intersect_key($options, $joinOptions)]); + $query->join([$this->_name => array_intersect_key($options, $joinOptions)]); $this->_appendFields($query, $dummy, $options); $this->_formatAssociationResults($query, $dummy, $options); diff --git a/EagerLoader.php b/EagerLoader.php index a3b2d8f7..2a138d65 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -64,7 +64,8 @@ class EagerLoader 'queryBuilder' => 1, 'finder' => 1, 'joinType' => 1, - 'strategy' => 1 + 'strategy' => 1, + 'negateMatch' => 1 ]; /** diff --git a/Query.php b/Query.php index e73460c3..f6eb8fe5 100644 --- a/Query.php +++ b/Query.php @@ -455,6 +455,17 @@ public function innerJoinWith($assoc, callable $builder = null) return $this; } + public function notMatching($assoc, callable $builder = null) + { + $this->eagerLoader()->matching($assoc, $builder, [ + 'joinType' => 'LEFT', + 'fields' => false, + 'negateMatch' => true + ]); + $this->_dirty(); + return $this; + } + /** * Returns a key => value array representing a single aliased field * that can be passed directly to the select() method. From 09922ed808477aab9cee605a7c829d8105976468 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 7 Jun 2015 00:55:59 +0200 Subject: [PATCH 0386/2059] Implemented notMatching for belongsToMany tables --- Association.php | 21 +++++++++++++-------- Association/BelongsToMany.php | 12 ++++++++++++ EagerLoader.php | 4 +++- 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/Association.php b/Association.php index 267ce128..036ec104 100644 --- a/Association.php +++ b/Association.php @@ -499,14 +499,6 @@ public function attachTo(Query $query, array $options = []) } } - if ($options['negateMatch']) { - $primaryKey = $query->aliasFields((array)$target->primaryKey(), $this->_name); - $query->andWhere(function ($exp) use ($primaryKey) { - array_map([$exp, 'isNull'], $primaryKey); - return $exp; - }); - } - list($finder, $opts) = $this->_extractFinder($options['finder']); $dummy = $this ->find($finder, $opts) @@ -531,6 +523,19 @@ public function attachTo(Query $query, array $options = []) $this->_appendFields($query, $dummy, $options); $this->_formatAssociationResults($query, $dummy, $options); $this->_bindNewAssociations($query, $dummy, $options); + $this->_appendNotMatching($query, $options); + } + + protected function _appendNotMatching($query, $options) + { + $target = $this->_targetTable; + if (!empty($options['negateMatch'])) { + $primaryKey = $query->aliasFields((array)$target->primaryKey(), $this->_name); + $query->andWhere(function ($exp) use ($primaryKey) { + array_map([$exp, 'isNull'], $primaryKey); + return $exp; + }); + } } /** diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index d057be51..1036450e 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -262,6 +262,18 @@ public function attachTo(Query $query, array $options = []) $query->eagerLoader()->addToJoinsMap($junction->alias(), $assoc, true); } + protected function _appendNotMatching($query, $options) + { + $target = $junction = $this->junction(); + if (!empty($options['negateMatch'])) { + $primaryKey = $query->aliasFields((array)$target->primaryKey(), $target->alias()); + $query->andWhere(function ($exp) use ($primaryKey) { + array_map([$exp, 'isNull'], $primaryKey); + return $exp; + }); + } + } + /** * {@inheritDoc} */ diff --git a/EagerLoader.php b/EagerLoader.php index 2a138d65..076da01b 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -191,9 +191,11 @@ public function matching($assoc = null, callable $builder = null, $options = []) $containments = []; $pointer =& $containments; $options += ['joinType' => 'INNER']; + $opts = ['matching' => true] + $options; + unset($opts['negateMatch']); foreach ($assocs as $name) { - $pointer[$name] = ['matching' => true] + $options; + $pointer[$name] = $opts; $pointer =& $pointer[$name]; } From bb0570c8e8d5ea0e88f916e3c5a27abaedce44c2 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 7 Jun 2015 15:33:27 +0200 Subject: [PATCH 0387/2059] Adding some doc blocks --- Association.php | 10 ++++++++++ Association/BelongsToMany.php | 3 +++ 2 files changed, 13 insertions(+) diff --git a/Association.php b/Association.php index 036ec104..0e1baf34 100644 --- a/Association.php +++ b/Association.php @@ -471,6 +471,8 @@ protected function _options(array $options) * properties to be followed from the passed query main entity to this * association * - joinType: The SQL join type to use in the query. + * - negateMatch: Will append a condition to the passed query for excluding matches. + * with this association. * * @param Query $query the query to be altered to include the target table data * @param array $options Any extra options or overrides to be taken in account @@ -526,6 +528,14 @@ public function attachTo(Query $query, array $options = []) $this->_appendNotMatching($query, $options); } + /** + * Conditionally adds a condition to the passed Query that will make it find + * records where there is no match with this association. + * + * @param \Cake\Database\Query $query The query to modify + * @param array $options Options array containing the `negateMatch` key. + * @return void + */ protected function _appendNotMatching($query, $options) { $target = $this->_targetTable; diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 1036450e..b6469cf4 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -262,6 +262,9 @@ public function attachTo(Query $query, array $options = []) $query->eagerLoader()->addToJoinsMap($junction->alias(), $assoc, true); } + /** + * {@inheritDoc} + */ protected function _appendNotMatching($query, $options) { $target = $junction = $this->junction(); From 6c091afa33d54fae4166122d1dfa3acb779b26f1 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 7 Jun 2015 15:37:22 +0200 Subject: [PATCH 0388/2059] Adding a doc block for notMatching --- Query.php | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/Query.php b/Query.php index f6eb8fe5..912bc932 100644 --- a/Query.php +++ b/Query.php @@ -455,6 +455,56 @@ public function innerJoinWith($assoc, callable $builder = null) return $this; } + /** + * Adds filtering conditions to this query to only bring rows that have no match + * to another from an associated table, based on conditions in the associated table. + * + * This function will add entries in the `contain` graph. + * + * ### Example: + * + * ``` + * // Bring only articles that were not tagged with 'cake' + * $query->notMatching('Tags', function ($q) { + * return $q->where(['name' => 'cake']); + * ); + * ``` + * + * It is possible to filter by deep associations by using dot notation: + * + * ### Example: + * + * ``` + * // Bring only articles that weren't commented by 'markstory' + * $query->notMatching('Comments.Users', function ($q) { + * return $q->where(['username' => 'markstory']); + * ); + * ``` + * + * As this function will create a `LEFT JOIN`, you might want to consider + * calling `distinct` on this query as you might get duplicate rows if + * your conditions don't filter them already. This might be the case, for example, + * of the same article having multiple comments. + * + * ### Example: + * + * ``` + * // Bring unique articles that were commented by 'markstory' + * $query->distinct(['Articles.id']) + * ->notMatching('Comments.Users', function ($q) { + * return $q->where(['username' => 'markstory']); + * ); + * ``` + * + * Please note that the query passed to the closure will only accept calling + * `select`, `where`, `andWhere` and `orWhere` on it. If you wish to + * add more complex clauses you can do it directly in the main query. + * + * @param string $assoc The association to filter by + * @param callable $builder a function that will receive a pre-made query object + * that can be used to add custom conditions or selecting some fields + * @return $this + */ public function notMatching($assoc, callable $builder = null) { $this->eagerLoader()->matching($assoc, $builder, [ From c982c3a7798ab195ebb2f3a61c4d7f4d1137b385 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 7 Jun 2015 21:04:27 +0530 Subject: [PATCH 0389/2059] Allow passing array to AssociationCollection::type(). --- AssociationCollection.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/AssociationCollection.php b/AssociationCollection.php index f793cd12..089569f6 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -111,14 +111,17 @@ public function keys() /** * Get an array of associations matching a specific type. * - * @param string $class The type of associations you want. For example 'BelongsTo' + * @param string|array $class The type of associations you want. + * For example 'BelongsTo' or array like ['BelongsTo', 'HasOne'] * @return array An array of Association objects. */ public function type($class) { + $class = (array)$class; + $out = array_filter($this->_items, function ($assoc) use ($class) { list(, $name) = namespaceSplit(get_class($assoc)); - return $class === $name; + return in_array($name, $class); }); return array_values($out); } From 9b19dc76708aac41dea475eaed6f38296be8c13a Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 8 Jun 2015 13:52:28 +0530 Subject: [PATCH 0390/2059] Use strict check --- AssociationCollection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AssociationCollection.php b/AssociationCollection.php index 089569f6..4b0d6412 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -121,7 +121,7 @@ public function type($class) $out = array_filter($this->_items, function ($assoc) use ($class) { list(, $name) = namespaceSplit(get_class($assoc)); - return in_array($name, $class); + return in_array($name, $class, true); }); return array_values($out); } From bc720ba7a043e216807f25fb057057b17723d1f4 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 8 Jun 2015 23:19:28 +0530 Subject: [PATCH 0391/2059] Fix order of path nodes. Closes #6724 --- Behavior/TreeBehavior.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 33947219..2f130cec 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -362,7 +362,8 @@ function ($field) { ->where([ "$left <=" => $node->get($config['left']), "$right >=" => $node->get($config['right']) - ]); + ]) + ->order([$left => 'ASC']); } /** From 604c0c94c7fa1423471580a4e6bccde49b48c3a1 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 11 Jun 2015 08:13:47 -0400 Subject: [PATCH 0392/2059] Include joint table columns in default type maps Including the joint table columns in the default type maps will allow joint table types to be mapped correctly without developers having to declare the types. Refs #6775 --- Association/BelongsToMany.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 8bce84c0..de1ac112 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -941,6 +941,7 @@ protected function _buildQuery($options) $assoc = $this->target()->association($name); $query + ->addDefaultTypes($assoc->target()) ->join($matching + $joins, [], true) ->autoFields($query->clause('select') === []) ->select($query->aliasFields((array)$assoc->foreignKey(), $name)); From 14d7571957df467598e0835afc8d476a7d8ddb8a Mon Sep 17 00:00:00 2001 From: mark_story Date: Thu, 11 Jun 2015 09:42:36 -0400 Subject: [PATCH 0393/2059] Make Query::addDefaultTypes() do what it says. Instead of adding, addDefaultTypes() was replacing. While this is useful, adding is more useful as we want the typemap to accumulate type information throughout its lifetime. --- Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Query.php b/Query.php index c830e655..7f9c6b02 100644 --- a/Query.php +++ b/Query.php @@ -143,7 +143,7 @@ public function addDefaultTypes(Table $table) foreach ($schema->columns() as $f) { $fields[$f] = $fields[$alias . '.' . $f] = $schema->columnType($f); } - $this->defaultTypes($fields); + $this->typeMap()->addDefaults($fields); return $this; } From 7f88c52c5402bdaf68fe4bb30eb08f394dc50f83 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 12 Jun 2015 19:32:05 +0530 Subject: [PATCH 0394/2059] Alias fields to avoid ambiguity. Closes #6794 --- Behavior/TreeBehavior.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 2f130cec..6c4ce759 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -772,19 +772,20 @@ protected function _recoverTree($counter = 0, $parentId = null, $level = -1) { $config = $this->config(); list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']]; - $pk = (array)$this->_table->primaryKey(); + $primaryKey = $this->_getPrimaryKey(); + $aliasedPrimaryKey = $this->_table->aliasField($primaryKey); $query = $this->_scope($this->_table->query()) - ->select($pk) - ->where([$parent . ' IS' => $parentId]) - ->order($pk) + ->select([$aliasedPrimaryKey]) + ->where([$this->_table->aliasField($parent) . ' IS' => $parentId]) + ->order([$aliasedPrimaryKey]) ->hydrate(false); $leftCounter = $counter; $nextLevel = $level + 1; foreach ($query as $row) { $counter++; - $counter = $this->_recoverTree($counter, $row[$pk[0]], $nextLevel); + $counter = $this->_recoverTree($counter, $row[$primaryKey], $nextLevel); } if ($parentId === null) { @@ -798,7 +799,7 @@ protected function _recoverTree($counter = 0, $parentId = null, $level = -1) $this->_table->updateAll( $fields, - [$pk[0] => $parentId] + [$primaryKey => $parentId] ); return $counter + 1; From d2c9d6f7b247f54cad18b0a1c8c56463e68f6d48 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 12 Jun 2015 19:33:44 +0530 Subject: [PATCH 0395/2059] Use Table::aliasField() method --- Behavior/TreeBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 6c4ce759..15a341ba 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -737,7 +737,7 @@ protected function _getNode($id) $node = $this->_scope($this->_table->find()) ->select($fields) - ->where([$this->_table->alias() . '.' . $primaryKey => $id]) + ->where([$this->_table->aliasField($primaryKey) => $id]) ->first(); if (!$node) { From 68770d24846e290139a485dfa311445018f8faf5 Mon Sep 17 00:00:00 2001 From: Patrick Conroy Date: Fri, 12 Jun 2015 00:08:12 -0400 Subject: [PATCH 0396/2059] Fixing how subqueries use keys when building --- Association/SelectableAssociationTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/SelectableAssociationTrait.php b/Association/SelectableAssociationTrait.php index 187a9230..6a067e34 100644 --- a/Association/SelectableAssociationTrait.php +++ b/Association/SelectableAssociationTrait.php @@ -234,7 +234,7 @@ protected function _buildSubquery($query) $filterQuery->offset(null); } - $keys = (array)$query->repository()->primaryKey(); + $keys = (array)$this->source()->primaryKey(); if ($this->type() === $this::MANY_TO_ONE) { $keys = (array)$this->foreignKey(); From 871c7386b4f94b02f7428e2f77adf9e1582d4d45 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Mon, 15 Jun 2015 22:11:43 +0200 Subject: [PATCH 0397/2059] Fixing issue in contain/mathching regarding dot notation Previously it was impossible to use dot notation on two different calls to matching/contain when part of the string was shared (for example Articles.SpecialTags.Tags and Articles.SpecialTags.Authors) The issue was that the second call would override the settings created for the first one. --- EagerLoader.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/EagerLoader.php b/EagerLoader.php index f2e53989..d9aada4f 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -281,7 +281,10 @@ protected function _reformatContain($associations, $original) $options = isset($options['config']) ? $options['config'] + $options['associations'] : $options; - $options = $this->_reformatContain($options, []); + $options = $this->_reformatContain( + $options, + isset($pointer[$table]) ? $pointer[$table] : [] + ); } if ($options instanceof Closure) { From b9388dd0a099fe936ef80b5a85440aa9f78e5c48 Mon Sep 17 00:00:00 2001 From: Josh Lockhart Date: Tue, 16 Jun 2015 07:42:13 -0400 Subject: [PATCH 0398/2059] Fix TableRegistry::get() when merging pre-configuration options --- TableRegistry.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TableRegistry.php b/TableRegistry.php index ad75ce9f..57697bd7 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -184,7 +184,7 @@ public static function get($alias, array $options = []) } if (isset(static::$_config[$alias])) { - $options += static::$_config[$alias]; + $options = array_merge($options, static::$_config[$alias]); } if (empty($options['connection'])) { $connectionName = $options['className']::defaultConnectionName(); From 0570cfb1de41161a5abf2a953dea1dc7419ae5ec Mon Sep 17 00:00:00 2001 From: Josh Lockhart Date: Tue, 16 Jun 2015 07:58:15 -0400 Subject: [PATCH 0399/2059] Update TableRegistry::get() options merge strategy --- TableRegistry.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TableRegistry.php b/TableRegistry.php index 57697bd7..014a4c15 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -184,7 +184,7 @@ public static function get($alias, array $options = []) } if (isset(static::$_config[$alias])) { - $options = array_merge($options, static::$_config[$alias]); + $options = static::$_config[$alias] + $options; } if (empty($options['connection'])) { $connectionName = $options['className']::defaultConnectionName(); From 1e8999fb72194d78b3aea0bf43a815556e6c66c6 Mon Sep 17 00:00:00 2001 From: Josh Lockhart Date: Tue, 16 Jun 2015 08:38:27 -0400 Subject: [PATCH 0400/2059] Relocate config options merge --- TableRegistry.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/TableRegistry.php b/TableRegistry.php index 014a4c15..4988bf05 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -169,6 +169,10 @@ public static function get($alias, array $options = []) list(, $classAlias) = pluginSplit($alias); $options = ['alias' => $classAlias] + $options; + if (isset(static::$_config[$alias])) { + $options += static::$_config[$alias]; + } + if (empty($options['className'])) { $options['className'] = Inflector::camelize($alias); } @@ -183,9 +187,6 @@ public static function get($alias, array $options = []) $options['className'] = 'Cake\ORM\Table'; } - if (isset(static::$_config[$alias])) { - $options = static::$_config[$alias] + $options; - } if (empty($options['connection'])) { $connectionName = $options['className']::defaultConnectionName(); $options['connection'] = ConnectionManager::get($connectionName); From 7864d252a2a5b39aaf8050a8dca444f9a7ddc255 Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 17 Jun 2015 15:57:09 +0200 Subject: [PATCH 0401/2059] Empty translation entity fix. --- Behavior/Translate/TranslateTrait.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Behavior/Translate/TranslateTrait.php b/Behavior/Translate/TranslateTrait.php index 8f97251c..b02bd678 100644 --- a/Behavior/Translate/TranslateTrait.php +++ b/Behavior/Translate/TranslateTrait.php @@ -14,7 +14,7 @@ */ namespace Cake\ORM\Behavior\Translate; -use Cake\ORM\Entity; +use Cake\Datasource\EntityInterface; /** * Contains a translation method aimed to help managing multiple translations @@ -46,8 +46,10 @@ public function translation($language) $created = true; } - if ($created || empty($i18n[$language]) || !($i18n[$language] instanceof Entity)) { - $i18n[$language] = new Entity(); + if ($created || empty($i18n[$language]) || !($i18n[$language] instanceof EntityInterface)) { + $className = get_class($this); + + $i18n[$language] = new $className; $created = true; } From c6128e8c7bebe8717af76b12ef29db1ef92dddfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Wed, 17 Jun 2015 19:31:44 +0200 Subject: [PATCH 0402/2059] Added extra parentheses --- Behavior/Translate/TranslateTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/Translate/TranslateTrait.php b/Behavior/Translate/TranslateTrait.php index b02bd678..df56bdef 100644 --- a/Behavior/Translate/TranslateTrait.php +++ b/Behavior/Translate/TranslateTrait.php @@ -49,7 +49,7 @@ public function translation($language) if ($created || empty($i18n[$language]) || !($i18n[$language] instanceof EntityInterface)) { $className = get_class($this); - $i18n[$language] = new $className; + $i18n[$language] = new $className(); $created = true; } From f7488916d603d3850c9578ad8f387b920638d2e3 Mon Sep 17 00:00:00 2001 From: mark_story Date: Wed, 17 Jun 2015 13:36:33 -0400 Subject: [PATCH 0403/2059] Typehint against the interface. Typehinting against an implementation is generally not a good idea. --- ResultSet.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ResultSet.php b/ResultSet.php index dff7ca9a..31cada50 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -19,6 +19,7 @@ use Cake\Database\Exception; use Cake\Database\Type; use Cake\Datasource\ResultSetInterface; +use Cake\Datasource\EntityInterface; use SplFixedArray; /** @@ -589,7 +590,7 @@ protected function _groupResult($row) if (isset($results[$defaultAlias])) { $results = $results[$defaultAlias]; } - if ($this->_hydrate && !($results instanceof Entity)) { + if ($this->_hydrate && !($results instanceof EntityInterface)) { $results = new $this->_entityClass($results, $options); } From f936e0bf21d7c93752ed117e8489075f1df64423 Mon Sep 17 00:00:00 2001 From: mark_story Date: Wed, 17 Jun 2015 14:47:49 -0400 Subject: [PATCH 0404/2059] Fix PHPCS errors. --- ResultSet.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ResultSet.php b/ResultSet.php index 31cada50..30d5d826 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -18,8 +18,8 @@ use Cake\Collection\CollectionTrait; use Cake\Database\Exception; use Cake\Database\Type; -use Cake\Datasource\ResultSetInterface; use Cake\Datasource\EntityInterface; +use Cake\Datasource\ResultSetInterface; use SplFixedArray; /** From dede2a3bf623ecb16c515815f14e6f631ebb938e Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 21 Jun 2015 16:10:12 -0400 Subject: [PATCH 0405/2059] Treat [] and '' the same when saving belongsToMany associations. Both [] and '' are semantically the same in this association type (empty data). Because '', false, and null can all be interpreted as no data, we'll use all 3 to represent an empty set. This matches the behavior we use when handling hasMany associations well. Going forward when updating a belongsToMany association these empty values will clear out links. Previously '' would cause saving to silently fail and possibly emit errors when doing updates which was bad. Refs #6817 --- Association/BelongsToMany.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index de1ac112..1389d8e6 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -430,13 +430,13 @@ public function saveAssociated(EntityInterface $entity, array $options = []) $targetEntity = $entity->get($this->property()); $strategy = $this->saveStrategy(); - if ($targetEntity === null) { - return false; - } - - if ($targetEntity === [] && $entity->isNew()) { + $isEmpty = in_array($targetEntity, [null, [], '', false], true); + if ($isEmpty && $entity->isNew()) { return $entity; } + if ($isEmpty) { + $targetEntity = []; + } if ($strategy === self::SAVE_APPEND) { return $this->_saveTarget($entity, $targetEntity, $options); From eec310db83fa10bf393ce9c6721fde805d356c74 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 23 Jun 2015 08:57:50 +0200 Subject: [PATCH 0406/2059] Added BehaviorRegistry::setTable method. --- BehaviorRegistry.php | 17 +++++++++++++++-- Table.php | 7 ++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 46876a56..b2def2cb 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -58,9 +58,22 @@ class BehaviorRegistry extends ObjectRegistry /** * Constructor * - * @param \Cake\ORM\Table $table The table this registry is attached to + * @param \Cake\ORM\Table $table The table this registry is attached to. */ - public function __construct(Table $table) + public function __construct(Table $table = null) + { + if ($table !== null) { + $this->setTable($table); + } + } + + /** + * Attaches a table instance to this registry. + * + * @param \Cake\ORM\Table $table The table this registry is attached to. + * @return void + */ + public function setTable(Table $table) { $this->_table = $table; $this->eventManager($table->eventManager()); diff --git a/Table.php b/Table.php index c81f8461..03770521 100644 --- a/Table.php +++ b/Table.php @@ -279,7 +279,12 @@ public function __construct(array $config = []) } } $this->_eventManager = $eventManager ?: new EventManager(); - $this->_behaviors = $behaviors ?: new BehaviorRegistry($this); + if ($behaviors) { + $behaviors->setTable($this); + } else { + $behaviors = new BehaviorRegistry($this); + } + $this->_behaviors = $behaviors; $this->_associations = $associations ?: new AssociationCollection(); $this->initialize($config); From 7777fbc932131511f8f82b6096716675a4a86ba8 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 23 Jun 2015 10:35:15 +0200 Subject: [PATCH 0407/2059] Removed Table typehint from a constructor --- BehaviorRegistry.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index b2def2cb..6efb1b35 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -60,7 +60,7 @@ class BehaviorRegistry extends ObjectRegistry * * @param \Cake\ORM\Table $table The table this registry is attached to. */ - public function __construct(Table $table = null) + public function __construct($table = null) { if ($table !== null) { $this->setTable($table); From 50f25bf206fb53925d4fdb2ff312198532d1f4e2 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 23 Jun 2015 10:36:27 +0200 Subject: [PATCH 0408/2059] Simplify behaviors init --- Table.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Table.php b/Table.php index 03770521..c81653d7 100644 --- a/Table.php +++ b/Table.php @@ -279,12 +279,8 @@ public function __construct(array $config = []) } } $this->_eventManager = $eventManager ?: new EventManager(); - if ($behaviors) { - $behaviors->setTable($this); - } else { - $behaviors = new BehaviorRegistry($this); - } - $this->_behaviors = $behaviors; + $this->_behaviors = $behaviors ?: new BehaviorRegistry(); + $this->_behaviors->setTable($this); $this->_associations = $associations ?: new AssociationCollection(); $this->initialize($config); From 753546797fd4a426eb64f76ce97904b7b48b8bde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Tue, 23 Jun 2015 18:07:28 +0200 Subject: [PATCH 0409/2059] Added null to docblock. --- BehaviorRegistry.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 6efb1b35..4f6457f1 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -58,7 +58,7 @@ class BehaviorRegistry extends ObjectRegistry /** * Constructor * - * @param \Cake\ORM\Table $table The table this registry is attached to. + * @param \Cake\ORM\Table|null $table The table this registry is attached to. */ public function __construct($table = null) { From a36a5d2e38f130748eebf57051d6ba43d7c4230b Mon Sep 17 00:00:00 2001 From: Patrick Conroy Date: Tue, 23 Jun 2015 14:06:43 -0400 Subject: [PATCH 0410/2059] Making btm marshalling use table aliases --- Marshaller.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index bf6cc702..b2f1625b 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -295,7 +295,9 @@ protected function _belongsToMany(Association $assoc, array $data, $options = [] if (array_intersect_key($primaryKey, $row) === $primaryKey) { $keys = array_intersect_key($row, $primaryKey); if (count($keys) === $primaryCount) { - $conditions[] = $keys; + foreach ($keys as $key => $value) { + $conditions[][$assoc->alias() . '.' . $key] = $value; + } } } else { $records[$i] = $this->one($row, $options); From 21a4ad4ab2483478e1f47c56fb0136f05f403a0f Mon Sep 17 00:00:00 2001 From: Fabio Sfuncia Date: Wed, 24 Jun 2015 02:00:22 +0200 Subject: [PATCH 0411/2059] typo in ORM README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 342b3d38..1b5a4a56 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ mappers if no explicit connection is defined. ## Creating Associations In your table classes you can define the relations between your tables. CakePHP -supports 4 association types out of the box: +ORM supports 4 association types out of the box: * belongsTo - E.g. Many articles belong to a user. * hasOne - E.g. A user has one profile @@ -85,7 +85,7 @@ $data = [ ]; $articles = TableRegistry::get('Articles'); -$article = $articles->newEnitity($data, [ +$article = $articles->newEntity($data, [ 'associated' => ['Tags', 'Comments'] ]); $articles->save($article, [ @@ -109,5 +109,5 @@ $articles->delete($article); ## Additional Documentation -Consult [the CakePHP documentation](http://book.cakephp.org/3.0/en/orm.html) +Consult [the CakePHP ORM documentation](http://book.cakephp.org/3.0/en/orm.html) for more in-depth documentation. From 8f5a50aa3425683933d47d44b95dabfaee1989c8 Mon Sep 17 00:00:00 2001 From: Fabio Sfuncia Date: Wed, 24 Jun 2015 02:42:51 +0200 Subject: [PATCH 0412/2059] Fix grammatical error --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1b5a4a56..24b2a6f8 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,8 @@ mappers if no explicit connection is defined. ## Creating Associations -In your table classes you can define the relations between your tables. CakePHP -ORM supports 4 association types out of the box: +In your table classes you can define the relations between your tables. CakePHP's ORM +supports 4 association types out of the box: * belongsTo - E.g. Many articles belong to a user. * hasOne - E.g. A user has one profile From 60c054164908adcffedf6f825c9b3e3c897371da Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 23 Jun 2015 23:11:03 -0400 Subject: [PATCH 0413/2059] Use the RepositoryInterface in the Paginator. We half did this correctly, but there were a few typehints that hinted against Table instead of RepositoryInterface. Changing these, and broadening the interface to contain the methods that PaginatorComponent relies on as well. This makes it possible for the ElasticSearch plugin to be compatible with PaginatorComponent. --- Table.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Table.php b/Table.php index c81f8461..f151c16b 100644 --- a/Table.php +++ b/Table.php @@ -346,10 +346,7 @@ public function table($table = null) } /** - * Returns the table alias or sets a new one - * - * @param string|null $alias the new table alias - * @return string + * {@inheritDoc} */ public function alias($alias = null) { From 96ab20066cfabdb8576ddf268cbdb56a2ffdadcd Mon Sep 17 00:00:00 2001 From: Patrick Conroy Date: Wed, 24 Jun 2015 12:20:30 -0400 Subject: [PATCH 0414/2059] Making target alias more extensible --- Marshaller.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index b2f1625b..c9236263 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -287,6 +287,7 @@ protected function _belongsToMany(Association $assoc, array $data, $options = [] $records = []; $conditions = []; $primaryCount = count($primaryKey); + $target = $assoc->target(); foreach ($data as $i => $row) { if (!is_array($row)) { @@ -296,7 +297,7 @@ protected function _belongsToMany(Association $assoc, array $data, $options = [] $keys = array_intersect_key($row, $primaryKey); if (count($keys) === $primaryCount) { foreach ($keys as $key => $value) { - $conditions[][$assoc->alias() . '.' . $key] = $value; + $conditions[][$target->aliasfield($key)] = $value; } } } else { From 47dc576bb2e6ab3153c1014805fd972b9e09f05f Mon Sep 17 00:00:00 2001 From: Mark Story Date: Wed, 24 Jun 2015 22:48:36 -0400 Subject: [PATCH 0415/2059] Make better use of local variable. Call target() fewer times when marshalling belongsToMany associations. Refs #6867 --- Marshaller.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index c9236263..389db164 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -283,11 +283,10 @@ protected function _belongsToMany(Association $assoc, array $data, $options = [] } $data = array_values($data); - $primaryKey = array_flip($assoc->target()->schema()->primaryKey()); - $records = []; - $conditions = []; - $primaryCount = count($primaryKey); $target = $assoc->target(); + $primaryKey = array_flip($target->schema()->primaryKey()); + $records = $conditions = []; + $primaryCount = count($primaryKey); foreach ($data as $i => $row) { if (!is_array($row)) { @@ -306,7 +305,7 @@ protected function _belongsToMany(Association $assoc, array $data, $options = [] } if (!empty($conditions)) { - $query = $assoc->target()->find(); + $query = $target->find(); $query->andWhere(function ($exp) use ($conditions) { return $exp->or_($conditions); }); From ab7b2d65e717ff6a9896ad3ea2aa55eb0a204ebf Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 27 Jun 2015 21:17:04 +0530 Subject: [PATCH 0416/2059] Use association conditions when replacing belongToMany links. This is necessary especially for polymorphic belongsToMany to ensure links for other models are not deleted. --- Association/BelongsToMany.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 1389d8e6..d94f26a4 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -733,6 +733,11 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { $existing = $hasMany->find('all') ->where(array_combine($foreignKey, $primaryValue)); + $associationConditions = $this->conditions(); + if ($associationConditions) { + $existing->andWhere($associationConditions); + } + $jointEntities = $this->_collectJointEntities($sourceEntity, $targetEntities); $inserts = $this->_diffLinks($existing, $jointEntities, $targetEntities); From b3504f949d64d95507d3ee19f4ab82a73cf1b373 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sat, 20 Jun 2015 19:47:58 +0200 Subject: [PATCH 0417/2059] Implementing a first naive version of Table::loadInto() --- Table.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Table.php b/Table.php index 83f056ba..53b61eef 100644 --- a/Table.php +++ b/Table.php @@ -2181,6 +2181,24 @@ public function buildRules(RulesChecker $rules) return $rules; } + public function loadInto($object, array $contain) + { + $query = $this + ->find() + ->where(['id' => $object->id]) + ->contain($contain); + + $loaded = $query->first(); + $assocs = $this->associations(); + foreach ($query->contain() as $assoc => $config) { + $property = $assocs->get($assoc)->property(); + $object->set($property, $loaded->get($property), ['useSetters' => false]); + $object->dirty($property, false); + } + + return $object; + } + /** * Returns an array that can be used to describe the internal state of this * object. From 9719972c7d7bee0670e08e117d6e36989fe40f31 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Wed, 24 Jun 2015 22:11:20 +0200 Subject: [PATCH 0418/2059] Refatoring Table::loadInto() for start accepting a list of objects --- Table.php | 43 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/Table.php b/Table.php index 53b61eef..8fd2c4fa 100644 --- a/Table.php +++ b/Table.php @@ -16,6 +16,7 @@ use ArrayObject; use BadMethodCallException; +use Cake\Collection\Collection; use Cake\Core\App; use Cake\Database\Connection; use Cake\Database\Schema\Table as Schema; @@ -2181,22 +2182,46 @@ public function buildRules(RulesChecker $rules) return $rules; } - public function loadInto($object, array $contain) + public function loadInto($objects, array $contain) { + $returnSingle = false; + + if ($objects instanceof EntityInterface) { + $objects = [$objects]; + $returnSingle = true; + } + + $objects = new Collection($objects); + $primaryKey = $this->primaryKey(); + $method = is_string($primaryKey) ? 'get' : 'extract'; + + $keys = $objects->map(function ($entity) use ($primaryKey, $method) { + return $entity->{$method}($primaryKey); + }); + $query = $this ->find() - ->where(['id' => $object->id]) + ->where(function ($exp) use ($primaryKey, $keys) { + return $exp->in($this->primaryKey(), $keys->toList()); + }) ->contain($contain); - $loaded = $query->first(); $assocs = $this->associations(); - foreach ($query->contain() as $assoc => $config) { - $property = $assocs->get($assoc)->property(); - $object->set($property, $loaded->get($property), ['useSetters' => false]); - $object->dirty($property, false); - } + $contain = $query->contain(); + $results = $query->indexBy($primaryKey)->toArray(); + $objects = $objects + ->indexBy($primaryKey) + ->map(function ($object, $key) use ($results, $contain, $assocs) { + $loaded = $results[$key]; + foreach ($contain as $assoc => $config) { + $property = $assocs->get($assoc)->property(); + $object->set($property, $loaded->get($property), ['useSetters' => false]); + $object->dirty($property, false); + } + return $object; + }); - return $object; + return $returnSingle ? $objects->first() : $objects->toList(); } /** From 244750d6818a520fb2ab4b689e75e30f2c5a96c5 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 28 Jun 2015 12:19:14 +0200 Subject: [PATCH 0419/2059] Fixing bugs and adding more tests for loadInto() --- Table.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Table.php b/Table.php index 8fd2c4fa..2464b566 100644 --- a/Table.php +++ b/Table.php @@ -2202,16 +2202,21 @@ public function loadInto($objects, array $contain) $query = $this ->find() ->where(function ($exp) use ($primaryKey, $keys) { - return $exp->in($this->primaryKey(), $keys->toList()); + return $exp->in($this->aliasField($this->primaryKey()), $keys->toList()); }) ->contain($contain); $assocs = $this->associations(); $contain = $query->contain(); $results = $query->indexBy($primaryKey)->toArray(); + $primaryKey = (array)$primaryKey; $objects = $objects - ->indexBy($primaryKey) - ->map(function ($object, $key) use ($results, $contain, $assocs) { + ->map(function ($object) use ($results, $contain, $assocs, $primaryKey) { + $key = implode(';', $object->extract($primaryKey)); + if (!isset($results[$key])) { + return $object; + } + $loaded = $results[$key]; foreach ($contain as $assoc => $config) { $property = $assocs->get($assoc)->property(); From 9936fc075e7bf30c0474bca1987bb34ed084e56d Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 28 Jun 2015 12:24:10 +0200 Subject: [PATCH 0420/2059] small refactoring --- Table.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Table.php b/Table.php index 2464b566..d99aac11 100644 --- a/Table.php +++ b/Table.php @@ -2206,12 +2206,20 @@ public function loadInto($objects, array $contain) }) ->contain($contain); - $assocs = $this->associations(); + $properties = (new Collection($this->associations())) + ->indexBy(function ($assoc) { + return $assoc->name(); + }) + ->map(function ($assoc) { + return $assoc->property(); + }) + ->toArray(); + $contain = $query->contain(); $results = $query->indexBy($primaryKey)->toArray(); $primaryKey = (array)$primaryKey; $objects = $objects - ->map(function ($object) use ($results, $contain, $assocs, $primaryKey) { + ->map(function ($object) use ($results, $contain, $properties, $primaryKey) { $key = implode(';', $object->extract($primaryKey)); if (!isset($results[$key])) { return $object; @@ -2219,7 +2227,7 @@ public function loadInto($objects, array $contain) $loaded = $results[$key]; foreach ($contain as $assoc => $config) { - $property = $assocs->get($assoc)->property(); + $property = $properties[$assoc]; $object->set($property, $loaded->get($property), ['useSetters' => false]); $object->dirty($property, false); } From 44c975e2703bad5fb467705e6e355a633f1dbdb2 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 28 Jun 2015 12:55:12 +0200 Subject: [PATCH 0421/2059] Adding composite key support to loadInto() --- Table.php | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index d99aac11..2eff91cf 100644 --- a/Table.php +++ b/Table.php @@ -2202,7 +2202,16 @@ public function loadInto($objects, array $contain) $query = $this ->find() ->where(function ($exp) use ($primaryKey, $keys) { - return $exp->in($this->aliasField($this->primaryKey()), $keys->toList()); + if (is_array($primaryKey) && count($primaryKey) === 1) { + $primaryKey = current($primaryKey); + } + + if (is_string($primaryKey)) { + return $exp->in($this->aliasField($primaryKey), $keys->toList()); + } + + $primaryKey = array_map([$this, 'aliasField'], $primaryKey); + return new \Cake\Database\Expression\TupleComparison($primaryKey, $keys->toList()); }) ->contain($contain); @@ -2216,8 +2225,13 @@ public function loadInto($objects, array $contain) ->toArray(); $contain = $query->contain(); - $results = $query->indexBy($primaryKey)->toArray(); $primaryKey = (array)$primaryKey; + $results = $query + ->indexBy(function ($e) use ($primaryKey) { + return implode(';', $e->extract($primaryKey)); + }) + ->toArray(); + $objects = $objects ->map(function ($object) use ($results, $contain, $properties, $primaryKey) { $key = implode(';', $object->extract($primaryKey)); From e574c4057cc43d60c5fecc13628a76a9404cb699 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 28 Jun 2015 13:31:33 +0200 Subject: [PATCH 0422/2059] Extracting loadInto implementation to another class --- LazyEagerLoader.php | 95 +++++++++++++++++++++++++++++++++++++++++++++ Table.php | 67 +------------------------------- 2 files changed, 96 insertions(+), 66 deletions(-) create mode 100644 LazyEagerLoader.php diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php new file mode 100644 index 00000000..1175737d --- /dev/null +++ b/LazyEagerLoader.php @@ -0,0 +1,95 @@ +primaryKey(); + $method = is_string($primaryKey) ? 'get' : 'extract'; + + $keys = $objects->map(function ($entity) use ($primaryKey, $method) { + return $entity->{$method}($primaryKey); + }); + + $query = $source + ->find() + ->where(function ($exp) use ($primaryKey, $keys, $source) { + if (is_array($primaryKey) && count($primaryKey) === 1) { + $primaryKey = current($primaryKey); + } + + if (is_string($primaryKey)) { + return $exp->in($source->aliasField($primaryKey), $keys->toList()); + } + + $primaryKey = array_map([$source, 'aliasField'], $primaryKey); + return new TupleComparison($primaryKey, $keys->toList()); + }) + ->contain($contain); + + $properties = (new Collection($source->associations())) + ->indexBy(function ($assoc) { + return $assoc->name(); + }) + ->map(function ($assoc) { + return $assoc->property(); + }) + ->toArray(); + + $contain = $query->contain(); + $primaryKey = (array)$primaryKey; + $results = $query + ->indexBy(function ($e) use ($primaryKey) { + return implode(';', $e->extract($primaryKey)); + }) + ->toArray(); + + $objects = $objects + ->map(function ($object) use ($results, $contain, $properties, $primaryKey) { + $key = implode(';', $object->extract($primaryKey)); + if (!isset($results[$key])) { + return $object; + } + + $loaded = $results[$key]; + foreach ($contain as $assoc => $config) { + $property = $properties[$assoc]; + $object->set($property, $loaded->get($property), ['useSetters' => false]); + $object->dirty($property, false); + } + return $object; + }); + + return $returnSingle ? $objects->first() : $objects->toList(); + } + +} diff --git a/Table.php b/Table.php index 2eff91cf..7e6fd7a4 100644 --- a/Table.php +++ b/Table.php @@ -16,7 +16,6 @@ use ArrayObject; use BadMethodCallException; -use Cake\Collection\Collection; use Cake\Core\App; use Cake\Database\Connection; use Cake\Database\Schema\Table as Schema; @@ -2184,71 +2183,7 @@ public function buildRules(RulesChecker $rules) public function loadInto($objects, array $contain) { - $returnSingle = false; - - if ($objects instanceof EntityInterface) { - $objects = [$objects]; - $returnSingle = true; - } - - $objects = new Collection($objects); - $primaryKey = $this->primaryKey(); - $method = is_string($primaryKey) ? 'get' : 'extract'; - - $keys = $objects->map(function ($entity) use ($primaryKey, $method) { - return $entity->{$method}($primaryKey); - }); - - $query = $this - ->find() - ->where(function ($exp) use ($primaryKey, $keys) { - if (is_array($primaryKey) && count($primaryKey) === 1) { - $primaryKey = current($primaryKey); - } - - if (is_string($primaryKey)) { - return $exp->in($this->aliasField($primaryKey), $keys->toList()); - } - - $primaryKey = array_map([$this, 'aliasField'], $primaryKey); - return new \Cake\Database\Expression\TupleComparison($primaryKey, $keys->toList()); - }) - ->contain($contain); - - $properties = (new Collection($this->associations())) - ->indexBy(function ($assoc) { - return $assoc->name(); - }) - ->map(function ($assoc) { - return $assoc->property(); - }) - ->toArray(); - - $contain = $query->contain(); - $primaryKey = (array)$primaryKey; - $results = $query - ->indexBy(function ($e) use ($primaryKey) { - return implode(';', $e->extract($primaryKey)); - }) - ->toArray(); - - $objects = $objects - ->map(function ($object) use ($results, $contain, $properties, $primaryKey) { - $key = implode(';', $object->extract($primaryKey)); - if (!isset($results[$key])) { - return $object; - } - - $loaded = $results[$key]; - foreach ($contain as $assoc => $config) { - $property = $properties[$assoc]; - $object->set($property, $loaded->get($property), ['useSetters' => false]); - $object->dirty($property, false); - } - return $object; - }); - - return $returnSingle ? $objects->first() : $objects->toList(); + return (new LazyEagerLoader)->loadInto($objects, $contain, $this); } /** From 3e5cb4e9cd71f37f7946854aa1c88a2727856e45 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 28 Jun 2015 15:09:08 +0200 Subject: [PATCH 0423/2059] Refactoring LazyEagerLoader --- LazyEagerLoader.php | 70 ++++++++++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 29 deletions(-) diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 1175737d..ba2ea5e1 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -33,6 +33,15 @@ public function loadInto($objects, array $contain, Table $source) } $objects = new Collection($objects); + $query = $this->_getQuery($objects, $contain, $source); + $associations = array_keys($query->contain()); + + $objects = $this->_injectResults($objects, $query, $associations, $source); + return $returnSingle ? array_shift($objects) : $objects; + } + + protected function _getQuery($objects, $contain, $source) + { $primaryKey = $source->primaryKey(); $method = is_string($primaryKey) ? 'get' : 'extract'; @@ -40,7 +49,7 @@ public function loadInto($objects, array $contain, Table $source) return $entity->{$method}($primaryKey); }); - $query = $source + return $source ->find() ->where(function ($exp) use ($primaryKey, $keys, $source) { if (is_array($primaryKey) && count($primaryKey) === 1) { @@ -55,41 +64,44 @@ public function loadInto($objects, array $contain, Table $source) return new TupleComparison($primaryKey, $keys->toList()); }) ->contain($contain); + } - $properties = (new Collection($source->associations())) - ->indexBy(function ($assoc) { - return $assoc->name(); - }) - ->map(function ($assoc) { - return $assoc->property(); - }) - ->toArray(); + protected function _getPropertyMap($source, $associations) + { + $map = []; + foreach ($associations as $assoc) { + $map[$assoc] = $source->associations()->get($assoc)->property(); + } + return $map; + } - $contain = $query->contain(); - $primaryKey = (array)$primaryKey; - $results = $query + protected function _injectResults($objects, $results, $associations, $source) + { + $injected = []; + $properties = $this->_getPropertyMap($source, $associations); + $primaryKey = (array)$source->primaryKey(); + $results = $results ->indexBy(function ($e) use ($primaryKey) { return implode(';', $e->extract($primaryKey)); }) ->toArray(); - $objects = $objects - ->map(function ($object) use ($results, $contain, $properties, $primaryKey) { - $key = implode(';', $object->extract($primaryKey)); - if (!isset($results[$key])) { - return $object; - } - - $loaded = $results[$key]; - foreach ($contain as $assoc => $config) { - $property = $properties[$assoc]; - $object->set($property, $loaded->get($property), ['useSetters' => false]); - $object->dirty($property, false); - } - return $object; - }); + foreach ($objects as $k => $object) { + $key = implode(';', $object->extract($primaryKey)); + if (!isset($results[$key])) { + $injected[$k] = $object; + continue; + } + + $loaded = $results[$key]; + foreach ($associations as $assoc) { + $property = $properties[$assoc]; + $object->set($property, $loaded->get($property), ['useSetters' => false]); + $object->dirty($property, false); + } + $injected[$k] = $object; + } - return $returnSingle ? $objects->first() : $objects->toList(); + return $injected; } - } From 7bc78a248bb1578fa622819c92e813157296bf04 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 28 Jun 2015 15:41:46 +0200 Subject: [PATCH 0424/2059] Optimized column fetching in loadInto() --- LazyEagerLoader.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index ba2ea5e1..804e2d49 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -49,8 +49,9 @@ protected function _getQuery($objects, $contain, $source) return $entity->{$method}($primaryKey); }); - return $source + $query = $source ->find() + ->select((array)$primaryKey) ->where(function ($exp) use ($primaryKey, $keys, $source) { if (is_array($primaryKey) && count($primaryKey) === 1) { $primaryKey = current($primaryKey); @@ -64,6 +65,14 @@ protected function _getQuery($objects, $contain, $source) return new TupleComparison($primaryKey, $keys->toList()); }) ->contain($contain); + + foreach ($query->eagerLoader()->attachableAssociations($source) as $loadable) { + $config = $loadable->config(); + $config['includeFields'] = true; + $loadable->config($config); + } + + return $query; } protected function _getPropertyMap($source, $associations) From d16409f1e925f85d4ee4325763b4d297b1266b93 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 28 Jun 2015 16:01:37 +0200 Subject: [PATCH 0425/2059] Documenting Table::loadInto() --- Table.php | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index 7e6fd7a4..ee8a2a86 100644 --- a/Table.php +++ b/Table.php @@ -2181,9 +2181,39 @@ public function buildRules(RulesChecker $rules) return $rules; } - public function loadInto($objects, array $contain) + /** + * Loads the specified associations in the passed entity or list of entities + * by executing extra queries in the database and merging the results in the + * appropriate properties. + * + * ### Example: + * + * ``` + * $user = $usersTable->get(1); + * $user = $usersTable->loadInto($user, ['Articles.Tags', 'Articles.Comments']); + * echo $user->articles[0]->title; + * ``` + * + * You can also load associations for multiple entities at once + * + * ### Example: + * + * ``` + * $users = $usersTable->find()->where([...])->toList(); + * $users = $usersTable->loadInto($users, ['Articles.Tags', 'Articles.Comments']); + * echo $user[1]->articles[0]->title; + * ``` + * + * The properties for the associations to be loaded will be overwritten on each entity. + * + * @param Cake\Datasource\EntityInterface|array $entities a single entity or list of entities + * @param array $contain A `contain()` compatible array. + * @see Cake\ORM\Query\contain() + * @return Cake\Datasource\EntityInterface|array + */ + public function loadInto($entities, array $contain) { - return (new LazyEagerLoader)->loadInto($objects, $contain, $this); + return (new LazyEagerLoader)->loadInto($entities, $contain, $this); } /** From 186572443922ffeedde2998a7323a5c502ba2068 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 28 Jun 2015 16:37:21 +0200 Subject: [PATCH 0426/2059] Documenting LazyEagerLoader --- LazyEagerLoader.php | 58 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 6 deletions(-) diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 804e2d49..62179ebb 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -20,26 +20,54 @@ use Cake\ORM\Table; +/** + * Contains methods that are capable of injecting eagerly loaded associations into + * entities or lists of entities by using the same syntax as the EagerLoader. + * + * @internal + */ class LazyEagerLoader { - public function loadInto($objects, array $contain, Table $source) + /** + * Loads the specified associations in the passed entity or list of entities + * by executing extra queries in the database and merging the results in the + * appropriate properties. + * + * The properties for the associations to be loaded will be overwritten on each entity. + * + * @param Cake\Datasource\EntityInterface|array $entities a single entity or list of entities + * @param array $contain A `contain()` compatible array. + * @see Cake\ORM\Query\contain() + * @param Cake\ORM\Table The table to use for fetching the top level entities + * @return Cake\Datasource\EntityInterface|array + */ + public function loadInto($entities, array $contain, Table $source) { $returnSingle = false; - if ($objects instanceof EntityInterface) { - $objects = [$objects]; + if ($entities instanceof EntityInterface) { + $entities = [$entities]; $returnSingle = true; } - $objects = new Collection($objects); + $entities = new Collection($entities); $query = $this->_getQuery($objects, $contain, $source); $associations = array_keys($query->contain()); - $objects = $this->_injectResults($objects, $query, $associations, $source); - return $returnSingle ? array_shift($objects) : $objects; + $entities = $this->_injectResults($entities, $query, $associations, $source); + return $returnSingle ? array_shift($entities) : $entities; } + /** + * Builds a query for loading the passed list of entity objects along with the + * associations specified in $contain. + * + * @param Cake\Collection\CollectionInterface $objects + * @param array $contain The associations to be loaded + * @param Cake\ORM\Table The table to use for fetching the top level entities + * @return Cake\ORM\Query + */ protected function _getQuery($objects, $contain, $source) { $primaryKey = $source->primaryKey(); @@ -75,6 +103,14 @@ protected function _getQuery($objects, $contain, $source) return $query; } + /** + * Returns a map of property names where the association results should be injected + * in the top level entities. + * + * @param Cake\ORM\Table $table The table having the top level associations + * @param array $associations The name of the top level associations + * @return array + */ protected function _getPropertyMap($source, $associations) { $map = []; @@ -84,6 +120,16 @@ protected function _getPropertyMap($source, $associations) return $map; } + /** + * Injects the results of the eager loader query into the original list of + * entities. + * + * @param array|Traversable $objects The original list of entities + * @param Cake\Collection\CollectionInterface $results The loaded results + * @param array $associations The top level associations that were loaded + * @param Cake\ORM\Table $table The table where the entities came from + * @return array + */ protected function _injectResults($objects, $results, $associations, $source) { $injected = []; From f30e696994c1bab6f36d4adc8926e54526f3f9e1 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 28 Jun 2015 16:43:23 +0200 Subject: [PATCH 0427/2059] Fixed variable renaming --- LazyEagerLoader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 62179ebb..e0c56610 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -52,7 +52,7 @@ public function loadInto($entities, array $contain, Table $source) } $entities = new Collection($entities); - $query = $this->_getQuery($objects, $contain, $source); + $query = $this->_getQuery($entities, $contain, $source); $associations = array_keys($query->contain()); $entities = $this->_injectResults($entities, $query, $associations, $source); From 31d0952acde4e879567823c2ab13266dbfc71b07 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 28 Jun 2015 17:57:48 +0200 Subject: [PATCH 0428/2059] Fixed CS errors --- LazyEagerLoader.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index e0c56610..0e9d5b13 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -19,7 +19,6 @@ use Cake\Datasource\EntityInterface; use Cake\ORM\Table; - /** * Contains methods that are capable of injecting eagerly loaded associations into * entities or lists of entities by using the same syntax as the EagerLoader. @@ -39,7 +38,7 @@ class LazyEagerLoader * @param Cake\Datasource\EntityInterface|array $entities a single entity or list of entities * @param array $contain A `contain()` compatible array. * @see Cake\ORM\Query\contain() - * @param Cake\ORM\Table The table to use for fetching the top level entities + * @param Cake\ORM\Table $source The table to use for fetching the top level entities * @return Cake\Datasource\EntityInterface|array */ public function loadInto($entities, array $contain, Table $source) @@ -52,7 +51,7 @@ public function loadInto($entities, array $contain, Table $source) } $entities = new Collection($entities); - $query = $this->_getQuery($entities, $contain, $source); + $query = $this->_getQuery($entities, $contain, $source); $associations = array_keys($query->contain()); $entities = $this->_injectResults($entities, $query, $associations, $source); @@ -65,7 +64,7 @@ public function loadInto($entities, array $contain, Table $source) * * @param Cake\Collection\CollectionInterface $objects * @param array $contain The associations to be loaded - * @param Cake\ORM\Table The table to use for fetching the top level entities + * @param Cake\ORM\Table $source The table to use for fetching the top level entities * @return Cake\ORM\Query */ protected function _getQuery($objects, $contain, $source) @@ -107,7 +106,7 @@ protected function _getQuery($objects, $contain, $source) * Returns a map of property names where the association results should be injected * in the top level entities. * - * @param Cake\ORM\Table $table The table having the top level associations + * @param Cake\ORM\Table $source The table having the top level associations * @param array $associations The name of the top level associations * @return array */ @@ -127,7 +126,7 @@ protected function _getPropertyMap($source, $associations) * @param array|Traversable $objects The original list of entities * @param Cake\Collection\CollectionInterface $results The loaded results * @param array $associations The top level associations that were loaded - * @param Cake\ORM\Table $table The table where the entities came from + * @param Cake\ORM\Table $source The table where the entities came from * @return array */ protected function _injectResults($objects, $results, $associations, $source) From ed01ea57d96998d463faad5d3f7d02e4d6ac4a37 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 28 Jun 2015 18:05:59 +0200 Subject: [PATCH 0429/2059] Fixing composite key tests in mysql --- LazyEagerLoader.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 0e9d5b13..4c72e9e6 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -79,7 +79,7 @@ protected function _getQuery($objects, $contain, $source) $query = $source ->find() ->select((array)$primaryKey) - ->where(function ($exp) use ($primaryKey, $keys, $source) { + ->where(function ($exp, $q) use ($primaryKey, $keys, $source) { if (is_array($primaryKey) && count($primaryKey) === 1) { $primaryKey = current($primaryKey); } @@ -88,11 +88,13 @@ protected function _getQuery($objects, $contain, $source) return $exp->in($source->aliasField($primaryKey), $keys->toList()); } + $types = array_intersect_key($q->defaultTypes(), array_flip($primaryKey)); $primaryKey = array_map([$source, 'aliasField'], $primaryKey); - return new TupleComparison($primaryKey, $keys->toList()); + return new TupleComparison($primaryKey, $keys->toList(), $types, 'IN'); }) ->contain($contain); + foreach ($query->eagerLoader()->attachableAssociations($source) as $loadable) { $config = $loadable->config(); $config['includeFields'] = true; From a3950396cb210e860228e1f4f54f48a6bde5f1f9 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 28 Jun 2015 19:15:33 +0200 Subject: [PATCH 0430/2059] Improving doc block --- LazyEagerLoader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 4c72e9e6..2d5b5794 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -62,7 +62,7 @@ public function loadInto($entities, array $contain, Table $source) * Builds a query for loading the passed list of entity objects along with the * associations specified in $contain. * - * @param Cake\Collection\CollectionInterface $objects + * @param Cake\Collection\CollectionInterface $objects The original entitites * @param array $contain The associations to be loaded * @param Cake\ORM\Table $source The table to use for fetching the top level entities * @return Cake\ORM\Query From b0811b7050a9f52bc90d00e1c9bd33fae65570ba Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 28 Jun 2015 19:16:36 +0200 Subject: [PATCH 0431/2059] Extracting function call into local variable --- LazyEagerLoader.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 2d5b5794..eb412977 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -115,8 +115,9 @@ protected function _getQuery($objects, $contain, $source) protected function _getPropertyMap($source, $associations) { $map = []; + $container = $source->associations(); foreach ($associations as $assoc) { - $map[$assoc] = $source->associations()->get($assoc)->property(); + $map[$assoc] = $container->get($assoc)->property(); } return $map; } From 161ca1e946893adc2e0636a0c5cad65a06a4c125 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Wed, 1 Jul 2015 13:51:48 +0200 Subject: [PATCH 0432/2059] Initial implementation of Association::bindingKey() --- Association.php | 32 ++++++++++++++++++++++ Association/BelongsTo.php | 14 +++++----- Association/DependentDeleteTrait.php | 4 +-- Association/HasMany.php | 2 +- Association/HasOne.php | 2 +- Association/SelectableAssociationTrait.php | 8 +++--- 6 files changed, 47 insertions(+), 15 deletions(-) diff --git a/Association.php b/Association.php index 63267975..fe1b0d12 100644 --- a/Association.php +++ b/Association.php @@ -99,6 +99,13 @@ abstract class Association */ protected $_className; + /** + * The field name in the owning side table that is used to match with the foreignKey + * + * @var string|array + */ + protected $_bindingKey; + /** * The name of the field representing the foreign key to the table to load * @@ -196,6 +203,7 @@ public function __construct($alias, array $options = []) 'conditions', 'dependent', 'finder', + 'bindingKey', 'foreignKey', 'joinType', 'propertyName', @@ -316,6 +324,30 @@ public function conditions($conditions = null) return $this->_conditions; } + /** + * Sets the name of the field representing the binding field with the target table. + * When no manually specified the primary key of the owning side table is used. + * + * If no parameters are passed the current field is returned + * + * @param string|null $key the table field to be used to link both tables together + * @return string|array + */ + public function bindingKey($key = null) + { + if ($key !== null) { + $this->_bindingKey = $key; + } + + if ($this->_bindingKey === null) { + $this->_bindingKey = $this->isOwningSide($this->source()) ? + $this->source()->primaryKey() : + $this->target()->primaryKey(); + } + + return $this->_bindingKey; + } + /** * Sets the name of the field representing the foreign key to the target table. * If no parameters are passed the current field is returned diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index e8653157..ca02e35d 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -143,7 +143,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) $properties = array_combine( (array)$this->foreignKey(), - $targetEntity->extract((array)$table->primaryKey()) + $targetEntity->extract((array)$this->bindingKey()) ); $entity->set($properties, ['guard' => false]); return $entity; @@ -164,20 +164,20 @@ protected function _joinCondition($options) $tAlias = $this->target()->alias(); $sAlias = $this->_sourceTable->alias(); $foreignKey = (array)$options['foreignKey']; - $primaryKey = (array)$this->_targetTable->primaryKey(); + $bindingKey = (array)$this->bindingKey(); - if (count($foreignKey) !== count($primaryKey)) { + if (count($foreignKey) !== count($bindingKey)) { $msg = 'Cannot match provided foreignKey for "%s", got "(%s)" but expected foreign key for "(%s)"'; throw new RuntimeException(sprintf( $msg, $this->_name, implode(', ', $foreignKey), - implode(', ', $primaryKey) + implode(', ', $bindingKey) )); } foreach ($foreignKey as $k => $f) { - $field = sprintf('%s.%s', $tAlias, $primaryKey[$k]); + $field = sprintf('%s.%s', $tAlias, $bindingKey[$k]); $value = new IdentifierExpression(sprintf('%s.%s', $sAlias, $f)); $conditions[$field] = $value; } @@ -193,7 +193,7 @@ protected function _linkField($options) $links = []; $name = $this->alias(); - foreach ((array)$this->target()->primaryKey() as $key) { + foreach ((array)$this->bindin as $key) { $links[] = sprintf('%s.%s', $name, $key); } @@ -210,7 +210,7 @@ protected function _linkField($options) protected function _buildResultMap($fetchQuery, $options) { $resultMap = []; - $key = (array)$this->target()->primaryKey(); + $key = (array)$this->bindingKey(); foreach ($fetchQuery->all() as $result) { $values = []; diff --git a/Association/DependentDeleteTrait.php b/Association/DependentDeleteTrait.php index 517cb73c..b83cc2c3 100644 --- a/Association/DependentDeleteTrait.php +++ b/Association/DependentDeleteTrait.php @@ -40,8 +40,8 @@ public function cascadeDelete(EntityInterface $entity, array $options = []) } $table = $this->target(); $foreignKey = (array)$this->foreignKey(); - $primaryKey = (array)$this->source()->primaryKey(); - $conditions = array_combine($foreignKey, $entity->extract($primaryKey)); + $bindingKey = (array)$this->bindingKey(); + $conditions = array_combine($foreignKey, $entity->extract($bindingKey)); if ($this->_cascadeCallbacks) { $query = $this->find('all')->where($conditions); diff --git a/Association/HasMany.php b/Association/HasMany.php index cc6f33b4..fa09485d 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -98,7 +98,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) $properties = array_combine( (array)$this->foreignKey(), - $entity->extract((array)$this->source()->primaryKey()) + $entity->extract((array)$this->bindingKey()) ); $target = $this->target(); $original = $targetEntities; diff --git a/Association/HasOne.php b/Association/HasOne.php index af6dad73..6c06ac3f 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -123,7 +123,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) $properties = array_combine( (array)$this->foreignKey(), - $entity->extract((array)$this->source()->primaryKey()) + $entity->extract((array)$this->bindingKey()) ); $targetEntity->set($properties, ['guard' => false]); diff --git a/Association/SelectableAssociationTrait.php b/Association/SelectableAssociationTrait.php index 6a067e34..53a76357 100644 --- a/Association/SelectableAssociationTrait.php +++ b/Association/SelectableAssociationTrait.php @@ -25,8 +25,8 @@ trait SelectableAssociationTrait { /** - * Returns true if the eager loading process will require a set of parent table's - * primary keys in order to use them as a filter in the finder query. + * Returns true if the eager loading process will require a set of the owning table's + * binding keys in order to use them as a filter in the finder query. * * @param array $options The options containing the strategy to be used. * @return bool true if a list of keys will be required @@ -234,7 +234,7 @@ protected function _buildSubquery($query) $filterQuery->offset(null); } - $keys = (array)$this->source()->primaryKey(); + $keys = (array)$this->bindingKey(); if ($this->type() === $this::MANY_TO_ONE) { $keys = (array)$this->foreignKey(); @@ -271,7 +271,7 @@ protected function _resultInjector($fetchQuery, $resultMap, $options) $sAlias = $source->alias(); $keys = $this->type() === $this::MANY_TO_ONE ? $this->foreignKey() : - $source->primaryKey(); + $this->bindingKey(); $sourceKeys = []; foreach ((array)$keys as $key) { From 84c3e744551a328d491ac3fedf10a18bbc4231a7 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Wed, 1 Jul 2015 13:55:21 +0200 Subject: [PATCH 0433/2059] Fixing autocomplete typo --- Association/BelongsTo.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index ca02e35d..7859b625 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -193,7 +193,7 @@ protected function _linkField($options) $links = []; $name = $this->alias(); - foreach ((array)$this->bindin as $key) { + foreach ((array)$this->bindingKey() as $key) { $links[] = sprintf('%s.%s', $name, $key); } From 9185b1c4d38a41a82674ad6eee26a678b8aa61e6 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Wed, 1 Jul 2015 14:31:34 +0200 Subject: [PATCH 0434/2059] Using the bindingKey method in BelongsToMany --- Association/BelongsToMany.php | 16 ++++++++-------- EagerLoader.php | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index d94f26a4..ca3d0383 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -348,11 +348,11 @@ public function cascadeDelete(EntityInterface $entity, array $options = []) return true; } $foreignKey = (array)$this->foreignKey(); - $primaryKey = (array)$this->source()->primaryKey(); + $bindingKey = (array)$this->bindingKey(); $conditions = []; - if ($primaryKey) { - $conditions = array_combine($foreignKey, $entity->extract((array)$primaryKey)); + if ($bindingKey) { + $conditions = array_combine($foreignKey, $entity->extract($bindingKey)); } $table = $this->junction(); @@ -533,7 +533,7 @@ protected function _saveLinks(EntityInterface $sourceEntity, $targetEntities, $o $foreignKey = (array)$this->foreignKey(); $assocForeignKey = (array)$belongsTo->foreignKey(); $targetPrimaryKey = (array)$target->primaryKey(); - $sourcePrimaryKey = (array)$source->primaryKey(); + $bindingKey = (array)$this->bindingKey(); $jointProperty = $this->_junctionProperty; $junctionAlias = $junction->alias(); @@ -545,7 +545,7 @@ protected function _saveLinks(EntityInterface $sourceEntity, $targetEntities, $o $joint->set(array_combine( $foreignKey, - $sourceEntity->extract($sourcePrimaryKey) + $sourceEntity->extract($bindingKey) ), ['guard' => false]); $joint->set(array_combine($assocForeignKey, $e->extract($targetPrimaryKey)), ['guard' => false]); $saved = $junction->save($joint, $options); @@ -718,10 +718,10 @@ function () use ($sourceEntity, $targetEntities) { */ public function replaceLinks(EntityInterface $sourceEntity, array $targetEntities, array $options = []) { - $primaryKey = (array)$this->source()->primaryKey(); - $primaryValue = $sourceEntity->extract($primaryKey); + $bindingKey = (array)$this->bindingKey(); + $primaryValue = $sourceEntity->extract($bindingKey); - if (count(array_filter($primaryValue, 'strlen')) !== count($primaryKey)) { + if (count(array_filter($primaryValue, 'strlen')) !== count($bindingKey)) { $message = 'Could not find primary key value for source entity'; throw new InvalidArgumentException($message); } diff --git a/EagerLoader.php b/EagerLoader.php index d9aada4f..2ba81c7d 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -636,7 +636,7 @@ protected function _collectKeys($external, $query, $statement) $source = $instance->source(); $keys = $instance->type() === Association::MANY_TO_ONE ? (array)$instance->foreignKey() : - (array)$source->primaryKey(); + (array)$instance->bindingKey(); $alias = $source->alias(); $pkFields = []; From 5d6b11bd895ee57dc2086e87e0b72112ee721853 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 2 Jul 2015 11:27:31 +0200 Subject: [PATCH 0435/2059] Create transaltion entities using $table->newEntity() instead of new Entity(). --- Behavior/TranslateBehavior.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index ad9fe203..2c0b7a22 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -16,6 +16,7 @@ use ArrayObject; use Cake\Collection\Collection; +use Cake\Datasource\EntityInterface; use Cake\Event\Event; use Cake\I18n\I18n; use Cake\ORM\Behavior; @@ -247,11 +248,11 @@ public function beforeFind(Event $event, Query $query, $options) * in the database too. * * @param \Cake\Event\Event $event The beforeSave event that was fired - * @param \Cake\ORM\Entity $entity The entity that is going to be saved + * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved * @param \ArrayObject $options the options passed to the save method * @return void */ - public function beforeSave(Event $event, Entity $entity, ArrayObject $options) + public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options) { $locale = $entity->get('_locale') ?: $this->locale(); $newOptions = [$this->_translationTable->alias() => ['validate' => false]]; @@ -303,10 +304,10 @@ public function beforeSave(Event $event, Entity $entity, ArrayObject $options) * Unsets the temporary `_i18n` property after the entity has been saved * * @param \Cake\Event\Event $event The beforeSave event that was fired - * @param \Cake\ORM\Entity $entity The entity that is going to be saved + * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved * @return void */ - public function afterSave(Event $event, Entity $entity) + public function afterSave(Event $event, EntityInterface $entity) { $entity->unsetProperty('_i18n'); } @@ -443,10 +444,11 @@ public function groupTranslations($results) $result = []; foreach ($grouped->combine('field', 'content', 'locale') as $locale => $keys) { - $translation = new Entity($keys + ['locale' => $locale], [ + $translation = $this->_table->newEntity($keys + ['locale' => $locale], [ 'markNew' => false, 'useSetters' => false, - 'markClean' => true + 'markClean' => true, + 'validate' => false ]); $result[$locale] = $translation; } @@ -464,7 +466,7 @@ public function groupTranslations($results) * out of the data found in the `_translations` property in the passed * entity. The result will be put into its `_i18n` property * - * @param \Cake\ORM\Entity $entity Entity + * @param \Cake\Datasource\EntityInterface $entity Entity * @return void */ protected function _bundleTranslatedFields($entity) From 14b99d241f7697dc7c5efa7e5713c307bbd27fc4 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Thu, 2 Jul 2015 11:44:00 +0200 Subject: [PATCH 0436/2059] Fixing bindingKey for HasOne and adding test for it --- Association.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Association.php b/Association.php index fe1b0d12..fec90815 100644 --- a/Association.php +++ b/Association.php @@ -781,20 +781,20 @@ protected function _joinCondition($options) $tAlias = $this->target()->alias(); $sAlias = $this->source()->alias(); $foreignKey = (array)$options['foreignKey']; - $primaryKey = (array)$this->_sourceTable->primaryKey(); + $bindingKey = (array)$this->bindingKey(); - if (count($foreignKey) !== count($primaryKey)) { + if (count($foreignKey) !== count($bindingKey)) { $msg = 'Cannot match provided foreignKey for "%s", got "(%s)" but expected foreign key for "(%s)"'; throw new RuntimeException(sprintf( $msg, $this->_name, implode(', ', $foreignKey), - implode(', ', $primaryKey) + implode(', ', $bindingKey) )); } foreach ($foreignKey as $k => $f) { - $field = sprintf('%s.%s', $sAlias, $primaryKey[$k]); + $field = sprintf('%s.%s', $sAlias, $bindingKey[$k]); $value = new IdentifierExpression(sprintf('%s.%s', $tAlias, $f)); $conditions[$field] = $value; } From db41844a64f17c4fa2f2f478a926a1fb197c3bbe Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 2 Jul 2015 12:19:04 +0200 Subject: [PATCH 0437/2059] Use new $table->entityClass() instead of $table->newEntity() --- Behavior/TranslateBehavior.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 2c0b7a22..7b4d0041 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -444,11 +444,11 @@ public function groupTranslations($results) $result = []; foreach ($grouped->combine('field', 'content', 'locale') as $locale => $keys) { - $translation = $this->_table->newEntity($keys + ['locale' => $locale], [ + $entityClass = $this->_table->entityClass(); + $translation = new $entityClass($keys + ['locale' => $locale], [ 'markNew' => false, 'useSetters' => false, - 'markClean' => true, - 'validate' => false + 'markClean' => true ]); $result[$locale] = $translation; } From 753872346fa04a5baff7ef7f22df121c3f875543 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Lorenzo=20Rodr=C3=ADguez?= Date: Sun, 5 Jul 2015 12:08:24 +0200 Subject: [PATCH 0438/2059] Fixed typo in docblock --- Association.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association.php b/Association.php index fec90815..18cd6042 100644 --- a/Association.php +++ b/Association.php @@ -326,7 +326,7 @@ public function conditions($conditions = null) /** * Sets the name of the field representing the binding field with the target table. - * When no manually specified the primary key of the owning side table is used. + * When not manually specified the primary key of the owning side table is used. * * If no parameters are passed the current field is returned * From e8666bbcf9d35cb54c51b7edfa22b4d7b2c14597 Mon Sep 17 00:00:00 2001 From: mark_story Date: Wed, 8 Jul 2015 17:05:19 -0400 Subject: [PATCH 0439/2059] Correctly marshall _joinData when patching entities. When patching belongsToMany associations with existing target records, and including _joinData, the joinData should be correctly marshalled. Getting this to work required accepting `accessibleFields` in merge() and mergeMany(). This allows the internals of the marshaller to whitelist _joinData and not rely on the entity having it marked as accessible. Refs #6971 --- Marshaller.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Marshaller.php b/Marshaller.php index 389db164..efe7f903 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -408,6 +408,7 @@ protected function _loadBelongsToMany($assoc, $ids) * also be set to a string to use a specific validator. Defaults to true/default. * * fieldList: A whitelist of fields to be assigned to the entity. If not present * the accessible fields list in the entity will be used. + * * accessibleFields: A list of fields to allow or deny in entity accessible fields. * * @param \Cake\Datasource\EntityInterface $entity the entity that will get the * data merged in @@ -427,6 +428,12 @@ public function merge(EntityInterface $entity, array $data, array $options = []) $keys = $entity->extract((array)$this->_table->primaryKey()); } + if (isset($options['accessibleFields'])) { + foreach ((array)$options['accessibleFields'] as $key => $value) { + $entity->accessible($key, $value); + } + } + $errors = $this->_validate($data + $keys, $options, $isNew); $schema = $this->_table->schema(); $properties = $marshalledAssocs = []; @@ -501,6 +508,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) * - associated: Associations listed here will be marshalled as well. * - fieldList: A whitelist of fields to be assigned to the entity. If not present, * the accessible fields list in the entity will be used. + * - accessibleFields: A list of fields to allow or deny in entity accessible fields. * * @param array|\Traversable $entities the entities that will get the * data merged in @@ -660,6 +668,7 @@ protected function _mergeJoinData($original, $assoc, $value, $options) $nested = (array)$associated['_joinData']; } + $options['accessibleFields'] = ['_joinData' => true]; $records = $this->mergeMany($original, $value, $options); foreach ($records as $record) { $hash = spl_object_hash($record); From 37d6a9bb20584e3f3361e6225cc1761eb389c523 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Wed, 15 Jul 2015 11:43:07 +0200 Subject: [PATCH 0440/2059] Marking an enty as clean after the afterSave event. By letting the entity be dirty during the event, listeners can inspect the changes done to the entity during the save process. I think this is a very important information to have and does not negatively affect anything else. --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index f151c16b..29902e22 100644 --- a/Table.php +++ b/Table.php @@ -1445,8 +1445,8 @@ protected function _processSave($entity, $options) ['_primary' => false] + $options->getArrayCopy() ); if ($success || !$options['atomic']) { - $entity->clean(); $this->dispatchEvent('Model.afterSave', compact('entity', 'options')); + $entity->clean(); if (!$options['atomic'] && !$options['_primary']) { $entity->isNew(false); $entity->source($this->registryAlias()); From 6492776e6f641f72489fcf8f0dc987d8c2fe1d77 Mon Sep 17 00:00:00 2001 From: Yasuo Harada Date: Wed, 15 Jul 2015 22:23:31 +0900 Subject: [PATCH 0441/2059] Fixed bug TreeBehavior::beforeSave() SetLevelExistingNode --- Behavior/TreeBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 15a341ba..b6921662 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -126,7 +126,7 @@ public function beforeSave(Event $event, Entity $entity) if ($level) { $parentNode = $this->_getNode($parent); - $entity->set($config[$level], $parentNode[$level] + 1); + $entity->set($level, $parentNode[$level] + 1); } return; } From 0b9763962823c0c220722a755018acc92ed2b64d Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Wed, 15 Jul 2015 17:15:02 +0200 Subject: [PATCH 0442/2059] Avoding wastful UPDATE statements for HasMany associations This prevents the ORM from issueing an update statement to the database if no actual changes could be found --- Association/HasMany.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index fa09485d..682076e5 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -96,8 +96,9 @@ public function saveAssociated(EntityInterface $entity, array $options = []) throw new InvalidArgumentException($message); } + $foreignKey = (array)$this->foreignKey(); $properties = array_combine( - (array)$this->foreignKey(), + $foreignKey, $entity->extract((array)$this->bindingKey()) ); $target = $this->target(); @@ -113,7 +114,10 @@ public function saveAssociated(EntityInterface $entity, array $options = []) $targetEntity = clone $targetEntity; } - $targetEntity->set($properties, ['guard' => false]); + if ($properties !== $targetEntity->extract($foreignKey)) { + $targetEntity->set($properties, ['guard' => false]); + } + if ($target->save($targetEntity, $options)) { $targetEntities[$k] = $targetEntity; continue; From 6bdcfa691061ac96c32195cbcfe60ce55b4378cd Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Thu, 16 Jul 2015 11:02:25 +0200 Subject: [PATCH 0443/2059] Avoiding bonus UPDATE statements when saving belongsToMny associations --- Association/BelongsToMany.php | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index ca3d0383..1671530f 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -543,11 +543,17 @@ protected function _saveLinks(EntityInterface $sourceEntity, $targetEntities, $o $joint = new $entityClass([], ['markNew' => true, 'source' => $junctionAlias]); } - $joint->set(array_combine( - $foreignKey, - $sourceEntity->extract($bindingKey) - ), ['guard' => false]); - $joint->set(array_combine($assocForeignKey, $e->extract($targetPrimaryKey)), ['guard' => false]); + $sourceKeys = array_combine($foreignKey, $sourceEntity->extract($bindingKey)); + $targetKeys = array_combine($assocForeignKey, $e->extract($targetPrimaryKey)); + + if ($sourceKeys !== $joint->extract($foreignKey)) { + $joint->set($sourceKeys, ['guard' => false]); + } + + if ($targetKeys !== $joint->extract($assocForeignKey)) { + $joint->set($targetKeys, ['guard' => false]); + } + $saved = $junction->save($joint, $options); if (!$saved && !empty($options['atomic'])) { From 913b43e06616e99f91a3e2d9dd96a8cd6396b712 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 20 Jul 2015 16:02:27 +0200 Subject: [PATCH 0444/2059] Make registries implement EventDispatcherInterface. --- BehaviorRegistry.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 46876a56..7e092aa8 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -17,6 +17,7 @@ use BadMethodCallException; use Cake\Core\App; use Cake\Core\ObjectRegistry; +use Cake\Event\EventDispatcherInterface; use Cake\Event\EventManagerTrait; use Cake\ORM\Behavior; use Cake\ORM\Exception\MissingBehaviorException; @@ -29,7 +30,7 @@ * * This class also provides method for checking and dispatching behavior methods. */ -class BehaviorRegistry extends ObjectRegistry +class BehaviorRegistry extends ObjectRegistry implements EventDispatcherInterface { use EventManagerTrait; From 54ff37335001ad6c751338253e28543b41d52b3f Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 20 Jul 2015 15:50:51 +0200 Subject: [PATCH 0445/2059] Renamed EventManagerTrait to EventDispatcherTrait to better reflect that it implements EventDispatcherInterface. --- BehaviorRegistry.php | 4 ++-- Table.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 7e092aa8..20486cc3 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -18,7 +18,7 @@ use Cake\Core\App; use Cake\Core\ObjectRegistry; use Cake\Event\EventDispatcherInterface; -use Cake\Event\EventManagerTrait; +use Cake\Event\EventDispatcherTrait; use Cake\ORM\Behavior; use Cake\ORM\Exception\MissingBehaviorException; use Cake\ORM\Table; @@ -33,7 +33,7 @@ class BehaviorRegistry extends ObjectRegistry implements EventDispatcherInterface { - use EventManagerTrait; + use EventDispatcherTrait; /** * The table using this registry. diff --git a/Table.php b/Table.php index 29902e22..b96a813b 100644 --- a/Table.php +++ b/Table.php @@ -27,7 +27,7 @@ use Cake\Event\EventDispatcherInterface; use Cake\Event\EventListenerInterface; use Cake\Event\EventManager; -use Cake\Event\EventManagerTrait; +use Cake\Event\EventDispatcherTrait; use Cake\ORM\AssociationCollection; use Cake\ORM\Association\BelongsTo; use Cake\ORM\Association\BelongsToMany; @@ -122,7 +122,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatcherInterface { - use EventManagerTrait; + use EventDispatcherTrait; use RulesAwareTrait; use ValidatorAwareTrait; From 75c72ba179c953275c093df4bdddee0683e56613 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 21 Jul 2015 15:12:00 +0200 Subject: [PATCH 0446/2059] Allow for custom finder name in Table::get(). --- Table.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index 29902e22..c138d11c 100644 --- a/Table.php +++ b/Table.php @@ -1158,9 +1158,10 @@ public function get($primaryKey, $options = []) $cacheConfig = isset($options['cache']) ? $options['cache'] : false; $cacheKey = isset($options['key']) ? $options['key'] : false; - unset($options['key'], $options['cache']); + $finderName = isset($options['finderName']) ? $options['finderName'] : 'all'; + unset($options['key'], $options['cache'], $options['finderName']); - $query = $this->find('all', $options)->where($conditions); + $query = $this->find($finderName, $options)->where($conditions); if ($cacheConfig) { if (!$cacheKey) { From a92b0021279a1a6a09762f8b15cc873f5d0174f6 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 21 Jul 2015 15:49:02 +0200 Subject: [PATCH 0447/2059] Rename 'finderName' option to 'finder'. --- Table.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Table.php b/Table.php index c138d11c..e413b9f9 100644 --- a/Table.php +++ b/Table.php @@ -1158,10 +1158,10 @@ public function get($primaryKey, $options = []) $cacheConfig = isset($options['cache']) ? $options['cache'] : false; $cacheKey = isset($options['key']) ? $options['key'] : false; - $finderName = isset($options['finderName']) ? $options['finderName'] : 'all'; - unset($options['key'], $options['cache'], $options['finderName']); + $finder = isset($options['finder']) ? $options['finder'] : 'all'; + unset($options['key'], $options['cache'], $options['finder']); - $query = $this->find($finderName, $options)->where($conditions); + $query = $this->find($finder, $options)->where($conditions); if ($cacheConfig) { if (!$cacheKey) { From 21854377d97309eeb2eb47223d523633f460055d Mon Sep 17 00:00:00 2001 From: antograssiot Date: Wed, 22 Jul 2015 08:43:48 +0200 Subject: [PATCH 0448/2059] correct use statement order --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index b96a813b..1c697c52 100644 --- a/Table.php +++ b/Table.php @@ -25,9 +25,9 @@ use Cake\Datasource\RepositoryInterface; use Cake\Datasource\RulesAwareTrait; use Cake\Event\EventDispatcherInterface; +use Cake\Event\EventDispatcherTrait; use Cake\Event\EventListenerInterface; use Cake\Event\EventManager; -use Cake\Event\EventDispatcherTrait; use Cake\ORM\AssociationCollection; use Cake\ORM\Association\BelongsTo; use Cake\ORM\Association\BelongsToMany; From a2a9157e2997fcc2872f44e339270ab30949d7a9 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 27 Jul 2015 23:21:56 -0400 Subject: [PATCH 0449/2059] Trigger an exception when alias names do not match. Raising an exception in the scenario where a developer mis-types an association name prevents accidental alias mistakes, and more importantly prevents data from being returned as an array when it should be entity instances. Refs #7110 --- EagerLoader.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/EagerLoader.php b/EagerLoader.php index 2ba81c7d..c8b02cf9 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -385,6 +385,13 @@ protected function _normalizeContain(Table $parent, $alias, $options, $paths) sprintf('%s is not associated with %s', $parent->alias(), $alias) ); } + if ($instance->alias() !== $alias) { + throw new InvalidArgumentException(sprintf( + "You have contained '%s' but that association was bound as '%s'.", + $alias, + $instance->alias() + )); + } $paths += ['aliasPath' => '', 'propertyPath' => '', 'root' => $alias]; $paths['aliasPath'] .= '.' . $alias; From 02ec76763c36cde8ca8bbb2a0777495672edb8a5 Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 29 Jul 2015 09:27:38 +0200 Subject: [PATCH 0450/2059] Quick fix for #7127 --- Behavior/TranslateBehavior.php | 36 ++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 7b4d0041..ac52811b 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -439,24 +439,26 @@ protected function _rowMapper($results, $locale) public function groupTranslations($results) { return $results->map(function ($row) { - $translations = (array)$row->get('_i18n'); - $grouped = new Collection($translations); - - $result = []; - foreach ($grouped->combine('field', 'content', 'locale') as $locale => $keys) { - $entityClass = $this->_table->entityClass(); - $translation = new $entityClass($keys + ['locale' => $locale], [ - 'markNew' => false, - 'useSetters' => false, - 'markClean' => true - ]); - $result[$locale] = $translation; - } + if ($row instanceof EntityInterface) { + $translations = (array)$row->get('_i18n'); + $grouped = new Collection($translations); + + $result = []; + foreach ($grouped->combine('field', 'content', 'locale') as $locale => $keys) { + $entityClass = $this->_table->entityClass(); + $translation = new $entityClass($keys + ['locale' => $locale], [ + 'markNew' => false, + 'useSetters' => false, + 'markClean' => true + ]); + $result[$locale] = $translation; + } - $options = ['setter' => false, 'guard' => false]; - $row->set('_translations', $result, $options); - unset($row['_i18n']); - $row->clean(); + $options = ['setter' => false, 'guard' => false]; + $row->set('_translations', $result, $options); + unset($row['_i18n']); + $row->clean(); + } return $row; }); } From 63f467cef4a971839d2d4cf172c709b527646eb4 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 30 Jul 2015 08:26:51 +0200 Subject: [PATCH 0451/2059] Return no-EntityInterface $row earlier. --- Behavior/TranslateBehavior.php | 39 +++++++++++++++++----------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index ac52811b..373eb945 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -439,26 +439,27 @@ protected function _rowMapper($results, $locale) public function groupTranslations($results) { return $results->map(function ($row) { - if ($row instanceof EntityInterface) { - $translations = (array)$row->get('_i18n'); - $grouped = new Collection($translations); - - $result = []; - foreach ($grouped->combine('field', 'content', 'locale') as $locale => $keys) { - $entityClass = $this->_table->entityClass(); - $translation = new $entityClass($keys + ['locale' => $locale], [ - 'markNew' => false, - 'useSetters' => false, - 'markClean' => true - ]); - $result[$locale] = $translation; - } - - $options = ['setter' => false, 'guard' => false]; - $row->set('_translations', $result, $options); - unset($row['_i18n']); - $row->clean(); + if (!$row instanceof EntityInterface) { + return $row; } + $translations = (array)$row->get('_i18n'); + $grouped = new Collection($translations); + + $result = []; + foreach ($grouped->combine('field', 'content', 'locale') as $locale => $keys) { + $entityClass = $this->_table->entityClass(); + $translation = new $entityClass($keys + ['locale' => $locale], [ + 'markNew' => false, + 'useSetters' => false, + 'markClean' => true + ]); + $result[$locale] = $translation; + } + + $options = ['setter' => false, 'guard' => false]; + $row->set('_translations', $result, $options); + unset($row['_i18n']); + $row->clean(); return $row; }); } From c6c65283cf7b638cee48109a3fc6c78085ec9a8d Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Thu, 6 Aug 2015 10:05:50 +0200 Subject: [PATCH 0452/2059] Fixing nullable detection in ExistsIn rule, closes #7174 --- Rule/ExistsIn.php | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 6890dd0f..940e3eea 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -65,13 +65,14 @@ public function __invoke(EntityInterface $entity, array $options) $this->_repository = $options['repository']->association($this->_repository); } - if (!empty($options['_sourceTable'])) { - $source = $this->_repository instanceof Association ? - $this->_repository->target() : - $this->_repository; - if ($source === $options['_sourceTable']) { - return true; - } + $source = !empty($options['repository']) ? $options['repository'] : $this->_repository; + $source = $source instanceof Association ? $source->source() : $source; + $target = $this->_repository instanceof Association ? + $this->_repository->target() : + $this->_repository; + + if (!empty($options['_sourceTable']) && $target === $options['_sourceTable']) { + return true; } if (!$entity->extract($this->_fields, true)) { @@ -79,9 +80,9 @@ public function __invoke(EntityInterface $entity, array $options) } $nulls = 0; - $schema = $this->_repository->schema(); + $schema = $source->schema(); foreach ($this->_fields as $field) { - if ($schema->isNullable($field) && $entity->get($field) === null) { + if ($schema->column($field) && $schema->isNullable($field) && $entity->get($field) === null) { $nulls++; } } @@ -89,11 +90,8 @@ public function __invoke(EntityInterface $entity, array $options) return true; } - $alias = $this->_repository->alias(); $primary = array_map( - function ($key) use ($alias) { - return "$alias.$key"; - }, + [$this->_repository, 'aliasField'], (array)$this->_repository->primaryKey() ); $conditions = array_combine( From 269f4096994afdd42ceb35cc361af337e6cbd244 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 6 Aug 2015 07:16:59 -0400 Subject: [PATCH 0453/2059] Use the registryAlias when setting entity source. Refs #7187 --- ResultSet.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ResultSet.php b/ResultSet.php index dff7ca9a..0387ae7f 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -507,7 +507,7 @@ protected function _groupResult($row) ) ); if ($this->_hydrate) { - $options['source'] = $alias; + $options['source'] = $matching['instance']->registryAlias(); $entity = new $matching['entityClass']($results['_matchingData'][$alias], $options); $entity->clean(); $results['_matchingData'][$alias] = $entity; @@ -546,7 +546,7 @@ protected function _groupResult($row) } $target = $instance->target(); - $options['source'] = $target->alias(); + $options['source'] = $target->registryAlias(); unset($presentAliases[$alias]); if ($assoc['canBeJoined']) { From 7b67d9f4ca3169ca36e17b9719c0136b608e9c8b Mon Sep 17 00:00:00 2001 From: antograssiot Date: Thu, 13 Aug 2015 15:06:53 +0200 Subject: [PATCH 0454/2059] correct the case for `Cake\Datasource\EntityInterface` as it can be misunderstood by some IDE and static analysis tools. --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 372b555e..da87ce4e 100644 --- a/Table.php +++ b/Table.php @@ -1656,7 +1656,7 @@ public function delete(EntityInterface $entity, $options = []) * Will delete the entity provided. Will remove rows from any * dependent associations, and clear out join tables for BelongsToMany associations. * - * @param \Cake\DataSource\EntityInterface $entity The entity to delete. + * @param \Cake\Datasource\EntityInterface $entity The entity to delete. * @param \ArrayObject $options The options for the delete. * @throws \InvalidArgumentException if there are no primary key values of the * passed entity From 24c5f4e63aeef1d2ae2674f8087a2b5ab2ebc6f6 Mon Sep 17 00:00:00 2001 From: Daren Sipes Date: Mon, 17 Aug 2015 13:36:57 -0400 Subject: [PATCH 0455/2059] Adding having clause as another complex condition --- Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Query.php b/Query.php index 1b3248be..21e26abb 100644 --- a/Query.php +++ b/Query.php @@ -720,7 +720,7 @@ public function count() } $count = ['count' => $query->func()->count('*')]; - $complex = count($query->clause('group')) || $query->clause('distinct'); + $complex = count($query->clause('group')) || $query->clause('distinct') || $query->clause('having'); $complex = $complex || count($query->clause('union')); if (!$complex) { From ab2e51b3198a8366564659fac4bc9c24d514d624 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 22 Aug 2015 10:35:27 -0400 Subject: [PATCH 0456/2059] Fix Query::count() failing when fields have expressions. Because we can't easily introspect and remove any bound parameters that will be unused, we must use a subselect query instead. Also include additional test coverage for count() with subqueries, and matching(). Refs #7148 Refs #7272 --- Query.php | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/Query.php b/Query.php index 7f9c6b02..291a8c6e 100644 --- a/Query.php +++ b/Query.php @@ -15,6 +15,7 @@ namespace Cake\ORM; use ArrayObject; +use Cake\Database\ExpressionInterface; use Cake\Database\Query as DatabaseQuery; use Cake\Database\ValueBinder; use Cake\Datasource\QueryTrait; @@ -510,15 +511,27 @@ public function count() { $query = $this->cleanCopy(); $counter = $this->_counter; - if ($counter) { $query->counter(null); return (int)$counter($query); } + $complex = ( + $query->clause('distinct') || + count($query->clause('group')) || + count($query->clause('union')) + ); + if (!$complex) { + // Expression fields could have bound parameters. + foreach ($query->clause('select') as $field) { + if ($field instanceof ExpressionInterface) { + $complex = true; + break; + } + } + } + $count = ['count' => $query->func()->count('*')]; - $complex = count($query->clause('group')) || $query->clause('distinct'); - $complex = $complex || count($query->clause('union')); if (!$complex) { $query->eagerLoader()->autoFields(false); From db9d37bd22f0bf9e3be6b84259c4424ee9fb5952 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 29 Aug 2015 23:38:23 -0400 Subject: [PATCH 0457/2059] Don't overwrite an entity's id when it was specified. When an entity is persisted with a specific ID, we should not overwrite that value with the generated, but unused value. Refs #7225 --- Table.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Table.php b/Table.php index da87ce4e..2c6b0765 100644 --- a/Table.php +++ b/Table.php @@ -1487,7 +1487,11 @@ protected function _insert($entity, $data) } $keys = array_fill(0, count($primary), null); $id = (array)$this->_newId($primary) + $keys; + + // Generate primary keys preferring values in $data. $primary = array_combine($primary, $id); + $primary = array_intersect_key($data, $primary) + $primary; + $filteredKeys = array_filter($primary, 'strlen'); $data = $data + $filteredKeys; From 1426cb34b0e5394d8ed164d6e90e015f540e7734 Mon Sep 17 00:00:00 2001 From: antograssiot Date: Mon, 31 Aug 2015 21:19:50 +0200 Subject: [PATCH 0458/2059] minors corrections - correct return type - remove extra unused parameter - add missing class attribute definition --- Association.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association.php b/Association.php index 18cd6042..26a57896 100644 --- a/Association.php +++ b/Association.php @@ -609,7 +609,7 @@ public function defaultRowValue($row, $joined) public function find($type = null, array $options = []) { $type = $type ?: $this->finder(); - list($type, $opts) = $this->_extractFinder($type, $options); + list($type, $opts) = $this->_extractFinder($type); return $this->target() ->find($type, $options + $opts) ->where($this->conditions()); From 20338c1b0313eae33d8b686b29b0331ca93a0bf9 Mon Sep 17 00:00:00 2001 From: pulse14 Date: Thu, 18 Jun 2015 11:22:13 +0200 Subject: [PATCH 0459/2059] Add $_ids option in association --- Marshaller.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index efe7f903..b13957e2 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -623,10 +623,12 @@ protected function _mergeBelongsToMany($original, $assoc, $value, $options) { $hasIds = array_key_exists('_ids', $value); $associated = isset($options['associated']) ? $options['associated'] : []; + $_ids = array_key_exists('_ids', $options) && $options['_ids']; + if ($hasIds && is_array($value['_ids'])) { return $this->_loadAssociatedByIds($assoc, $value['_ids']); } - if ($hasIds) { + if ($hasIds || $_ids) { return []; } From 47441818674060ebc376a4f6256fb5daacab5b94 Mon Sep 17 00:00:00 2001 From: pulse14 Date: Thu, 18 Jun 2015 21:51:35 +0200 Subject: [PATCH 0460/2059] Adding support both hasMany and belongsToMany + cake conventions --- Marshaller.php | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index b13957e2..019bf812 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -223,6 +223,9 @@ protected function _marshalAssociation($assoc, $value, $options) if (in_array($assoc->type(), $types)) { return $marshaller->one($value, (array)$options); } + if($this->_isAssociatedByIdsInvalid($assoc, $value, (array)$options)){ + return []; + } if ($assoc->type() === Association::MANY_TO_MANY) { return $marshaller->_belongsToMany($assoc, $value, (array)$options); } @@ -378,6 +381,13 @@ protected function _loadAssociatedByIds($assoc, $ids) return $target->find()->where($filter)->toArray(); } + protected function _isAssociatedByIdsInvalid($assoc, $value, $options){ + if($assoc->type() === Association::MANY_TO_MANY || $assoc->type() === Association::ONE_TO_MANY){ + return array_key_exists('ids', $options) && $options['ids'] && !array_key_exists('_ids', $value); + } + return false; + } + /** * Loads a list of belongs to many from ids. * @@ -603,6 +613,9 @@ protected function _mergeAssociation($original, $assoc, $value, $options) if (in_array($assoc->type(), $types)) { return $marshaller->merge($original, $value, (array)$options); } + if($this->_isAssociatedByIdsInvalid($assoc, $value, (array)$options)){ + return []; + } if ($assoc->type() === Association::MANY_TO_MANY) { return $marshaller->_mergeBelongsToMany($original, $assoc, $value, (array)$options); } @@ -623,12 +636,11 @@ protected function _mergeBelongsToMany($original, $assoc, $value, $options) { $hasIds = array_key_exists('_ids', $value); $associated = isset($options['associated']) ? $options['associated'] : []; - $_ids = array_key_exists('_ids', $options) && $options['_ids']; if ($hasIds && is_array($value['_ids'])) { return $this->_loadAssociatedByIds($assoc, $value['_ids']); } - if ($hasIds || $_ids) { + if ($hasIds) { return []; } From 6737d01532ed9138a0edbdad2491ac095ea64faa Mon Sep 17 00:00:00 2001 From: pulse14 Date: Thu, 18 Jun 2015 21:51:35 +0200 Subject: [PATCH 0461/2059] Adding support both hasMany and belongsToMany --- Marshaller.php | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 019bf812..b13957e2 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -223,9 +223,6 @@ protected function _marshalAssociation($assoc, $value, $options) if (in_array($assoc->type(), $types)) { return $marshaller->one($value, (array)$options); } - if($this->_isAssociatedByIdsInvalid($assoc, $value, (array)$options)){ - return []; - } if ($assoc->type() === Association::MANY_TO_MANY) { return $marshaller->_belongsToMany($assoc, $value, (array)$options); } @@ -381,13 +378,6 @@ protected function _loadAssociatedByIds($assoc, $ids) return $target->find()->where($filter)->toArray(); } - protected function _isAssociatedByIdsInvalid($assoc, $value, $options){ - if($assoc->type() === Association::MANY_TO_MANY || $assoc->type() === Association::ONE_TO_MANY){ - return array_key_exists('ids', $options) && $options['ids'] && !array_key_exists('_ids', $value); - } - return false; - } - /** * Loads a list of belongs to many from ids. * @@ -613,9 +603,6 @@ protected function _mergeAssociation($original, $assoc, $value, $options) if (in_array($assoc->type(), $types)) { return $marshaller->merge($original, $value, (array)$options); } - if($this->_isAssociatedByIdsInvalid($assoc, $value, (array)$options)){ - return []; - } if ($assoc->type() === Association::MANY_TO_MANY) { return $marshaller->_mergeBelongsToMany($original, $assoc, $value, (array)$options); } @@ -636,11 +623,12 @@ protected function _mergeBelongsToMany($original, $assoc, $value, $options) { $hasIds = array_key_exists('_ids', $value); $associated = isset($options['associated']) ? $options['associated'] : []; + $_ids = array_key_exists('_ids', $options) && $options['_ids']; if ($hasIds && is_array($value['_ids'])) { return $this->_loadAssociatedByIds($assoc, $value['_ids']); } - if ($hasIds) { + if ($hasIds || $_ids) { return []; } From 530ac866f933dff5a242e501ccb17706c1063a27 Mon Sep 17 00:00:00 2001 From: pulse14 Date: Fri, 19 Jun 2015 11:01:41 +0200 Subject: [PATCH 0462/2059] Adding support both hasMany and BelongsToMany + support marshall --- Marshaller.php | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index b13957e2..748df387 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -223,12 +223,20 @@ protected function _marshalAssociation($assoc, $value, $options) if (in_array($assoc->type(), $types)) { return $marshaller->one($value, (array)$options); } + if ($assoc->type() === Association::ONE_TO_MANY || $assoc->type() === Association::MANY_TO_MANY) { + $hasIds = array_key_exists('_ids', $data); + $idsOption = array_key_exists('ids', $options) && $options['ids']; + + if ($hasIds && is_array($data['_ids'])) { + return $this->_loadAssociatedByIds($assoc, $data['_ids']); + } + if ($hasIds || $idsOption) { + return []; + } + } if ($assoc->type() === Association::MANY_TO_MANY) { return $marshaller->_belongsToMany($assoc, $value, (array)$options); } - if ($assoc->type() === Association::ONE_TO_MANY && array_key_exists('_ids', $value) && is_array($value['_ids'])) { - return $this->_loadAssociatedByIds($assoc, $value['_ids']); - } return $marshaller->many($value, (array)$options); } @@ -272,15 +280,8 @@ public function many(array $data, array $options = []) */ protected function _belongsToMany(Association $assoc, array $data, $options = []) { - // Accept _ids = [1, 2] $associated = isset($options['associated']) ? $options['associated'] : []; - $hasIds = array_key_exists('_ids', $data); - if ($hasIds && is_array($data['_ids'])) { - return $this->_loadAssociatedByIds($assoc, $data['_ids']); - } - if ($hasIds) { - return []; - } + $data = array_values($data); $target = $assoc->target(); @@ -621,14 +622,15 @@ protected function _mergeAssociation($original, $assoc, $value, $options) */ protected function _mergeBelongsToMany($original, $assoc, $value, $options) { - $hasIds = array_key_exists('_ids', $value); $associated = isset($options['associated']) ? $options['associated'] : []; - $_ids = array_key_exists('_ids', $options) && $options['_ids']; - if ($hasIds && is_array($value['_ids'])) { - return $this->_loadAssociatedByIds($assoc, $value['_ids']); + $hasIds = array_key_exists('_ids', $data); + $idsOption = array_key_exists('ids', $options) && $options['ids']; + + if ($hasIds && is_array($data['_ids'])) { + return $this->_loadAssociatedByIds($assoc, $data['_ids']); } - if ($hasIds || $_ids) { + if ($hasIds || $idsOption) { return []; } From d71e0c258d710882916389b1293e3c8118922bdc Mon Sep 17 00:00:00 2001 From: pulse14 Date: Fri, 19 Jun 2015 11:01:41 +0200 Subject: [PATCH 0463/2059] Adding support both hasMany and BelongsToMany + support marshall --- Marshaller.php | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 748df387..b13957e2 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -223,20 +223,12 @@ protected function _marshalAssociation($assoc, $value, $options) if (in_array($assoc->type(), $types)) { return $marshaller->one($value, (array)$options); } - if ($assoc->type() === Association::ONE_TO_MANY || $assoc->type() === Association::MANY_TO_MANY) { - $hasIds = array_key_exists('_ids', $data); - $idsOption = array_key_exists('ids', $options) && $options['ids']; - - if ($hasIds && is_array($data['_ids'])) { - return $this->_loadAssociatedByIds($assoc, $data['_ids']); - } - if ($hasIds || $idsOption) { - return []; - } - } if ($assoc->type() === Association::MANY_TO_MANY) { return $marshaller->_belongsToMany($assoc, $value, (array)$options); } + if ($assoc->type() === Association::ONE_TO_MANY && array_key_exists('_ids', $value) && is_array($value['_ids'])) { + return $this->_loadAssociatedByIds($assoc, $value['_ids']); + } return $marshaller->many($value, (array)$options); } @@ -280,8 +272,15 @@ public function many(array $data, array $options = []) */ protected function _belongsToMany(Association $assoc, array $data, $options = []) { + // Accept _ids = [1, 2] $associated = isset($options['associated']) ? $options['associated'] : []; - + $hasIds = array_key_exists('_ids', $data); + if ($hasIds && is_array($data['_ids'])) { + return $this->_loadAssociatedByIds($assoc, $data['_ids']); + } + if ($hasIds) { + return []; + } $data = array_values($data); $target = $assoc->target(); @@ -622,15 +621,14 @@ protected function _mergeAssociation($original, $assoc, $value, $options) */ protected function _mergeBelongsToMany($original, $assoc, $value, $options) { + $hasIds = array_key_exists('_ids', $value); $associated = isset($options['associated']) ? $options['associated'] : []; + $_ids = array_key_exists('_ids', $options) && $options['_ids']; - $hasIds = array_key_exists('_ids', $data); - $idsOption = array_key_exists('ids', $options) && $options['ids']; - - if ($hasIds && is_array($data['_ids'])) { - return $this->_loadAssociatedByIds($assoc, $data['_ids']); + if ($hasIds && is_array($value['_ids'])) { + return $this->_loadAssociatedByIds($assoc, $value['_ids']); } - if ($hasIds || $idsOption) { + if ($hasIds || $_ids) { return []; } From 6ec73eecf5ed05ab0f9779aa1e871af810c2ae65 Mon Sep 17 00:00:00 2001 From: pulse14 Date: Fri, 19 Jun 2015 11:06:59 +0200 Subject: [PATCH 0464/2059] Addind ids option support for both hasMany & belongsToMany --- Marshaller.php | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index b13957e2..748df387 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -223,12 +223,20 @@ protected function _marshalAssociation($assoc, $value, $options) if (in_array($assoc->type(), $types)) { return $marshaller->one($value, (array)$options); } + if ($assoc->type() === Association::ONE_TO_MANY || $assoc->type() === Association::MANY_TO_MANY) { + $hasIds = array_key_exists('_ids', $data); + $idsOption = array_key_exists('ids', $options) && $options['ids']; + + if ($hasIds && is_array($data['_ids'])) { + return $this->_loadAssociatedByIds($assoc, $data['_ids']); + } + if ($hasIds || $idsOption) { + return []; + } + } if ($assoc->type() === Association::MANY_TO_MANY) { return $marshaller->_belongsToMany($assoc, $value, (array)$options); } - if ($assoc->type() === Association::ONE_TO_MANY && array_key_exists('_ids', $value) && is_array($value['_ids'])) { - return $this->_loadAssociatedByIds($assoc, $value['_ids']); - } return $marshaller->many($value, (array)$options); } @@ -272,15 +280,8 @@ public function many(array $data, array $options = []) */ protected function _belongsToMany(Association $assoc, array $data, $options = []) { - // Accept _ids = [1, 2] $associated = isset($options['associated']) ? $options['associated'] : []; - $hasIds = array_key_exists('_ids', $data); - if ($hasIds && is_array($data['_ids'])) { - return $this->_loadAssociatedByIds($assoc, $data['_ids']); - } - if ($hasIds) { - return []; - } + $data = array_values($data); $target = $assoc->target(); @@ -621,14 +622,15 @@ protected function _mergeAssociation($original, $assoc, $value, $options) */ protected function _mergeBelongsToMany($original, $assoc, $value, $options) { - $hasIds = array_key_exists('_ids', $value); $associated = isset($options['associated']) ? $options['associated'] : []; - $_ids = array_key_exists('_ids', $options) && $options['_ids']; - if ($hasIds && is_array($value['_ids'])) { - return $this->_loadAssociatedByIds($assoc, $value['_ids']); + $hasIds = array_key_exists('_ids', $data); + $idsOption = array_key_exists('ids', $options) && $options['ids']; + + if ($hasIds && is_array($data['_ids'])) { + return $this->_loadAssociatedByIds($assoc, $data['_ids']); } - if ($hasIds || $_ids) { + if ($hasIds || $idsOption) { return []; } From 35b681783a865c8be1abd7a29eb2008e3c43a925 Mon Sep 17 00:00:00 2001 From: pulse14 Date: Fri, 19 Jun 2015 11:22:00 +0200 Subject: [PATCH 0465/2059] Documentation for _ids format and ids option added * Minor fixes --- Marshaller.php | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 748df387..a07f318f 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -86,6 +86,10 @@ protected function _buildPropertyMap($options) /** * Hydrate one entity and its associated data. * + * When marshalling HasMany or BelongsToMany associations, `_ids` format can be used. + * `ids` option can also be used to determine whether the association must use the `_ids` + * format. + * * ### Options: * * * associated: Associations listed here will be marshalled as well. @@ -224,16 +228,16 @@ protected function _marshalAssociation($assoc, $value, $options) return $marshaller->one($value, (array)$options); } if ($assoc->type() === Association::ONE_TO_MANY || $assoc->type() === Association::MANY_TO_MANY) { - $hasIds = array_key_exists('_ids', $data); + $hasIds = array_key_exists('_ids', $value); $idsOption = array_key_exists('ids', $options) && $options['ids']; - if ($hasIds && is_array($data['_ids'])) { - return $this->_loadAssociatedByIds($assoc, $data['_ids']); + if ($hasIds && is_array($value['_ids'])) { + return $this->_loadAssociatedByIds($assoc, $value['_ids']); } if ($hasIds || $idsOption) { return []; } - } + } if ($assoc->type() === Association::MANY_TO_MANY) { return $marshaller->_belongsToMany($assoc, $value, (array)$options); } @@ -400,7 +404,8 @@ protected function _loadBelongsToMany($assoc, $ids) * * When merging HasMany or BelongsToMany associations, all the entities in the * `$data` array will appear, those that can be matched by primary key will get - * the data merged, but those that cannot, will be discarded. + * the data merged, but those that cannot, will be discarded. `ids` option can be used + * to determine whether the association must use the `_ids` format. * * ### Options: * @@ -624,11 +629,11 @@ protected function _mergeBelongsToMany($original, $assoc, $value, $options) { $associated = isset($options['associated']) ? $options['associated'] : []; - $hasIds = array_key_exists('_ids', $data); + $hasIds = array_key_exists('_ids', $value); $idsOption = array_key_exists('ids', $options) && $options['ids']; - if ($hasIds && is_array($data['_ids'])) { - return $this->_loadAssociatedByIds($assoc, $data['_ids']); + if ($hasIds && is_array($value['_ids'])) { + return $this->_loadAssociatedByIds($assoc, $value['_ids']); } if ($hasIds || $idsOption) { return []; From 70b0a99c278627c63b394e57ea52b87dc4330e02 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Wed, 2 Sep 2015 22:00:52 -0400 Subject: [PATCH 0466/2059] Add additional documentation for `ids` option. --- Marshaller.php | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index a07f318f..353d2f97 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -86,10 +86,6 @@ protected function _buildPropertyMap($options) /** * Hydrate one entity and its associated data. * - * When marshalling HasMany or BelongsToMany associations, `_ids` format can be used. - * `ids` option can also be used to determine whether the association must use the `_ids` - * format. - * * ### Options: * * * associated: Associations listed here will be marshalled as well. @@ -97,6 +93,16 @@ protected function _buildPropertyMap($options) * the accessible fields list in the entity will be used. * * accessibleFields: A list of fields to allow or deny in entity accessible fields. * + * The above options can be used in each nested `associated` array. In addition to the above + * options you can also use the `ids` option for HasMany and BelongsToMany associations. + * When true this option restricts the request data to only be read from `_ids`. + * + * ``` + * $result = $marshaller->one($data, [ + * 'associated' => ['Tags' => ['ids' => true]] + * ]); + * ``` + * * @param array $data The data to hydrate. * @param array $options List of options * @return \Cake\ORM\Entity @@ -416,6 +422,16 @@ protected function _loadBelongsToMany($assoc, $ids) * the accessible fields list in the entity will be used. * * accessibleFields: A list of fields to allow or deny in entity accessible fields. * + * The above options can be used in each nested `associated` array. In addition to the above + * options you can also use the `ids` option for HasMany and BelongsToMany associations. + * When true this option restricts the request data to only be read from `_ids`. + * + * ``` + * $result = $marshaller->merge($entity, $data, [ + * 'associated' => ['Tags' => ['ids' => true]] + * ]); + * ``` + * * @param \Cake\Datasource\EntityInterface $entity the entity that will get the * data merged in * @param array $data key value list of fields to be merged into the entity From f827199f3db5e9eef32d411d6b35b69eeb1c49f9 Mon Sep 17 00:00:00 2001 From: mark_story Date: Fri, 4 Sep 2015 17:25:23 -0400 Subject: [PATCH 0467/2059] Rename to onlyIds. This name better fits what the option actually does. --- Marshaller.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 353d2f97..87770c94 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -94,12 +94,12 @@ protected function _buildPropertyMap($options) * * accessibleFields: A list of fields to allow or deny in entity accessible fields. * * The above options can be used in each nested `associated` array. In addition to the above - * options you can also use the `ids` option for HasMany and BelongsToMany associations. + * options you can also use the `onlyIds` option for HasMany and BelongsToMany associations. * When true this option restricts the request data to only be read from `_ids`. * * ``` * $result = $marshaller->one($data, [ - * 'associated' => ['Tags' => ['ids' => true]] + * 'associated' => ['Tags' => ['onlyIds' => true]] * ]); * ``` * @@ -235,12 +235,12 @@ protected function _marshalAssociation($assoc, $value, $options) } if ($assoc->type() === Association::ONE_TO_MANY || $assoc->type() === Association::MANY_TO_MANY) { $hasIds = array_key_exists('_ids', $value); - $idsOption = array_key_exists('ids', $options) && $options['ids']; + $onlyIds = array_key_exists('onlyIds', $options) && $options['onlyIds']; if ($hasIds && is_array($value['_ids'])) { return $this->_loadAssociatedByIds($assoc, $value['_ids']); } - if ($hasIds || $idsOption) { + if ($hasIds || $onlyIds) { return []; } } @@ -423,12 +423,12 @@ protected function _loadBelongsToMany($assoc, $ids) * * accessibleFields: A list of fields to allow or deny in entity accessible fields. * * The above options can be used in each nested `associated` array. In addition to the above - * options you can also use the `ids` option for HasMany and BelongsToMany associations. + * options you can also use the `onlyIds` option for HasMany and BelongsToMany associations. * When true this option restricts the request data to only be read from `_ids`. * * ``` * $result = $marshaller->merge($entity, $data, [ - * 'associated' => ['Tags' => ['ids' => true]] + * 'associated' => ['Tags' => ['onlyIds' => true]] * ]); * ``` * @@ -646,12 +646,12 @@ protected function _mergeBelongsToMany($original, $assoc, $value, $options) $associated = isset($options['associated']) ? $options['associated'] : []; $hasIds = array_key_exists('_ids', $value); - $idsOption = array_key_exists('ids', $options) && $options['ids']; + $onlyIds = array_key_exists('onlyIds', $options) && $options['onlyIds']; if ($hasIds && is_array($value['_ids'])) { return $this->_loadAssociatedByIds($assoc, $value['_ids']); } - if ($hasIds || $idsOption) { + if ($hasIds || $onlyIds) { return []; } From d195a4b0c0222ef37fe42bc99f51ee5ad29e832e Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 6 Sep 2015 02:03:24 +0530 Subject: [PATCH 0468/2059] Code cleanup courtesy scrutinizer-ci. --- Association/BelongsToMany.php | 3 +-- Behavior/TreeBehavior.php | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index a6252ef0..643bdbfb 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -267,7 +267,7 @@ public function attachTo(Query $query, array $options = []) */ protected function _appendNotMatching($query, $options) { - $target = $junction = $this->junction(); + $target = $this->junction(); if (!empty($options['negateMatch'])) { $primaryKey = $query->aliasFields((array)$target->primaryKey(), $target->alias()); $query->andWhere(function ($exp) use ($primaryKey) { @@ -543,7 +543,6 @@ protected function _saveLinks(EntityInterface $sourceEntity, $targetEntities, $o { $target = $this->target(); $junction = $this->junction(); - $source = $this->source(); $entityClass = $junction->entityClass(); $belongsTo = $junction->association($target->alias()); $foreignKey = (array)$this->foreignKey(); diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index b6921662..a5765251 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -615,7 +615,7 @@ protected function _moveUp($node, $number) } } - list($targetLeft, $targetRight) = array_values($targetNode->extract([$left, $right])); + list($targetLeft) = array_values($targetNode->extract([$left, $right])); $edge = $this->_getMax(); $leftBoundary = $targetLeft; $rightBoundary = $nodeLeft - 1; @@ -697,7 +697,7 @@ protected function _moveDown($node, $number) } } - list($targetLeft, $targetRight) = array_values($targetNode->extract([$left, $right])); + list(, $targetRight) = array_values($targetNode->extract([$left, $right])); $edge = $this->_getMax(); $leftBoundary = $nodeRight + 1; $rightBoundary = $targetRight; From b267c4d77aec153fbc13f18e5f1ce28d84a1bd0b Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 6 Sep 2015 03:02:09 +0530 Subject: [PATCH 0469/2059] Remove unused vars. --- ResultSet.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ResultSet.php b/ResultSet.php index 50a439b9..7c734cc6 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -171,7 +171,7 @@ public function __construct($query, $statement) $repository = $query->repository(); $this->_query = $query; $this->_statement = $statement; - $this->_driver = $driver = $this->_query->connection()->driver(); + $this->_driver = $this->_query->connection()->driver(); $this->_defaultTable = $this->_query->repository(); $this->_calculateAssociationMap(); $this->_hydrate = $this->_query->hydrate(); @@ -254,7 +254,6 @@ public function rewind() */ public function valid() { - $valid = true; if ($this->_useBuffering) { $valid = $this->_index < $this->_count; if ($valid && $this->_results[$this->_index] !== null) { From 7f5226179e52cab9e00c6f86e5ed1ac8c4188dc7 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 14 Sep 2015 20:57:32 -0400 Subject: [PATCH 0470/2059] Document missing strategy and finder options. Refs #7397 --- Table.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Table.php b/Table.php index 2c6b0765..588c1bb0 100644 --- a/Table.php +++ b/Table.php @@ -720,6 +720,10 @@ public function addAssociations(array $params) * will be used * - conditions: array with a list of conditions to filter the join with * - joinType: The type of join to be used (e.g. INNER) + * - strategy: The loading strategy to use. 'join' and 'select' are supported. + * - finder: The finder method to use when loading records from this association. + * Defaults to 'all'. When the strategy is 'join', only the fields, containments, + * and where conditions will be used from the finder. * * This method will return the association object that was built. * @@ -758,6 +762,10 @@ public function belongsTo($associated, array $options = []) * When true records will be loaded and then deleted. * - conditions: array with a list of conditions to filter the join with * - joinType: The type of join to be used (e.g. LEFT) + * - strategy: The loading strategy to use. 'join' and 'select' are supported. + * - finder: The finder method to use when loading records from this association. + * Defaults to 'all'. When the strategy is 'join', only the fields, containments, + * and where conditions will be used from the finder. * * This method will return the association object that was built. * @@ -800,6 +808,8 @@ public function hasOne($associated, array $options = []) * or 'subquery'. If subquery is selected the query used to return results * in the source table will be used as conditions for getting rows in the * target table. + * - finder: The finder method to use when loading records from this association. + * Defaults to 'all'. * * This method will return the association object that was built. * @@ -846,6 +856,9 @@ public function hasMany($associated, array $options = []) * for saving associated entities. The former will only create new links * between both side of the relation and the latter will do a wipe and * replace to create the links between the passed entities when saving. + * - strategy: The loading strategy to use. 'select' and 'subquery' are supported. + * - finder: The finder method to use when loading records from this association. + * Defaults to 'all'. * * This method will return the association object that was built. * From f1dc199cfc91bb054c92ea39057478a538ac7594 Mon Sep 17 00:00:00 2001 From: Bryan Crowe Date: Mon, 14 Sep 2015 23:06:35 -0400 Subject: [PATCH 0471/2059] Remove unused use statements --- Association.php | 3 --- Association/BelongsTo.php | 1 - Association/ExternalAssociationTrait.php | 1 - Association/HasMany.php | 2 -- Association/HasOne.php | 2 -- AssociationCollection.php | 4 ---- BehaviorRegistry.php | 2 -- EagerLoader.php | 4 ---- Marshaller.php | 3 --- Query.php | 3 --- Table.php | 4 ---- 11 files changed, 29 deletions(-) diff --git a/Association.php b/Association.php index 26a57896..027054b2 100644 --- a/Association.php +++ b/Association.php @@ -18,9 +18,6 @@ use Cake\Database\Expression\IdentifierExpression; use Cake\Datasource\EntityInterface; use Cake\Datasource\ResultSetDecorator; -use Cake\ORM\Query; -use Cake\ORM\Table; -use Cake\ORM\TableRegistry; use Cake\Utility\Inflector; use InvalidArgumentException; use RuntimeException; diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 7859b625..bc3d430f 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -17,7 +17,6 @@ use Cake\Database\Expression\IdentifierExpression; use Cake\Datasource\EntityInterface; use Cake\ORM\Association; -use Cake\ORM\Association\SelectableAssociationTrait; use Cake\ORM\Table; use Cake\Utility\Inflector; use RuntimeException; diff --git a/Association/ExternalAssociationTrait.php b/Association/ExternalAssociationTrait.php index d25649ae..79587b0e 100644 --- a/Association/ExternalAssociationTrait.php +++ b/Association/ExternalAssociationTrait.php @@ -14,7 +14,6 @@ */ namespace Cake\ORM\Association; -use Cake\ORM\Association\SelectableAssociationTrait; /** * Represents a type of association that that needs to be recovered by performing diff --git a/Association/HasMany.php b/Association/HasMany.php index 682076e5..efd39494 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -17,8 +17,6 @@ use Cake\Datasource\EntityInterface; use Cake\ORM\Association; -use Cake\ORM\Association\DependentDeleteTrait; -use Cake\ORM\Association\ExternalAssociationTrait; use Cake\ORM\Table; use InvalidArgumentException; use RuntimeException; diff --git a/Association/HasOne.php b/Association/HasOne.php index 6c06ac3f..25026da1 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -16,8 +16,6 @@ use Cake\Datasource\EntityInterface; use Cake\ORM\Association; -use Cake\ORM\Association\DependentDeleteTrait; -use Cake\ORM\Association\SelectableAssociationTrait; use Cake\ORM\Table; use Cake\Utility\Inflector; diff --git a/AssociationCollection.php b/AssociationCollection.php index 4b0d6412..f440680f 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -15,10 +15,6 @@ namespace Cake\ORM; use ArrayIterator; -use Cake\ORM\Association; -use Cake\ORM\AssociationsNormalizerTrait; -use Cake\ORM\Entity; -use Cake\ORM\Table; use InvalidArgumentException; use IteratorAggregate; diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 20486cc3..c41112bb 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -19,9 +19,7 @@ use Cake\Core\ObjectRegistry; use Cake\Event\EventDispatcherInterface; use Cake\Event\EventDispatcherTrait; -use Cake\ORM\Behavior; use Cake\ORM\Exception\MissingBehaviorException; -use Cake\ORM\Table; use LogicException; /** diff --git a/EagerLoader.php b/EagerLoader.php index c8b02cf9..e6e0663c 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -16,10 +16,6 @@ use Cake\Database\Statement\BufferedStatement; use Cake\Database\Statement\CallbackStatement; -use Cake\ORM\Association; -use Cake\ORM\EagerLoadable; -use Cake\ORM\Query; -use Cake\ORM\Table; use Closure; use InvalidArgumentException; diff --git a/Marshaller.php b/Marshaller.php index efe7f903..eea04d1e 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -18,9 +18,6 @@ use Cake\Database\Expression\TupleComparison; use Cake\Database\Type; use Cake\Datasource\EntityInterface; -use Cake\ORM\Association; -use Cake\ORM\AssociationsNormalizerTrait; -use Cake\ORM\Table; use \RuntimeException; /** diff --git a/Query.php b/Query.php index 291a8c6e..0fdf68cc 100644 --- a/Query.php +++ b/Query.php @@ -19,9 +19,6 @@ use Cake\Database\Query as DatabaseQuery; use Cake\Database\ValueBinder; use Cake\Datasource\QueryTrait; -use Cake\ORM\EagerLoader; -use Cake\ORM\ResultSet; -use Cake\ORM\Table; use JsonSerializable; use RuntimeException; diff --git a/Table.php b/Table.php index 2c6b0765..a1a10945 100644 --- a/Table.php +++ b/Table.php @@ -28,15 +28,11 @@ use Cake\Event\EventDispatcherTrait; use Cake\Event\EventListenerInterface; use Cake\Event\EventManager; -use Cake\ORM\AssociationCollection; use Cake\ORM\Association\BelongsTo; use Cake\ORM\Association\BelongsToMany; use Cake\ORM\Association\HasMany; use Cake\ORM\Association\HasOne; -use Cake\ORM\BehaviorRegistry; use Cake\ORM\Exception\MissingEntityException; -use Cake\ORM\Marshaller; -use Cake\ORM\RulesChecker; use Cake\ORM\Rule\IsUnique; use Cake\Utility\Inflector; use Cake\Validation\ValidatorAwareTrait; From 5771a70068bf46c66b97c78bdf6613ff8d9faea5 Mon Sep 17 00:00:00 2001 From: Bryan Crowe Date: Mon, 14 Sep 2015 23:31:12 -0400 Subject: [PATCH 0472/2059] Fix CS --- Association/ExternalAssociationTrait.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Association/ExternalAssociationTrait.php b/Association/ExternalAssociationTrait.php index 79587b0e..90ad6be9 100644 --- a/Association/ExternalAssociationTrait.php +++ b/Association/ExternalAssociationTrait.php @@ -14,7 +14,6 @@ */ namespace Cake\ORM\Association; - /** * Represents a type of association that that needs to be recovered by performing * an extra query. From aeefa014d58eba709db88900e3bc137ecbb78f26 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 15 Sep 2015 22:17:32 -0400 Subject: [PATCH 0473/2059] Make debugInfo methods more resilient. Make the debugInfo methods for ORM\Table and ORM\Query not fail when they are used on partially complete objects. Refs #7290 --- Query.php | 4 ++-- Table.php | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Query.php b/Query.php index 0fdf68cc..0462b222 100644 --- a/Query.php +++ b/Query.php @@ -826,8 +826,8 @@ public function __debugInfo() 'buffered' => $this->_useBufferedResults, 'formatters' => count($this->_formatters), 'mapReducers' => count($this->_mapReduce), - 'contain' => $eagerLoader->contain(), - 'matching' => $eagerLoader->matching(), + 'contain' => $eagerLoader ? $eagerLoader->contain() : [], + 'matching' => $eagerLoader ? $eagerLoader->matching() : [], 'extraOptions' => $this->_options, 'repository' => $this->_repository ]; diff --git a/Table.php b/Table.php index 6ddc2bdc..ad91207e 100644 --- a/Table.php +++ b/Table.php @@ -2203,13 +2203,15 @@ public function buildRules(RulesChecker $rules) public function __debugInfo() { $conn = $this->connection(); + $associations = $this->_associations ?: false; + $behaviors = $this->_behaviors ?: false; return [ 'registryAlias' => $this->registryAlias(), 'table' => $this->table(), 'alias' => $this->alias(), 'entityClass' => $this->entityClass(), - 'associations' => $this->_associations->keys(), - 'behaviors' => $this->_behaviors->loaded(), + 'associations' => $associations ? $associations->keys() : false, + 'behaviors' => $behaviors ? $behaviors->loaded() : false, 'defaultConnection' => $this->defaultConnectionName(), 'connectionName' => $conn ? $conn->configName() : null ]; From 9be28580effdb2c3fd2ff07f80f04d81f6053529 Mon Sep 17 00:00:00 2001 From: Berry Goudswaard Date: Wed, 16 Sep 2015 23:16:43 +0200 Subject: [PATCH 0474/2059] Add recoverSort to TreeBehavior --- Behavior/TreeBehavior.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index b6921662..e055d910 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -71,7 +71,8 @@ class TreeBehavior extends Behavior 'left' => 'lft', 'right' => 'rght', 'scope' => null, - 'level' => null + 'level' => null, + 'recoverOrder' => null ]; /** @@ -774,11 +775,12 @@ protected function _recoverTree($counter = 0, $parentId = null, $level = -1) list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']]; $primaryKey = $this->_getPrimaryKey(); $aliasedPrimaryKey = $this->_table->aliasField($primaryKey); + $order = $config['recoverOrder'] ? $config['recoverOrder'] : $aliasedPrimaryKey; $query = $this->_scope($this->_table->query()) ->select([$aliasedPrimaryKey]) ->where([$this->_table->aliasField($parent) . ' IS' => $parentId]) - ->order([$aliasedPrimaryKey]) + ->order($order) ->hydrate(false); $leftCounter = $counter; From 4a7536bd2e323f58fd6ad1d17716f81d99b2a68d Mon Sep 17 00:00:00 2001 From: Berry Goudswaard Date: Thu, 17 Sep 2015 09:27:21 +0200 Subject: [PATCH 0475/2059] Change ternary operator --- Behavior/TreeBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index e055d910..5cc2b8e5 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -775,7 +775,7 @@ protected function _recoverTree($counter = 0, $parentId = null, $level = -1) list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']]; $primaryKey = $this->_getPrimaryKey(); $aliasedPrimaryKey = $this->_table->aliasField($primaryKey); - $order = $config['recoverOrder'] ? $config['recoverOrder'] : $aliasedPrimaryKey; + $order = $config['recoverOrder'] ?: $aliasedPrimaryKey; $query = $this->_scope($this->_table->query()) ->select([$aliasedPrimaryKey]) From 226e904b636b783013fa5c50d2a253a5568081e9 Mon Sep 17 00:00:00 2001 From: antograssiot Date: Thu, 17 Sep 2015 21:40:11 +0200 Subject: [PATCH 0476/2059] typo in "entities" --- Association/BelongsToMany.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 1671530f..92878185 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -777,7 +777,7 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { * @param \Cake\ORM\Query $existing a query for getting existing links * @param array $jointEntities link entities that should be persisted * @param array $targetEntities entities in target table that are related to - * the `$jointEntitites` + * the `$jointEntities` * @return array */ protected function _diffLinks($existing, $jointEntities, $targetEntities) From 735fd2f2d49f23f638164dac08991bcbed19d425 Mon Sep 17 00:00:00 2001 From: Yevgeny Tomenko Date: Fri, 18 Sep 2015 16:58:24 +0300 Subject: [PATCH 0477/2059] Change method parameters declaration from Entity to EntityInterface. --- Association/BelongsToMany.php | 4 +-- AssociationCollection.php | 21 ++++++------ Behavior.php | 12 +++---- Behavior/CounterCacheBehavior.php | 19 +++++------ Behavior/TimestampBehavior.php | 13 ++++---- Behavior/TreeBehavior.php | 54 +++++++++++++++---------------- Table.php | 12 +++---- 7 files changed, 69 insertions(+), 66 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 643bdbfb..6a6a3755 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -854,14 +854,14 @@ protected function _diffLinks($existing, $jointEntities, $targetEntities) /** * Throws an exception should any of the passed entities is not persisted. * - * @param \Cake\ORM\Entity $sourceEntity the row belonging to the `source` side + * @param \Cake\Datasource\EntityInterface $sourceEntity the row belonging to the `source` side * of this association * @param array $targetEntities list of entities belonging to the `target` side * of this association * @return bool * @throws \InvalidArgumentException */ - protected function _checkPersistenceStatus($sourceEntity, array $targetEntities) + protected function _checkPersistenceStatus(EntityInterface $sourceEntity, array $targetEntities) { if ($sourceEntity->isNew()) { $error = 'Source entity needs to be persisted before proceeding'; diff --git a/AssociationCollection.php b/AssociationCollection.php index 4b0d6412..0669159c 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -15,6 +15,7 @@ namespace Cake\ORM; use ArrayIterator; +use Cake\Datasource\EntityInterface; use Cake\ORM\Association; use Cake\ORM\AssociationsNormalizerTrait; use Cake\ORM\Entity; @@ -160,13 +161,13 @@ public function removeAll() * is the owning side. * * @param \Cake\ORM\Table $table The table entity is for. - * @param \Cake\ORM\Entity $entity The entity to save associated data for. + * @param \Cake\Datasource\EntityInterface $entity The entity to save associated data for. * @param array $associations The list of associations to save parents from. * associations not in this list will not be saved. * @param array $options The options for the save operation. * @return bool Success */ - public function saveParents(Table $table, Entity $entity, $associations, array $options = []) + public function saveParents(Table $table, EntityInterface $entity, $associations, array $options = []) { if (empty($associations)) { return true; @@ -181,13 +182,13 @@ public function saveParents(Table $table, Entity $entity, $associations, array $ * is not the owning side. * * @param \Cake\ORM\Table $table The table entity is for. - * @param \Cake\ORM\Entity $entity The entity to save associated data for. + * @param \Cake\Datasource\EntityInterface $entity The entity to save associated data for. * @param array $associations The list of associations to save children from. * associations not in this list will not be saved. * @param array $options The options for the save operation. * @return bool Success */ - public function saveChildren(Table $table, Entity $entity, array $associations, array $options) + public function saveChildren(Table $table, EntityInterface $entity, array $associations, array $options) { if (empty($associations)) { return true; @@ -199,7 +200,7 @@ public function saveChildren(Table $table, Entity $entity, array $associations, * Helper method for saving an association's data. * * @param \Cake\ORM\Table $table The table the save is currently operating on - * @param \Cake\ORM\Entity $entity The entity to save + * @param \Cake\Datasource\EntityInterface $entity The entity to save * @param array $associations Array of associations to save. * @param array $options Original options * @param bool $owningSide Compared with association classes' @@ -207,7 +208,7 @@ public function saveChildren(Table $table, Entity $entity, array $associations, * @return bool Success * @throws \InvalidArgumentException When an unknown alias is used. */ - protected function _saveAssociations($table, $entity, $associations, $options, $owningSide) + protected function _saveAssociations($table, EntityInterface $entity, $associations, $options, $owningSide) { unset($options['associated']); foreach ($associations as $alias => $nested) { @@ -238,12 +239,12 @@ protected function _saveAssociations($table, $entity, $associations, $options, $ * Helper method for saving an association's data. * * @param \Cake\ORM\Association $association The association object to save with. - * @param \Cake\ORM\Entity $entity The entity to save + * @param \Cake\Datasource\EntityInterface $entity The entity to save * @param array $nested Options for deeper associations * @param array $options Original options * @return bool Success */ - protected function _save($association, $entity, $nested, $options) + protected function _save($association, EntityInterface $entity, $nested, $options) { if (!$entity->dirty($association->property())) { return true; @@ -258,11 +259,11 @@ protected function _save($association, $entity, $nested, $options) * Cascade a delete across the various associations. * Cascade first across associations for which cascadeCallbacks is true. * - * @param \Cake\ORM\Entity $entity The entity to delete associations for. + * @param \Cake\Datasource\EntityInterface $entity The entity to delete associations for. * @param array $options The options used in the delete operation. * @return void */ - public function cascadeDelete(Entity $entity, array $options) + public function cascadeDelete(EntityInterface $entity, array $options) { $noCascade = []; foreach ($this->_items as $assoc) { diff --git a/Behavior.php b/Behavior.php index 6137f599..ec73c4ae 100644 --- a/Behavior.php +++ b/Behavior.php @@ -61,26 +61,26 @@ * Fired when the rules checking object for the table is being built. You can use this * callback to add more rules to the set. * - * - `beforeRules(Event $event, Entity $entity, ArrayObject $options, $operation)` + * - `beforeRules(Event $event, EntityInterface $entity, ArrayObject $options, $operation)` * Fired before an entity is validated using by a rules checker. By stopping this event, * you can return the final value of the rules checking operation. * - * - `afterRules(Event $event, Entity $entity, ArrayObject $options, bool $result, $operation)` + * - `afterRules(Event $event, EntityInterface $entity, ArrayObject $options, bool $result, $operation)` * Fired after the rules have been checked on the entity. By stopping this event, * you can return the final value of the rules checking operation. * - * - `beforeSave(Event $event, Entity $entity, ArrayObject $options)` + * - `beforeSave(Event $event, EntityInterface $entity, ArrayObject $options)` * Fired before each entity is saved. Stopping this event will abort the save * operation. When the event is stopped the result of the event will be returned. * - * - `afterSave(Event $event, Entity $entity, ArrayObject $options)` + * - `afterSave(Event $event, EntityInterface $entity, ArrayObject $options)` * Fired after an entity is saved. * - * - `beforeDelete(Event $event, Entity $entity, ArrayObject $options)` + * - `beforeDelete(Event $event, EntityInterface $entity, ArrayObject $options)` * Fired before an entity is deleted. By stopping this event you will abort * the delete operation. * - * - `afterDelete(Event $event, Entity $entity, ArrayObject $options)` + * - `afterDelete(Event $event, EntityInterface $entity, ArrayObject $options)` * Fired after an entity has been deleted. * * In addition to the core events, behaviors can respond to any diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 0d1c4b10..005d86fd 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -15,6 +15,7 @@ namespace Cake\ORM\Behavior; use Cake\Event\Event; +use Cake\Datasource\EntityInterface; use Cake\ORM\Association; use Cake\ORM\Behavior; use Cake\ORM\Entity; @@ -64,7 +65,7 @@ * ``` * [ * 'Users' => [ - * 'posts_published' => function (Event $event, Entity $entity, Table $table) { + * 'posts_published' => function (Event $event, EntityInterface $entity, Table $table) { * $query = $table->find('all')->where([ * 'published' => true, * 'user_id' => $entity->get('user_id') @@ -85,10 +86,10 @@ class CounterCacheBehavior extends Behavior * Makes sure to update counter cache when a new record is created or updated. * * @param \Cake\Event\Event $event The afterSave event that was fired. - * @param \Cake\ORM\Entity $entity The entity that was saved. + * @param \Cake\Datasource\EntityInterface $entity The entity that was saved. * @return void */ - public function afterSave(Event $event, Entity $entity) + public function afterSave(Event $event, EntityInterface $entity) { $this->_processAssociations($event, $entity); } @@ -99,10 +100,10 @@ public function afterSave(Event $event, Entity $entity) * Makes sure to update counter cache when a record is deleted. * * @param \Cake\Event\Event $event The afterDelete event that was fired. - * @param \Cake\ORM\Entity $entity The entity that was deleted. + * @param \Cake\Datasource\EntityInterface $entity The entity that was deleted. * @return void */ - public function afterDelete(Event $event, Entity $entity) + public function afterDelete(Event $event, EntityInterface $entity) { $this->_processAssociations($event, $entity); } @@ -111,10 +112,10 @@ public function afterDelete(Event $event, Entity $entity) * Iterate all associations and update counter caches. * * @param \Cake\Event\Event $event Event instance. - * @param \Cake\ORM\Entity $entity Entity. + * @param \Cake\Datasource\EntityInterface $entity Entity. * @return void */ - protected function _processAssociations(Event $event, Entity $entity) + protected function _processAssociations(Event $event, EntityInterface $entity) { foreach ($this->_config as $assoc => $settings) { $assoc = $this->_table->association($assoc); @@ -126,12 +127,12 @@ protected function _processAssociations(Event $event, Entity $entity) * Updates counter cache for a single association * * @param \Cake\Event\Event $event Event instance. - * @param \Cake\ORM\Entity $entity Entity + * @param \Cake\Datasource\EntityInterface $entity Entity * @param Association $assoc The association object * @param array $settings The settings for for counter cache for this association * @return void */ - protected function _processAssociation(Event $event, Entity $entity, Association $assoc, array $settings) + protected function _processAssociation(Event $event, EntityInterface $entity, Association $assoc, array $settings) { $foreignKeys = (array)$assoc->foreignKey(); $primaryKeys = (array)$assoc->target()->primaryKey(); diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 4e17ec03..1ef234ac 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -14,6 +14,7 @@ */ namespace Cake\ORM\Behavior; +use Cake\Datasource\EntityInterface; use Cake\Event\Event; use Cake\I18n\Time; use Cake\ORM\Behavior; @@ -79,12 +80,12 @@ public function initialize(array $config) * There is only one event handler, it can be configured to be called for any event * * @param \Cake\Event\Event $event Event instance. - * @param \Cake\ORM\Entity $entity Entity instance. + * @param \Cake\Datasource\EntityInterface $entity Entity instance. * @throws \UnexpectedValueException if a field's when value is misdefined * @return true (irrespective of the behavior logic, the save will not be prevented) * @throws \UnexpectedValueException When the value for an event is not 'always', 'new' or 'existing' */ - public function handleEvent(Event $event, Entity $entity) + public function handleEvent(Event $event, EntityInterface $entity) { $eventName = $event->name(); $events = $this->_config['events']; @@ -153,11 +154,11 @@ public function timestamp(\DateTime $ts = null, $refreshTimestamp = false) * "always" or "existing", update the timestamp value. This method will overwrite * any pre-existing value. * - * @param \Cake\ORM\Entity $entity Entity instance. + * @param \Cake\Datasource\EntityInterface $entity Entity instance. * @param string $eventName Event name. * @return bool true if a field is updated, false if no action performed */ - public function touch(Entity $entity, $eventName = 'Model.beforeSave') + public function touch(EntityInterface $entity, $eventName = 'Model.beforeSave') { $events = $this->_config['events']; if (empty($events[$eventName])) { @@ -181,12 +182,12 @@ public function touch(Entity $entity, $eventName = 'Model.beforeSave') /** * Update a field, if it hasn't been updated already * - * @param \Cake\ORM\Entity $entity Entity instance. + * @param \Cake\Datasource\EntityInterface $entity Entity instance. * @param string $field Field name * @param bool $refreshTimestamp Whether to refresh timestamp. * @return void */ - protected function _updateField(Entity $entity, $field, $refreshTimestamp) + protected function _updateField(EntityInterface $entity, $field, $refreshTimestamp) { if ($entity->dirty($field)) { return; diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index a5765251..88dc42eb 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -80,11 +80,11 @@ class TreeBehavior extends Behavior * included in the parameters to be saved. * * @param \Cake\Event\Event $event The beforeSave event that was fired - * @param \Cake\ORM\Entity $entity the entity that is going to be saved + * @param \Cake\Datasource\EntityInterface $entity the entity that is going to be saved * @return void * @throws \RuntimeException if the parent to set for the node is invalid */ - public function beforeSave(Event $event, Entity $entity) + public function beforeSave(Event $event, EntityInterface $entity) { $isNew = $entity->isNew(); $config = $this->config(); @@ -146,10 +146,10 @@ public function beforeSave(Event $event, Entity $entity) * Manages updating level of descendents of currently saved entity. * * @param \Cake\Event\Event $event The beforeSave event that was fired - * @param \Cake\ORM\Entity $entity the entity that is going to be saved + * @param \Cake\Datasource\EntityInterface $entity the entity that is going to be saved * @return void */ - public function afterSave(Event $event, Entity $entity) + public function afterSave(Event $event, EntityInterface $entity) { if (!$this->_config['level'] || $entity->isNew()) { return; @@ -161,10 +161,10 @@ public function afterSave(Event $event, Entity $entity) /** * Set level for descendents. * - * @param \Cake\ORM\Entity $entity The entity whose descendents need to be updated. + * @param \Cake\Datasource\EntityInterface $entity The entity whose descendents need to be updated. * @return void */ - protected function _setChildrenLevel(Entity $entity) + protected function _setChildrenLevel(EntityInterface $entity) { $config = $this->config(); @@ -198,10 +198,10 @@ protected function _setChildrenLevel(Entity $entity) * Also deletes the nodes in the subtree of the entity to be delete * * @param \Cake\Event\Event $event The beforeDelete event that was fired - * @param \Cake\ORM\Entity $entity The entity that is going to be saved + * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved * @return void */ - public function beforeDelete(Event $event, Entity $entity) + public function beforeDelete(Event $event, EntityInterface $entity) { $config = $this->config(); $this->_ensureFields($entity); @@ -224,12 +224,12 @@ public function beforeDelete(Event $event, Entity $entity) * updated to a new parent. It also makes the hole in the tree so the node * move can be done without corrupting the structure. * - * @param \Cake\ORM\Entity $entity The entity to re-parent + * @param \Cake\Datasource\EntityInterface $entity The entity to re-parent * @param mixed $parent the id of the parent to set * @return void * @throws \RuntimeException if the parent to set to the entity is not valid */ - protected function _setParent($entity, $parent) + protected function _setParent(EntityInterface $entity, $parent) { $config = $this->config(); $parentNode = $this->_getNode($parent); @@ -286,10 +286,10 @@ protected function _setParent($entity, $parent) * a new root in the tree. It also modifies the ordering in the rest of the tree * so the structure remains valid * - * @param \Cake\ORM\Entity $entity The entity to set as a new root + * @param \Cake\Datasource\EntityInterface $entity The entity to set as a new root * @return void */ - protected function _setAsRoot($entity) + protected function _setAsRoot(EntityInterface $entity) { $config = $this->config(); $edge = $this->_getMax(); @@ -369,12 +369,12 @@ function ($field) { /** * Get the number of children nodes. * - * @param \Cake\ORM\Entity $node The entity to count children for + * @param \Cake\Datasource\EntityInterface $node The entity to count children for * @param bool $direct whether to count all nodes in the subtree or just * direct children * @return int Number of children nodes. */ - public function childCount(Entity $node, $direct = false) + public function childCount(EntityInterface $node, $direct = false) { $config = $this->config(); $parent = $this->_table->aliasField($config['parent']); @@ -503,11 +503,11 @@ public function formatTreeList(Query $query, array $options = []) * Note that the node will not be deleted just moved away from its current position * without moving its children with it. * - * @param \Cake\ORM\Entity $node The node to remove from the tree + * @param \Cake\Datasource\EntityInterface $node The node to remove from the tree * @return \Cake\ORM\Entity|false the node after being removed from the tree or * false on error */ - public function removeFromTree(Entity $node) + public function removeFromTree(EntityInterface $node) { return $this->_table->connection()->transactional(function () use ($node) { $this->_ensureFields($node); @@ -518,11 +518,11 @@ public function removeFromTree(Entity $node) /** * Helper function containing the actual code for removeFromTree * - * @param \Cake\ORM\Entity $node The node to remove from the tree + * @param \Cake\Datasource\EntityInterface $node The node to remove from the tree * @return \Cake\ORM\Entity|false the node after being removed from the tree or * false on error */ - protected function _removeFromTree($node) + protected function _removeFromTree(EntityInterface $node) { $config = $this->config(); $left = $node->get($config['left']); @@ -561,12 +561,12 @@ protected function _removeFromTree($node) * If the node is the first child, or is a top level node with no previous node * this method will return false * - * @param \Cake\ORM\Entity $node The node to move + * @param \Cake\Datasource\EntityInterface $node The node to move * @param int|bool $number How many places to move the node, or true to move to first position * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found * @return \Cake\ORM\Entity|bool $node The node after being moved or false on failure */ - public function moveUp(Entity $node, $number = 1) + public function moveUp(EntityInterface $node, $number = 1) { if ($number < 1) { return false; @@ -581,12 +581,12 @@ public function moveUp(Entity $node, $number = 1) /** * Helper function used with the actual code for moveUp * - * @param \Cake\ORM\Entity $node The node to move + * @param \Cake\Datasource\EntityInterface $node The node to move * @param int|bool $number How many places to move the node, or true to move to first position * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found * @return \Cake\ORM\Entity|bool $node The node after being moved or false on failure */ - protected function _moveUp($node, $number) + protected function _moveUp(EntityInterface $node, $number) { $config = $this->config(); list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']]; @@ -642,12 +642,12 @@ protected function _moveUp($node, $number) * If the node is the last child, or is a top level node with no subsequent node * this method will return false * - * @param \Cake\ORM\Entity $node The node to move + * @param \Cake\Datasource\EntityInterface $node The node to move * @param int|bool $number How many places to move the node or true to move to last position * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found * @return \Cake\ORM\Entity|bool the entity after being moved or false on failure */ - public function moveDown(Entity $node, $number = 1) + public function moveDown(EntityInterface $node, $number = 1) { if ($number < 1) { return false; @@ -662,12 +662,12 @@ public function moveDown(Entity $node, $number = 1) /** * Helper function used with the actual code for moveDown * - * @param \Cake\ORM\Entity $node The node to move + * @param \Cake\Datasource\EntityInterface $node The node to move * @param int|bool $number How many places to move the node, or true to move to last position * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found * @return \Cake\ORM\Entity|bool $node The node after being moved or false on failure */ - protected function _moveDown($node, $number) + protected function _moveDown(EntityInterface $node, $number) { $config = $this->config(); list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']]; @@ -879,7 +879,7 @@ protected function _scope($query) * Ensures that the provided entity contains non-empty values for the left and * right fields * - * @param \Cake\ORM\Entity $entity The entity to ensure fields for + * @param \Cake\Datasource\EntityInterface $entity The entity to ensure fields for * @return void */ protected function _ensureFields($entity) diff --git a/Table.php b/Table.php index e5888e9f..955e5fd0 100644 --- a/Table.php +++ b/Table.php @@ -95,26 +95,26 @@ * - `buildRules(Event $event, RulesChecker $rules)` * Allows listeners to modify the rules checker by adding more rules. * - * - `beforeRules(Event $event, Entity $entity, ArrayObject $options, string $operation)` + * - `beforeRules(Event $event, EntityInterface $entity, ArrayObject $options, string $operation)` * Fired before an entity is validated using the rules checker. By stopping this event, * you can return the final value of the rules checking operation. * - * - `afterRules(Event $event, Entity $entity, ArrayObject $options, bool $result, string $operation)` + * - `afterRules(Event $event, EntityInterface $entity, ArrayObject $options, bool $result, string $operation)` * Fired after the rules have been checked on the entity. By stopping this event, * you can return the final value of the rules checking operation. * - * - `beforeSave(Event $event, Entity $entity, ArrayObject $options)` + * - `beforeSave(Event $event, EntityInterface $entity, ArrayObject $options)` * Fired before each entity is saved. Stopping this event will abort the save * operation. When the event is stopped the result of the event will be returned. * - * - `afterSave(Event $event, Entity $entity, ArrayObject $options)` + * - `afterSave(Event $event, EntityInterface $entity, ArrayObject $options)` * Fired after an entity is saved. * - * - `beforeDelete(Event $event, Entity $entity, ArrayObject $options)` + * - `beforeDelete(Event $event, EntityInterface $entity, ArrayObject $options)` * Fired before an entity is deleted. By stopping this event you will abort * the delete operation. * - * - `afterDelete(Event $event, Entity $entity, ArrayObject $options)` + * - `afterDelete(Event $event, EntityInterface $entity, ArrayObject $options)` * Fired after an entity has been deleted. * * @see \Cake\Event\EventManager for reference on the events system. From 8be24341b5ffc6555c8c8906f9fba01a113f4266 Mon Sep 17 00:00:00 2001 From: Yevgeny Tomenko Date: Fri, 18 Sep 2015 17:13:16 +0300 Subject: [PATCH 0478/2059] Removed added method's types for protected methods --- Association/BelongsToMany.php | 2 +- AssociationCollection.php | 4 ++-- Behavior/TimestampBehavior.php | 2 +- Behavior/TreeBehavior.php | 12 ++++++------ 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 6a6a3755..92f8691d 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -861,7 +861,7 @@ protected function _diffLinks($existing, $jointEntities, $targetEntities) * @return bool * @throws \InvalidArgumentException */ - protected function _checkPersistenceStatus(EntityInterface $sourceEntity, array $targetEntities) + protected function _checkPersistenceStatus($sourceEntity, array $targetEntities) { if ($sourceEntity->isNew()) { $error = 'Source entity needs to be persisted before proceeding'; diff --git a/AssociationCollection.php b/AssociationCollection.php index 0669159c..460566e2 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -208,7 +208,7 @@ public function saveChildren(Table $table, EntityInterface $entity, array $assoc * @return bool Success * @throws \InvalidArgumentException When an unknown alias is used. */ - protected function _saveAssociations($table, EntityInterface $entity, $associations, $options, $owningSide) + protected function _saveAssociations($table, $entity, $associations, $options, $owningSide) { unset($options['associated']); foreach ($associations as $alias => $nested) { @@ -244,7 +244,7 @@ protected function _saveAssociations($table, EntityInterface $entity, $associati * @param array $options Original options * @return bool Success */ - protected function _save($association, EntityInterface $entity, $nested, $options) + protected function _save($association, $entity, $nested, $options) { if (!$entity->dirty($association->property())) { return true; diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 1ef234ac..eb544e83 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -187,7 +187,7 @@ public function touch(EntityInterface $entity, $eventName = 'Model.beforeSave') * @param bool $refreshTimestamp Whether to refresh timestamp. * @return void */ - protected function _updateField(EntityInterface $entity, $field, $refreshTimestamp) + protected function _updateField($entity, $field, $refreshTimestamp) { if ($entity->dirty($field)) { return; diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 88dc42eb..1e4f25a0 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -164,7 +164,7 @@ public function afterSave(Event $event, EntityInterface $entity) * @param \Cake\Datasource\EntityInterface $entity The entity whose descendents need to be updated. * @return void */ - protected function _setChildrenLevel(EntityInterface $entity) + protected function _setChildrenLevel($entity) { $config = $this->config(); @@ -229,7 +229,7 @@ public function beforeDelete(Event $event, EntityInterface $entity) * @return void * @throws \RuntimeException if the parent to set to the entity is not valid */ - protected function _setParent(EntityInterface $entity, $parent) + protected function _setParent($entity, $parent) { $config = $this->config(); $parentNode = $this->_getNode($parent); @@ -289,7 +289,7 @@ protected function _setParent(EntityInterface $entity, $parent) * @param \Cake\Datasource\EntityInterface $entity The entity to set as a new root * @return void */ - protected function _setAsRoot(EntityInterface $entity) + protected function _setAsRoot($entity) { $config = $this->config(); $edge = $this->_getMax(); @@ -522,7 +522,7 @@ public function removeFromTree(EntityInterface $node) * @return \Cake\ORM\Entity|false the node after being removed from the tree or * false on error */ - protected function _removeFromTree(EntityInterface $node) + protected function _removeFromTree($node) { $config = $this->config(); $left = $node->get($config['left']); @@ -586,7 +586,7 @@ public function moveUp(EntityInterface $node, $number = 1) * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found * @return \Cake\ORM\Entity|bool $node The node after being moved or false on failure */ - protected function _moveUp(EntityInterface $node, $number) + protected function _moveUp($node, $number) { $config = $this->config(); list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']]; @@ -667,7 +667,7 @@ public function moveDown(EntityInterface $node, $number = 1) * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found * @return \Cake\ORM\Entity|bool $node The node after being moved or false on failure */ - protected function _moveDown(EntityInterface $node, $number) + protected function _moveDown($node, $number) { $config = $this->config(); list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']]; From 89feecb8d75f584164a962f0ce614d6972c44353 Mon Sep 17 00:00:00 2001 From: Yevgeny Tomenko Date: Fri, 18 Sep 2015 17:15:12 +0300 Subject: [PATCH 0479/2059] Cange use classes order --- Behavior/CounterCacheBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 005d86fd..a42cbb7f 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -14,8 +14,8 @@ */ namespace Cake\ORM\Behavior; -use Cake\Event\Event; use Cake\Datasource\EntityInterface; +use Cake\Event\Event; use Cake\ORM\Association; use Cake\ORM\Behavior; use Cake\ORM\Entity; From 801a0c183dcae7e743437f1f4ac43579e65b4c3d Mon Sep 17 00:00:00 2001 From: Yevgeny Tomenko Date: Fri, 18 Sep 2015 17:15:12 +0300 Subject: [PATCH 0480/2059] Change use classes order --- Behavior/CounterCacheBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 005d86fd..a42cbb7f 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -14,8 +14,8 @@ */ namespace Cake\ORM\Behavior; -use Cake\Event\Event; use Cake\Datasource\EntityInterface; +use Cake\Event\Event; use Cake\ORM\Association; use Cake\ORM\Behavior; use Cake\ORM\Entity; From 71fb027f0529886f3a75a714d156a4ae5f629e01 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 20 Sep 2015 12:10:09 -0400 Subject: [PATCH 0481/2059] Allow any contain call to overwrite the eagerloader. The override parameter should always work when used. Refs #7412 --- Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Query.php b/Query.php index 6a5f966f..2247c0db 100644 --- a/Query.php +++ b/Query.php @@ -287,7 +287,7 @@ public function eagerLoader(EagerLoader $instance = null) */ public function contain($associations = null, $override = false) { - if (empty($associations) && $override) { + if ($override) { $this->_eagerLoader = null; } From 66b80346ada44dbd0e149b7d02549a3c1346e85d Mon Sep 17 00:00:00 2001 From: ndm2 Date: Wed, 23 Sep 2015 19:19:15 +0200 Subject: [PATCH 0482/2059] Fix type hints/references. --- LazyEagerLoader.php | 20 ++++++++++---------- Table.php | 6 +++--- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index eb412977..710e15e3 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -35,11 +35,11 @@ class LazyEagerLoader * * The properties for the associations to be loaded will be overwritten on each entity. * - * @param Cake\Datasource\EntityInterface|array $entities a single entity or list of entities + * @param \Cake\Datasource\EntityInterface|array $entities a single entity or list of entities * @param array $contain A `contain()` compatible array. * @see Cake\ORM\Query\contain() - * @param Cake\ORM\Table $source The table to use for fetching the top level entities - * @return Cake\Datasource\EntityInterface|array + * @param \Cake\ORM\Table $source The table to use for fetching the top level entities + * @return \Cake\Datasource\EntityInterface|array */ public function loadInto($entities, array $contain, Table $source) { @@ -62,10 +62,10 @@ public function loadInto($entities, array $contain, Table $source) * Builds a query for loading the passed list of entity objects along with the * associations specified in $contain. * - * @param Cake\Collection\CollectionInterface $objects The original entitites + * @param \Cake\Collection\CollectionInterface $objects The original entitites * @param array $contain The associations to be loaded - * @param Cake\ORM\Table $source The table to use for fetching the top level entities - * @return Cake\ORM\Query + * @param \Cake\ORM\Table $source The table to use for fetching the top level entities + * @return \Cake\ORM\Query */ protected function _getQuery($objects, $contain, $source) { @@ -108,7 +108,7 @@ protected function _getQuery($objects, $contain, $source) * Returns a map of property names where the association results should be injected * in the top level entities. * - * @param Cake\ORM\Table $source The table having the top level associations + * @param \Cake\ORM\Table $source The table having the top level associations * @param array $associations The name of the top level associations * @return array */ @@ -126,10 +126,10 @@ protected function _getPropertyMap($source, $associations) * Injects the results of the eager loader query into the original list of * entities. * - * @param array|Traversable $objects The original list of entities - * @param Cake\Collection\CollectionInterface $results The loaded results + * @param array|\Traversable $objects The original list of entities + * @param \Cake\Collection\CollectionInterface $results The loaded results * @param array $associations The top level associations that were loaded - * @param Cake\ORM\Table $source The table where the entities came from + * @param \Cake\ORM\Table $source The table where the entities came from * @return array */ protected function _injectResults($objects, $results, $associations, $source) diff --git a/Table.php b/Table.php index 2139a502..d321c950 100644 --- a/Table.php +++ b/Table.php @@ -2220,10 +2220,10 @@ public function buildRules(RulesChecker $rules) * * The properties for the associations to be loaded will be overwritten on each entity. * - * @param Cake\Datasource\EntityInterface|array $entities a single entity or list of entities + * @param \Cake\Datasource\EntityInterface|array $entities a single entity or list of entities * @param array $contain A `contain()` compatible array. - * @see Cake\ORM\Query\contain() - * @return Cake\Datasource\EntityInterface|array + * @see \Cake\ORM\Query::contain() + * @return \Cake\Datasource\EntityInterface|array */ public function loadInto($entities, array $contain) { From 5fd7814d71c479e0123254d49cb47d6e8d2a4577 Mon Sep 17 00:00:00 2001 From: ndm2 Date: Wed, 23 Sep 2015 22:45:56 +0200 Subject: [PATCH 0483/2059] Always import classes. --- Association/BelongsToMany.php | 6 ++++-- Association/HasMany.php | 3 ++- Behavior.php | 6 ++++-- Behavior/TimestampBehavior.php | 6 ++++-- Marshaller.php | 7 ++++--- Query.php | 3 ++- Table.php | 4 ++-- 7 files changed, 22 insertions(+), 13 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 701526c3..918f8148 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -21,6 +21,8 @@ use Cake\Utility\Inflector; use InvalidArgumentException; use RuntimeException; +use SplObjectStorage; +use Traversable; /** * Represents an M - N relationship where there exists a junction - or join - table @@ -487,7 +489,7 @@ protected function _saveTarget(EntityInterface $parentEntity, $entities, $option } unset($options['associated'][$this->_junctionProperty]); - if (!(is_array($entities) || $entities instanceof \Traversable)) { + if (!(is_array($entities) || $entities instanceof Traversable)) { $name = $this->property(); $message = sprintf('Could not save %s, it cannot be traversed', $name); throw new InvalidArgumentException($message); @@ -673,7 +675,7 @@ function () use ($sourceEntity, $targetEntities) { return; } - $storage = new \SplObjectStorage; + $storage = new SplObjectStorage; foreach ($targetEntities as $e) { $storage->attach($e); } diff --git a/Association/HasMany.php b/Association/HasMany.php index efd39494..c3084c97 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -20,6 +20,7 @@ use Cake\ORM\Table; use InvalidArgumentException; use RuntimeException; +use Traversable; /** * Represents an N - 1 relationship where the target side of the relationship @@ -88,7 +89,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) return $entity; } - if (!is_array($targetEntities) && !($targetEntities instanceof \Traversable)) { + if (!is_array($targetEntities) && !($targetEntities instanceof Traversable)) { $name = $this->property(); $message = sprintf('Could not save %s, it cannot be traversed', $name); throw new InvalidArgumentException($message); diff --git a/Behavior.php b/Behavior.php index ec73c4ae..9eea7e23 100644 --- a/Behavior.php +++ b/Behavior.php @@ -17,6 +17,8 @@ use Cake\Core\Exception\Exception; use Cake\Core\InstanceConfigTrait; use Cake\Event\EventListenerInterface; +use ReflectionClass; +use ReflectionMethod; /** * Base class for behaviors. @@ -377,9 +379,9 @@ protected function _reflectionCache() 'methods' => [] ]; - $reflection = new \ReflectionClass($class); + $reflection = new ReflectionClass($class); - foreach ($reflection->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) { + foreach ($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { $methodName = $method->getName(); if (in_array($methodName, $baseMethods) || isset($eventMethods[$methodName]) diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index eb544e83..0b6ac637 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -19,6 +19,8 @@ use Cake\I18n\Time; use Cake\ORM\Behavior; use Cake\ORM\Entity; +use DateTime; +use UnexpectedValueException; class TimestampBehavior extends Behavior { @@ -95,7 +97,7 @@ public function handleEvent(Event $event, EntityInterface $entity) foreach ($events[$eventName] as $field => $when) { if (!in_array($when, ['always', 'new', 'existing'])) { - throw new \UnexpectedValueException( + throw new UnexpectedValueException( sprintf('When should be one of "always", "new" or "existing". The passed value "%s" is invalid', $when) ); } @@ -133,7 +135,7 @@ public function implementedEvents() * @param bool $refreshTimestamp If true timestamp is refreshed. * @return \Cake\I18n\Time */ - public function timestamp(\DateTime $ts = null, $refreshTimestamp = false) + public function timestamp(DateTime $ts = null, $refreshTimestamp = false) { if ($ts) { if ($this->_config['refreshTimestamp']) { diff --git a/Marshaller.php b/Marshaller.php index eaf0c177..323d967a 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -14,11 +14,12 @@ */ namespace Cake\ORM; +use ArrayObject; use Cake\Collection\Collection; use Cake\Database\Expression\TupleComparison; use Cake\Database\Type; use Cake\Datasource\EntityInterface; -use \RuntimeException; +use RuntimeException; /** * Contains logic to convert array data into entities. @@ -204,8 +205,8 @@ protected function _prepareDataAndOptions($data, $options) $data = $data[$tableName]; } - $data = new \ArrayObject($data); - $options = new \ArrayObject($options); + $data = new ArrayObject($data); + $options = new ArrayObject($options); $this->_table->dispatchEvent('Model.beforeMarshal', compact('data', 'options')); return [(array)$data, (array)$options]; diff --git a/Query.php b/Query.php index 6a5f966f..b9ec39d5 100644 --- a/Query.php +++ b/Query.php @@ -15,6 +15,7 @@ namespace Cake\ORM; use ArrayObject; +use BadMethodCallException; use Cake\Database\ExpressionInterface; use Cake\Database\Query as DatabaseQuery; use Cake\Database\ValueBinder; @@ -1014,7 +1015,7 @@ public function __call($method, $arguments) return $this->_call($method, $arguments); } - throw new \BadMethodCallException( + throw new BadMethodCallException( sprintf('Cannot call method "%s" on a "%s" query', $method, $this->type()) ); } diff --git a/Table.php b/Table.php index d321c950..31f0f0c1 100644 --- a/Table.php +++ b/Table.php @@ -1762,7 +1762,7 @@ public function callFinder($type, Query $query, array $options = []) return $this->_behaviors->callFinder($type, [$query, $options]); } - throw new \BadMethodCallException( + throw new BadMethodCallException( sprintf('Unknown finder method "%s"', $type) ); } @@ -1849,7 +1849,7 @@ public function __call($method, $args) return $this->_dynamicFinder($method, $args); } - throw new \BadMethodCallException( + throw new BadMethodCallException( sprintf('Unknown method "%s"', $method) ); } From bb94c89d0cd059c566667df75a0021b49e8a8b06 Mon Sep 17 00:00:00 2001 From: Patrick Conroy Date: Fri, 25 Sep 2015 14:24:32 -0400 Subject: [PATCH 0484/2059] Add a check for row's key before assuming it's there --- Association/SelectableAssociationTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/SelectableAssociationTrait.php b/Association/SelectableAssociationTrait.php index 53a76357..9b334ef7 100644 --- a/Association/SelectableAssociationTrait.php +++ b/Association/SelectableAssociationTrait.php @@ -285,7 +285,7 @@ protected function _resultInjector($fetchQuery, $resultMap, $options) $sourceKey = $sourceKeys[0]; return function ($row) use ($resultMap, $sourceKey, $nestKey) { - if (isset($resultMap[$row[$sourceKey]])) { + if (isset($row[$sourceKey]) && isset($resultMap[$row[$sourceKey]])) { $row[$nestKey] = $resultMap[$row[$sourceKey]]; } return $row; From 58f944d8c439812cc3f05c23b721e199c94cf55b Mon Sep 17 00:00:00 2001 From: Yves P Date: Fri, 25 Sep 2015 23:16:44 +0200 Subject: [PATCH 0485/2059] Typehint should be against the ConnectionInterface and not a Connection object --- Table.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Table.php b/Table.php index 31f0f0c1..584f7101 100644 --- a/Table.php +++ b/Table.php @@ -17,9 +17,9 @@ use ArrayObject; use BadMethodCallException; use Cake\Core\App; -use Cake\Database\Connection; use Cake\Database\Schema\Table as Schema; use Cake\Database\Type; +use Cake\Datasource\ConnectionInterface; use Cake\Datasource\EntityInterface; use Cake\Datasource\Exception\InvalidPrimaryKeyException; use Cake\Datasource\RepositoryInterface; @@ -389,10 +389,10 @@ public function registryAlias($registryAlias = null) /** * Returns the connection instance or sets a new one * - * @param \Cake\Database\Connection|null $conn The new connection instance - * @return \Cake\Database\Connection + * @param \Cake\Datasource\ConnectionInterface|null $conn The new connection instance + * @return \Cake\Datasource\ConnectionInterface */ - public function connection(Connection $conn = null) + public function connection(ConnectionInterface $conn = null) { if ($conn === null) { return $this->_connection; From 65357445d3107cfc66ed1d7361a9ac2fb36888d6 Mon Sep 17 00:00:00 2001 From: Luan Hospodarsky Date: Mon, 28 Sep 2015 23:25:21 -0300 Subject: [PATCH 0486/2059] Implemented feature discussed in #7462 --- Association/HasMany.php | 68 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index c3084c97..5cbaf442 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -32,7 +32,9 @@ class HasMany extends Association { use DependentDeleteTrait; - use ExternalAssociationTrait; + use ExternalAssociationTrait { + _options as _externalOptions; + } /** * The type of join to be used when adding the association to a query @@ -55,6 +57,27 @@ class HasMany extends Association */ protected $_validStrategies = [self::STRATEGY_SELECT, self::STRATEGY_SUBQUERY]; + /** + * Saving strategy that will only append to the links set + * + * @var string + */ + const SAVE_APPEND = 'append'; + + /** + * Saving strategy that will replace the links with the provided set + * + * @var string + */ + const SAVE_REPLACE = 'replace'; + + /** + * Saving strategy to be used by this association + * + * @var string + */ + protected $_saveStrategy = self::SAVE_APPEND; + /** * Returns whether or not the passed table is the owning side for this * association. This means that rows in the 'target' table would miss important @@ -68,6 +91,26 @@ public function isOwningSide(Table $side) return $side === $this->source(); } + /** + * Sets the strategy that should be used for saving. If called with no + * arguments, it will return the currently configured strategy + * + * @param string|null $strategy the strategy name to be used + * @throws \InvalidArgumentException if an invalid strategy name is passed + * @return string the strategy to be used for saving + */ + public function saveStrategy($strategy = null) + { + if ($strategy === null) { + return $this->_saveStrategy; + } + if (!in_array($strategy, [self::SAVE_APPEND, self::SAVE_REPLACE])) { + $msg = sprintf('Invalid save strategy "%s"', $strategy); + throw new InvalidArgumentException($msg); + } + return $this->_saveStrategy = $strategy; + } + /** * Takes an entity from the source table and looks if there is a field * matching the property name for this association. The found entity will be @@ -104,6 +147,11 @@ public function saveAssociated(EntityInterface $entity, array $options = []) $original = $targetEntities; $options['_sourceTable'] = $this->source(); + if ($this->_saveStrategy === self::SAVE_REPLACE) { + $updateFields = array_fill_keys(array_keys($properties), null); + $target->updateAll($updateFields, $properties); + } + foreach ($targetEntities as $k => $targetEntity) { if (!($targetEntity instanceof EntityInterface)) { break; @@ -117,6 +165,10 @@ public function saveAssociated(EntityInterface $entity, array $options = []) $targetEntity->set($properties, ['guard' => false]); } + if ($this->_saveStrategy === self::SAVE_REPLACE) { + $targetEntity->isNew(true); + } + if ($target->save($targetEntity, $options)) { $targetEntities[$k] = $targetEntity; continue; @@ -166,4 +218,18 @@ public function type() { return self::ONE_TO_MANY; } + + /** + * Parse extra options passed in the constructor. + * + * @param array $opts original list of options passed in constructor + * @return void + */ + protected function _options(array $opts) + { + $this->_externalOptions($opts); + if (!empty($opts['saveStrategy'])) { + $this->saveStrategy($opts['saveStrategy']); + } + } } From 42898b390b42c5a92116eee0a1d04a20e0a44737 Mon Sep 17 00:00:00 2001 From: Luan Hospodarsky Date: Tue, 29 Sep 2015 23:47:42 -0300 Subject: [PATCH 0487/2059] Adding cascade delete for dependent HasMany associations --- Association/HasMany.php | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index 5cbaf442..d373fdfa 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -148,8 +148,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) $options['_sourceTable'] = $this->source(); if ($this->_saveStrategy === self::SAVE_REPLACE) { - $updateFields = array_fill_keys(array_keys($properties), null); - $target->updateAll($updateFields, $properties); + $this->_unlinkAssociated($properties, $entity, $target, $options); } foreach ($targetEntities as $k => $targetEntity) { @@ -185,6 +184,25 @@ public function saveAssociated(EntityInterface $entity, array $options = []) return $entity; } + /** + * Deletes/sets null the related objects according to the dependency between source and targets + * + * @param array $properties array of foreignKey properties + * @param EntityInterface $entity the entity which should have its associated entities unassigned + * @param Table $target The associated table + * @param array $options original list of options passed in constructor + * @return void + */ + protected function _unlinkAssociated(array $properties, EntityInterface $entity, Table $target, array $options) + { + if ($this->dependent()) { + $this->cascadeDelete($entity, $options); + } else { + $updateFields = array_fill_keys(array_keys($properties), null); + $target->updateAll($updateFields, $properties); + } + } + /** * {@inheritDoc} */ From 4114691171110fe450d77d6b874fb341d65710db Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 29 Sep 2015 23:26:53 -0400 Subject: [PATCH 0488/2059] Only reset contain options, don't clear matching() state. Refs #7412 --- EagerLoader.php | 15 +++++++++++++++ Query.php | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/EagerLoader.php b/EagerLoader.php index 21421465..c82b1ad3 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -141,6 +141,21 @@ public function contain($associations = []) return $this->_containments = $associations; } + /** + * Remove any existing non-matching based containments. + * + * This will reset/clear out any contained associations that were not + * added via matching(). + * + * @return void + */ + public function clearContain() + { + $this->_containments = []; + $this->_normalized = $this->_loadExternal = null; + $this->_aliasList = []; + } + /** * Set whether or not contained associations will load fields automatically. * diff --git a/Query.php b/Query.php index 2247c0db..fc83dd03 100644 --- a/Query.php +++ b/Query.php @@ -288,7 +288,7 @@ public function eagerLoader(EagerLoader $instance = null) public function contain($associations = null, $override = false) { if ($override) { - $this->_eagerLoader = null; + $this->_eagerLoader->clearContain(); } $result = $this->eagerLoader()->contain($associations); From fbe6e9d56c5c67d4ec9c5a6bfb37c3ff9700c04c Mon Sep 17 00:00:00 2001 From: Luan Hospodarsky Date: Thu, 1 Oct 2015 01:06:58 -0300 Subject: [PATCH 0489/2059] Cascade delete also triggered when foreign key does not accept nullvalues --- Association/HasMany.php | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index d373fdfa..2d57d0bc 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -185,7 +185,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) } /** - * Deletes/sets null the related objects according to the dependency between source and targets + * Deletes/sets null the related objects according to the dependency between source and targets and foreign key nullability * * @param array $properties array of foreignKey properties * @param EntityInterface $entity the entity which should have its associated entities unassigned @@ -195,7 +195,10 @@ public function saveAssociated(EntityInterface $entity, array $options = []) */ protected function _unlinkAssociated(array $properties, EntityInterface $entity, Table $target, array $options) { - if ($this->dependent()) { + $mustBeDependent = (!$this->_foreignKeyAcceptsNull($target, $properties) || $this->dependent()); + $this->dependent($mustBeDependent); + + if ($mustBeDependent) { $this->cascadeDelete($entity, $options); } else { $updateFields = array_fill_keys(array_keys($properties), null); @@ -203,6 +206,25 @@ protected function _unlinkAssociated(array $properties, EntityInterface $entity, } } + /** + * Checks the nullable flag of the foreign key + * + * @param Table $table the table containing the foreign key + * @param array $properties the list of fields that compose the foreign key + * @return bool + */ + protected function _foreignKeyAcceptsNull(Table $table, array $properties) + { + return array_product( + array_map( + function ($prop) use ($table) { + return $table->schema()->isNullable($prop); + }, + array_keys($properties) + ) + ); + } + /** * {@inheritDoc} */ From 2ddeb4a565dd018af215d51c7577d6103ff4e9b5 Mon Sep 17 00:00:00 2001 From: Yves P Date: Thu, 1 Oct 2015 19:27:05 +0200 Subject: [PATCH 0490/2059] Update docblocks to hint to the ConnectionInterface --- Query.php | 2 +- Table.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Query.php b/Query.php index 1992b97a..a56ee226 100644 --- a/Query.php +++ b/Query.php @@ -110,7 +110,7 @@ class Query extends DatabaseQuery implements JsonSerializable /** * Constructor * - * @param \Cake\Database\Connection $connection The connection object + * @param \Cake\Datasource\ConnectionInterface $connection The connection object * @param \Cake\ORM\Table $table The table this query is starting on */ public function __construct($connection, $table) diff --git a/Table.php b/Table.php index 584f7101..b98b8054 100644 --- a/Table.php +++ b/Table.php @@ -161,7 +161,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc /** * Connection instance * - * @var \Cake\Database\Connection + * @var \Cake\Datasource\ConnectionInterface */ protected $_connection; From 8b41782796f515344f9e312d27938a690361d714 Mon Sep 17 00:00:00 2001 From: antograssiot Date: Thu, 1 Oct 2015 21:31:52 +0200 Subject: [PATCH 0491/2059] remove unnecessary use statements --- Association.php | 2 -- AssociationCollection.php | 4 ---- LazyEagerLoader.php | 1 - Locator/TableLocator.php | 1 - 4 files changed, 8 deletions(-) diff --git a/Association.php b/Association.php index b7133d15..c78f0563 100644 --- a/Association.php +++ b/Association.php @@ -19,8 +19,6 @@ use Cake\Datasource\EntityInterface; use Cake\Datasource\ResultSetDecorator; use Cake\ORM\Locator\LocatorAwareTrait; -use Cake\ORM\Query; -use Cake\ORM\Table; use Cake\Utility\Inflector; use InvalidArgumentException; use RuntimeException; diff --git a/AssociationCollection.php b/AssociationCollection.php index 460566e2..e57a7bb2 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -16,10 +16,6 @@ use ArrayIterator; use Cake\Datasource\EntityInterface; -use Cake\ORM\Association; -use Cake\ORM\AssociationsNormalizerTrait; -use Cake\ORM\Entity; -use Cake\ORM\Table; use InvalidArgumentException; use IteratorAggregate; diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 710e15e3..b546bc85 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -17,7 +17,6 @@ use Cake\Collection\Collection; use Cake\Database\Expression\TupleComparison; use Cake\Datasource\EntityInterface; -use Cake\ORM\Table; /** * Contains methods that are capable of injecting eagerly loaded associations into diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 64996777..e4f25a31 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -16,7 +16,6 @@ use Cake\Core\App; use Cake\Datasource\ConnectionManager; -use Cake\ORM\Locator\LocatorInterface; use Cake\ORM\Table; use Cake\Utility\Inflector; use RuntimeException; From dc431a7ed01dc3ce139b89c5993ece9d7d45e90b Mon Sep 17 00:00:00 2001 From: Luan Hospodarsky Date: Fri, 2 Oct 2015 01:13:25 -0300 Subject: [PATCH 0492/2059] Changed _unlinkAssociated method. It now skips entities that are not necessary to delete instead of deleting all and re-adding --- Association/HasMany.php | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index 2d57d0bc..b95105d0 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -148,7 +148,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) $options['_sourceTable'] = $this->source(); if ($this->_saveStrategy === self::SAVE_REPLACE) { - $this->_unlinkAssociated($properties, $entity, $target, $options); + $this->_unlinkAssociated($properties, $entity, $target, $targetEntities); } foreach ($targetEntities as $k => $targetEntity) { @@ -164,9 +164,9 @@ public function saveAssociated(EntityInterface $entity, array $options = []) $targetEntity->set($properties, ['guard' => false]); } - if ($this->_saveStrategy === self::SAVE_REPLACE) { + /*if ($this->_saveStrategy === self::SAVE_REPLACE) { $targetEntity->isNew(true); - } + }*/ if ($target->save($targetEntity, $options)) { $targetEntities[$k] = $targetEntity; @@ -186,23 +186,39 @@ public function saveAssociated(EntityInterface $entity, array $options = []) /** * Deletes/sets null the related objects according to the dependency between source and targets and foreign key nullability + * Skips deleting records present in $remainingEntities * * @param array $properties array of foreignKey properties * @param EntityInterface $entity the entity which should have its associated entities unassigned * @param Table $target The associated table - * @param array $options original list of options passed in constructor + * @param array $remainingEntities Entities that should not be deleted * @return void */ - protected function _unlinkAssociated(array $properties, EntityInterface $entity, Table $target, array $options) + protected function _unlinkAssociated(array $properties, EntityInterface $entity, Table $target, array $remainingEntities) { + $primaryKey = (array)$target->primaryKey(); $mustBeDependent = (!$this->_foreignKeyAcceptsNull($target, $properties) || $this->dependent()); + $conditions = [ + 'AND' => [ + 'NOT' => [ + 'AND' => array_map( + function ($ent) use ($primaryKey) { + return $ent->extract($primaryKey); + }, + $remainingEntities + ) + ], + $properties + ] + ]; + $this->dependent($mustBeDependent); if ($mustBeDependent) { - $this->cascadeDelete($entity, $options); + $target->deleteAll($conditions); } else { $updateFields = array_fill_keys(array_keys($properties), null); - $target->updateAll($updateFields, $properties); + $target->updateAll($updateFields, $conditions); } } @@ -215,7 +231,7 @@ protected function _unlinkAssociated(array $properties, EntityInterface $entity, */ protected function _foreignKeyAcceptsNull(Table $table, array $properties) { - return array_product( + return (bool)array_product( array_map( function ($prop) use ($table) { return $table->schema()->isNullable($prop); From 566b1c46de8bcb2d4ef6d352a9a717469ccb9a33 Mon Sep 17 00:00:00 2001 From: Luan Hospodarsky Date: Fri, 2 Oct 2015 01:35:26 -0300 Subject: [PATCH 0493/2059] Removing commented code --- Association/HasMany.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index b95105d0..b09d8454 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -164,10 +164,6 @@ public function saveAssociated(EntityInterface $entity, array $options = []) $targetEntity->set($properties, ['guard' => false]); } - /*if ($this->_saveStrategy === self::SAVE_REPLACE) { - $targetEntity->isNew(true); - }*/ - if ($target->save($targetEntity, $options)) { $targetEntities[$k] = $targetEntity; continue; From fed625a8dbc5ecdcbc465c15205929184d567c98 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 3 Oct 2015 10:04:13 -0400 Subject: [PATCH 0494/2059] Fix contained associations not having types mapped. When contain() is called, use the normalized information to set type mappings. This forces associations to be normalized earlier, but allows the ORM to be more consistent and not require developers to set type information. Refs #6975 --- Query.php | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Query.php b/Query.php index a56ee226..9276f8b5 100644 --- a/Query.php +++ b/Query.php @@ -150,8 +150,8 @@ public function select($fields = [], $overwrite = false) /** * Hints this object to associate the correct types when casting conditions * for the database. This is done by extracting the field types from the schema - * associated to the passed table object. This prevents the user from repeating - * himself when specifying conditions. + * associated to the passed table object. This prevents developers from repeating + * themselves when specifying conditions. * * This method returns the same query object for chaining. * @@ -281,6 +281,10 @@ public function eagerLoader(EagerLoader $instance = null) * If called with an empty first argument and $override is set to true, the * previous list will be emptied. * + * Contained associations will have their column types mapped allowing you + * to use complex types in where() conditions. Nested associations will not have + * their types mapped. + * * @param array|string $associations list of table aliases to be queried * @param bool $override whether override previous list with the one passed * defaults to merging previous list with the new one. @@ -300,6 +304,10 @@ public function contain($associations = null, $override = false) return $result; } + foreach ($this->eagerLoader()->normalized($this->repository()) as $loader) { + $this->addDefaultTypes($loader->instance()->target()); + } + return $this; } @@ -851,8 +859,7 @@ public function sql(ValueBinder $binder = null) $this->triggerBeforeFind(); $this->_transformQuery(); - $sql = parent::sql($binder); - return $sql; + return parent::sql($binder); } /** @@ -880,6 +887,7 @@ protected function _execute() * specified and applies the joins required to eager load associations defined * using `contain` * + * @see \Cake\ORM\Query::sql() * @see \Cake\Database\Query::execute() * @return void */ From 5bc9e6aa0a18bcf7c9b0a6fd5dbf18812f4c0033 Mon Sep 17 00:00:00 2001 From: Luan Hospodarsky Date: Sat, 3 Oct 2015 13:39:30 -0300 Subject: [PATCH 0495/2059] Removed unecessary line, changed the stratagy in _foreignKeyAcceptsNull, so now it does not make use of array_product. Improved tests in those responsivle for testing save strategy adding one more entity to be saved. Changed the SQL operator in _unlinkAssociated, so it will be compliant to what it is supposed to do --- Association/HasMany.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index b09d8454..f21189ac 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -197,7 +197,7 @@ protected function _unlinkAssociated(array $properties, EntityInterface $entity, $conditions = [ 'AND' => [ 'NOT' => [ - 'AND' => array_map( + 'OR' => array_map( function ($ent) use ($primaryKey) { return $ent->extract($primaryKey); }, @@ -208,8 +208,6 @@ function ($ent) use ($primaryKey) { ] ]; - $this->dependent($mustBeDependent); - if ($mustBeDependent) { $target->deleteAll($conditions); } else { @@ -227,7 +225,8 @@ function ($ent) use ($primaryKey) { */ protected function _foreignKeyAcceptsNull(Table $table, array $properties) { - return (bool)array_product( + return !in_array( + false, array_map( function ($prop) use ($table) { return $table->schema()->isNullable($prop); From 84ca993f581ae5bfc09fcee89546c1e75ebe1abe Mon Sep 17 00:00:00 2001 From: Luan Hospodarsky Date: Sat, 3 Oct 2015 21:17:11 -0300 Subject: [PATCH 0496/2059] Implementing CascadeCallbacks when using saveStrategy. optimized in _unlinkAssociated --- Association/HasMany.php | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index f21189ac..166989ef 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -195,21 +195,26 @@ protected function _unlinkAssociated(array $properties, EntityInterface $entity, $primaryKey = (array)$target->primaryKey(); $mustBeDependent = (!$this->_foreignKeyAcceptsNull($target, $properties) || $this->dependent()); $conditions = [ - 'AND' => [ - 'NOT' => [ - 'OR' => array_map( - function ($ent) use ($primaryKey) { - return $ent->extract($primaryKey); - }, - $remainingEntities - ) - ], - $properties - ] + 'NOT' => [ + 'OR' => array_map( + function ($ent) use ($primaryKey) { + return $ent->extract($primaryKey); + }, + $remainingEntities + ) + ], + $properties ]; if ($mustBeDependent) { + if ($this->_cascadeCallbacks) { + $query = $this->find('all')->where($conditions); + foreach ($query as $assoc) { + $target->delete($assoc); + } + } else { $target->deleteAll($conditions); + } } else { $updateFields = array_fill_keys(array_keys($properties), null); $target->updateAll($updateFields, $conditions); From 61da57542c6ac1f33b3a22e5d31d567400a797b6 Mon Sep 17 00:00:00 2001 From: Luan Hospodarsky Date: Sun, 4 Oct 2015 16:10:09 -0300 Subject: [PATCH 0497/2059] Fixed bug in _unlinkAssociated where non necessary null values would appear into $conditions variable, causing trouble with replace save strategy and new associated entities to be created. Added a test to cover this case --- Association/HasMany.php | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index 166989ef..b847b6c7 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -196,11 +196,19 @@ protected function _unlinkAssociated(array $properties, EntityInterface $entity, $mustBeDependent = (!$this->_foreignKeyAcceptsNull($target, $properties) || $this->dependent()); $conditions = [ 'NOT' => [ - 'OR' => array_map( - function ($ent) use ($primaryKey) { - return $ent->extract($primaryKey); - }, - $remainingEntities + 'OR' => array_merge( + array_filter( + array_map( + function ($ent) use ($primaryKey) { + return $ent->extract($primaryKey); + }, + $remainingEntities + ), + function ($v) { + return !in_array(null, array_values($v), true); + } + ), + ['1 =' => 0] ) ], $properties From f043104e99acb79b56d383b57e68f9d5fa58ddf7 Mon Sep 17 00:00:00 2001 From: Luan Hospodarsky Date: Sun, 4 Oct 2015 16:51:11 -0300 Subject: [PATCH 0498/2059] Improving _unlinkAssociated to avoid unecessary queries --- Association/HasMany.php | 57 +++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index b847b6c7..ce301b79 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -194,38 +194,39 @@ protected function _unlinkAssociated(array $properties, EntityInterface $entity, { $primaryKey = (array)$target->primaryKey(); $mustBeDependent = (!$this->_foreignKeyAcceptsNull($target, $properties) || $this->dependent()); - $conditions = [ - 'NOT' => [ - 'OR' => array_merge( - array_filter( - array_map( - function ($ent) use ($primaryKey) { - return $ent->extract($primaryKey); - }, - $remainingEntities - ), - function ($v) { - return !in_array(null, array_values($v), true); - } - ), - ['1 =' => 0] - ) - ], - $properties - ]; + $exclusions = array_filter( + array_map( + function ($ent) use ($primaryKey) { + return $ent->extract($primaryKey); + }, + $remainingEntities + ), + function ($v) { + return !in_array(null, array_values($v), true); + } + ); + + if (count($exclusions) > 0) { + $conditions = [ + 'NOT' => [ + 'OR' => $exclusions + ], + $properties + ]; - if ($mustBeDependent) { - if ($this->_cascadeCallbacks) { - $query = $this->find('all')->where($conditions); - foreach ($query as $assoc) { - $target->delete($assoc); + if ($mustBeDependent) { + if ($this->_cascadeCallbacks) { + $query = $this->find('all')->where($conditions); + foreach ($query as $assoc) { + $target->delete($assoc); + } + } else { + $target->deleteAll($conditions); } } else { - $target->deleteAll($conditions); + $updateFields = array_fill_keys(array_keys($properties), null); + $target->updateAll($updateFields, $conditions); } - } else { - $updateFields = array_fill_keys(array_keys($properties), null); - $target->updateAll($updateFields, $conditions); } } From e2c7a2a7062ea0b97f2e7d003c3c3ed41bcb6712 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 5 Oct 2015 23:10:37 -0400 Subject: [PATCH 0499/2059] Make additional mappings for contained associations more efficient. normalized() is a bit of a hog. Not calling that halved the performance impact of mapping association column types. Include a test showing nested association type mapping as well. Refs #6975 --- Query.php | 60 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/Query.php b/Query.php index 9276f8b5..6c85c401 100644 --- a/Query.php +++ b/Query.php @@ -15,7 +15,6 @@ namespace Cake\ORM; use ArrayObject; -use BadMethodCallException; use Cake\Database\ExpressionInterface; use Cake\Database\Query as DatabaseQuery; use Cake\Database\ValueBinder; @@ -110,7 +109,7 @@ class Query extends DatabaseQuery implements JsonSerializable /** * Constructor * - * @param \Cake\Datasource\ConnectionInterface $connection The connection object + * @param \Cake\Database\Connection $connection The connection object * @param \Cake\ORM\Table $table The table this query is starting on */ public function __construct($connection, $table) @@ -150,8 +149,8 @@ public function select($fields = [], $overwrite = false) /** * Hints this object to associate the correct types when casting conditions * for the database. This is done by extracting the field types from the schema - * associated to the passed table object. This prevents developers from repeating - * themselves when specifying conditions. + * associated to the passed table object. This prevents the user from repeating + * himself when specifying conditions. * * This method returns the same query object for chaining. * @@ -281,10 +280,6 @@ public function eagerLoader(EagerLoader $instance = null) * If called with an empty first argument and $override is set to true, the * previous list will be emptied. * - * Contained associations will have their column types mapped allowing you - * to use complex types in where() conditions. Nested associations will not have - * their types mapped. - * * @param array|string $associations list of table aliases to be queried * @param bool $override whether override previous list with the one passed * defaults to merging previous list with the new one. @@ -292,25 +287,48 @@ public function eagerLoader(EagerLoader $instance = null) */ public function contain($associations = null, $override = false) { - if ($override) { - $this->_eagerLoader->clearContain(); - } - - $result = $this->eagerLoader()->contain($associations); - if ($associations !== null || $override) { + $loader = $this->eagerLoader(); + if ($override === true) { + $loader->clearContain(); $this->_dirty(); } + + $result = $loader->contain($associations); if ($associations === null) { return $result; } - foreach ($this->eagerLoader()->normalized($this->repository()) as $loader) { - $this->addDefaultTypes($loader->instance()->target()); - } - + $this->_addAssociationsToTypeMap($this->repository(), $this->typeMap(), $result); return $this; } + /** + * Used to recursively add contained association column types to + * the query. + * + * @param \Cake\ORM\Table $table The table instance to pluck associations from. + * @param array $associations The nested tree of associations to walk. + * @return void + */ + protected function _addAssociationsToTypeMap($table, $typeMap, $associations) + { + $typeMap = $this->typeMap(); + foreach ($associations as $name => $nested) { + $association = $table->association($name); + if (!$association) { + continue; + } + $target = $association->target(); + $primary = (array)$target->primaryKey(); + if ($typeMap->type($target->aliasField($primary[0])) === null) { + $this->addDefaultTypes($target); + } + if (!empty($nested)) { + $this->_addAssociationsToTypeMap($target, $typeMap, $nested); + } + } + } + /** * Adds filtering conditions to this query to only bring rows that have a relation * to another from an associated table, based on conditions in the associated table. @@ -859,7 +877,8 @@ public function sql(ValueBinder $binder = null) $this->triggerBeforeFind(); $this->_transformQuery(); - return parent::sql($binder); + $sql = parent::sql($binder); + return $sql; } /** @@ -887,7 +906,6 @@ protected function _execute() * specified and applies the joins required to eager load associations defined * using `contain` * - * @see \Cake\ORM\Query::sql() * @see \Cake\Database\Query::execute() * @return void */ @@ -1023,7 +1041,7 @@ public function __call($method, $arguments) return $this->_call($method, $arguments); } - throw new BadMethodCallException( + throw new \BadMethodCallException( sprintf('Cannot call method "%s" on a "%s" query', $method, $this->type()) ); } From 52e3aa78f282ba7548a201dfc9a40cccb69791b4 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Wed, 7 Oct 2015 21:44:18 -0400 Subject: [PATCH 0500/2059] Add missing doc comment. Refs #6975 --- Query.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Query.php b/Query.php index 6c85c401..67bf23df 100644 --- a/Query.php +++ b/Query.php @@ -307,6 +307,8 @@ public function contain($associations = null, $override = false) * the query. * * @param \Cake\ORM\Table $table The table instance to pluck associations from. + * @param \Cake\Database\TypeMap $typeMap The typemap to check for columns in. + * This typemap is indirectly mutated via Cake\ORM\Query::addDefaultTypes() * @param array $associations The nested tree of associations to walk. * @return void */ From 23d49d7688c516ae829eca622d38601d725797fb Mon Sep 17 00:00:00 2001 From: Marlin Cremers Date: Thu, 1 Oct 2015 01:03:11 +0200 Subject: [PATCH 0501/2059] Make it possible to paginate queries other than ORM/Query --- Query.php | 79 +++++-------------------------------------------------- 1 file changed, 6 insertions(+), 73 deletions(-) diff --git a/Query.php b/Query.php index 1992b97a..8b8167ae 100644 --- a/Query.php +++ b/Query.php @@ -19,6 +19,7 @@ use Cake\Database\ExpressionInterface; use Cake\Database\Query as DatabaseQuery; use Cake\Database\ValueBinder; +use Cake\Datasource\QueryInterface; use Cake\Datasource\QueryTrait; use JsonSerializable; use RuntimeException; @@ -30,7 +31,7 @@ * required. * */ -class Query extends DatabaseQuery implements JsonSerializable +class Query extends DatabaseQuery implements JsonSerializable, QueryInterface { use QueryTrait { @@ -540,61 +541,8 @@ public function notMatching($assoc, callable $builder = null) } /** - * Returns a key => value array representing a single aliased field - * that can be passed directly to the select() method. - * The key will contain the alias and the value the actual field name. - * - * If the field is already aliased, then it will not be changed. - * If no $alias is passed, the default table for this query will be used. - * - * @param string $field The field to alias - * @param string $alias the alias used to prefix the field - * @return array - */ - public function aliasField($field, $alias = null) - { - $namespaced = strpos($field, '.') !== false; - $aliasedField = $field; - - if ($namespaced) { - list($alias, $field) = explode('.', $field); - } - - if (!$alias) { - $alias = $this->repository()->alias(); - } - - $key = sprintf('%s__%s', $alias, $field); - if (!$namespaced) { - $aliasedField = $alias . '.' . $field; - } - - return [$key => $aliasedField]; - } - - /** - * Runs `aliasField()` for each field in the provided list and returns - * the result under a single array. + * {@inheritDoc} * - * @param array $fields The fields to alias - * @param string|null $defaultAlias The default alias - * @return array - */ - public function aliasFields($fields, $defaultAlias = null) - { - $aliased = []; - foreach ($fields as $alias => $field) { - if (is_numeric($alias) && is_string($field)) { - $aliased += $this->aliasField($field, $defaultAlias); - continue; - } - $aliased[$alias] = $field; - } - - return $aliased; - } - - /** * Populates or adds parts to current query clauses using an array. * This is handy for passing all query clauses at once. The option array accepts: * @@ -629,9 +577,6 @@ public function aliasFields($fields, $defaultAlias = null) * ->where(['created >=' => '2013-01-01']) * ->limit(10) * ``` - * - * @param array $options list of query clauses to apply new parts to. - * @return $this */ public function applyOptions(array $options) { @@ -704,9 +649,9 @@ public function __clone() } /** - * Returns the COUNT(*) for the query. + * {@inheritDoc} * - * @return int + * Returns the COUNT(*) for the query. */ public function count() { @@ -920,20 +865,8 @@ protected function _addDefaultFields() } /** - * Apply custom finds to against an existing query object. - * - * Allows custom find methods to be combined and applied to each other. - * - * ``` - * $table->find('all')->find('recent'); - * ``` - * - * The above is an example of stacking multiple finder methods onto - * a single query. + * {@inheritDoc} * - * @param string $finder The finder method to use. - * @param array $options The options for the finder. - * @return $this Returns a modified query. * @see \Cake\ORM\Table::find() */ public function find($finder, array $options = []) From 0e8b79b4a8e418ab48f10d96bb3b03f8f2e38cef Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Mon, 12 Oct 2015 20:52:34 +0200 Subject: [PATCH 0502/2059] Cleanup return types. --- Marshaller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index 323d967a..2d730122 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -223,7 +223,7 @@ protected function _prepareDataAndOptions($data, $options) protected function _marshalAssociation($assoc, $value, $options) { if (!is_array($value)) { - return; + return null; } $targetTable = $assoc->target(); $marshaller = $targetTable->marshaller(); From 7caff0689df6bef07812a2d37a0ff47d1d554742 Mon Sep 17 00:00:00 2001 From: Luan Hospodarsky Date: Wed, 14 Oct 2015 21:37:40 -0300 Subject: [PATCH 0503/2059] Using Collections instead of pure arrays.Fixed wrong property name in TableTest. --- Association/HasMany.php | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index ce301b79..bd3d4ea8 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -15,6 +15,7 @@ */ namespace Cake\ORM\Association; +use Cake\Collection\Collection; use Cake\Datasource\EntityInterface; use Cake\ORM\Association; use Cake\ORM\Table; @@ -190,21 +191,22 @@ public function saveAssociated(EntityInterface $entity, array $options = []) * @param array $remainingEntities Entities that should not be deleted * @return void */ - protected function _unlinkAssociated(array $properties, EntityInterface $entity, Table $target, array $remainingEntities) + protected function _unlinkAssociated(array $properties, EntityInterface $entity, Table $target, array $remainingEntities = []) { $primaryKey = (array)$target->primaryKey(); $mustBeDependent = (!$this->_foreignKeyAcceptsNull($target, $properties) || $this->dependent()); - $exclusions = array_filter( - array_map( - function ($ent) use ($primaryKey) { - return $ent->extract($primaryKey); - }, - $remainingEntities - ), + $exclusions = new Collection($remainingEntities); + $exclusions = $exclusions->map( + function ($ent) use ($primaryKey) { + return $ent->extract($primaryKey); + } + ) + ->filter( function ($v) { return !in_array(null, array_values($v), true); } - ); + ) + ->toArray(); if (count($exclusions) > 0) { $conditions = [ From 2dd767f6eeb569cf12d0e3acc49b05940166a052 Mon Sep 17 00:00:00 2001 From: Luan Hospodarsky Date: Sat, 17 Oct 2015 18:03:42 -0300 Subject: [PATCH 0504/2059] Implemented public methods: link, unlink in HasMany association. A bit of refactory was necessary in _unlinkAssociated method, separating the parts of the code responsible to build the exclusion list and the part responsible to actually unlink. This last part is now present in _unlink method. This method is used when unlinking all or unlinking some associated objects. --- Association/HasMany.php | 153 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 140 insertions(+), 13 deletions(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index bd3d4ea8..61a5efe2 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -129,7 +129,7 @@ public function saveStrategy($strategy = null) public function saveAssociated(EntityInterface $entity, array $options = []) { $targetEntities = $entity->get($this->property()); - if (empty($targetEntities)) { + if (empty($targetEntities) && $this->_saveStrategy !== self::SAVE_REPLACE) { return $entity; } @@ -181,6 +181,116 @@ public function saveAssociated(EntityInterface $entity, array $options = []) return $entity; } + /** + * Associates the source entity to each of the target entities provided. + * When using this method, all entities in `$targetEntities` will be appended to + * the source entity's property corresponding to this association object. + * + * This method does not check link uniqueness. + * + * ### Example: + * + * ``` + * $user = $users->get(1); + * $allArticles = $articles->find('all')->execute(); + * $users->Articles->link($user, $allArticles); + * ``` + * + * `$user->get('articles')` will contain all articles in `$allArticles` after linking + * + * @param \Cake\Datasource\EntityInterface $sourceEntity the row belonging to the `source` side + * of this association + * @param array $targetEntities list of entities belonging to the `target` side + * of this association + * @param array $options list of options to be passed to the save method + * @return bool true on success, false otherwise + */ + public function link(EntityInterface $sourceEntity, array $targetEntities, array $options = []) + { + $saveStrategy = $this->saveStrategy(); + $this->saveStrategy(self::SAVE_APPEND); + $source = $this->source(); + $property = $this->property(); + + $currentEntities = (new Collection((array)$sourceEntity->get($property)))->append($targetEntities); + + $sourceEntity->set($property, $currentEntities->toList()); + + $savedEntity = $this->saveAssociated($sourceEntity); + + $ok = ($savedEntity instanceof EntityInterface); + + $this->saveStrategy($saveStrategy); + + if ($ok) { + $sourceEntity->set($property, $savedEntity->get($property)); + $sourceEntity->clean($property); + } + + return $ok; + } + + /** + * Removes all links between the passed source entity and each of the provided + * target entities. This method assumes that all passed objects are already persisted + * in the database and that each of them contain a primary key value. + * + * By default this method will also unset each of the entity objects stored inside + * the source entity. + * + * ### Example: + * + * ``` + * $user = $users->get(1); + * $user->articles = [$article1, $article2, $article3, $article4]; + * $users->save($user, ['Associated' => ['Articles']]); + * $allArticles = [$article1, $article2, $article3]; + * $users->Articles->unlink($user, $allArticles); + * ``` + * + * `$article->get('articles')` will contain only `[$article4]` after deleting in the database + * + * @param \Cake\Datasource\EntityInterface $sourceEntity an entity persisted in the source table for + * this association + * @param array $targetEntities list of entities persisted in the target table for + * this association + * @param bool $cleanProperty whether or not to remove all the objects in $targetEntities + * that are stored in $sourceEntity + * @throws \InvalidArgumentException if non persisted entities are passed or if + * any of them is lacking a primary key value + * @return void + */ + public function unlink(EntityInterface $sourceEntity, array $targetEntities, $cleanProperty = true) + { + $foreignKey = (array)$this->foreignKey(); + $target = $this->target(); + $targetPrimaryKey = array_merge((array)$target->primaryKey(), (array)$foreignKey); + $property = $this->property(); + + $conditions = [ + 'OR' => (new Collection($targetEntities) ) + ->map(function ($entity) use ($targetPrimaryKey) { + return $entity->extract($targetPrimaryKey); + }) + ->toList() + ]; + + $this->_unlink($foreignKey, $target, $conditions); + + if ($cleanProperty) { + $sourceEntity->set( + $property, + (new Collection($sourceEntity->get($property))) + ->reject( + function ($assoc) use ($targetEntities) { + return in_array($assoc, $targetEntities); + } + ) + ->toList() + ); + } + } + /** * Deletes/sets null the related objects according to the dependency between source and targets and foreign key nullability * Skips deleting records present in $remainingEntities @@ -194,7 +304,6 @@ public function saveAssociated(EntityInterface $entity, array $options = []) protected function _unlinkAssociated(array $properties, EntityInterface $entity, Table $target, array $remainingEntities = []) { $primaryKey = (array)$target->primaryKey(); - $mustBeDependent = (!$this->_foreignKeyAcceptsNull($target, $properties) || $this->dependent()); $exclusions = new Collection($remainingEntities); $exclusions = $exclusions->map( function ($ent) use ($primaryKey) { @@ -208,6 +317,8 @@ function ($v) { ) ->toArray(); + $conditions = $properties; + if (count($exclusions) > 0) { $conditions = [ 'NOT' => [ @@ -215,20 +326,36 @@ function ($v) { ], $properties ]; + } - if ($mustBeDependent) { - if ($this->_cascadeCallbacks) { - $query = $this->find('all')->where($conditions); - foreach ($query as $assoc) { - $target->delete($assoc); - } - } else { - $target->deleteAll($conditions); + $this->_unlink(array_keys($properties), $target, $conditions); + } + + /** + * Deletes/sets null the related objects matching $conditions. + * The action which is taken depends on the dependency between source and targets and also on foreign key nullability + * + * @param array $foreignKey array of foreign key properties + * @param Table $target The associated table + * @param array $conditions The conditions that specifies what are the objects to be unlinked + * @return void + */ + protected function _unlink(array $foreignKey, Table $target, array $conditions = []) + { + $mustBeDependent = (!$this->_foreignKeyAcceptsNull($target, $foreignKey) || $this->dependent()); + if ($mustBeDependent) { + if ($this->_cascadeCallbacks) { + $query = $this->find('all')->where($conditions); + foreach ($query as $assoc) { + $target->delete($assoc); } } else { - $updateFields = array_fill_keys(array_keys($properties), null); - $target->updateAll($updateFields, $conditions); + $target->deleteAll($conditions); } + } else { + $updateFields = array_fill_keys($foreignKey, null); + $target->updateAll($updateFields, $conditions); + } } @@ -247,7 +374,7 @@ protected function _foreignKeyAcceptsNull(Table $table, array $properties) function ($prop) use ($table) { return $table->schema()->isNullable($prop); }, - array_keys($properties) + $properties ) ); } From 2cd4123da992efa8334678309ccbfd8247af13c8 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 15 Oct 2015 08:26:33 -0400 Subject: [PATCH 0505/2059] Separate the behavior for cloning and cleanCopy(). cleanCopy() is not just a simple clone as it resets a number of clauses on the query object, whereas a clone should recursively copy all those objects. --- Query.php | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/Query.php b/Query.php index 4cb04262..def8426b 100644 --- a/Query.php +++ b/Query.php @@ -652,7 +652,18 @@ public function applyOptions(array $options) */ public function cleanCopy() { - return clone $this; + $clone = clone $this; + $clone->_iterator = null; + $clone->triggerBeforeFind(); + $clone->eagerLoader(clone $this->eagerLoader()); + $clone->valueBinder(clone $this->valueBinder()); + $clone->autoFields(false); + $clone->limit(null); + $clone->order([], true); + $clone->offset(null); + $clone->mapReduce(null, null, true); + $clone->formatResults(null, true); + return $clone; } /** @@ -665,15 +676,8 @@ public function cleanCopy() public function __clone() { $this->_iterator = null; - $this->triggerBeforeFind(); $this->eagerLoader(clone $this->eagerLoader()); $this->valueBinder(clone $this->valueBinder()); - $this->autoFields(false); - $this->limit(null); - $this->order([], true); - $this->offset(null); - $this->mapReduce(null, null, true); - $this->formatResults(null, true); } /** From 62749eda6bdd3c3a21b4ad12c8d17cdf1ce22426 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 15 Oct 2015 21:33:47 -0400 Subject: [PATCH 0506/2059] Implement __clone() in Database\Query and remaining expressions Move __clone() into Database\Query and implement deep cloning on all the inner parts. This solves shared state in clones. Also implement __clone() on the other expression objects that contain mutable state and might be cloned with queries. Refs #7533 --- Query.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Query.php b/Query.php index def8426b..5725603e 100644 --- a/Query.php +++ b/Query.php @@ -675,9 +675,10 @@ public function cleanCopy() */ public function __clone() { - $this->_iterator = null; - $this->eagerLoader(clone $this->eagerLoader()); - $this->valueBinder(clone $this->valueBinder()); + parent::__clone(); + if ($this->_eagerLoader) { + $this->_eagerLoader = clone $this->_eagerLoader; + } } /** From d074d75e5efbfe0da77fa15ae32075134dffc798 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 17 Oct 2015 10:04:38 -0400 Subject: [PATCH 0507/2059] Remove duplicate code. These properties are cloned by Database\Query. --- Query.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/Query.php b/Query.php index 5725603e..54bfd05a 100644 --- a/Query.php +++ b/Query.php @@ -653,10 +653,7 @@ public function applyOptions(array $options) public function cleanCopy() { $clone = clone $this; - $clone->_iterator = null; $clone->triggerBeforeFind(); - $clone->eagerLoader(clone $this->eagerLoader()); - $clone->valueBinder(clone $this->valueBinder()); $clone->autoFields(false); $clone->limit(null); $clone->order([], true); From a9525de9802b5fd454d9ea77288927a9bd32a827 Mon Sep 17 00:00:00 2001 From: Luan Hospodarsky Date: Sun, 18 Oct 2015 01:14:56 -0200 Subject: [PATCH 0508/2059] Implemented one more test to link function, in which an already existing link is present in the input data. Implemented replaceLinks method and a test for it. --- Association/HasMany.php | 61 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index 61a5efe2..d14f281b 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -214,7 +214,7 @@ public function link(EntityInterface $sourceEntity, array $targetEntities, array $currentEntities = (new Collection((array)$sourceEntity->get($property)))->append($targetEntities); - $sourceEntity->set($property, $currentEntities->toList()); + $sourceEntity->set($property, array_unique($currentEntities->toList())); $savedEntity = $this->saveAssociated($sourceEntity); @@ -291,6 +291,65 @@ function ($assoc) use ($targetEntities) { } } + /** + * Replaces existing association links between the source entity and the target + * with the ones passed. This method does a smart cleanup, links that are already + * persisted and present in `$targetEntities` will not be deleted, new links will + * be created for the passed target entities that are not already in the database + * and the rest will be removed. + * + * For example, if an author has many articles, such as 'article1','article 2' and 'article 3' and you pass + * to this method an array containing the entities for articles 'article 1' and 'article 4', + * only the link for 'article 1' will be kept in database, the links for 'article 2' and 'article 3' will be + * deleted and the link for 'article 4' will be created. + * + * Existing links are not deleted and created again, they are either left untouched + * or updated. + * + * This method does not check link uniqueness. + * + * On success, the passed `$sourceEntity` will contain `$targetEntities` as value + * in the corresponding property for this association. + * + * Additional options for new links to be saved can be passed in the third argument, + * check `Table::save()` for information on the accepted options. + * + * ### Example: + * + * ``` + * $author->articles = [$article1, $article2, $article3, $article4]; + * $authors->save($author); + * $articles = [$article1, $article3]; + * $authors->association('articles')->replaceLinks($author, $articles); + * ``` + * + * `$author->get('articles')` will contain only `[$article1, $article3]` at the end + * + * @param \Cake\Datasource\EntityInterface $sourceEntity an entity persisted in the source table for + * this association + * @param array $targetEntities list of entities from the target table to be linked + * @param array $options list of options to be passed to `save` persisting or + * updating new links + * @throws \InvalidArgumentException if non persisted entities are passed or if + * any of them is lacking a primary key value + * @return bool success + */ + public function replaceLinks(EntityInterface $sourceEntity, array $targetEntities, array $options = []) + { + $property = $this->property(); + $sourceEntity->set($property, $targetEntities); + $saveStrategy = $this->saveStrategy(); + $this->saveStrategy(self::SAVE_REPLACE); + $result = $this->saveAssociated($sourceEntity, $options); + $ok = ($result instanceof EntityInterface); + + if ($ok) { + $sourceEntity = $result; + } + $this->saveStrategy($saveStrategy); + return $ok; + } + /** * Deletes/sets null the related objects according to the dependency between source and targets and foreign key nullability * Skips deleting records present in $remainingEntities From 13938d2a6a338dd3043ff5657ff09c36d092d9d9 Mon Sep 17 00:00:00 2001 From: Luan Hospodarsky Date: Sun, 18 Oct 2015 11:40:08 -0200 Subject: [PATCH 0509/2059] Removing unused line of code. Fixing style issue --- Association/HasMany.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index d14f281b..e5f9e3e3 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -209,7 +209,6 @@ public function link(EntityInterface $sourceEntity, array $targetEntities, array { $saveStrategy = $this->saveStrategy(); $this->saveStrategy(self::SAVE_APPEND); - $source = $this->source(); $property = $this->property(); $currentEntities = (new Collection((array)$sourceEntity->get($property)))->append($targetEntities); @@ -268,11 +267,11 @@ public function unlink(EntityInterface $sourceEntity, array $targetEntities, $cl $property = $this->property(); $conditions = [ - 'OR' => (new Collection($targetEntities) ) - ->map(function ($entity) use ($targetPrimaryKey) { - return $entity->extract($targetPrimaryKey); - }) - ->toList() + 'OR' => (new Collection($targetEntities)) + ->map(function ($entity) use ($targetPrimaryKey) { + return $entity->extract($targetPrimaryKey); + }) + ->toList() ]; $this->_unlink($foreignKey, $target, $conditions); From 3496b53c047dc0c5c1778ff20f3353d9c1b6a898 Mon Sep 17 00:00:00 2001 From: Luan Hospodarsky Date: Sun, 18 Oct 2015 21:53:08 -0200 Subject: [PATCH 0510/2059] fixed flipped assertion values in TableTest. Changed assertEquals to assertCount where applicable. Methods link, unlink and replaceLinks now clean the entity when finished. dirty verification added in TableTest for link, unlink and replaceLinks tests. Switched to pure arrays in HasMany, cause of unnecessary use of Collection. Improved PHP doc. --- Association/HasMany.php | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index e5f9e3e3..2de3a2b6 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -187,6 +187,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) * the source entity's property corresponding to this association object. * * This method does not check link uniqueness. + * Changes are persisted in the database and also in the source entity. * * ### Example: * @@ -211,9 +212,14 @@ public function link(EntityInterface $sourceEntity, array $targetEntities, array $this->saveStrategy(self::SAVE_APPEND); $property = $this->property(); - $currentEntities = (new Collection((array)$sourceEntity->get($property)))->append($targetEntities); + $currentEntities = array_unique( + array_merge( + (array)$sourceEntity->get($property), + $targetEntities + ) + ); - $sourceEntity->set($property, array_unique($currentEntities->toList())); + $sourceEntity->set($property, $currentEntities); $savedEntity = $this->saveAssociated($sourceEntity); @@ -223,7 +229,7 @@ public function link(EntityInterface $sourceEntity, array $targetEntities, array if ($ok) { $sourceEntity->set($property, $savedEntity->get($property)); - $sourceEntity->clean($property); + $sourceEntity->clean(); } return $ok; @@ -237,6 +243,8 @@ public function link(EntityInterface $sourceEntity, array $targetEntities, array * By default this method will also unset each of the entity objects stored inside * the source entity. * + * Changes are persisted in the database and also in the source entity. + * * ### Example: * * ``` @@ -263,7 +271,7 @@ public function unlink(EntityInterface $sourceEntity, array $targetEntities, $cl { $foreignKey = (array)$this->foreignKey(); $target = $this->target(); - $targetPrimaryKey = array_merge((array)$target->primaryKey(), (array)$foreignKey); + $targetPrimaryKey = array_merge((array)$target->primaryKey(), $foreignKey); $property = $this->property(); $conditions = [ @@ -288,6 +296,8 @@ function ($assoc) use ($targetEntities) { ->toList() ); } + + $sourceEntity->clean(); } /** From f709fd1663a93ee13aa4604f268c8a7f654f2aab Mon Sep 17 00:00:00 2001 From: Luan Hospodarsky Date: Tue, 20 Oct 2015 22:30:48 -0200 Subject: [PATCH 0511/2059] Cleaning only the affected properties in link and unlink methods --- Association/HasMany.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index 2de3a2b6..f462a6a4 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -229,7 +229,7 @@ public function link(EntityInterface $sourceEntity, array $targetEntities, array if ($ok) { $sourceEntity->set($property, $savedEntity->get($property)); - $sourceEntity->clean(); + $sourceEntity->dirty($property, false); } return $ok; @@ -297,7 +297,7 @@ function ($assoc) use ($targetEntities) { ); } - $sourceEntity->clean(); + $sourceEntity->dirty($property, false); } /** From 73059ff8ed7c69c11eb70625e9dbfc8f8fe7c706 Mon Sep 17 00:00:00 2001 From: AD7six Date: Sun, 11 Oct 2015 18:49:59 +0000 Subject: [PATCH 0512/2059] Only issue a count once With code like this: $query->count(); ... $query->count(); Only issue one count query --- Query.php | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Query.php b/Query.php index 54bfd05a..442c87a3 100644 --- a/Query.php +++ b/Query.php @@ -107,6 +107,15 @@ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface */ protected $_beforeFindFired = false; + /** + * The COUNT(*) for the query. + * + * When set, count query execution will be bypassed. + * + * @var int + */ + protected $_resultsCount; + /** * Constructor * @@ -685,6 +694,10 @@ public function __clone() */ public function count() { + if (isset($this->_resultsCount)) { + return $this->_resultsCount; + } + $query = $this->cleanCopy(); $counter = $this->_counter; if ($counter) { @@ -723,9 +736,10 @@ public function count() ->execute(); } - $result = $statement->fetch('assoc')['count']; + $this->_resultsCount = (int)$statement->fetch('assoc')['count']; $statement->closeCursor(); - return (int)$result; + + return $this->_resultsCount; } /** From dfb73361957bf853cc5e1765715a97bc85c2cdb9 Mon Sep 17 00:00:00 2001 From: AD7six Date: Wed, 21 Oct 2015 08:29:41 +0000 Subject: [PATCH 0513/2059] Only if the query object is not dirty --- Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Query.php b/Query.php index 442c87a3..7739e103 100644 --- a/Query.php +++ b/Query.php @@ -694,7 +694,7 @@ public function __clone() */ public function count() { - if (isset($this->_resultsCount)) { + if (!$this->_dirty && isset($this->_resultsCount)) { return $this->_resultsCount; } From 7e64f3727d247f743e5a173deaf9103c980b4cbe Mon Sep 17 00:00:00 2001 From: AD7six Date: Wed, 21 Oct 2015 08:38:05 +0000 Subject: [PATCH 0514/2059] Split the actual count into a seperate method --- Query.php | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/Query.php b/Query.php index 7739e103..591523dd 100644 --- a/Query.php +++ b/Query.php @@ -690,14 +690,26 @@ public function __clone() /** * {@inheritDoc} * - * Returns the COUNT(*) for the query. + * Returns the COUNT(*) for the query. If the query has not been + * modified, and the count has already been performed the cached + * value is returned */ public function count() { - if (!$this->_dirty && isset($this->_resultsCount)) { - return $this->_resultsCount; + if ($this->_dirty || $this->_resultsCount === null) { + $this->_resultsCount = $this->_performCount(); } + return $this->_resultsCount; + } + + /** + * Performs and returns the COUNT(*) for the query. + * + * @return int + */ + protected function _performCount() + { $query = $this->cleanCopy(); $counter = $this->_counter; if ($counter) { @@ -736,10 +748,9 @@ public function count() ->execute(); } - $this->_resultsCount = (int)$statement->fetch('assoc')['count']; + $result = $statement->fetch('assoc')['count']; $statement->closeCursor(); - - return $this->_resultsCount; + return (int)$result; } /** From 9124d1896a466a7166d9b292da6ef433faed36f2 Mon Sep 17 00:00:00 2001 From: AD7six Date: Wed, 21 Oct 2015 09:46:35 +0000 Subject: [PATCH 0515/2059] Use the dirty function to clear the class property --- Query.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Query.php b/Query.php index 591523dd..6d720b5a 100644 --- a/Query.php +++ b/Query.php @@ -696,7 +696,7 @@ public function __clone() */ public function count() { - if ($this->_dirty || $this->_resultsCount === null) { + if ($this->_resultsCount === null) { $this->_resultsCount = $this->_performCount(); } @@ -938,6 +938,7 @@ public function find($finder, array $options = []) protected function _dirty() { $this->_results = null; + $this->_resultsCount = null; parent::_dirty(); } From b78fe3794e569ebd5c8c7bba41beff9b498d9c05 Mon Sep 17 00:00:00 2001 From: Walther Lalk Date: Wed, 21 Oct 2015 15:23:12 +0200 Subject: [PATCH 0516/2059] Fix for #7589 --- Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Query.php b/Query.php index 54bfd05a..19fc65f6 100644 --- a/Query.php +++ b/Query.php @@ -809,12 +809,12 @@ public function triggerBeforeFind() { if (!$this->_beforeFindFired && $this->_type === 'select') { $table = $this->repository(); + $this->_beforeFindFired = true; $table->dispatchEvent('Model.beforeFind', [ $this, new ArrayObject($this->_options), !$this->eagerLoaded() ]); - $this->_beforeFindFired = true; } } From 1a63e4c81db67a1ee1c3eff512aba7bba99a0b24 Mon Sep 17 00:00:00 2001 From: Luan Hospodarsky Date: Thu, 22 Oct 2015 22:42:35 -0200 Subject: [PATCH 0517/2059] Adding PHPDoc for testReplaceHasMany method in test case. Renaming method replaceLinks to replace on HasMany.php. Grouping together HasMany tests. --- Association/HasMany.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index f462a6a4..e2cbc45d 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -343,7 +343,7 @@ function ($assoc) use ($targetEntities) { * any of them is lacking a primary key value * @return bool success */ - public function replaceLinks(EntityInterface $sourceEntity, array $targetEntities, array $options = []) + public function replace(EntityInterface $sourceEntity, array $targetEntities, array $options = []) { $property = $this->property(); $sourceEntity->set($property, $targetEntities); From 32eec56ad81e6b95624a68dcd526e4931b37eb06 Mon Sep 17 00:00:00 2001 From: Dustin Haggard Date: Wed, 28 Oct 2015 20:15:58 -0400 Subject: [PATCH 0518/2059] Use binding key in counter cache. Ref #7609 --- Behavior/CounterCacheBehavior.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index a42cbb7f..da5260e4 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -135,11 +135,11 @@ protected function _processAssociations(Event $event, EntityInterface $entity) protected function _processAssociation(Event $event, EntityInterface $entity, Association $assoc, array $settings) { $foreignKeys = (array)$assoc->foreignKey(); - $primaryKeys = (array)$assoc->target()->primaryKey(); + $primaryKeys = (array)$assoc->bindingKey(); $countConditions = $entity->extract($foreignKeys); $updateConditions = array_combine($primaryKeys, $countConditions); - $countOriginalConditions = $entity->extractOriginalChanged($foreignKeys); + if ($countOriginalConditions !== []) { $updateOriginalConditions = array_combine($primaryKeys, $countOriginalConditions); } From e8ee4aa5981a8da55955d57617261308c0f3ca06 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Wed, 28 Oct 2015 23:37:54 -0400 Subject: [PATCH 0519/2059] PHPCS fixes and doc block corrections. --- Behavior/CounterCacheBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index da5260e4..e969355d 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -139,7 +139,7 @@ protected function _processAssociation(Event $event, EntityInterface $entity, As $countConditions = $entity->extract($foreignKeys); $updateConditions = array_combine($primaryKeys, $countConditions); $countOriginalConditions = $entity->extractOriginalChanged($foreignKeys); - + if ($countOriginalConditions !== []) { $updateOriginalConditions = array_combine($primaryKeys, $countOriginalConditions); } From 3296be0fca4eb2c2ef11db735598f31323170f27 Mon Sep 17 00:00:00 2001 From: Walther Lalk Date: Thu, 29 Oct 2015 09:26:34 +0200 Subject: [PATCH 0520/2059] Add afterSaveCommit and afterDeleteCommit callbacks to behaviours --- Behavior.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Behavior.php b/Behavior.php index 9eea7e23..f749ac15 100644 --- a/Behavior.php +++ b/Behavior.php @@ -252,8 +252,10 @@ public function implementedEvents() 'Model.beforeFind' => 'beforeFind', 'Model.beforeSave' => 'beforeSave', 'Model.afterSave' => 'afterSave', + 'Model.afterSaveCommit' => 'afterSaveCommit', 'Model.beforeDelete' => 'beforeDelete', 'Model.afterDelete' => 'afterDelete', + 'Model.afterDeleteCommit' => 'afterDeleteCommit', 'Model.buildValidator' => 'buildValidator', 'Model.buildRules' => 'buildRules', 'Model.beforeRules' => 'beforeRules', From cefab06a11479713448a4fd35f0d12345f13b1c1 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Thu, 29 Oct 2015 13:25:08 +0100 Subject: [PATCH 0521/2059] Optimizing a bit more the default type inference in the Query --- Query.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Query.php b/Query.php index 463b69a5..1a8aa730 100644 --- a/Query.php +++ b/Query.php @@ -170,10 +170,10 @@ public function select($fields = [], $overwrite = false) public function addDefaultTypes(Table $table) { $alias = $table->alias(); - $schema = $table->schema(); + $map = $table->schema()->typeMap(); $fields = []; - foreach ($schema->columns() as $f) { - $fields[$f] = $fields[$alias . '.' . $f] = $schema->columnType($f); + foreach ($map as $f => $type) { + $fields[$f] = $fields[$alias . '.' . $f] = $type; } $this->typeMap()->addDefaults($fields); From de0c905c61b894842f016328940eba27ac07f34f Mon Sep 17 00:00:00 2001 From: Brian Porter Date: Fri, 30 Oct 2015 09:24:41 -0500 Subject: [PATCH 0522/2059] Contain target table in BelongsToMany::replaceLinks() when conditions present. Ref: https://github.com/beporter/cake3-tests#applying-conditions-using-the-far-table-in-a-belongstomany-relationship --- Association/BelongsToMany.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 918f8148..2f329b5f 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -758,6 +758,7 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { $associationConditions = $this->conditions(); if ($associationConditions) { + $existing->contain($this->target()->alias()); $existing->andWhere($associationConditions); } From 5a8454dfb4e660c70a261c618d2b56d6c5c6a963 Mon Sep 17 00:00:00 2001 From: Luan Hospodarsky Date: Sun, 1 Nov 2015 22:19:57 -0200 Subject: [PATCH 0523/2059] HasMany::replace taking into consideration success of delete statements while unlinking --- Association/HasMany.php | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index e2cbc45d..79bbf623 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -149,7 +149,11 @@ public function saveAssociated(EntityInterface $entity, array $options = []) $options['_sourceTable'] = $this->source(); if ($this->_saveStrategy === self::SAVE_REPLACE) { - $this->_unlinkAssociated($properties, $entity, $target, $targetEntities); + $unlinkSuccessful = $this->_unlinkAssociated($properties, $entity, $target, $targetEntities); + } + + if (isset($unlinkSuccessful) && !$unlinkSuccessful) { + return false; } foreach ($targetEntities as $k => $targetEntity) { @@ -367,7 +371,7 @@ public function replace(EntityInterface $sourceEntity, array $targetEntities, ar * @param EntityInterface $entity the entity which should have its associated entities unassigned * @param Table $target The associated table * @param array $remainingEntities Entities that should not be deleted - * @return void + * @return bool success */ protected function _unlinkAssociated(array $properties, EntityInterface $entity, Table $target, array $remainingEntities = []) { @@ -396,7 +400,7 @@ function ($v) { ]; } - $this->_unlink(array_keys($properties), $target, $conditions); + return $this->_unlink(array_keys($properties), $target, $conditions); } /** @@ -406,25 +410,31 @@ function ($v) { * @param array $foreignKey array of foreign key properties * @param Table $target The associated table * @param array $conditions The conditions that specifies what are the objects to be unlinked - * @return void + * @return bool success */ protected function _unlink(array $foreignKey, Table $target, array $conditions = []) { + $ok = true; $mustBeDependent = (!$this->_foreignKeyAcceptsNull($target, $foreignKey) || $this->dependent()); - if ($mustBeDependent) { - if ($this->_cascadeCallbacks) { - $query = $this->find('all')->where($conditions); - foreach ($query as $assoc) { - $target->delete($assoc); + $persistedEntitiesExist = $this->exists($conditions); + + if ($persistedEntitiesExist) { + if ($mustBeDependent) { + if ($this->_cascadeCallbacks) { + $query = $this->find('all')->where($conditions); + foreach ($query as $assoc) { + $ok = $ok && $target->delete($assoc); + } + } else { + $ok = ($target->deleteAll($conditions) > 0); } } else { - $target->deleteAll($conditions); - } - } else { - $updateFields = array_fill_keys($foreignKey, null); - $target->updateAll($updateFields, $conditions); + $updateFields = array_fill_keys($foreignKey, null); + $ok = $target->updateAll($updateFields, $conditions); + } } + return $ok; } /** From 31aed9bf2740817f2772647acc6e8c54ef16c753 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Mon, 2 Nov 2015 10:51:47 +0100 Subject: [PATCH 0524/2059] Throwing exception when passing empty list of values as condition Fixes #7092 --- Marshaller.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Marshaller.php b/Marshaller.php index 2d730122..db7a079c 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -368,6 +368,10 @@ protected function _belongsToMany(Association $assoc, array $data, $options = [] */ protected function _loadAssociatedByIds($assoc, $ids) { + if (empty($ids)) { + return []; + } + $target = $assoc->target(); $primaryKey = (array)$target->primaryKey(); $multi = count($primaryKey) > 1; From df60e99654a87a4714afb29a5204df6680fce9b2 Mon Sep 17 00:00:00 2001 From: Luan Hospodarsky Date: Tue, 3 Nov 2015 23:06:16 -0200 Subject: [PATCH 0525/2059] Assuming that deleteAll statement inside _unlink is always OK if no exceptions occurred. Removing unnecessary test --- Association/HasMany.php | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index 79bbf623..185527fd 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -148,11 +148,12 @@ public function saveAssociated(EntityInterface $entity, array $options = []) $original = $targetEntities; $options['_sourceTable'] = $this->source(); + $unlinkSuccessful = null; if ($this->_saveStrategy === self::SAVE_REPLACE) { $unlinkSuccessful = $this->_unlinkAssociated($properties, $entity, $target, $targetEntities); } - if (isset($unlinkSuccessful) && !$unlinkSuccessful) { + if ($unlinkSuccessful === false) { return false; } @@ -414,27 +415,25 @@ function ($v) { */ protected function _unlink(array $foreignKey, Table $target, array $conditions = []) { - $ok = true; $mustBeDependent = (!$this->_foreignKeyAcceptsNull($target, $foreignKey) || $this->dependent()); - $persistedEntitiesExist = $this->exists($conditions); - - if ($persistedEntitiesExist) { - if ($mustBeDependent) { - if ($this->_cascadeCallbacks) { - $query = $this->find('all')->where($conditions); - foreach ($query as $assoc) { - $ok = $ok && $target->delete($assoc); - } - } else { - $ok = ($target->deleteAll($conditions) > 0); + + if ($mustBeDependent) { + if ($this->_cascadeCallbacks) { + $query = $this->find('all')->where($conditions); + $ok = true; + foreach ($query as $assoc) { + $ok = $ok && $target->delete($assoc); } + return $ok; } else { - $updateFields = array_fill_keys($foreignKey, null); - $ok = $target->updateAll($updateFields, $conditions); - + $target->deleteAll($conditions); + return true; } + } else { + $updateFields = array_fill_keys($foreignKey, null); + return $target->updateAll($updateFields, $conditions); + } - return $ok; } /** From 7b998b6da9da425d4bd4020b85d120940a9fa8d9 Mon Sep 17 00:00:00 2001 From: Luan Hospodarsky Date: Wed, 4 Nov 2015 09:03:06 -0200 Subject: [PATCH 0526/2059] Assuming that updateAll is always OK if no exceptions occurred in _unlink. Removing unnecessary test. --- Association/HasMany.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index 185527fd..81b0f542 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -431,7 +431,8 @@ protected function _unlink(array $foreignKey, Table $target, array $conditions = } } else { $updateFields = array_fill_keys($foreignKey, null); - return $target->updateAll($updateFields, $conditions); + $target->updateAll($updateFields, $conditions); + return true; } } From a55843ed4e41fe2fdfaf61c99a8da568526e07d5 Mon Sep 17 00:00:00 2001 From: jeffblack360 Date: Sat, 7 Nov 2015 17:20:29 -0600 Subject: [PATCH 0527/2059] Remove superfluous Model.afterDelete from Events doc-block --- Table.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/Table.php b/Table.php index b98b8054..355990ce 100644 --- a/Table.php +++ b/Table.php @@ -1623,8 +1623,6 @@ protected function _update($entity, $data) * will be aborted. Receives the event, entity, and options. * - `Model.afterDelete` Fired after the delete has been successful. Receives * the event, entity, and options. - * - `Model.afterDelete` Fired after the delete has been successful. Receives - * the event, entity, and options. * - `Model.afterDeleteCommit` Fired after the transaction is committed for * an atomic delete. Receives the event, entity, and options. * From 71c4ab828898d9a1174431f989b890e5314db1ec Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 9 Nov 2015 18:03:22 +0530 Subject: [PATCH 0528/2059] Fix "undefined index" error when table doesn't have primary key. --- Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Query.php b/Query.php index 1a8aa730..10e894c8 100644 --- a/Query.php +++ b/Query.php @@ -332,7 +332,7 @@ protected function _addAssociationsToTypeMap($table, $typeMap, $associations) } $target = $association->target(); $primary = (array)$target->primaryKey(); - if ($typeMap->type($target->aliasField($primary[0])) === null) { + if (empty($primary) || $typeMap->type($target->aliasField($primary[0])) === null) { $this->addDefaultTypes($target); } if (!empty($nested)) { From 6d89a2a6becc392408fa12a8fae104a35748217d Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 10 Nov 2015 21:49:48 -0500 Subject: [PATCH 0529/2059] Fix invalid order clauses being left on subqueries When eager loading associations that contain an ORDER, we need to augment the selected fields to include the fields being ordered by. If we were to remove the ORDER BY, incorrect association data would be loaded. If we were to leave the fields alone, we'd have problems with postgres. Instead, if we find fields in the ORDER BY, that also exist in the column list, those fields should be preserved in the generated subquery. Refs #7669 --- Association/SelectableAssociationTrait.php | 39 +++++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/Association/SelectableAssociationTrait.php b/Association/SelectableAssociationTrait.php index 9b334ef7..150ffc4f 100644 --- a/Association/SelectableAssociationTrait.php +++ b/Association/SelectableAssociationTrait.php @@ -145,7 +145,11 @@ public function _addFilteringJoin($query, $key, $subquery) $aliasedTable = $this->source()->alias(); foreach ($subquery->clause('select') as $aliasedField => $field) { - $filter[] = new IdentifierExpression($field); + if (is_int($aliasedField)) { + $filter[] = new IdentifierExpression($field); + } else { + $filter[$aliasedField] = $field; + } } $subquery->select($filter, true); @@ -234,15 +238,40 @@ protected function _buildSubquery($query) $filterQuery->offset(null); } - $keys = (array)$this->bindingKey(); + $fields = $this->_subqueryFields($query); + $filterQuery->select($fields['select'], true)->group($fields['group']); + return $filterQuery; + } + /** + * Calculate the fields that need to participate in a subquery. + * + * Normally this includes the binding key columns. If there is a an ORDER BY, + * those columns are also included as the fields may be calculated or constant values, + * that need to be present to ensure the correct association data is loaded. + * + * @param \Cake\ORM\Query $query The query to get fields from. + * @return array The list of fields for the subquery. + */ + protected function _subqueryFields($query) + { + $keys = (array)$this->bindingKey(); if ($this->type() === $this::MANY_TO_ONE) { $keys = (array)$this->foreignKey(); } - $fields = $query->aliasFields($keys, $this->source()->alias()); - $filterQuery->select($fields, true)->group(array_values($fields)); - return $filterQuery; + $group = $fields = array_values($fields); + + $order = $query->clause('order'); + if ($order) { + $columns = $query->clause('select'); + $order->iterateParts(function($direction, $field) use (&$fields, $columns) { + if (isset($columns[$field])) { + $fields[$field] = $columns[$field]; + } + }); + } + return ['select' => $fields, 'group' => $group]; } /** From 3a90496d3019dc8f6d864d8d2450ee1810ad6cc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Lorenzo=20Rodr=C3=ADguez?= Date: Wed, 11 Nov 2015 10:57:11 +0100 Subject: [PATCH 0530/2059] Fixed CS error --- Association/SelectableAssociationTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/SelectableAssociationTrait.php b/Association/SelectableAssociationTrait.php index 150ffc4f..4e78a943 100644 --- a/Association/SelectableAssociationTrait.php +++ b/Association/SelectableAssociationTrait.php @@ -265,7 +265,7 @@ protected function _subqueryFields($query) $order = $query->clause('order'); if ($order) { $columns = $query->clause('select'); - $order->iterateParts(function($direction, $field) use (&$fields, $columns) { + $order->iterateParts(function ($direction, $field) use (&$fields, $columns) { if (isset($columns[$field])) { $fields[$field] = $columns[$field]; } From 5c6a835bbe5732d3348a4dd1577408801d0ff129 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 15 Nov 2015 21:24:08 -0500 Subject: [PATCH 0531/2059] Fix custom keys in belongsToMany Use custom keys correctly in the generated associations on the junction table for belongsToMany associations. Refs #7600 --- Association/BelongsToMany.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 2f329b5f..af4d97fe 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -210,6 +210,13 @@ public function junction($table = null) ]); } + if (!$source->association($junctionAlias)) { + $source->hasMany($junctionAlias, [ + 'targetTable' => $table, + 'foreignKey' => $this->foreignKey(), + ]); + } + if (!$target->association($sAlias)) { $target->belongsToMany($sAlias, [ 'sourceTable' => $target, @@ -220,10 +227,6 @@ public function junction($table = null) ]); } - if (!$source->association($table->alias())) { - $source->hasMany($junctionAlias)->target($table); - } - return $this->_junctionTable = $table; } From 8fc21e29a6104ab958559d195b80e98ec19d7843 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 15 Nov 2015 21:45:19 -0500 Subject: [PATCH 0532/2059] Extract some methods to break down a mega method. I find that having smaller helper methods makes code easier to read as each section can be named and grokked individually. --- Association/BelongsToMany.php | 112 ++++++++++++++++++++++++++-------- 1 file changed, 87 insertions(+), 25 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index af4d97fe..419a59a5 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -161,10 +161,6 @@ public function targetForeignKey($key = null) */ public function junction($table = null) { - $target = $this->target(); - $source = $this->source(); - $sAlias = $source->alias(); - $tAlias = $target->alias(); $tableLocator = $this->tableLocator(); if ($table === null) { @@ -189,45 +185,111 @@ public function junction($table = null) if (is_string($table)) { $table = $tableLocator->get($table); } - $junctionAlias = $table->alias(); + $target = $this->target(); + $source = $this->source(); - if (!$table->association($sAlias)) { - $table - ->belongsTo($sAlias, ['foreignKey' => $this->foreignKey()]) - ->target($source); - } + $this->_generateSourceAssociations($table, $source); + $this->_generateTargetAssociations($table, $source, $target); + $this->_generateJunctionAssociations($table, $source, $target); + return $this->_junctionTable = $table; + } - if (!$table->association($tAlias)) { - $table - ->belongsTo($tAlias, ['foreignKey' => $this->targetForeignKey()]) - ->target($target); - } + /** + * Generate reciprocal associations as necessary. + * + * Generates the following associations: + * + * - target hasMany junction e.g. Articles hasMany ArticlesTags + * - target belongsToMany source e.g Articles belongsToMany Tags. + * + * You can override these generated associations by defining associations + * with the correct aliases. + * + * @param \Cake\ORM\Table $junction The junction table. + * @param \Cake\ORM\Table $source The source table. + * @param \Cake\ORM\Table $target The target table. + * @return void + */ + protected function _generateTargetAssociations($junction, $source, $target) + { + $junctionAlias = $junction->alias(); + $sAlias = $source->alias(); if (!$target->association($junctionAlias)) { $target->hasMany($junctionAlias, [ - 'targetTable' => $table, + 'targetTable' => $junction, 'foreignKey' => $this->targetForeignKey(), ]); } + if (!$target->association($sAlias)) { + $target->belongsToMany($sAlias, [ + 'sourceTable' => $target, + 'targetTable' => $source, + 'foreignKey' => $this->targetForeignKey(), + 'targetForeignKey' => $this->foreignKey(), + 'through' => $junction + ]); + } + } + /** + * Generate additional source table associations as necessary. + * + * Generates the following associations: + * + * - source hasMany junction e.g. Tags hasMany ArticlesTags + * + * You can override these generated associations by defining associations + * with the correct aliases. + * + * @param \Cake\ORM\Table $junction The junction table. + * @param \Cake\ORM\Table $source The source table. + * @return void + */ + protected function _generateSourceAssociations($junction, $source) + { + $junctionAlias = $junction->alias(); if (!$source->association($junctionAlias)) { $source->hasMany($junctionAlias, [ - 'targetTable' => $table, + 'targetTable' => $junction, 'foreignKey' => $this->foreignKey(), ]); } + } - if (!$target->association($sAlias)) { - $target->belongsToMany($sAlias, [ - 'sourceTable' => $target, - 'targetTable' => $source, + /** + * Generate associations on the junction table as necessary + * + * Generates the following associations: + * + * - junction belongsTo source e.g. ArticlesTags belongsTo Tags + * - junction belongsTo target e.g. ArticlesTags belongsTo Articles + * + * You can override these generated associations by defining associations + * with the correct aliases. + * + * @param \Cake\ORM\Table $junction The junction table. + * @param \Cake\ORM\Table $source The source table. + * @param \Cake\ORM\Table $target The target table. + * @return void + */ + protected function _generateJunctionAssociations($junction, $source, $target) + { + $tAlias = $target->alias(); + $sAlias = $source->alias(); + + if (!$junction->association($tAlias)) { + $junction->belongsTo($tAlias, [ 'foreignKey' => $this->targetForeignKey(), - 'targetForeignKey' => $this->foreignKey(), - 'through' => $table + 'targetTable' => $target + ]); + } + if (!$junction->association($sAlias)) { + $junction->belongsTo($sAlias, [ + 'foreignKey' => $this->foreignKey(), + 'targetTable' => $source ]); } - - return $this->_junctionTable = $table; } /** From 0c0e3d832765ef3863c33159797128bda0830af3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Thu, 19 Nov 2015 11:59:06 +0100 Subject: [PATCH 0533/2059] Split the locator class name getting in a separate method. --- Locator/TableLocator.php | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 64996777..b1ea118f 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -146,10 +146,7 @@ public function get($alias, array $options = []) $options += $this->_config[$alias]; } - if (empty($options['className'])) { - $options['className'] = Inflector::camelize($alias); - } - $className = App::className($options['className'], 'Model/Table', 'Table'); + $className = $this->_getClassName($alias, $options); if ($className) { $options['className'] = $className; } else { @@ -175,6 +172,20 @@ public function get($alias, array $options = []) return $this->_instances[$alias]; } + /** + * Gets the table class name. + * + * @param string $alias The alias name you want to get. + * @param array $options Table options array. + * @return string + */ + protected function _getClassName($alias, array $options = []) { + if (empty($options['className'])) { + $options['className'] = Inflector::camelize($alias); + } + return App::className($options['className'], 'Model/Table', 'Table'); + } + /** * Wrapper for creating table instances * From d02704c872352fd211e6b87c66044f95971c5441 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Thu, 19 Nov 2015 12:16:49 +0100 Subject: [PATCH 0534/2059] Minor fix to the previous TableLocator change. --- Locator/TableLocator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index b1ea118f..a33b2f7d 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -146,7 +146,7 @@ public function get($alias, array $options = []) $options += $this->_config[$alias]; } - $className = $this->_getClassName($alias, $options); + $className = $options['className'] = $this->_getClassName($alias, $options); if ($className) { $options['className'] = $className; } else { From 7a9a8ae643e164c9c07f861cc310b3e7d4c9d882 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Thu, 19 Nov 2015 16:28:15 +0100 Subject: [PATCH 0535/2059] Fixing the TableLocator --- Locator/TableLocator.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index a33b2f7d..243a4a04 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -16,7 +16,6 @@ use Cake\Core\App; use Cake\Datasource\ConnectionManager; -use Cake\ORM\Locator\LocatorInterface; use Cake\ORM\Table; use Cake\Utility\Inflector; use RuntimeException; @@ -146,7 +145,11 @@ public function get($alias, array $options = []) $options += $this->_config[$alias]; } - $className = $options['className'] = $this->_getClassName($alias, $options); + if (empty($options['className'])) { + $options['className'] = Inflector::camelize($alias); + } + + $className = $this->_getClassName($alias, $options); if ($className) { $options['className'] = $className; } else { From d671c02402a1d66c085be5377396c5d6f3b0a3e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Thu, 19 Nov 2015 18:00:45 +0100 Subject: [PATCH 0536/2059] phpcs fix --- Locator/TableLocator.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 243a4a04..9d09e89b 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -182,7 +182,8 @@ public function get($alias, array $options = []) * @param array $options Table options array. * @return string */ - protected function _getClassName($alias, array $options = []) { + protected function _getClassName($alias, array $options = []) + { if (empty($options['className'])) { $options['className'] = Inflector::camelize($alias); } From 799210a6f163d648141928b0f2da32b3a504511c Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 22 Nov 2015 13:54:44 +0100 Subject: [PATCH 0537/2059] Moving result type conversion to the statements layer This is part of the work needed for automatically converting SQL functions to their correspondiny PHP types. I also cleans up quite a bit the ResultSet class --- Query.php | 41 +++++++++++++++++++++++++++++++++-------- ResultSet.php | 48 +++++------------------------------------------- 2 files changed, 38 insertions(+), 51 deletions(-) diff --git a/Query.php b/Query.php index 1a8aa730..dfd0ecc4 100644 --- a/Query.php +++ b/Query.php @@ -17,6 +17,7 @@ use ArrayObject; use Cake\Database\ExpressionInterface; use Cake\Database\Query as DatabaseQuery; +use Cake\Database\TypeMap; use Cake\Database\ValueBinder; use Cake\Datasource\QueryInterface; use Cake\Datasource\QueryTrait; @@ -173,7 +174,7 @@ public function addDefaultTypes(Table $table) $map = $table->schema()->typeMap(); $fields = []; foreach ($map as $f => $type) { - $fields[$f] = $fields[$alias . '.' . $f] = $type; + $fields[$f] = $fields[$alias . '.' . $f] = $fields[$alias . '__' . $f] = $type; } $this->typeMap()->addDefaults($fields); @@ -669,6 +670,8 @@ public function cleanCopy() $clone->offset(null); $clone->mapReduce(null, null, true); $clone->formatResults(null, true); + $clone->selectTypeMap(new TypeMap()); + $clone->decorateResults(null, true); return $clone; } @@ -869,6 +872,7 @@ protected function _execute() $decorator = $this->_decoratorClass(); return new $decorator($this->_results); } + $statement = $this->eagerLoader()->loadExternal($this, $this->execute()); return new ResultSet($this, $statement); } @@ -880,22 +884,23 @@ protected function _execute() * specified and applies the joins required to eager load associations defined * using `contain` * + * It also sets the default types for the columns in the select clause + * * @see \Cake\Database\Query::execute() * @return void */ protected function _transformQuery() { - if (!$this->_dirty) { + if (!$this->_dirty || $this->_type !== 'select') { return; } - if ($this->_type === 'select') { - if (empty($this->_parts['from'])) { - $this->from([$this->_repository->alias() => $this->_repository->table()]); - } - $this->_addDefaultFields(); - $this->eagerLoader()->attachAssociations($this, $this->_repository, !$this->_hasFields); + if (empty($this->_parts['from'])) { + $this->from([$this->_repository->alias() => $this->_repository->table()]); } + $this->_addDefaultFields(); + $this->eagerLoader()->attachAssociations($this, $this->_repository, !$this->_hasFields); + $this->_addDefaultSelectTypes(); } /** @@ -919,6 +924,26 @@ protected function _addDefaultFields() $this->select($aliased, true); } + /** + * Sets the default types for converting the fields in the select clause + * + * @return void + */ + protected function _addDefaultSelectTypes() + { + $typeMap = $this->typeMap()->defaults(); + $selectTypeMap = $this->selectTypeMap(); + $select = array_keys($this->clause('select')); + $types = []; + + foreach ($select as $alias) { + if (isset($typeMap[$alias])) { + $types[$alias] = $typeMap[$alias]; + } + } + $this->selectTypeMap()->addDefaults($types); + } + /** * {@inheritDoc} * diff --git a/ResultSet.php b/ResultSet.php index 7c734cc6..97b47ae3 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -179,7 +179,6 @@ public function __construct($query, $statement) $this->_useBuffering = $query->bufferResults(); $this->_defaultAlias = $this->_defaultTable->alias(); $this->_calculateColumnMap(); - $this->_calculateTypeMap(); if ($this->_useBuffering) { $count = $this->count(); @@ -397,34 +396,11 @@ protected function _calculateColumnMap() * Creates a map of Type converter classes for each of the columns that should * be fetched by this object. * + * @deprecated 3.2.0 Not used anymore. Type casting is done at the statement level * @return void */ protected function _calculateTypeMap() { - if (isset($this->_map[$this->_defaultAlias])) { - $this->_types[$this->_defaultAlias] = $this->_getTypes( - $this->_defaultTable, - $this->_map[$this->_defaultAlias] - ); - } - - foreach ($this->_matchingMapColumns as $alias => $keys) { - $this->_types[$alias] = $this->_getTypes( - $this->_matchingMap[$alias]['instance']->target(), - $keys - ); - } - - foreach ($this->_containMap as $assoc) { - $alias = $assoc['alias']; - if (isset($this->_types[$alias]) || !$assoc['canBeJoined'] || !isset($this->_map[$alias])) { - continue; - } - $this->_types[$alias] = $this->_getTypes( - $assoc['instance']->target(), - $this->_map[$alias] - ); - } } /** @@ -499,12 +475,9 @@ protected function _groupResult($row) foreach ($this->_matchingMapColumns as $alias => $keys) { $matching = $this->_matchingMap[$alias]; - $results['_matchingData'][$alias] = $this->_castValues( - $alias, - array_combine( - $keys, - array_intersect_key($row, $keys) - ) + $results['_matchingData'][$alias] = array_combine( + $keys, + array_intersect_key($row, $keys) ); if ($this->_hydrate) { $options['source'] = $matching['instance']->registryAlias(); @@ -519,12 +492,6 @@ protected function _groupResult($row) $presentAliases[$table] = true; } - if (isset($presentAliases[$defaultAlias])) { - $results[$defaultAlias] = $this->_castValues( - $defaultAlias, - $results[$defaultAlias] - ); - } unset($presentAliases[$defaultAlias]); foreach ($this->_containMap as $assoc) { @@ -550,8 +517,6 @@ protected function _groupResult($row) unset($presentAliases[$alias]); if ($assoc['canBeJoined']) { - $results[$alias] = $this->_castValues($assoc['alias'], $results[$alias]); - $hasData = false; foreach ($results[$alias] as $v) { if ($v !== null && $v !== []) { @@ -602,14 +567,11 @@ protected function _groupResult($row) * * @param string $alias The table object alias * @param array $values The values to cast + * @deprecated 3.2.0 Not used anymore. Type casting is done at the statement level * @return array */ protected function _castValues($alias, $values) { - foreach ($this->_types[$alias] as $field => $type) { - $values[$field] = $type->toPHP($values[$field], $this->_driver); - } - return $values; } From c4f2ecca7e3ecee084600a39e60be48d2093ffa1 Mon Sep 17 00:00:00 2001 From: ndm2 Date: Wed, 25 Nov 2015 00:41:59 +0100 Subject: [PATCH 0538/2059] Pass options to internal `Table` calls. Ensures that options passed to `Table::save()`, and `HasMany`/`BelongsToMany` link/unlink/replace methods are being passed through to the internal `Table::save/delete()` calls. --- Association/BelongsToMany.php | 24 +++++++++++++----------- Association/HasMany.php | 25 ++++++++++++++----------- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 419a59a5..795958e5 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -540,7 +540,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) * entities to be saved. * @param array|\Traversable $entities list of entities to persist in target table and to * link to the parent entity - * @param array $options list of options accepted by Table::save() + * @param array $options list of options accepted by `Table::save()` * @throws \InvalidArgumentException if the property representing the association * in the parent entity cannot be traversed * @return \Cake\Datasource\EntityInterface|bool The parent entity after all links have been @@ -603,7 +603,7 @@ protected function _saveTarget(EntityInterface $parentEntity, $entities, $option * association * @param array $targetEntities list of entities to link to link to the source entity using the * junction table - * @param array $options list of options accepted by Table::save() + * @param array $options list of options accepted by `Table::save()` * @return bool success */ protected function _saveLinks(EntityInterface $sourceEntity, $targetEntities, $options) @@ -673,7 +673,7 @@ protected function _saveLinks(EntityInterface $sourceEntity, $targetEntities, $o * of this association * @param array $targetEntities list of entities belonging to the `target` side * of this association - * @param array $options list of options to be passed to the save method + * @param array $options list of options to be passed to the internal `save` call * @throws \InvalidArgumentException when any of the values in $targetEntities is * detected to not be already persisted * @return bool true on success, false otherwise @@ -717,20 +717,21 @@ function () use ($sourceEntity, $targetEntities, $options) { * this association * @param bool $cleanProperty whether or not to remove all the objects in $targetEntities * that are stored in $sourceEntity + * @param array $options list of options to be passed to the internal `delete` call * @throws \InvalidArgumentException if non persisted entities are passed or if * any of them is lacking a primary key value * @return void */ - public function unlink(EntityInterface $sourceEntity, array $targetEntities, $cleanProperty = true) + public function unlink(EntityInterface $sourceEntity, array $targetEntities, $cleanProperty = true, array $options = []) { $this->_checkPersistenceStatus($sourceEntity, $targetEntities); $property = $this->property(); $this->junction()->connection()->transactional( - function () use ($sourceEntity, $targetEntities) { + function () use ($sourceEntity, $targetEntities, $options) { $links = $this->_collectJointEntities($sourceEntity, $targetEntities); foreach ($links as $entity) { - $this->_junctionTable->delete($entity); + $this->_junctionTable->delete($entity, $options); } } ); @@ -798,8 +799,8 @@ function () use ($sourceEntity, $targetEntities) { * @param \Cake\Datasource\EntityInterface $sourceEntity an entity persisted in the source table for * this association * @param array $targetEntities list of entities from the target table to be linked - * @param array $options list of options to be passed to `save` persisting or - * updating new links + * @param array $options list of options to be passed to the internal `save`/`delete` calls + * when persisting/updating new links, or deleting existing ones * @throws \InvalidArgumentException if non persisted entities are passed or if * any of them is lacking a primary key value * @return bool success @@ -828,7 +829,7 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { } $jointEntities = $this->_collectJointEntities($sourceEntity, $targetEntities); - $inserts = $this->_diffLinks($existing, $jointEntities, $targetEntities); + $inserts = $this->_diffLinks($existing, $jointEntities, $targetEntities, $options); if ($inserts && !$this->_saveTarget($sourceEntity, $inserts, $options)) { return false; @@ -861,9 +862,10 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { * @param array $jointEntities link entities that should be persisted * @param array $targetEntities entities in target table that are related to * the `$jointEntities` + * @param array $options list of options accepted by `Table::delete()` * @return array */ - protected function _diffLinks($existing, $jointEntities, $targetEntities) + protected function _diffLinks($existing, $jointEntities, $targetEntities, $options = []) { $junction = $this->junction(); $target = $this->target(); @@ -912,7 +914,7 @@ protected function _diffLinks($existing, $jointEntities, $targetEntities) if ($deletes) { foreach ($deletes as $entity) { - $junction->delete($entity); + $junction->delete($entity, $options); } } diff --git a/Association/HasMany.php b/Association/HasMany.php index 81b0f542..3d6fb647 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -150,7 +150,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) $unlinkSuccessful = null; if ($this->_saveStrategy === self::SAVE_REPLACE) { - $unlinkSuccessful = $this->_unlinkAssociated($properties, $entity, $target, $targetEntities); + $unlinkSuccessful = $this->_unlinkAssociated($properties, $entity, $target, $targetEntities, $options); } if ($unlinkSuccessful === false) { @@ -208,7 +208,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) * of this association * @param array $targetEntities list of entities belonging to the `target` side * of this association - * @param array $options list of options to be passed to the save method + * @param array $options list of options to be passed to the internal `save` call * @return bool true on success, false otherwise */ public function link(EntityInterface $sourceEntity, array $targetEntities, array $options = []) @@ -226,7 +226,7 @@ public function link(EntityInterface $sourceEntity, array $targetEntities, array $sourceEntity->set($property, $currentEntities); - $savedEntity = $this->saveAssociated($sourceEntity); + $savedEntity = $this->saveAssociated($sourceEntity, $options); $ok = ($savedEntity instanceof EntityInterface); @@ -268,11 +268,12 @@ public function link(EntityInterface $sourceEntity, array $targetEntities, array * this association * @param bool $cleanProperty whether or not to remove all the objects in $targetEntities * that are stored in $sourceEntity + * @param array $options list of options to be passed to the internal `delete` call * @throws \InvalidArgumentException if non persisted entities are passed or if * any of them is lacking a primary key value * @return void */ - public function unlink(EntityInterface $sourceEntity, array $targetEntities, $cleanProperty = true) + public function unlink(EntityInterface $sourceEntity, array $targetEntities, $cleanProperty = true, array $options = []) { $foreignKey = (array)$this->foreignKey(); $target = $this->target(); @@ -287,7 +288,7 @@ public function unlink(EntityInterface $sourceEntity, array $targetEntities, $cl ->toList() ]; - $this->_unlink($foreignKey, $target, $conditions); + $this->_unlink($foreignKey, $target, $conditions, $options); if ($cleanProperty) { $sourceEntity->set( @@ -342,8 +343,8 @@ function ($assoc) use ($targetEntities) { * @param \Cake\Datasource\EntityInterface $sourceEntity an entity persisted in the source table for * this association * @param array $targetEntities list of entities from the target table to be linked - * @param array $options list of options to be passed to `save` persisting or - * updating new links + * @param array $options list of options to be passed to the internal `save`/`delete` calls + * when persisting/updating new links, or deleting existing ones * @throws \InvalidArgumentException if non persisted entities are passed or if * any of them is lacking a primary key value * @return bool success @@ -372,9 +373,10 @@ public function replace(EntityInterface $sourceEntity, array $targetEntities, ar * @param EntityInterface $entity the entity which should have its associated entities unassigned * @param Table $target The associated table * @param array $remainingEntities Entities that should not be deleted + * @param array $options list of options accepted by `Table::delete()` * @return bool success */ - protected function _unlinkAssociated(array $properties, EntityInterface $entity, Table $target, array $remainingEntities = []) + protected function _unlinkAssociated(array $properties, EntityInterface $entity, Table $target, array $remainingEntities = [], array $options = []) { $primaryKey = (array)$target->primaryKey(); $exclusions = new Collection($remainingEntities); @@ -401,7 +403,7 @@ function ($v) { ]; } - return $this->_unlink(array_keys($properties), $target, $conditions); + return $this->_unlink(array_keys($properties), $target, $conditions, $options); } /** @@ -411,9 +413,10 @@ function ($v) { * @param array $foreignKey array of foreign key properties * @param Table $target The associated table * @param array $conditions The conditions that specifies what are the objects to be unlinked + * @param array $options list of options accepted by `Table::delete()` * @return bool success */ - protected function _unlink(array $foreignKey, Table $target, array $conditions = []) + protected function _unlink(array $foreignKey, Table $target, array $conditions = [], array $options = []) { $mustBeDependent = (!$this->_foreignKeyAcceptsNull($target, $foreignKey) || $this->dependent()); @@ -422,7 +425,7 @@ protected function _unlink(array $foreignKey, Table $target, array $conditions = $query = $this->find('all')->where($conditions); $ok = true; foreach ($query as $assoc) { - $ok = $ok && $target->delete($assoc); + $ok = $ok && $target->delete($assoc, $options); } return $ok; } else { From 69713ddf5d48316adf3f6c6fbb998c9fc6058384 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Wed, 25 Nov 2015 10:55:41 +0100 Subject: [PATCH 0539/2059] Fixed problem in translate behavior when all translate fields are clean Fixes #7728 --- Behavior/TranslateBehavior.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 5ce88bef..85376358 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -276,6 +276,11 @@ public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $o $values = $entity->extract($this->_config['fields'], true); $fields = array_keys($values); + + if (empty($fields)) { + return; + } + $primaryKey = (array)$this->_table->primaryKey(); $key = $entity->get(current($primaryKey)); $model = $this->_config['referenceName']; From 5c40319273d240f543dde2af58a529189b728328 Mon Sep 17 00:00:00 2001 From: ndm2 Date: Wed, 25 Nov 2015 23:12:06 +0100 Subject: [PATCH 0540/2059] Make the `$cleanProperty` argument part of `$options`. --- Association/BelongsToMany.php | 29 ++++++++++++++++++++++------- Association/HasMany.php | 26 ++++++++++++++++++++------ 2 files changed, 42 insertions(+), 13 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 795958e5..ceb1d5bc 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -698,8 +698,16 @@ function () use ($sourceEntity, $targetEntities, $options) { * target entities. This method assumes that all passed objects are already persisted * in the database and that each of them contain a primary key value. * - * By default this method will also unset each of the entity objects stored inside - * the source entity. + * ### Options + * + * Additionally to the default options accepted by `Table::delete()`, the following + * keys are supported: + * + * - cleanProperty: Whether or not to remove all the objects in `$targetEntities` that + * are stored in `$sourceEntity` (default: true) + * + * By default this method will unset each of the entity objects stored inside the + * source entity. * * ### Example: * @@ -715,15 +723,22 @@ function () use ($sourceEntity, $targetEntities, $options) { * this association * @param array $targetEntities list of entities persisted in the target table for * this association - * @param bool $cleanProperty whether or not to remove all the objects in $targetEntities - * that are stored in $sourceEntity - * @param array $options list of options to be passed to the internal `delete` call + * @param array|bool $options list of options to be passed to the internal `delete` call, + * or a `boolean` * @throws \InvalidArgumentException if non persisted entities are passed or if * any of them is lacking a primary key value * @return void */ - public function unlink(EntityInterface $sourceEntity, array $targetEntities, $cleanProperty = true, array $options = []) + public function unlink(EntityInterface $sourceEntity, array $targetEntities, $options = []) { + if (is_bool($options)) { + $options = [ + 'cleanProperty' => $options + ]; + } else { + $options += ['cleanProperty' => true]; + } + $this->_checkPersistenceStatus($sourceEntity, $targetEntities); $property = $this->property(); @@ -737,7 +752,7 @@ function () use ($sourceEntity, $targetEntities, $options) { ); $existing = $sourceEntity->get($property) ?: []; - if (!$cleanProperty || empty($existing)) { + if (!$options['cleanProperty'] || empty($existing)) { return; } diff --git a/Association/HasMany.php b/Association/HasMany.php index 3d6fb647..913d0c9b 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -245,8 +245,16 @@ public function link(EntityInterface $sourceEntity, array $targetEntities, array * target entities. This method assumes that all passed objects are already persisted * in the database and that each of them contain a primary key value. * - * By default this method will also unset each of the entity objects stored inside - * the source entity. + * ### Options + * + * Additionally to the default options accepted by `Table::delete()`, the following + * keys are supported: + * + * - cleanProperty: Whether or not to remove all the objects in `$targetEntities` that + * are stored in `$sourceEntity` (default: true) + * + * By default this method will unset each of the entity objects stored inside the + * source entity. * * Changes are persisted in the database and also in the source entity. * @@ -266,15 +274,21 @@ public function link(EntityInterface $sourceEntity, array $targetEntities, array * this association * @param array $targetEntities list of entities persisted in the target table for * this association - * @param bool $cleanProperty whether or not to remove all the objects in $targetEntities - * that are stored in $sourceEntity * @param array $options list of options to be passed to the internal `delete` call * @throws \InvalidArgumentException if non persisted entities are passed or if * any of them is lacking a primary key value * @return void */ - public function unlink(EntityInterface $sourceEntity, array $targetEntities, $cleanProperty = true, array $options = []) + public function unlink(EntityInterface $sourceEntity, array $targetEntities, $options = []) { + if (is_bool($options)) { + $options = [ + 'cleanProperty' => $options + ]; + } else { + $options += ['cleanProperty' => true]; + } + $foreignKey = (array)$this->foreignKey(); $target = $this->target(); $targetPrimaryKey = array_merge((array)$target->primaryKey(), $foreignKey); @@ -290,7 +304,7 @@ public function unlink(EntityInterface $sourceEntity, array $targetEntities, $cl $this->_unlink($foreignKey, $target, $conditions, $options); - if ($cleanProperty) { + if ($options['cleanProperty']) { $sourceEntity->set( $property, (new Collection($sourceEntity->get($property))) From 7190cffe85d71b7525d4cf287b860e9598771853 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Tue, 1 Dec 2015 22:47:05 +0100 Subject: [PATCH 0541/2059] Automatically converting SQL functions to the corresponding type --- Query.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Query.php b/Query.php index 76f3080c..5ef29849 100644 --- a/Query.php +++ b/Query.php @@ -18,6 +18,7 @@ use Cake\Database\ExpressionInterface; use Cake\Database\Query as DatabaseQuery; use Cake\Database\TypeMap; +use Cake\Database\TypedResultInterface; use Cake\Database\ValueBinder; use Cake\Datasource\QueryInterface; use Cake\Datasource\QueryTrait; @@ -933,12 +934,16 @@ protected function _addDefaultSelectTypes() { $typeMap = $this->typeMap()->defaults(); $selectTypeMap = $this->selectTypeMap(); - $select = array_keys($this->clause('select')); + $select = $this->clause('select'); $types = []; - foreach ($select as $alias) { + foreach ($select as $alias => $value) { if (isset($typeMap[$alias])) { $types[$alias] = $typeMap[$alias]; + continue; + } + if ($value instanceof TypedResultInterface) { + $types[$alias] = $value->returnType(); } } $this->selectTypeMap()->addDefaults($types); From 20870c9fcdcfac0a8042d52aebbf4ff241fd51b0 Mon Sep 17 00:00:00 2001 From: Bryan Crowe Date: Wed, 2 Dec 2015 06:11:38 -0500 Subject: [PATCH 0542/2059] Condense multi isset calls --- Association/SelectableAssociationTrait.php | 2 +- Query.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Association/SelectableAssociationTrait.php b/Association/SelectableAssociationTrait.php index 4e78a943..b02291e2 100644 --- a/Association/SelectableAssociationTrait.php +++ b/Association/SelectableAssociationTrait.php @@ -314,7 +314,7 @@ protected function _resultInjector($fetchQuery, $resultMap, $options) $sourceKey = $sourceKeys[0]; return function ($row) use ($resultMap, $sourceKey, $nestKey) { - if (isset($row[$sourceKey]) && isset($resultMap[$row[$sourceKey]])) { + if (isset($row[$sourceKey], $resultMap[$row[$sourceKey]])) { $row[$nestKey] = $resultMap[$row[$sourceKey]]; } return $row; diff --git a/Query.php b/Query.php index 10e894c8..cceba5db 100644 --- a/Query.php +++ b/Query.php @@ -632,7 +632,7 @@ public function applyOptions(array $options) ksort($options); foreach ($options as $option => $values) { - if (isset($valid[$option]) && isset($values)) { + if (isset($valid[$option], $values)) { $this->{$valid[$option]}($values); } else { $this->_options[$option] = $values; From 8dd903dbe41e3322176b9f8c7fe7fa93041ffff2 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Wed, 2 Dec 2015 22:27:26 -0500 Subject: [PATCH 0543/2059] Contain the junction table when proxying finders. When proxying finder methods from a belongstomany association, if that association has conditions we should contain the junction table as the conditions very likely use columns on that table. Refs #7707 --- Association/BelongsToMany.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index ceb1d5bc..6436dd45 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -771,6 +771,29 @@ function () use ($sourceEntity, $targetEntities, $options) { $sourceEntity->dirty($property, false); } + /** + * Proxies the finding operation to the target table's find method + * and modifies the query accordingly based of this association + * configuration. + * + * If your association includes conditions, the junction table will be + * included in the query's contained associations. + * + * @param string|array $type the type of query to perform, if an array is passed, + * it will be interpreted as the `$options` parameter + * @param array $options The options to for the find + * @see \Cake\ORM\Table::find() + * @return \Cake\ORM\Query + */ + public function find($type = null, array $options = []) + { + $query = parent::find($type, $options); + if ($this->conditions()) { + $query->contain([$this->junction()->alias()]); + } + return $query; + } + /** * Replaces existing association links between the source entity and the target * with the ones passed. This method does a smart cleanup, links that are already From 8790825819fe4aac93cc29017eb4a8ac16462b73 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Thu, 3 Dec 2015 19:26:20 +0100 Subject: [PATCH 0544/2059] Pleases CS checker --- Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Query.php b/Query.php index 5ef29849..84176a8b 100644 --- a/Query.php +++ b/Query.php @@ -17,8 +17,8 @@ use ArrayObject; use Cake\Database\ExpressionInterface; use Cake\Database\Query as DatabaseQuery; -use Cake\Database\TypeMap; use Cake\Database\TypedResultInterface; +use Cake\Database\TypeMap; use Cake\Database\ValueBinder; use Cake\Datasource\QueryInterface; use Cake\Datasource\QueryTrait; From 2fb9a1d4e6750c407e20b551260324d564ef8d18 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 5 Dec 2015 23:22:21 -0500 Subject: [PATCH 0545/2059] Fix case where an association find() is then eagerloaded on. When an association find() then has the reverse side eagerloaded, the joins would be incorrectly ordered. Before attaching the eagerloader, we need to remove the old join to ensure the new join is put at the end. Refs #7707 --- Association/BelongsToMany.php | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 6436dd45..b018c66b 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -320,11 +320,13 @@ public function attachTo(Query $query, array $options = []) $includeFields = $options['includeFields']; } + $assoc = $this->_targetTable->association($junction->alias()); + $query->removeJoin($assoc->name()); + unset($options['queryBuilder']); $type = array_intersect_key($options, ['joinType' => 1, 'fields' => 1]); $options = ['conditions' => [$cond]] + compact('includeFields'); $options['foreignKey'] = $this->targetForeignKey(); - $assoc = $this->_targetTable->association($junction->alias()); $assoc->attachTo($query, $options + $type); $query->eagerLoader()->addToJoinsMap($junction->alias(), $assoc, true); } @@ -788,9 +790,25 @@ function () use ($sourceEntity, $targetEntities, $options) { public function find($type = null, array $options = []) { $query = parent::find($type, $options); - if ($this->conditions()) { - $query->contain([$this->junction()->alias()]); + if (!$this->conditions()) { + return $query; } + + $junction = $this->junction(); + $target = $this->target(); + $belongsTo = $junction->association($target->alias()); + + $conditions = $belongsTo->_joinCondition([ + 'foreignKey' => $this->foreignKey()] + ); + $join = [ + 'table' => $junction->table(), + 'conditions' => $conditions, + 'type' => 'INNER' + ]; + $name = $this->_junctionAssociationName(); + $query->join([$name => $join]); + $query->eagerLoader()->addToJoinsMap($name, $belongsTo); return $query; } @@ -1062,7 +1080,8 @@ protected function _buildQuery($options) { $name = $this->_junctionAssociationName(); $query = $this->_buildBaseQuery($options); - $joins = $query->join() ?: []; + + $joins = $query->join(); $keys = $this->_linkField($options); $matching = [ From 29030ec575e67758b5dd82dfd5cb2a7e5ba51589 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Lorenzo=20Rodr=C3=ADguez?= Date: Sun, 6 Dec 2015 11:47:24 +0100 Subject: [PATCH 0546/2059] Fixed CS error --- Association/BelongsToMany.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index b018c66b..0610de13 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -799,8 +799,8 @@ public function find($type = null, array $options = []) $belongsTo = $junction->association($target->alias()); $conditions = $belongsTo->_joinCondition([ - 'foreignKey' => $this->foreignKey()] - ); + 'foreignKey' => $this->foreignKey() + ]); $join = [ 'table' => $junction->table(), 'conditions' => $conditions, From 91b4e3ce62c02002d852cf09cfa0c99d7a79cec7 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 6 Dec 2015 10:25:05 -0500 Subject: [PATCH 0547/2059] Refactor out common code. Create a helper method that appends the junction join to a query. --- Association/BelongsToMany.php | 56 +++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 0610de13..e3baa664 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -794,21 +794,37 @@ public function find($type = null, array $options = []) return $query; } - $junction = $this->junction(); - $target = $this->target(); - $belongsTo = $junction->association($target->alias()); - + $belongsTo = $this->junction()->association($this->target()->alias()); $conditions = $belongsTo->_joinCondition([ 'foreignKey' => $this->foreignKey() ]); - $join = [ - 'table' => $junction->table(), - 'conditions' => $conditions, - 'type' => 'INNER' - ]; + return $this->_appendJunctionJoin($query, $conditions); + } + + /** + * Append a join to the junction table. + * + * @param \Cake\ORM\Query $query The query to append. + * @param string|array $conditions The query conditions to use. + * @return \Cake\ORM\Query The modified query. + */ + protected function _appendJunctionJoin($query, $conditions) + { $name = $this->_junctionAssociationName(); - $query->join([$name => $join]); - $query->eagerLoader()->addToJoinsMap($name, $belongsTo); + $joins = $query->join(); + $matching = [ + $name => [ + 'table' => $this->junction()->table(), + 'conditions' => $conditions, + 'type' => 'INNER' + ] + ]; + + $assoc = $this->target()->association($name); + $query + ->addDefaultTypes($assoc->target()) + ->join($matching + $joins, [], true); + $query->eagerLoader()->addToJoinsMap($name, $assoc); return $query; } @@ -1081,25 +1097,13 @@ protected function _buildQuery($options) $name = $this->_junctionAssociationName(); $query = $this->_buildBaseQuery($options); - $joins = $query->join(); $keys = $this->_linkField($options); - - $matching = [ - $name => [ - 'table' => $this->junction()->table(), - 'conditions' => $keys, - 'type' => 'INNER' - ] - ]; - + $query = $this->_appendJunctionJoin($query, $keys); $assoc = $this->target()->association($name); - $query - ->addDefaultTypes($assoc->target()) - ->join($matching + $joins, [], true) - ->autoFields($query->clause('select') === []) + + $query->autoFields($query->clause('select') === []) ->select($query->aliasFields((array)$assoc->foreignKey(), $name)); - $query->eagerLoader()->addToJoinsMap($name, $assoc); $assoc->attachTo($query); return $query; } From 4509d962aab2ae4f7f309f469c9448a1dd07ee4a Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 6 Dec 2015 16:36:05 +0100 Subject: [PATCH 0548/2059] Using bindingKey in existsIn, fixes #7709 --- Rule/ExistsIn.php | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 940e3eea..72925546 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -66,10 +66,16 @@ public function __invoke(EntityInterface $entity, array $options) } $source = !empty($options['repository']) ? $options['repository'] : $this->_repository; + $source = $source instanceof Association ? $source->source() : $source; - $target = $this->_repository instanceof Association ? - $this->_repository->target() : - $this->_repository; + $target = $this->_repository; + + if ($target instanceof Association) { + $bindingKey = (array)$target->bindingKey(); + $target = $this->_repository->target(); + } else { + $bindingKey = (array)$target->primaryKey(); + } if (!empty($options['_sourceTable']) && $target === $options['_sourceTable']) { return true; @@ -92,7 +98,7 @@ public function __invoke(EntityInterface $entity, array $options) $primary = array_map( [$this->_repository, 'aliasField'], - (array)$this->_repository->primaryKey() + $bindingKey ); $conditions = array_combine( $primary, From d671c3223085a32a4fbbdc18b0ca65f45d1fd535 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 6 Dec 2015 17:07:35 +0100 Subject: [PATCH 0549/2059] Merging queryBuilders in contain. Fixes #6696 This makes it a lot easir to break queries into multiple custom finders where the same assocaition is used multiple times in contain. It also brings the orm closer to the behavior in 2.x where you could appen to a contain clause anywhere. --- EagerLoader.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/EagerLoader.php b/EagerLoader.php index c82b1ad3..13eb91e2 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -309,6 +309,15 @@ protected function _reformatContain($associations, $original) } $pointer += [$table => []]; + + if (isset($options['queryBuilder']) && isset($pointer[$table]['queryBuilder'])) { + $first = $pointer[$table]['queryBuilder']; + $second = $options['queryBuilder']; + $options['queryBuilder'] = function ($query) use ($first, $second) { + return $second($first($query)); + }; + } + $pointer[$table] = $options + $pointer[$table]; } From 8cdb669d6c2b89c4bc6a147bc4dc6b4c69847480 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 6 Dec 2015 20:52:14 +0100 Subject: [PATCH 0550/2059] Fixes #7591 --- Association.php | 2 -- EagerLoader.php | 24 ++++++++++++++++-------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/Association.php b/Association.php index c78f0563..6ce405b8 100644 --- a/Association.php +++ b/Association.php @@ -771,13 +771,11 @@ protected function _bindNewAssociations($query, $surrogate, $options) $loader = $surrogate->eagerLoader(); $contain = $loader->contain(); $matching = $loader->matching(); - $target = $this->_targetTable; if (!$contain && !$matching) { return; } - $loader->attachAssociations($query, $target, $options['includeFields']); $newContain = []; foreach ($contain as $alias => $value) { $newContain[$options['aliasPath'] . '.' . $alias] = $value; diff --git a/EagerLoader.php b/EagerLoader.php index c82b1ad3..c01eb4fa 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -334,14 +334,22 @@ public function attachAssociations(Query $query, Table $repository, $includeFiel return; } - foreach ($this->attachableAssociations($repository) as $loadable) { - $config = $loadable->config() + [ - 'aliasPath' => $loadable->aliasPath(), - 'propertyPath' => $loadable->propertyPath(), - 'includeFields' => $includeFields, - ]; - $loadable->instance()->attachTo($query, $config); - } + $attachable = $this->attachableAssociations($repository); + $processed = []; + do { + foreach ($attachable as $alias => $loadable) { + $config = $loadable->config() + [ + 'aliasPath' => $loadable->aliasPath(), + 'propertyPath' => $loadable->propertyPath(), + 'includeFields' => $includeFields, + ]; + $loadable->instance()->attachTo($query, $config); + $processed[$alias] = true; + } + + $newAttachable = $this->attachableAssociations($repository); + $attachable = array_diff_key($newAttachable, $processed); + } while ($attachable); } /** From 9a9cddbce428b54f43f720631a2b0388060c589d Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 6 Dec 2015 21:36:20 +0100 Subject: [PATCH 0551/2059] Fixed some issues found by scrutinizer --- Association/BelongsToMany.php | 2 +- EagerLoadable.php | 4 ++-- LazyEagerLoader.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index ceb1d5bc..b4bca8cf 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -434,7 +434,7 @@ public function cascadeDelete(EntityInterface $entity, array $options = []) $bindingKey = (array)$this->bindingKey(); $conditions = []; - if ($bindingKey) { + if (!empty($bindingKey)) { $conditions = array_combine($foreignKey, $entity->extract($bindingKey)); } diff --git a/EagerLoadable.php b/EagerLoadable.php index a775f00e..555804eb 100644 --- a/EagerLoadable.php +++ b/EagerLoadable.php @@ -176,7 +176,7 @@ public function propertyPath() * If called with no arguments it returns the current value. * * @param bool|null $possible The value to set. - * @return bool|null + * @return bool */ public function canBeJoined($possible = null) { @@ -194,7 +194,7 @@ public function canBeJoined($possible = null) * value. * * @param array|null $config The value to set. - * @return array|null + * @return array */ public function config(array $config = null) { diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index b546bc85..74d07891 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -126,7 +126,7 @@ protected function _getPropertyMap($source, $associations) * entities. * * @param array|\Traversable $objects The original list of entities - * @param \Cake\Collection\CollectionInterface $results The loaded results + * @param \Cake\Collection\CollectionInterface|\Cake\Database\Query $results The loaded results * @param array $associations The top level associations that were loaded * @param \Cake\ORM\Table $source The table where the entities came from * @return array From 952e252eace81d4e99ad3ea2c62a0af9cee2cc6a Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 6 Dec 2015 21:46:33 +0100 Subject: [PATCH 0552/2059] Fixing another small issue --- ResultSet.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ResultSet.php b/ResultSet.php index 7c734cc6..319f9b8a 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -110,7 +110,7 @@ class ResultSet implements ResultSetInterface /** * Results that have been fetched or hydrated into the results. * - * @var array + * @var array|\ArrayAccess */ protected $_results = []; From 29330530900b6af70af019aec8947d2019240c9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Lorenzo=20Rodr=C3=ADguez?= Date: Sun, 6 Dec 2015 22:30:55 +0100 Subject: [PATCH 0553/2059] Used empty() instead of implicit conversion --- EagerLoader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EagerLoader.php b/EagerLoader.php index c01eb4fa..49a3be35 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -349,7 +349,7 @@ public function attachAssociations(Query $query, Table $repository, $includeFiel $newAttachable = $this->attachableAssociations($repository); $attachable = array_diff_key($newAttachable, $processed); - } while ($attachable); + } while (!empty($attachable)); } /** From f6643412ab35c9c802d48eb74cff2c443e9c2fa3 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Tue, 8 Dec 2015 21:09:48 +0100 Subject: [PATCH 0554/2059] Fixing memory leak in ResultSet As a side effect of this fix, the $_query propery of ResultSet was removed. The query will not appear anymore as part of the __debugInfo() for a ResultSet anymore as a consequence of this. --- ResultSet.php | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/ResultSet.php b/ResultSet.php index 319f9b8a..f8243253 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -38,6 +38,7 @@ class ResultSet implements ResultSetInterface * Original query from where results were generated * * @var Query + * @deprecated 3.1.6 Due to a memory leak, this property cannot be used anymore */ protected $_query; @@ -169,16 +170,15 @@ class ResultSet implements ResultSetInterface public function __construct($query, $statement) { $repository = $query->repository(); - $this->_query = $query; $this->_statement = $statement; - $this->_driver = $this->_query->connection()->driver(); - $this->_defaultTable = $this->_query->repository(); - $this->_calculateAssociationMap(); - $this->_hydrate = $this->_query->hydrate(); + $this->_driver = $query->connection()->driver(); + $this->_defaultTable = $query->repository(); + $this->_calculateAssociationMap($query); + $this->_hydrate = $query->hydrate(); $this->_entityClass = $repository->entityClass(); $this->_useBuffering = $query->bufferResults(); $this->_defaultAlias = $this->_defaultTable->alias(); - $this->_calculateColumnMap(); + $this->_calculateColumnMap($query); $this->_calculateTypeMap(); if ($this->_useBuffering) { @@ -347,11 +347,12 @@ public function count() * Calculates the list of associations that should get eager loaded * when fetching each record * + * @param \Cake\ORM\Query $query The query from where to derive the associations * @return void */ - protected function _calculateAssociationMap() + protected function _calculateAssociationMap($query) { - $map = $this->_query->eagerLoader()->associationsMap($this->_defaultTable); + $map = $query->eagerLoader()->associationsMap($this->_defaultTable); $this->_matchingMap = (new Collection($map)) ->match(['matching' => true]) ->indexBy('alias') @@ -367,12 +368,13 @@ protected function _calculateAssociationMap() * Creates a map of row keys out of the query select clause that can be * used to hydrate nested result sets more quickly. * + * @param \Cake\ORM\Query $query The query from where to derive the column map * @return void */ - protected function _calculateColumnMap() + protected function _calculateColumnMap($query) { $map = []; - foreach ($this->_query->clause('select') as $key => $field) { + foreach ($query->clause('select') as $key => $field) { $key = trim($key, '"`[]'); if (strpos($key, '__') > 0) { $parts = explode('__', $key, 2); @@ -622,7 +624,6 @@ protected function _castValues($alias, $values) public function __debugInfo() { return [ - 'query' => $this->_query, 'items' => $this->toArray(), ]; } From 5e0fb2fabf94f3340fa8b9f920aeeb797813e796 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 11 Dec 2015 21:17:40 -0500 Subject: [PATCH 0555/2059] Add docs for HasMany saveStrategy. Refs cakephp/docs#3553 --- Table.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Table.php b/Table.php index 355990ce..919cb1d7 100644 --- a/Table.php +++ b/Table.php @@ -801,6 +801,10 @@ public function hasOne($associated, array $options = []) * When true records will be loaded and then deleted. * - conditions: array with a list of conditions to filter the join with * - sort: The order in which results for this association should be returned + * - saveStrategy: Either 'append' or 'replace'. When 'append' the current records + * are appended to any records in the database. When 'replace' associated records + * not in the current set will be removed. If the foreign key is a null able column + * or if `dependent` is true records will be orphaned. * - strategy: The strategy to be used for selecting results Either 'select' * or 'subquery'. If subquery is selected the query used to return results * in the source table will be used as conditions for getting rows in the From faf995e99477cf5d020d7b857a421cc3f68bb492 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 11 Dec 2015 21:17:40 -0500 Subject: [PATCH 0556/2059] Add docs for HasMany saveStrategy. Refs cakephp/docs#3553 --- Table.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Table.php b/Table.php index 355990ce..919cb1d7 100644 --- a/Table.php +++ b/Table.php @@ -801,6 +801,10 @@ public function hasOne($associated, array $options = []) * When true records will be loaded and then deleted. * - conditions: array with a list of conditions to filter the join with * - sort: The order in which results for this association should be returned + * - saveStrategy: Either 'append' or 'replace'. When 'append' the current records + * are appended to any records in the database. When 'replace' associated records + * not in the current set will be removed. If the foreign key is a null able column + * or if `dependent` is true records will be orphaned. * - strategy: The strategy to be used for selecting results Either 'select' * or 'subquery'. If subquery is selected the query used to return results * in the source table will be used as conditions for getting rows in the From a63806967f8d062ef5f1be2499a4404a25b6da06 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sat, 19 Dec 2015 22:22:55 +0100 Subject: [PATCH 0557/2059] Fixing pagination with complex queries having expressions in order by Closes #7872 --- Query.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Query.php b/Query.php index cceba5db..eb5f1966 100644 --- a/Query.php +++ b/Query.php @@ -723,6 +723,7 @@ protected function _performCount() count($query->clause('union')) || $query->clause('having') ); + if (!$complex) { // Expression fields could have bound parameters. foreach ($query->clause('select') as $field) { @@ -733,6 +734,11 @@ protected function _performCount() } } + if (!$complex && $this->_valueBinder !== null) { + $order = $this->clause('order'); + $complex = $order === null ? false : $order->hasNestedExpression(); + } + $count = ['count' => $query->func()->count('*')]; if (!$complex) { From e99c8928b47aeb3a1fcfe54cc1ddc81a64697e36 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 20 Dec 2015 13:23:41 +0100 Subject: [PATCH 0558/2059] [WIP] Fix SQLServer This adds some debugging statements for appveyor Avoiding duplicate external associations after fixes done in nested contains Adding a oauth token in appvoyor for faster downloads. Reverting debugging changes Trying again to get the oauth token right Removing the token, it did not work More debugging to fix sql server --- EagerLoader.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index 49a3be35..873307d2 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -136,7 +136,8 @@ public function contain($associations = []) $associations = (array)$associations; $associations = $this->_reformatContain($associations, $this->_containments); - $this->_normalized = $this->_loadExternal = null; + $this->_normalized = null; + $this->_loadExternal = []; $this->_aliasList = []; return $this->_containments = $associations; } @@ -152,7 +153,8 @@ public function contain($associations = []) public function clearContain() { $this->_containments = []; - $this->_normalized = $this->_loadExternal = null; + $this->_normalized = null; + $this->_loadExternal = []; $this->_aliasList = []; } @@ -366,6 +368,7 @@ public function attachableAssociations(Table $repository) $contain = $this->normalized($repository); $matching = $this->_matching ? $this->_matching->normalized($repository) : []; $this->_fixStrategies(); + $this->_loadExternal = []; return $this->_resolveJoins($contain, $matching); } From 41e0b5aad2c32e3561093711883209eed9ffa314 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 24 Dec 2015 23:00:02 -0500 Subject: [PATCH 0559/2059] Add useful error message when invalid associations are used. When an invalid association is used in an existsIn rule no fatal error should be raised. Refs #7901 --- Rule/ExistsIn.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 72925546..e036dbc3 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -16,6 +16,7 @@ use Cake\Datasource\EntityInterface; use Cake\ORM\Association; +use RuntimeException; /** * Checks that the value provided in a field exists as the primary key of another @@ -57,16 +58,24 @@ public function __construct($fields, $repository) * @param \Cake\Datasource\EntityInterface $entity The entity from where to extract the fields * @param array $options Options passed to the check, * where the `repository` key is required. + * @throws \RuntimeException When the rule refers to an undefined association. * @return bool */ public function __invoke(EntityInterface $entity, array $options) { if (is_string($this->_repository)) { - $this->_repository = $options['repository']->association($this->_repository); + $alias = $this->_repository; + $this->_repository = $options['repository']->association($alias); + + if (empty($this->_repository)) { + throw new RuntimeException(sprintf( + "ExistsIn rule for 'author_id' is invalid. The '%s' association is not defined.", + $alias + )); + } } $source = !empty($options['repository']) ? $options['repository'] : $this->_repository; - $source = $source instanceof Association ? $source->source() : $source; $target = $this->_repository; From 56fcee4dd3b980d5f29aa89bb2c19db436fc63c9 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 24 Dec 2015 23:08:11 -0500 Subject: [PATCH 0560/2059] Extract a method. The invoke method was getting quite long. Having a separate method for this chunk helps reveal the intent of the code more. --- Rule/ExistsIn.php | 45 ++++++++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index e036dbc3..353ddc9b 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -75,13 +75,16 @@ public function __invoke(EntityInterface $entity, array $options) } } - $source = !empty($options['repository']) ? $options['repository'] : $this->_repository; - $source = $source instanceof Association ? $source->source() : $source; - $target = $this->_repository; - + $source = $target = $this->_repository; + if (!empty($options['repository'])) { + $source = $options['repository']; + } + if ($source instanceof Association) { + $source = $source->source(); + } if ($target instanceof Association) { $bindingKey = (array)$target->bindingKey(); - $target = $this->_repository->target(); + $target = $target->target(); } else { $bindingKey = (array)$target->primaryKey(); } @@ -94,25 +97,37 @@ public function __invoke(EntityInterface $entity, array $options) return true; } - $nulls = 0; - $schema = $source->schema(); - foreach ($this->_fields as $field) { - if ($schema->column($field) && $schema->isNullable($field) && $entity->get($field) === null) { - $nulls++; - } - } - if ($nulls === count($this->_fields)) { + if ($this->_fieldsAreNull($entity, $source)) { return true; } $primary = array_map( - [$this->_repository, 'aliasField'], + [$target, 'aliasField'], $bindingKey ); $conditions = array_combine( $primary, $entity->extract($this->_fields) ); - return $this->_repository->exists($conditions); + return $target->exists($conditions); + } + + /** + * Check whether or not the entity fields are null and nullable. + * + * @param \Cake\ORM\EntityInterface $entity The entity to check. + * @param \Cake\ORM\Table $table The table to use schema from. + * @return bool + */ + protected function _fieldsAreNull($entity, $source) + { + $nulls = 0; + $schema = $source->schema(); + foreach ($this->_fields as $field) { + if ($schema->column($field) && $schema->isNullable($field) && $entity->get($field) === null) { + $nulls++; + } + } + return $nulls === count($this->_fields); } } From 7130238f25ba46c33ae6656ec63021385ffdc057 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 26 Dec 2015 14:58:16 -0500 Subject: [PATCH 0561/2059] Fix mistakes. Refs #7901 --- Rule/ExistsIn.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 353ddc9b..5b20f61b 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -69,7 +69,8 @@ public function __invoke(EntityInterface $entity, array $options) if (empty($this->_repository)) { throw new RuntimeException(sprintf( - "ExistsIn rule for 'author_id' is invalid. The '%s' association is not defined.", + "ExistsIn rule for '%s' is invalid. The '%s' association is not defined.", + implode(', ', $this->_fields), $alias )); } @@ -113,10 +114,10 @@ public function __invoke(EntityInterface $entity, array $options) } /** - * Check whether or not the entity fields are null and nullable. + * Check whether or not the entity fields nullable and null. * * @param \Cake\ORM\EntityInterface $entity The entity to check. - * @param \Cake\ORM\Table $table The table to use schema from. + * @param \Cake\ORM\Table $source The table to use schema from. * @return bool */ protected function _fieldsAreNull($entity, $source) From f25e0b811045b64657964314fb756adff8577fa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Lorenzo=20Rodr=C3=ADguez?= Date: Sat, 26 Dec 2015 22:29:50 +0100 Subject: [PATCH 0562/2059] Fixed doc block --- Rule/ExistsIn.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 5b20f61b..09789287 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -114,7 +114,7 @@ public function __invoke(EntityInterface $entity, array $options) } /** - * Check whether or not the entity fields nullable and null. + * Check whether or not the entity fields are nullable and null. * * @param \Cake\ORM\EntityInterface $entity The entity to check. * @param \Cake\ORM\Table $source The table to use schema from. From 89fba8d2ff2637a8bb4ca64daa88355282831738 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Wed, 30 Dec 2015 22:46:40 +0100 Subject: [PATCH 0563/2059] Avoiding forced all fields in a contained belongsToMany, fixes #7913 --- Association/BelongsToMany.php | 14 +++++++++++++- Association/SelectableAssociationTrait.php | 11 +---------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index e851c366..df4d37e4 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1095,11 +1095,23 @@ protected function _collectJointEntities($sourceEntity, $targetEntities) protected function _buildQuery($options) { $name = $this->_junctionAssociationName(); + $assoc = $this->target()->association($name); + $queryBuilder = false; + + if (!empty($options['queryBuilder'])) { + $queryBuilder = $options['queryBuilder']; + unset($options['queryBuilder']); + } + $query = $this->_buildBaseQuery($options); + $query->addDefaultTypes($assoc->target()); + + if ($queryBuilder) { + $query = $queryBuilder($query); + } $keys = $this->_linkField($options); $query = $this->_appendJunctionJoin($query, $keys); - $assoc = $this->target()->association($name); $query->autoFields($query->clause('select') === []) ->select($query->aliasFields((array)$assoc->foreignKey(), $name)); diff --git a/Association/SelectableAssociationTrait.php b/Association/SelectableAssociationTrait.php index b02291e2..56e2f942 100644 --- a/Association/SelectableAssociationTrait.php +++ b/Association/SelectableAssociationTrait.php @@ -43,16 +43,7 @@ public function requiresKeys(array $options = []) public function eagerLoader(array $options) { $options += $this->_defaultOptions(); - $queryBuilder = false; - if (!empty($options['queryBuilder'])) { - $queryBuilder = $options['queryBuilder']; - unset($options['queryBuilder']); - } - $fetchQuery = $this->_buildQuery($options); - if ($queryBuilder) { - $fetchQuery = $queryBuilder($fetchQuery); - } $resultMap = $this->_buildResultMap($fetchQuery, $options); return $this->_resultInjector($fetchQuery, $resultMap, $options); } @@ -123,7 +114,7 @@ protected function _buildQuery($options) } if (!empty($options['queryBuilder'])) { - $options['queryBuilder']($fetchQuery); + $fetchQuery = $options['queryBuilder']($fetchQuery); } return $fetchQuery; From 1a6528204def6fe1b23f1cd9e221d7a21f503ccd Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Thu, 31 Dec 2015 13:12:35 +0100 Subject: [PATCH 0564/2059] Trying to fix some tests in appveyor --- Association/BelongsToMany.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index df4d37e4..02e3c4d6 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -443,7 +443,7 @@ public function cascadeDelete(EntityInterface $entity, array $options = []) $table = $this->junction(); $hasMany = $this->source()->association($table->alias()); if ($this->_cascadeCallbacks) { - foreach ($hasMany->find('all')->where($conditions) as $related) { + foreach ($hasMany->find('all')->where($conditions)->toList() as $related) { $table->delete($related, $options); } return true; From 0a6beb30f678a91e0855633dce49d05f7ef738e8 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Thu, 31 Dec 2015 13:26:53 +0100 Subject: [PATCH 0565/2059] Repeating previous fix for HasMany associations. This makes appveyor happy --- Association/DependentDeleteTrait.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Association/DependentDeleteTrait.php b/Association/DependentDeleteTrait.php index b83cc2c3..ee68f2b2 100644 --- a/Association/DependentDeleteTrait.php +++ b/Association/DependentDeleteTrait.php @@ -44,8 +44,7 @@ public function cascadeDelete(EntityInterface $entity, array $options = []) $conditions = array_combine($foreignKey, $entity->extract($bindingKey)); if ($this->_cascadeCallbacks) { - $query = $this->find('all')->where($conditions); - foreach ($query as $related) { + foreach ($this->find()->where($conditions)->toList() as $related) { $table->delete($related, $options); } return true; From 50190c355d05c3b3df6504363e086132d86bb812 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Thu, 31 Dec 2015 14:57:38 +0100 Subject: [PATCH 0566/2059] Caching composer packages in appveyor Some debugging code for appveyor More debugging for appveyor Better cache folder for appveyor More debugging code for appveyor --- Query.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Query.php b/Query.php index eb5f1966..593f2619 100644 --- a/Query.php +++ b/Query.php @@ -723,11 +723,13 @@ protected function _performCount() count($query->clause('union')) || $query->clause('having') ); + debug($complex); if (!$complex) { // Expression fields could have bound parameters. foreach ($query->clause('select') as $field) { if ($field instanceof ExpressionInterface) { + debug($field); $complex = true; break; } @@ -737,6 +739,7 @@ protected function _performCount() if (!$complex && $this->_valueBinder !== null) { $order = $this->clause('order'); $complex = $order === null ? false : $order->hasNestedExpression(); + debug($complex); } $count = ['count' => $query->func()->count('*')]; From 8dcfb47f723347305fb4e3316edb26853ab7bece Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Thu, 31 Dec 2015 16:16:12 +0100 Subject: [PATCH 0567/2059] Trying to figure out what sql server is doing --- Query.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Query.php b/Query.php index 593f2619..383f4368 100644 --- a/Query.php +++ b/Query.php @@ -723,13 +723,11 @@ protected function _performCount() count($query->clause('union')) || $query->clause('having') ); - debug($complex); if (!$complex) { // Expression fields could have bound parameters. foreach ($query->clause('select') as $field) { if ($field instanceof ExpressionInterface) { - debug($field); $complex = true; break; } @@ -739,7 +737,7 @@ protected function _performCount() if (!$complex && $this->_valueBinder !== null) { $order = $this->clause('order'); $complex = $order === null ? false : $order->hasNestedExpression(); - debug($complex); + var_dump($order); } $count = ['count' => $query->func()->count('*')]; From 2d16c3f0c93aef4bf73a8fd33898f7b2dac93987 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Thu, 31 Dec 2015 16:25:31 +0100 Subject: [PATCH 0568/2059] Skipping test in SQLServer while we find an appropriate fix This test is at least an obscure feature of the ORM, and fixing it properly requires a fair bit of changes. Skipping for now --- Query.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Query.php b/Query.php index 383f4368..eb5f1966 100644 --- a/Query.php +++ b/Query.php @@ -737,7 +737,6 @@ protected function _performCount() if (!$complex && $this->_valueBinder !== null) { $order = $this->clause('order'); $complex = $order === null ? false : $order->hasNestedExpression(); - var_dump($order); } $count = ['count' => $query->func()->count('*')]; From b2c2c673fb972f24bc0c36dd0398391574554d97 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Thu, 31 Dec 2015 18:37:35 +0100 Subject: [PATCH 0569/2059] Fixed some issues reported by scrutinizer --- Query.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Query.php b/Query.php index eb5f1966..3ab40ff7 100644 --- a/Query.php +++ b/Query.php @@ -303,11 +303,11 @@ public function contain($associations = null, $override = false) $this->_dirty(); } - $result = $loader->contain($associations); if ($associations === null) { - return $result; + return $loader->contain(); } + $result = $loader->contain($associations); $this->_addAssociationsToTypeMap($this->repository(), $this->typeMap(), $result); return $this; } @@ -324,7 +324,6 @@ public function contain($associations = null, $override = false) */ protected function _addAssociationsToTypeMap($table, $typeMap, $associations) { - $typeMap = $this->typeMap(); foreach ($associations as $name => $nested) { $association = $table->association($name); if (!$association) { @@ -771,7 +770,10 @@ protected function _performCount() * The callback will receive as first argument a clone of this query and not this * query itself. * - * @param callable $counter The counter value + * If the first param is a null value, the built-in counter function will be called + * instead + * + * @param callable|null $counter The counter value * @return $this */ public function counter($counter) From b9baa7db9257160dccdd2df9cfe9e2445c9e52ff Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Thu, 31 Dec 2015 18:43:55 +0100 Subject: [PATCH 0570/2059] Fixing a couple other issues --- Association/SelectableAssociationTrait.php | 3 ++- Table.php | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Association/SelectableAssociationTrait.php b/Association/SelectableAssociationTrait.php index 56e2f942..5a94e698 100644 --- a/Association/SelectableAssociationTrait.php +++ b/Association/SelectableAssociationTrait.php @@ -295,7 +295,8 @@ protected function _resultInjector($fetchQuery, $resultMap, $options) $sourceKeys = []; foreach ((array)$keys as $key) { - $sourceKeys[] = key($fetchQuery->aliasField($key, $sAlias)); + $f = $fetchQuery->aliasField($key, $sAlias); + $sourceKeys[] = key($f); } $nestKey = $options['nestKey']; diff --git a/Table.php b/Table.php index 919cb1d7..25fbad4f 100644 --- a/Table.php +++ b/Table.php @@ -1814,6 +1814,7 @@ protected function _dynamicFinder($method, $args) ); } + $conditions = []; if ($hasOr === false && $hasAnd === false) { $conditions = $makeConditions([$fields], $args); } elseif ($hasOr !== false) { From 549547b17c69e27710660f68182885aca9114ecf Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sat, 2 Jan 2016 13:20:45 +0100 Subject: [PATCH 0571/2059] Added `@method` annotations for the collection methods in Query --- Query.php | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/Query.php b/Query.php index b482801f..2d8ce08c 100644 --- a/Query.php +++ b/Query.php @@ -15,15 +15,17 @@ namespace Cake\ORM; use ArrayObject; +use Cake\Collection\CollectionInterface; use Cake\Database\ExpressionInterface; use Cake\Database\Query as DatabaseQuery; -use Cake\Database\TypedResultInterface; use Cake\Database\TypeMap; +use Cake\Database\TypedResultInterface; use Cake\Database\ValueBinder; use Cake\Datasource\QueryInterface; use Cake\Datasource\QueryTrait; use JsonSerializable; use RuntimeException; +use Traversable; /** * Extends the base Query class to provide new methods related to association @@ -31,6 +33,40 @@ * into a specific iterator that will be responsible for hydrating results if * required. * + * @see CollectionInterface For a full description of the collection methods supported by this class + * @method CollectionInterface each(callable $c) Passes each of the query results to the callable + * @method CollectionInterface filter(callable $c = null) Keeps the results using passing the callable test + * @method CollectionInterface reject(callable $c) Removes the results passing the callable test + * @method bool every(callable $c) Returns true if all the results pass the callable test + * @method bool some(callable $c) Returns true if at least one of the results pass the callable test + * @method CollectionInterface map(callable $c) Modifies each of the results using the callable + * @method mixed reduce(callable $c, $zero = null) Folds all the results into a single value using the callable. + * @method CollectionInterface extract($field) Extracts a single column from each row + * @method mixed max($field, $type = SORT_NUMERIC) Returns the maximum value for a single column in all the results. + * @method mixed min($field, $type = SORT_NUMERIC) Returns the minimum value for a single column in all the results. + * @method CollectionInterface groupBy(string|callable $field) In-memory group all results by the value of a column. + * @method CollectionInterface indexBy(string|callable $field) Returns the results indexed by the value of a column. + * @method int countBy(string|callable $field) Returns the number of unique values for a column + * @method float sumOf(string|callable $field) Returns the sum of all values for a single column + * @method CollectionInterface shuffle() In-memory randomize he order the results are returned + * @method CollectionInterface sample($size = 10) In-memory shuffle the results and return a subset of them. + * @method CollectionInterface take($size = 1, $from = 0) In-memory limit and offset for the query results. + * @method CollectionInterface skip(int $howMany) Skips some rows from the start of the query result. + * @method mixed last() Return the last row of the query result + * @method CollectionInterface append(array|Traversable $items) Appends more rows to the result of the query. + * @method CollectionInterface combine($k, $v, $g = null) Returns the values of the column $v index by column $k, + * and grouped by $g. + * @method CollectionInterface nest($k, $p) Creates a tree structure by nesting the values of column $p into that + * with the same value for $k. + * @method array toArray() Returns a key-value array with the results of this query. + * @method array toList() Returns a numerically indexed array with the results of this query. + * @method CollectionInterface stopWhen(callable $c) Returns each row until the callable returns true. + * @method CollectionInterface zip(array|Traversable $c) Returns the first result of both the query and $c in an array, + * then the second results and so on. + * @method CollectionInterface zipWith(...$collections, callable $c) Returns each of the results out of calling $c + * with the first rows of the query and each of the items, then the second rows and so on. + * @method CollectionInterface chunk($size) Groups the results in arrays of $size rows each. + * @method bool isEmpty($size) Returns true if this query found no results. */ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface { From 0c03385437255d56bc947a542ea2ebe379edc2d5 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sat, 2 Jan 2016 14:42:24 +0100 Subject: [PATCH 0572/2059] Fomratting, fixing typos and adding another annotation --- Query.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Query.php b/Query.php index 2d8ce08c..6b56b850 100644 --- a/Query.php +++ b/Query.php @@ -18,8 +18,8 @@ use Cake\Collection\CollectionInterface; use Cake\Database\ExpressionInterface; use Cake\Database\Query as DatabaseQuery; -use Cake\Database\TypeMap; use Cake\Database\TypedResultInterface; +use Cake\Database\TypeMap; use Cake\Database\ValueBinder; use Cake\Datasource\QueryInterface; use Cake\Datasource\QueryTrait; @@ -48,25 +48,26 @@ * @method CollectionInterface indexBy(string|callable $field) Returns the results indexed by the value of a column. * @method int countBy(string|callable $field) Returns the number of unique values for a column * @method float sumOf(string|callable $field) Returns the sum of all values for a single column - * @method CollectionInterface shuffle() In-memory randomize he order the results are returned + * @method CollectionInterface shuffle() In-memory randomize the order the results are returned * @method CollectionInterface sample($size = 10) In-memory shuffle the results and return a subset of them. * @method CollectionInterface take($size = 1, $from = 0) In-memory limit and offset for the query results. * @method CollectionInterface skip(int $howMany) Skips some rows from the start of the query result. * @method mixed last() Return the last row of the query result * @method CollectionInterface append(array|Traversable $items) Appends more rows to the result of the query. * @method CollectionInterface combine($k, $v, $g = null) Returns the values of the column $v index by column $k, - * and grouped by $g. + * and grouped by $g. * @method CollectionInterface nest($k, $p) Creates a tree structure by nesting the values of column $p into that - * with the same value for $k. + * with the same value for $k. * @method array toArray() Returns a key-value array with the results of this query. * @method array toList() Returns a numerically indexed array with the results of this query. * @method CollectionInterface stopWhen(callable $c) Returns each row until the callable returns true. * @method CollectionInterface zip(array|Traversable $c) Returns the first result of both the query and $c in an array, - * then the second results and so on. + * then the second results and so on. * @method CollectionInterface zipWith(...$collections, callable $c) Returns each of the results out of calling $c - * with the first rows of the query and each of the items, then the second rows and so on. + * with the first rows of the query and each of the items, then the second rows and so on. * @method CollectionInterface chunk($size) Groups the results in arrays of $size rows each. * @method bool isEmpty($size) Returns true if this query found no results. + * @method $this find(string $type, array $options) Compose this query with another finder from the same table. */ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface { From d1fcbff65efde20fb8f437e2bc5fa0caaa0a9010 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Lorenzo=20Rodr=C3=ADguez?= Date: Sat, 2 Jan 2016 20:27:30 +0100 Subject: [PATCH 0573/2059] Removing unneeded annotation --- Query.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Query.php b/Query.php index 6b56b850..de22ba4b 100644 --- a/Query.php +++ b/Query.php @@ -67,7 +67,6 @@ * with the first rows of the query and each of the items, then the second rows and so on. * @method CollectionInterface chunk($size) Groups the results in arrays of $size rows each. * @method bool isEmpty($size) Returns true if this query found no results. - * @method $this find(string $type, array $options) Compose this query with another finder from the same table. */ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface { From 5a48ccfb8a4a9d29a67d86a2c64b10e1bf1aee14 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Sat, 26 Dec 2015 16:50:53 +0100 Subject: [PATCH 0574/2059] Allow invalid field data to be kept along with the error messages in the Entity. --- Marshaller.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Marshaller.php b/Marshaller.php index db7a079c..e9dbd959 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -128,6 +128,9 @@ public function one(array $data, array $options = []) $properties = []; foreach ($data as $key => $value) { if (!empty($errors[$key])) { + if (method_exists($entity, 'invalid')) { + $entity->invalid($key, $value); + } continue; } $columnType = $schema->columnType($key); @@ -463,6 +466,9 @@ public function merge(EntityInterface $entity, array $data, array $options = []) $properties = $marshalledAssocs = []; foreach ($data as $key => $value) { if (!empty($errors[$key])) { + if (method_exists($entity, 'invalid')) { + $entity->invalid($key, $value); + } continue; } From fcfaa713513b633ff39e9f4f305578ad57290dc9 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Sun, 3 Jan 2016 20:45:18 +0100 Subject: [PATCH 0575/2059] Rebase into 3.2 and set interface. --- Entity.php | 3 ++- Marshaller.php | 4 +--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Entity.php b/Entity.php index 93e4b2c3..58be2b33 100644 --- a/Entity.php +++ b/Entity.php @@ -15,13 +15,14 @@ namespace Cake\ORM; use Cake\Datasource\EntityInterface; +use Cake\Datasource\InvalidPropertyInterface; use Cake\Datasource\EntityTrait; /** * An entity represents a single result row from a repository. It exposes the * methods for retrieving and storing properties associated in this row. */ -class Entity implements EntityInterface +class Entity implements EntityInterface, InvalidPropertyInterface { use EntityTrait; diff --git a/Marshaller.php b/Marshaller.php index e9dbd959..56ccb1ec 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -128,9 +128,7 @@ public function one(array $data, array $options = []) $properties = []; foreach ($data as $key => $value) { if (!empty($errors[$key])) { - if (method_exists($entity, 'invalid')) { - $entity->invalid($key, $value); - } + $entity->invalid($key, $value); continue; } $columnType = $schema->columnType($key); From b2bd127fb95f7d955aa1972ee5d907850fa4ddb6 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Sun, 3 Jan 2016 22:39:12 +0100 Subject: [PATCH 0576/2059] Fix up tests. --- Entity.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Entity.php b/Entity.php index 58be2b33..c8248c87 100644 --- a/Entity.php +++ b/Entity.php @@ -15,8 +15,8 @@ namespace Cake\ORM; use Cake\Datasource\EntityInterface; -use Cake\Datasource\InvalidPropertyInterface; use Cake\Datasource\EntityTrait; +use Cake\Datasource\InvalidPropertyInterface; /** * An entity represents a single result row from a repository. It exposes the @@ -24,7 +24,6 @@ */ class Entity implements EntityInterface, InvalidPropertyInterface { - use EntityTrait; /** From d96adbb3bb36bd26b26d931bfecfc634d1da3f8c Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Mon, 4 Jan 2016 10:32:04 +0100 Subject: [PATCH 0577/2059] Add interface checks --- Marshaller.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index 56ccb1ec..cbbd8207 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -19,6 +19,7 @@ use Cake\Database\Expression\TupleComparison; use Cake\Database\Type; use Cake\Datasource\EntityInterface; +use Cake\Datasource\InvalidPropertyInterface; use RuntimeException; /** @@ -128,7 +129,9 @@ public function one(array $data, array $options = []) $properties = []; foreach ($data as $key => $value) { if (!empty($errors[$key])) { - $entity->invalid($key, $value); + if ($entity instanceof InvalidPropertyInterface) { + $entity->invalid($key, $value); + } continue; } $columnType = $schema->columnType($key); From 6215e3d5487300affd947353852e778e91ae3bdd Mon Sep 17 00:00:00 2001 From: mscherer Date: Tue, 5 Jan 2016 11:28:34 +0100 Subject: [PATCH 0578/2059] Add missing interface check --- Marshaller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index cbbd8207..eed5e210 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -467,7 +467,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) $properties = $marshalledAssocs = []; foreach ($data as $key => $value) { if (!empty($errors[$key])) { - if (method_exists($entity, 'invalid')) { + if ($entity instanceof InvalidPropertyInterface) { $entity->invalid($key, $value); } continue; From a7c36fae1315a63b179b207ced4f071caff30b3c Mon Sep 17 00:00:00 2001 From: Mark Story Date: Wed, 6 Jan 2016 22:27:54 -0500 Subject: [PATCH 0579/2059] Fix BelongsToMany saving incorrectly when _joinData is used. When the `_joinData` has not been fully marshalled, BelongsToMany associations will not save the joinData correctly. This can cause data to go 'missing' when associations with conditions are involved. Refs #7808 --- Association/BelongsToMany.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 02e3c4d6..b56a6bc5 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -622,11 +622,13 @@ protected function _saveLinks(EntityInterface $sourceEntity, $targetEntities, $o $junctionAlias = $junction->alias(); foreach ($targetEntities as $e) { - $joint = $e->get($jointProperty); + $joint = $jointVal = $e->get($jointProperty); if (!$joint || !($joint instanceof EntityInterface)) { $joint = new $entityClass([], ['markNew' => true, 'source' => $junctionAlias]); + if (is_array($jointVal)) { + $joint->set($jointVal); + } } - $sourceKeys = array_combine($foreignKey, $sourceEntity->extract($bindingKey)); $targetKeys = array_combine($assocForeignKey, $e->extract($targetPrimaryKey)); From bca774e9503cac30dcd81724fdf6d65b06510e30 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Wed, 6 Jan 2016 22:57:06 -0500 Subject: [PATCH 0580/2059] Merge BelongsToMany associations more consistently. The issue in #7808 was a marshaller issue not an association saving issue. By fixing the marshaller the association save does not have to change. Refs #7808 --- Association/BelongsToMany.php | 5 +---- Marshaller.php | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index b56a6bc5..6154a302 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -622,12 +622,9 @@ protected function _saveLinks(EntityInterface $sourceEntity, $targetEntities, $o $junctionAlias = $junction->alias(); foreach ($targetEntities as $e) { - $joint = $jointVal = $e->get($jointProperty); + $joint = $e->get($jointProperty); if (!$joint || !($joint instanceof EntityInterface)) { $joint = new $entityClass([], ['markNew' => true, 'source' => $junctionAlias]); - if (is_array($jointVal)) { - $joint->set($jointVal); - } } $sourceKeys = array_combine($foreignKey, $sourceEntity->extract($bindingKey)); $targetKeys = array_combine($assocForeignKey, $e->extract($targetPrimaryKey)); diff --git a/Marshaller.php b/Marshaller.php index db7a079c..3efa2bdc 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -657,7 +657,7 @@ protected function _mergeBelongsToMany($original, $assoc, $value, $options) return []; } - if (!in_array('_joinData', $associated) && !isset($associated['_joinData'])) { + if (!empty($associated) && !in_array('_joinData', $associated) && !isset($associated['_joinData'])) { return $this->mergeMany($original, $value, $options); } From 9de6c7ccac24fae4f1a94b4dc503e24d47796015 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 10 Jan 2016 08:58:41 -0500 Subject: [PATCH 0581/2059] Fix query->matching() failing on belongstomany with conditions. BelongsToMany associations with conditions failed when included in query->matching() because invalid SQL was emitted. When a belongstomany association contains conditions on the junction table, those conditions are emitted in the join to the target table creating SQL like ``` SELECT * from articles INNER JOIN tags ON (tags.name = 'php' and articles_tags.starred = 1) INNER JOIN articles_tags ON (tags.id = articles_tags.id and articles.id = articles_tags.article_id) ``` This query will fail as the articles_tags.starred condition references a table that has not been joined on to the query. These changes split the conditions into target & junction conditions and apply those conditions to the appropriate joins. Remove some dead code as the linkField result was not doing anything useful here. Refs #7994 --- Association/BelongsToMany.php | 62 +++++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 7 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 6154a302..e449be80 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -312,9 +312,11 @@ protected function _generateJunctionAssociations($junction, $source, $target) public function attachTo(Query $query, array $options = []) { parent::attachTo($query, $options); + $junction = $this->junction(); $belongsTo = $junction->association($this->source()->alias()); $cond = $belongsTo->_joinCondition(['foreignKey' => $belongsTo->foreignKey()]); + $cond += $this->junctionConditions(); if (isset($options['includeFields'])) { $includeFields = $options['includeFields']; @@ -324,8 +326,9 @@ public function attachTo(Query $query, array $options = []) $query->removeJoin($assoc->name()); unset($options['queryBuilder']); + // TODO clean up the definition of $options $type = array_intersect_key($options, ['joinType' => 1, 'fields' => 1]); - $options = ['conditions' => [$cond]] + compact('includeFields'); + $options = ['conditions' => $cond] + compact('includeFields'); $options['foreignKey'] = $this->targetForeignKey(); $assoc->attachTo($query, $options + $type); $query->eagerLoader()->addToJoinsMap($junction->alias(), $assoc, true); @@ -772,6 +775,32 @@ function () use ($sourceEntity, $targetEntities, $options) { $sourceEntity->dirty($property, false); } + protected function targetConditions() + { + $alias = $this->alias() . '.'; + $matching = []; + // TODO add handling for strings, expression objects. + foreach ($this->conditions() as $field => $value) { + if (strpos($field, $alias) === 0) { + $matching[$field] = $value; + } + } + return $matching; + } + + protected function junctionConditions() + { + $alias = $this->_junctionAssociationName() . '.'; + $matching = []; + foreach ($this->conditions() as $field => $value) { + // TODO add handling for strings, expression objects. + if (strpos($field, $alias) === 0) { + $matching[$field] = $value; + } + } + return $matching; + } + /** * Proxies the finding operation to the target table's find method * and modifies the query accordingly based of this association @@ -788,8 +817,24 @@ function () use ($sourceEntity, $targetEntities, $options) { */ public function find($type = null, array $options = []) { - $query = parent::find($type, $options); - if (!$this->conditions()) { + // The parent method applies the conditions. + // The application of the conditions causes issues as often the conditions + // reference the junction table. + // Ideally we'd get a query like: + // + // select * from articles + // inner join tags on (matching cond AND relvant assoc cond) + // inner join articles_tags on (tags.id = a_t.tag_id and articles.id = a_t.article_id and relevant assoc cond) + // + // Overriding conditions() and adding junctionConditions() might help here. Or filtering + // the conditions when defining the junction association. + $type = $type ?: $this->finder(); + list($type, $opts) = $this->_extractFinder($type); + $query = $this->target() + ->find($type, $options + $opts) + ->where($this->targetConditions()); + + if (!$this->junctionConditions()) { return $query; } @@ -797,6 +842,7 @@ public function find($type = null, array $options = []) $conditions = $belongsTo->_joinCondition([ 'foreignKey' => $this->foreignKey() ]); + $conditions += $this->junctionConditions(); return $this->_appendJunctionJoin($query, $conditions); } @@ -1109,10 +1155,12 @@ protected function _buildQuery($options) $query = $queryBuilder($query); } - $keys = $this->_linkField($options); - $query = $this->_appendJunctionJoin($query, $keys); - - $query->autoFields($query->clause('select') === []) + $query = $this->_appendJunctionJoin($query, []); + // Ensure that association conditions are applied + // and that the required keys are in the selected columns. + $query + ->where($this->junctionConditions()) + ->autoFields($query->clause('select') === []) ->select($query->aliasFields((array)$assoc->foreignKey(), $name)); $assoc->attachTo($query); From 6df45cd4c46099a51016dcd345828670fee41230 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 10 Jan 2016 16:25:34 -0500 Subject: [PATCH 0582/2059] Ensure that reciprocal associations have conditions. Without conditions the reciprocal belongs to many association will find different records than the original association definition. Refs #7994 --- Association/BelongsToMany.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index e449be80..6c2324f0 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -227,7 +227,8 @@ protected function _generateTargetAssociations($junction, $source, $target) 'targetTable' => $source, 'foreignKey' => $this->targetForeignKey(), 'targetForeignKey' => $this->foreignKey(), - 'through' => $junction + 'through' => $junction, + 'conditions' => $this->conditions(), ]); } } @@ -322,6 +323,8 @@ public function attachTo(Query $query, array $options = []) $includeFields = $options['includeFields']; } + // TODO see if this can be removed and replaced with eagerly splitting + // up conditions when defining associations. $assoc = $this->_targetTable->association($junction->alias()); $query->removeJoin($assoc->name()); From 0b987706f67a1f9f47a85a3bc75df00b7c64c17a Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 10 Jan 2016 17:48:39 -0500 Subject: [PATCH 0583/2059] Clean up condition filtering. Cache the two kinds of conditions so we can avoid recomputing them on each query. Refs #7994 --- Association/BelongsToMany.php | 105 ++++++++++++++++++++++++---------- 1 file changed, 75 insertions(+), 30 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 6c2324f0..26178ca5 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -14,6 +14,7 @@ */ namespace Cake\ORM\Association; +use Cake\Database\ExpressionInterface; use Cake\Datasource\EntityInterface; use Cake\ORM\Association; use Cake\ORM\Query; @@ -134,6 +135,20 @@ class BelongsToMany extends Association */ protected $_dependent = true; + /** + * Filtered conditions that reference the target table. + * + * @var null|array + */ + protected $_targetConditions; + + /** + * Filtered conditions that reference the junction table. + * + * @var null|array + */ + protected $_junctionConditions; + /** * Sets the name of the field representing the foreign key to the target table. * If no parameters are passed current field is returned @@ -323,17 +338,16 @@ public function attachTo(Query $query, array $options = []) $includeFields = $options['includeFields']; } - // TODO see if this can be removed and replaced with eagerly splitting - // up conditions when defining associations. + // Attach the junction table as well we need it to populate _joinData. $assoc = $this->_targetTable->association($junction->alias()); $query->removeJoin($assoc->name()); - - unset($options['queryBuilder']); - // TODO clean up the definition of $options - $type = array_intersect_key($options, ['joinType' => 1, 'fields' => 1]); - $options = ['conditions' => $cond] + compact('includeFields'); - $options['foreignKey'] = $this->targetForeignKey(); - $assoc->attachTo($query, $options + $type); + $options = array_intersect_key($options, ['joinType' => 1, 'fields' => 1]); + $options += [ + 'conditions' => $cond, + 'includeFields' => $includeFields, + 'foreignKey' => $this->targetForeignKey(), + ]; + $assoc->attachTo($query, $options); $query->eagerLoader()->addToJoinsMap($junction->alias(), $assoc, true); } @@ -778,30 +792,72 @@ function () use ($sourceEntity, $targetEntities, $options) { $sourceEntity->dirty($property, false); } + /** + * {@inheritDoc} + */ + public function conditions($conditions = null) + { + if ($conditions !== null) { + $this->_conditions = $conditions; + $this->_targetConditions = $this->_junctionConditions = []; + } + return $this->_conditions; + } + + /** + * Returns filtered conditions that reference the target table. + * + * Any string expressions, or expression objects will + * also be returned in this list. + * + * @return mixed Generally an array. If the conditions + * are not an array, the association conditions will be + * returned unmodified. + */ protected function targetConditions() { - $alias = $this->alias() . '.'; + if ($this->_targetConditions !== null) { + return $this->_targetConditions; + } + $conditions = $this->conditions(); + if (!is_array($conditions)) { + return $conditions; + } $matching = []; - // TODO add handling for strings, expression objects. - foreach ($this->conditions() as $field => $value) { - if (strpos($field, $alias) === 0) { + $alias = $this->alias() . '.'; + foreach ($conditions as $field => $value) { + if (is_string($field) && strpos($field, $alias) === 0) { + $matching[$field] = $value; + } elseif (is_int($field) || $value instanceof ExpressionInterface) { $matching[$field] = $value; } } - return $matching; + return $this->_targetConditions = $matching; } + /** + * Returns filtered conditions that specifically reference + * the junction table. + * + * @return array + */ protected function junctionConditions() { - $alias = $this->_junctionAssociationName() . '.'; + if ($this->_junctionConditions !== null) { + return $this->_junctionConditions; + } $matching = []; - foreach ($this->conditions() as $field => $value) { - // TODO add handling for strings, expression objects. - if (strpos($field, $alias) === 0) { + $conditions = $this->conditions(); + if (!is_array($conditions)) { + return $matching; + } + $alias = $this->_junctionAssociationName() . '.'; + foreach ($conditions as $field => $value) { + if (is_string($field) && strpos($field, $alias) === 0) { $matching[$field] = $value; } } - return $matching; + return $this->_junctionConditions = $matching; } /** @@ -820,17 +876,6 @@ protected function junctionConditions() */ public function find($type = null, array $options = []) { - // The parent method applies the conditions. - // The application of the conditions causes issues as often the conditions - // reference the junction table. - // Ideally we'd get a query like: - // - // select * from articles - // inner join tags on (matching cond AND relvant assoc cond) - // inner join articles_tags on (tags.id = a_t.tag_id and articles.id = a_t.article_id and relevant assoc cond) - // - // Overriding conditions() and adding junctionConditions() might help here. Or filtering - // the conditions when defining the junction association. $type = $type ?: $this->finder(); list($type, $opts) = $this->_extractFinder($type); $query = $this->target() From fc565dfefe3ec7a7190a353ac808996865e245d3 Mon Sep 17 00:00:00 2001 From: Yevgeny Tomenko Date: Wed, 13 Jan 2016 01:11:00 +0300 Subject: [PATCH 0584/2059] Implemented left and right fields quoting in generated plain text queries. --- Behavior/TreeBehavior.php | 50 ++++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index d50298ea..8b52feae 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -20,6 +20,7 @@ use Cake\ORM\Behavior; use Cake\ORM\Entity; use Cake\ORM\Query; +use Cake\ORM\Table; use InvalidArgumentException; use RuntimeException; @@ -75,6 +76,23 @@ class TreeBehavior extends Behavior 'recoverOrder' => null ]; + /** + * {@inheritDoc} + */ + public function __construct(Table $table, array $config = []) + { + parent::__construct($table, $config); + $connection = $table->connection(); + if (!is_null($connection)) { + $driver = $connection->driver(); + $this->config('leftField', $driver->quoteIdentifier($this->config('left'))); + $this->config('rightField', $driver->quoteIdentifier($this->config('right'))); + } else { + $this->config('leftField', $this->config('left')); + $this->config('rightField', $this->config('right')); + } + } + /** * Before save listener. * Transparently manages setting the lft and rght fields if the parent field is @@ -212,8 +230,8 @@ public function beforeDelete(Event $event, EntityInterface $entity) if ($diff > 2) { $this->_table->deleteAll([ - "{$config['left']} >=" => $left + 1, - "{$config['left']} <=" => $right - 1 + "{$config['leftField']} >=" => $left + 1, + "{$config['leftField']} <=" => $right - 1 ]); } @@ -328,9 +346,9 @@ protected function _unmarkInternalTree() $config = $this->config(); $query = $this->_table->query(); $this->_table->updateAll([ - $query->newExpr()->add("{$config['left']} = {$config['left']} * -1"), - $query->newExpr()->add("{$config['right']} = {$config['right']} * -1"), - ], [$config['left'] . ' <' => 0]); + $query->newExpr()->add("{$config['leftField']} = {$config['leftField']} * -1"), + $query->newExpr()->add("{$config['rightField']} = {$config['rightField']} * -1"), + ], [$config['leftField'] . ' <' => 0]); } /** @@ -590,15 +608,15 @@ public function moveUp(EntityInterface $node, $number = 1) protected function _moveUp($node, $number) { $config = $this->config(); - list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']]; + list($parent, $left, $right, $leftField, $rightField) = [$config['parent'], $config['left'], $config['right'], $config['leftField'], $config['rightField']]; list($nodeParent, $nodeLeft, $nodeRight) = array_values($node->extract([$parent, $left, $right])); $targetNode = null; if ($number !== true) { $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) - ->where(["$parent IS" => $nodeParent, "$right <" => $nodeLeft]) - ->order([$left => 'DESC']) + ->where(["$parent IS" => $nodeParent, "$rightField <" => $nodeLeft]) + ->order([$leftField => 'DESC']) ->offset($number - 1) ->limit(1) ->first(); @@ -606,8 +624,8 @@ protected function _moveUp($node, $number) if (!$targetNode) { $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) - ->where(["$parent IS" => $nodeParent, "$right <" => $nodeLeft]) - ->order([$left => 'ASC']) + ->where(["$parent IS" => $nodeParent, "$rightField <" => $nodeLeft]) + ->order([$leftField => 'ASC']) ->limit(1) ->first(); @@ -671,7 +689,7 @@ public function moveDown(EntityInterface $node, $number = 1) protected function _moveDown($node, $number) { $config = $this->config(); - list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']]; + list($parent, $left, $right, $leftField, $rightField) = [$config['parent'], $config['left'], $config['right'], $config['leftField'], $config['rightField']]; list($nodeParent, $nodeLeft, $nodeRight) = array_values($node->extract([$parent, $left, $right])); @@ -679,8 +697,8 @@ protected function _moveDown($node, $number) if ($number !== true) { $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) - ->where(["$parent IS" => $nodeParent, "$left >" => $nodeRight]) - ->order([$left => 'ASC']) + ->where(["$parent IS" => $nodeParent, "$leftField >" => $nodeRight]) + ->order([$leftField => 'ASC']) ->offset($number - 1) ->limit(1) ->first(); @@ -688,8 +706,8 @@ protected function _moveDown($node, $number) if (!$targetNode) { $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) - ->where(["$parent IS" => $nodeParent, "$left >" => $nodeRight]) - ->order([$left => 'DESC']) + ->where(["$parent IS" => $nodeParent, "$leftField >" => $nodeRight]) + ->order([$leftField => 'DESC']) ->limit(1) ->first(); @@ -845,7 +863,7 @@ protected function _sync($shift, $dir, $conditions, $mark = false) { $config = $this->config(); - foreach ([$config['left'], $config['right']] as $field) { + foreach ([$config['leftField'], $config['rightField']] as $field) { $query = $this->_scope($this->_table->query()); $mark = $mark ? '*-1' : ''; From 983a7f937a4fc7d9f906e70b6191a2bb5ebcbb7a Mon Sep 17 00:00:00 2001 From: Yevgeny Tomenko Date: Wed, 13 Jan 2016 01:36:27 +0300 Subject: [PATCH 0585/2059] Change is null check. --- Behavior/TreeBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 8b52feae..c654464b 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -83,7 +83,7 @@ public function __construct(Table $table, array $config = []) { parent::__construct($table, $config); $connection = $table->connection(); - if (!is_null($connection)) { + if ($connection !== null) { $driver = $connection->driver(); $this->config('leftField', $driver->quoteIdentifier($this->config('left'))); $this->config('rightField', $driver->quoteIdentifier($this->config('right'))); From 2deefc4c03c274b3a6562bb50a53378874deb6d5 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Wed, 13 Jan 2016 22:30:52 +0100 Subject: [PATCH 0586/2059] Adding callable support for Query::set() Using IndentifierExpression in TreeBehavior instead of pre-quoting --- Behavior/TreeBehavior.php | 105 +++++++++++++++++++++++--------------- 1 file changed, 64 insertions(+), 41 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index c654464b..4bb0a8c8 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -14,6 +14,7 @@ */ namespace Cake\ORM\Behavior; +use Cake\Database\Expression\IdentifierExpression; use Cake\Datasource\EntityInterface; use Cake\Datasource\Exception\RecordNotFoundException; use Cake\Event\Event; @@ -79,18 +80,10 @@ class TreeBehavior extends Behavior /** * {@inheritDoc} */ - public function __construct(Table $table, array $config = []) + public function initialize(array $config) { - parent::__construct($table, $config); - $connection = $table->connection(); - if ($connection !== null) { - $driver = $connection->driver(); - $this->config('leftField', $driver->quoteIdentifier($this->config('left'))); - $this->config('rightField', $driver->quoteIdentifier($this->config('right'))); - } else { - $this->config('leftField', $this->config('left')); - $this->config('rightField', $this->config('right')); - } + $this->_config['leftField'] = new IdentifierExpression($this->_config['left']); + $this->_config['rightField'] = new IdentifierExpression($this->_config['right']); } /** @@ -229,10 +222,11 @@ public function beforeDelete(Event $event, EntityInterface $entity) $diff = $right - $left + 1; if ($diff > 2) { - $this->_table->deleteAll([ - "{$config['leftField']} >=" => $left + 1, - "{$config['leftField']} <=" => $right - 1 - ]); + $this->_table->deleteAll(function ($exp) use ($config, $left, $right) { + return $exp + ->gte($config['leftField'], $left + 1) + ->lte($config['leftField'], $right - 1); + }); } $this->_sync($diff, '-', "> {$right}"); @@ -344,11 +338,19 @@ protected function _setAsRoot($entity) protected function _unmarkInternalTree() { $config = $this->config(); - $query = $this->_table->query(); - $this->_table->updateAll([ - $query->newExpr()->add("{$config['leftField']} = {$config['leftField']} * -1"), - $query->newExpr()->add("{$config['rightField']} = {$config['rightField']} * -1"), - ], [$config['leftField'] . ' <' => 0]); + $this->_table->updateAll( + function ($exp) use ($config) { + $leftInverse = clone $exp; + $leftInverse->add('-1')->type('*'); + $rightInverse = clone $leftInverse; + return $exp + ->eq($config['leftField'], $leftInverse->add($config['leftField'])) + ->eq($config['rightField'], $rightInverse->add($config['rightField'])); + }, + function ($exp) use ($config) { + return $exp->lt($config['leftField'], 0); + } + ); } /** @@ -608,15 +610,18 @@ public function moveUp(EntityInterface $node, $number = 1) protected function _moveUp($node, $number) { $config = $this->config(); - list($parent, $left, $right, $leftField, $rightField) = [$config['parent'], $config['left'], $config['right'], $config['leftField'], $config['rightField']]; + list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']]; list($nodeParent, $nodeLeft, $nodeRight) = array_values($node->extract([$parent, $left, $right])); $targetNode = null; if ($number !== true) { $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) - ->where(["$parent IS" => $nodeParent, "$rightField <" => $nodeLeft]) - ->order([$leftField => 'DESC']) + ->where(["$parent IS" => $nodeParent]) + ->where(function ($exp) use ($config, $nodeLeft) { + return $exp->lt($config['rightField'], $nodeLeft); + }) + ->orderDesc($config['leftField']) ->offset($number - 1) ->limit(1) ->first(); @@ -624,8 +629,11 @@ protected function _moveUp($node, $number) if (!$targetNode) { $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) - ->where(["$parent IS" => $nodeParent, "$rightField <" => $nodeLeft]) - ->order([$leftField => 'ASC']) + ->where(["$parent IS" => $nodeParent]) + ->where(function ($exp) use ($config, $nodeLeft) { + return $exp->lt($config['rightField'], $nodeLeft); + }) + ->orderAsc($config['leftField']) ->limit(1) ->first(); @@ -689,16 +697,18 @@ public function moveDown(EntityInterface $node, $number = 1) protected function _moveDown($node, $number) { $config = $this->config(); - list($parent, $left, $right, $leftField, $rightField) = [$config['parent'], $config['left'], $config['right'], $config['leftField'], $config['rightField']]; - + list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']]; list($nodeParent, $nodeLeft, $nodeRight) = array_values($node->extract([$parent, $left, $right])); $targetNode = null; if ($number !== true) { $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) - ->where(["$parent IS" => $nodeParent, "$leftField >" => $nodeRight]) - ->order([$leftField => 'ASC']) + ->where(["$parent IS" => $nodeParent]) + ->where(function ($exp) use ($config, $nodeRight) { + return $exp->gt($config['leftField'], $nodeRight); + }) + ->orderAsc($config['leftField']) ->offset($number - 1) ->limit(1) ->first(); @@ -706,8 +716,11 @@ protected function _moveDown($node, $number) if (!$targetNode) { $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) - ->where(["$parent IS" => $nodeParent, "$leftField >" => $nodeRight]) - ->order([$leftField => 'DESC']) + ->where(["$parent IS" => $nodeParent]) + ->where(function ($exp) use ($config, $nodeRight) { + return $exp->gt($config['leftField'], $nodeRight); + }) + ->orderDesc($config['leftField']) ->limit(1) ->first(); @@ -832,12 +845,11 @@ protected function _recoverTree($counter = 0, $parentId = null, $level = -1) */ protected function _getMax() { - $config = $this->config(); - $field = $config['right']; - + $field = $this->_config['right']; + $leftField = $this->_config['leftField']; $edge = $this->_scope($this->_table->find()) ->select([$field]) - ->order([$field => 'DESC']) + ->orderDesc($leftField) ->first(); if (empty($edge->{$field})) { @@ -861,17 +873,28 @@ protected function _getMax() */ protected function _sync($shift, $dir, $conditions, $mark = false) { - $config = $this->config(); + $config = $this->_config; foreach ([$config['leftField'], $config['rightField']] as $field) { $query = $this->_scope($this->_table->query()); + $exp = $query->newExpr(); + + $movement = clone $exp; + $movement ->add($field)->add("$shift")->type($dir); + + $inverse = clone $exp; + $movement = $mark ? + $inverse->add($movement)->add('-1')->type('*') : + $movement; + + $where = clone $exp; + $where->add($field)->add($conditions)->type(''); - $mark = $mark ? '*-1' : ''; - $template = sprintf('%s = (%s %s %s)%s', $field, $field, $dir, $shift, $mark); - $query->update()->set($query->newExpr()->add($template)); - $query->where("{$field} {$conditions}"); + $query->update() + ->set($exp->eq($field, $movement)) + ->where($where); - $query->execute(); + $query->execute()->closeCursor(); } } From 63e0aece6f9425fbe5f3572338d0f0ade1d5c022 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Wed, 13 Jan 2016 22:52:43 +0100 Subject: [PATCH 0587/2059] Improving code readability --- Behavior/TreeBehavior.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 4bb0a8c8..137ce6d5 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -341,7 +341,7 @@ protected function _unmarkInternalTree() $this->_table->updateAll( function ($exp) use ($config) { $leftInverse = clone $exp; - $leftInverse->add('-1')->type('*'); + $leftInverse->type('*')->add('-1'); $rightInverse = clone $leftInverse; return $exp ->eq($config['leftField'], $leftInverse->add($config['leftField'])) @@ -884,7 +884,7 @@ protected function _sync($shift, $dir, $conditions, $mark = false) $inverse = clone $exp; $movement = $mark ? - $inverse->add($movement)->add('-1')->type('*') : + $inverse->add($movement)->type('*')->add('-1'): $movement; $where = clone $exp; From a6fecaf7f72e6a5a9e09983c805bf9f0f5a784b8 Mon Sep 17 00:00:00 2001 From: Tzaoh Date: Sun, 17 Jan 2016 18:52:23 +0100 Subject: [PATCH 0588/2059] Update Marshaller.php --- Marshaller.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index 3efa2bdc..c774fd49 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -124,6 +124,11 @@ public function one(array $data, array $options = []) } } + $marshallOptions = []; + if (isset($options['forceTargetSave'])) { + $marshallOptions['forceTargetSave'] = $options['forceTargetSave']; + } + $errors = $this->_validate($data, $options, true); $properties = []; foreach ($data as $key => $value) { @@ -133,7 +138,7 @@ public function one(array $data, array $options = []) $columnType = $schema->columnType($key); if (isset($propertyMap[$key])) { $assoc = $propertyMap[$key]['association']; - $value = $this->_marshalAssociation($assoc, $value, $propertyMap[$key]); + $value = $this->_marshalAssociation($assoc, $value, $propertyMap[$key] + $marshallOptions); } elseif ($value === '' && in_array($key, $primaryKey, true)) { // Skip marshalling '' for pk fields. continue; @@ -289,6 +294,7 @@ public function many(array $data, array $options = []) protected function _belongsToMany(Association $assoc, array $data, $options = []) { $associated = isset($options['associated']) ? $options['associated'] : []; + $forceTargetSave = isset($options['forceTargetSave']) ? $options['forceTargetSave'] : false; $data = array_values($data); @@ -306,6 +312,9 @@ protected function _belongsToMany(Association $assoc, array $data, $options = [] if (count($keys) === $primaryCount) { foreach ($keys as $key => $value) { $conditions[][$target->aliasfield($key)] = $value; + } + if ($forceTargetSave && !$target->exists($conditions)) { + $records[$i] = $this->one($row, $options); } } } else { From e25dd1e7b280287cabd7ec260dd686b15a240195 Mon Sep 17 00:00:00 2001 From: Tzaoh Date: Sun, 17 Jan 2016 19:03:05 +0100 Subject: [PATCH 0589/2059] Update Marshaller.php Tab fixing --- Marshaller.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index c774fd49..074158ec 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -124,7 +124,7 @@ public function one(array $data, array $options = []) } } - $marshallOptions = []; + $marshallOptions = []; if (isset($options['forceTargetSave'])) { $marshallOptions['forceTargetSave'] = $options['forceTargetSave']; } @@ -313,7 +313,7 @@ protected function _belongsToMany(Association $assoc, array $data, $options = [] foreach ($keys as $key => $value) { $conditions[][$target->aliasfield($key)] = $value; } - if ($forceTargetSave && !$target->exists($conditions)) { + if ($forceTargetSave && !$target->exists($conditions)) { $records[$i] = $this->one($row, $options); } } From 18f09bb6ab30c0ad83a69d281936b49cf7ac0b30 Mon Sep 17 00:00:00 2001 From: Tzaoh Date: Sun, 17 Jan 2016 19:07:45 +0100 Subject: [PATCH 0590/2059] Update Marshaller.php Another one --- Marshaller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index 074158ec..f1845be9 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -294,7 +294,7 @@ public function many(array $data, array $options = []) protected function _belongsToMany(Association $assoc, array $data, $options = []) { $associated = isset($options['associated']) ? $options['associated'] : []; - $forceTargetSave = isset($options['forceTargetSave']) ? $options['forceTargetSave'] : false; + $forceTargetSave = isset($options['forceTargetSave']) ? $options['forceTargetSave'] : false; $data = array_values($data); From 5fceda6a0137070cacb70727da7ea70ed11c44eb Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 17 Jan 2016 18:17:40 -0500 Subject: [PATCH 0591/2059] Fix wonky formatting. --- Behavior/TranslateBehavior.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 85376358..c28ee667 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -234,10 +234,10 @@ public function beforeFind(Event $event, Query $query, $options) $name = $alias . '_' . $field . '_translation'; $contain[$name]['queryBuilder'] = $conditions( - $field, - $locale, - $query, - $select + $field, + $locale, + $query, + $select ); if ($changeFilter) { From 57576c448d2cda07da216ca20ce71428a7f7c200 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Mon, 18 Jan 2016 16:31:58 -0430 Subject: [PATCH 0592/2059] Reducing code nesting and removing unneeded property --- Entity.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Entity.php b/Entity.php index 93e4b2c3..f57073e2 100644 --- a/Entity.php +++ b/Entity.php @@ -54,7 +54,6 @@ public function __construct(array $properties = [], array $options = []) 'guard' => false, 'source' => null ]; - $this->_className = get_class($this); if (!empty($options['source'])) { $this->source($options['source']); From d6500001c3e4692957af941ac8254811d323167c Mon Sep 17 00:00:00 2001 From: antograssiot Date: Wed, 20 Jan 2016 03:15:14 +0100 Subject: [PATCH 0593/2059] add FCN in the docblock --- Association/HasMany.php | 8 ++++---- Behavior/CounterCacheBehavior.php | 2 +- EagerLoader.php | 4 ++-- Marshaller.php | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index 913d0c9b..3c42d29c 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -384,8 +384,8 @@ public function replace(EntityInterface $sourceEntity, array $targetEntities, ar * Skips deleting records present in $remainingEntities * * @param array $properties array of foreignKey properties - * @param EntityInterface $entity the entity which should have its associated entities unassigned - * @param Table $target The associated table + * @param \Cake\Datasource\EntityInterface $entity the entity which should have its associated entities unassigned + * @param \Cake\ORM\Table $target The associated table * @param array $remainingEntities Entities that should not be deleted * @param array $options list of options accepted by `Table::delete()` * @return bool success @@ -425,7 +425,7 @@ function ($v) { * The action which is taken depends on the dependency between source and targets and also on foreign key nullability * * @param array $foreignKey array of foreign key properties - * @param Table $target The associated table + * @param \Cake\ORM\Table $target The associated table * @param array $conditions The conditions that specifies what are the objects to be unlinked * @param array $options list of options accepted by `Table::delete()` * @return bool success @@ -457,7 +457,7 @@ protected function _unlink(array $foreignKey, Table $target, array $conditions = /** * Checks the nullable flag of the foreign key * - * @param Table $table the table containing the foreign key + * @param \Cake\ORM\Table $table the table containing the foreign key * @param array $properties the list of fields that compose the foreign key * @return bool */ diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index e969355d..79d02a80 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -128,7 +128,7 @@ protected function _processAssociations(Event $event, EntityInterface $entity) * * @param \Cake\Event\Event $event Event instance. * @param \Cake\Datasource\EntityInterface $entity Entity - * @param Association $assoc The association object + * @param \Cake\ORM\Association $assoc The association object * @param array $settings The settings for for counter cache for this association * @return void */ diff --git a/EagerLoader.php b/EagerLoader.php index 873307d2..52670ccf 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -394,7 +394,7 @@ public function externalAssociations(Table $repository) * Auxiliary function responsible for fully normalizing deep associations defined * using `contain()` * - * @param Table $parent owning side of the association + * @param \Cake\ORM\Table $parent owning side of the association * @param string $alias name of the association to be loaded * @param array $options list of extra options to use for this association * @param array $paths An array with two values, the first one is a list of dot @@ -656,7 +656,7 @@ public function addToJoinsMap($alias, Association $assoc, $asMatching = false) * * @param array $external the list of external associations to be loaded * @param \Cake\ORM\Query $query The query from which the results where generated - * @param BufferedStatement $statement The statement to work on + * @param \Cake\Database\Statement\BufferedStatement $statement The statement to work on * @return array */ protected function _collectKeys($external, $query, $statement) diff --git a/Marshaller.php b/Marshaller.php index 3efa2bdc..c9a2881a 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -281,7 +281,7 @@ public function many(array $data, array $options = []) * Builds the related entities and handles the special casing * for junction table entities. * - * @param Association $assoc The association to marshal. + * @param \Cake\ORM\Association $assoc The association to marshal. * @param array $data The data to convert into entities. * @param array $options List of options. * @return array An array of built entities. @@ -362,7 +362,7 @@ protected function _belongsToMany(Association $assoc, array $data, $options = [] /** * Loads a list of belongs to many from ids. * - * @param Association $assoc The association class for the belongsToMany association. + * @param \Cake\ORM\Association $assoc The association class for the belongsToMany association. * @param array $ids The list of ids to load. * @return array An array of entities. */ @@ -394,7 +394,7 @@ protected function _loadAssociatedByIds($assoc, $ids) /** * Loads a list of belongs to many from ids. * - * @param Association $assoc The association class for the belongsToMany association. + * @param \Cake\ORM\Association $assoc The association class for the belongsToMany association. * @param array $ids The list of ids to load. * @return array An array of entities. * @deprecated Use _loadAssociatedByIds() From ddf75e33125c76e4cf903f110643d68240abba3b Mon Sep 17 00:00:00 2001 From: tzaoh Date: Wed, 20 Jan 2016 23:41:47 +0100 Subject: [PATCH 0594/2059] Change parameter name to "forceTargetNew" --- Marshaller.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index f1845be9..2ec4e809 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -125,8 +125,8 @@ public function one(array $data, array $options = []) } $marshallOptions = []; - if (isset($options['forceTargetSave'])) { - $marshallOptions['forceTargetSave'] = $options['forceTargetSave']; + if (isset($options['forceTargetNew'])) { + $marshallOptions['forceTargetNew'] = $options['forceTargetNew']; } $errors = $this->_validate($data, $options, true); @@ -294,7 +294,7 @@ public function many(array $data, array $options = []) protected function _belongsToMany(Association $assoc, array $data, $options = []) { $associated = isset($options['associated']) ? $options['associated'] : []; - $forceTargetSave = isset($options['forceTargetSave']) ? $options['forceTargetSave'] : false; + $forceTargetNew = isset($options['forceTargetNew']) ? $options['forceTargetNew'] : false; $data = array_values($data); @@ -313,7 +313,7 @@ protected function _belongsToMany(Association $assoc, array $data, $options = [] foreach ($keys as $key => $value) { $conditions[][$target->aliasfield($key)] = $value; } - if ($forceTargetSave && !$target->exists($conditions)) { + if ($forceTargetNew && !$target->exists($conditions)) { $records[$i] = $this->one($row, $options); } } From a9ce3d330a7aa23068b84f5e2a911806739cfe8e Mon Sep 17 00:00:00 2001 From: tzaoh Date: Fri, 22 Jan 2016 22:51:48 +0100 Subject: [PATCH 0595/2059] Rename forceNew parameter + test Rename forceTargetNew parameter, and added testcase for its functionality --- Marshaller.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 2ec4e809..d03ce56d 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -125,8 +125,8 @@ public function one(array $data, array $options = []) } $marshallOptions = []; - if (isset($options['forceTargetNew'])) { - $marshallOptions['forceTargetNew'] = $options['forceTargetNew']; + if (isset($options['forceNew'])) { + $marshallOptions['forceNew'] = $options['forceNew']; } $errors = $this->_validate($data, $options, true); @@ -294,7 +294,7 @@ public function many(array $data, array $options = []) protected function _belongsToMany(Association $assoc, array $data, $options = []) { $associated = isset($options['associated']) ? $options['associated'] : []; - $forceTargetNew = isset($options['forceTargetNew']) ? $options['forceTargetNew'] : false; + $forceNew = isset($options['forceNew']) ? $options['forceNew'] : false; $data = array_values($data); @@ -302,6 +302,7 @@ protected function _belongsToMany(Association $assoc, array $data, $options = [] $primaryKey = array_flip($target->schema()->primaryKey()); $records = $conditions = []; $primaryCount = count($primaryKey); + $conditions = []; foreach ($data as $i => $row) { if (!is_array($row)) { @@ -310,12 +311,16 @@ protected function _belongsToMany(Association $assoc, array $data, $options = [] if (array_intersect_key($primaryKey, $row) === $primaryKey) { $keys = array_intersect_key($row, $primaryKey); if (count($keys) === $primaryCount) { + $rowConditions = []; foreach ($keys as $key => $value) { - $conditions[][$target->aliasfield($key)] = $value; + $rowConditions[][$target->aliasfield($key)] = $value; } - if ($forceTargetNew && !$target->exists($conditions)) { + $asd = $target->exists($rowConditions); + if ($forceNew && !$asd) { $records[$i] = $this->one($row, $options); } + + $conditions = array_merge($conditions, $rowConditions); } } else { $records[$i] = $this->one($row, $options); From 43465b2ec273f5e1c23a364e4b5cc5a39f7f50bc Mon Sep 17 00:00:00 2001 From: tzaoh Date: Fri, 22 Jan 2016 22:53:47 +0100 Subject: [PATCH 0596/2059] tidying --- Marshaller.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index d03ce56d..b61edf05 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -315,8 +315,8 @@ protected function _belongsToMany(Association $assoc, array $data, $options = [] foreach ($keys as $key => $value) { $rowConditions[][$target->aliasfield($key)] = $value; } - $asd = $target->exists($rowConditions); - if ($forceNew && !$asd) { + + if ($forceNew && !$target->exists($rowConditions)) { $records[$i] = $this->one($row, $options); } From b02882263439bc4241f3cf1b7979f3676491fdc1 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 23 Jan 2016 21:03:21 -0500 Subject: [PATCH 0597/2059] Merge data into existing belongsToMany associations. When creating a new entity that is linked to existing belongsToMany records, we should merge request data into the existing entities. Failing to do so makes it hard to create a new record, link it to an existing record and update the existing record and its associations at the same time. Refs #8082 --- Marshaller.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index c9a2881a..19d10072 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -318,9 +318,7 @@ protected function _belongsToMany(Association $assoc, array $data, $options = [] $query->andWhere(function ($exp) use ($conditions) { return $exp->or_($conditions); }); - } - if (isset($query)) { $keyFields = array_keys($primaryKey); $existing = []; @@ -337,8 +335,10 @@ protected function _belongsToMany(Association $assoc, array $data, $options = [] } } $key = implode(';', $key); + + // Update existing record and child associations if (isset($existing[$key])) { - $records[$i] = $existing[$key]; + $records[$i] = $this->merge($existing[$key], $data[$i], $options); } } } @@ -351,6 +351,7 @@ protected function _belongsToMany(Association $assoc, array $data, $options = [] } foreach ($records as $i => $record) { + // Update junction table data in _joinData. if (isset($data[$i]['_joinData'])) { $joinData = $jointMarshaller->one($data[$i]['_joinData'], $nested); $record->set('_joinData', $joinData); From de7da73a8b312c08ec58bac02d7e7531a6cb5da9 Mon Sep 17 00:00:00 2001 From: mark_story Date: Mon, 25 Jan 2016 12:23:47 -0500 Subject: [PATCH 0598/2059] Only update autoFields() if it is undefined. If a query builder has enabled/disabled autoFields() we should preserve the developer's choice. Refs #8052 --- Association/BelongsToMany.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 26178ca5..4feeb8b0 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1204,13 +1204,18 @@ protected function _buildQuery($options) } $query = $this->_appendJunctionJoin($query, []); + + if ($query->autoFields() === null) { + $query->autoFields($query->clause('select') === []); + } + // Ensure that association conditions are applied // and that the required keys are in the selected columns. $query ->where($this->junctionConditions()) - ->autoFields($query->clause('select') === []) ->select($query->aliasFields((array)$assoc->foreignKey(), $name)); + $assoc->attachTo($query); return $query; } From 1c210a8164fdd839ca590daf2545788b3b90be38 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 25 Jan 2016 21:42:35 -0500 Subject: [PATCH 0599/2059] Remove blank line. --- Association/BelongsToMany.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 4feeb8b0..cb5e2cac 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1215,7 +1215,6 @@ protected function _buildQuery($options) ->where($this->junctionConditions()) ->select($query->aliasFields((array)$assoc->foreignKey(), $name)); - $assoc->attachTo($query); return $query; } From 3f3dfe0dfb03caa90d403efd4c600272db7a7f56 Mon Sep 17 00:00:00 2001 From: mscherer Date: Thu, 28 Jan 2016 19:08:39 +0100 Subject: [PATCH 0600/2059] Declare splits as stable. --- composer.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 54a99515..2fad694e 100644 --- a/composer.json +++ b/composer.json @@ -24,6 +24,5 @@ }, "suggest": { "cakephp/i18n": "~3.0" - }, - "minimum-stability": "dev" + } } From 2b0accd5a66abb45213af6e42b2df8bceb50c658 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 31 Jan 2016 23:17:22 -0500 Subject: [PATCH 0601/2059] Fix matchingData not having type information set. As part of the refactoring work done to make typecasting lazy and work with functions the casting around `_matchingData` was accidentally broken. This adds each attached association to the type map when fields are selected on a given association. Refs #8147 --- Association.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Association.php b/Association.php index 6ce405b8..9af0e07e 100644 --- a/Association.php +++ b/Association.php @@ -699,14 +699,14 @@ protected function _dispatchBeforeFind($query) */ protected function _appendFields($query, $surrogate, $options) { - $fields = $surrogate->clause('select') ?: $options['fields']; - $target = $this->_targetTable; - $autoFields = $surrogate->autoFields(); - if ($query->eagerLoader()->autoFields() === false) { return; } + $fields = $surrogate->clause('select') ?: $options['fields']; + $target = $this->_targetTable; + $autoFields = $surrogate->autoFields(); + if (empty($fields) && !$autoFields) { if ($options['includeFields'] && ($fields === null || $fields !== false)) { $fields = $target->schema()->columns(); @@ -719,6 +719,7 @@ protected function _appendFields($query, $surrogate, $options) if (!empty($fields)) { $query->select($query->aliasFields($fields, $target->alias())); + $query->addDefaultTypes($target); } } From 81424f4489da0da0e9ddfc488a1e1b69d466055b Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 1 Feb 2016 22:09:06 -0500 Subject: [PATCH 0602/2059] Preserve Translation data in result formatter. When results are iterated in a result formatter, the _translations property would get wiped as the groupTranslations formatter would be invoked each time the results were iterated. Refs #8121 --- Behavior/TranslateBehavior.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index c28ee667..0c7ce98a 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -457,6 +457,9 @@ public function groupTranslations($results) return $row; } $translations = (array)$row->get('_i18n'); + if (empty($translations) && $row->get('_translations')) { + return $row; + } $grouped = new Collection($translations); $result = []; From 1a48417f009fa07a31334785e0723a26652d6393 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Tue, 2 Feb 2016 21:47:39 +0100 Subject: [PATCH 0603/2059] Make all doc block classes FQCN as per CS. --- Association.php | 2 +- Association/BelongsToMany.php | 2 +- AssociationCollection.php | 2 +- BehaviorRegistry.php | 2 +- EagerLoader.php | 2 +- LazyEagerLoader.php | 1 - Locator/TableLocator.php | 2 +- Query.php | 2 +- ResultSet.php | 2 +- 9 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Association.php b/Association.php index 6ce405b8..65579069 100644 --- a/Association.php +++ b/Association.php @@ -504,7 +504,7 @@ protected function _options(array $options) * - negateMatch: Will append a condition to the passed query for excluding matches. * with this association. * - * @param Query $query the query to be altered to include the target table data + * @param \Cake\ORM\Query $query the query to be altered to include the target table data * @param array $options Any extra options or overrides to be taken in account * @return void * @throws \RuntimeException if the query builder passed does not return a query diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index cb5e2cac..a59127ab 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -321,7 +321,7 @@ protected function _generateJunctionAssociations($junction, $source, $target) * - fields: a list of fields in the target table to include in the result * - type: The type of join to be used (e.g. INNER) * - * @param Query $query the query to be altered to include the target table data + * @param \Cake\ORM\Query $query the query to be altered to include the target table data * @param array $options Any extra options or overrides to be taken in account * @return void */ diff --git a/AssociationCollection.php b/AssociationCollection.php index e57a7bb2..41bad45a 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -298,7 +298,7 @@ public function normalizeKeys($keys) /** * Allow looping through the associations * - * @return ArrayIterator + * @return \ArrayIterator */ public function getIterator() { diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 086d1ab5..ebe5c7a4 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -122,7 +122,7 @@ protected function _throwMissingClassError($class, $plugin) * @param string $class The classname that is missing. * @param string $alias The alias of the object. * @param array $config An array of config to use for the behavior. - * @return Behavior The constructed behavior class. + * @return \Cake\ORM\Behavior The constructed behavior class. */ protected function _create($class, $alias, $config) { diff --git a/EagerLoader.php b/EagerLoader.php index 356a2e13..2ae80dc0 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -551,7 +551,7 @@ protected function _resolveJoins($associations, $matching = []) * @param \Cake\ORM\Query $query The query for which to eager load external * associations * @param \Cake\Database\StatementInterface $statement The statement created after executing the $query - * @return CallbackStatement statement modified statement with extra loaders + * @return \Cake\Database\Statement\CallbackStatement statement modified statement with extra loaders */ public function loadExternal($query, $statement) { diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 74d07891..a6de5d1f 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -93,7 +93,6 @@ protected function _getQuery($objects, $contain, $source) }) ->contain($contain); - foreach ($query->eagerLoader()->attachableAssociations($source) as $loadable) { $config = $loadable->config(); $config['includeFields'] = true; diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 9d09e89b..0fe21229 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -69,7 +69,7 @@ class TableLocator implements LocatorInterface * @param string|null $alias Name of the alias * @param array|null $options list of options for the alias * @return array The config data. - * @throws RuntimeException When you attempt to configure an existing table instance. + * @throws \RuntimeException When you attempt to configure an existing table instance. */ public function config($alias = null, $options = null) { diff --git a/Query.php b/Query.php index de22ba4b..c1f3c9ee 100644 --- a/Query.php +++ b/Query.php @@ -177,7 +177,7 @@ public function __construct($connection, $table) * all the fields in the schema of the table or the association will be added to * the select clause. * - * @param array|ExpressionInterface|string|\Cake\ORM\Table|\Cake\ORM\Association $fields fields + * @param array|\Cake\Database\ExpressionInterface|string|\Cake\ORM\Table|\Cake\ORM\Association $fields fields * to be added to the list. * @param bool $overwrite whether to reset fields with passed list or not */ diff --git a/ResultSet.php b/ResultSet.php index a3192c32..c806cc48 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -37,7 +37,7 @@ class ResultSet implements ResultSetInterface /** * Original query from where results were generated * - * @var Query + * @var \Cake\ORM\Query * @deprecated 3.1.6 Due to a memory leak, this property cannot be used anymore */ protected $_query; From bc7456aa9c59316194ad439791c4d721a62333d1 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 2 Feb 2016 21:27:10 -0500 Subject: [PATCH 0604/2059] Always map association fields. This ensures that association fields are in the typemap to cover cases where fields are used in conditions for the matching() clause. Refs #8147 --- Association.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association.php b/Association.php index 9af0e07e..e81b6a61 100644 --- a/Association.php +++ b/Association.php @@ -719,8 +719,8 @@ protected function _appendFields($query, $surrogate, $options) if (!empty($fields)) { $query->select($query->aliasFields($fields, $target->alias())); - $query->addDefaultTypes($target); } + $query->addDefaultTypes($target); } /** From 503d91f41dcc1ef34dd321b12ed435f66527c19f Mon Sep 17 00:00:00 2001 From: saeideng Date: Sun, 7 Feb 2016 00:34:49 +0330 Subject: [PATCH 0605/2059] Update HasMany.php --- Association/HasMany.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index 3c42d29c..b3e36b62 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -198,7 +198,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) * * ``` * $user = $users->get(1); - * $allArticles = $articles->find('all')->execute(); + * $allArticles = $articles->find('all')->toArray(); * $users->Articles->link($user, $allArticles); * ``` * From ec174aea7cd26502e2dfbec51ce274821a966ba5 Mon Sep 17 00:00:00 2001 From: saeideng Date: Sun, 7 Feb 2016 10:20:30 +0330 Subject: [PATCH 0606/2059] docs block : execute() to toArray() --- Association/BelongsToMany.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index a59127ab..89002702 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -684,7 +684,7 @@ protected function _saveLinks(EntityInterface $sourceEntity, $targetEntities, $o * ### Example: * * ``` - * $newTags = $tags->find('relevant')->execute(); + * $newTags = $tags->find('relevant')->toArray(); * $articles->association('tags')->link($article, $newTags); * ``` * From 0eeaf9781b404bbb03ead8b5bfa1e75dfc07474d Mon Sep 17 00:00:00 2001 From: gmponos Date: Mon, 8 Feb 2016 00:05:51 +0200 Subject: [PATCH 0607/2059] Added docblock on ORM/Table class about afterSaveCommit callback event --- Table.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Table.php b/Table.php index 25fbad4f..1b13eb3f 100644 --- a/Table.php +++ b/Table.php @@ -106,6 +106,12 @@ * - `afterSave(Event $event, EntityInterface $entity, ArrayObject $options)` * Fired after an entity is saved. * + * - `afterSaveCommit(Event $event, EntityInterface $entity, ArrayObject $options)` + * Fired after the transaction in which the save operation is wrapped has been committed. + * It’s also triggered for non atomic saves where database operations are implicitly committed. + * The event is triggered only for the primary table on which save() is directly called. + * The event is not triggered if a transaction is started before calling save. + * * - `beforeDelete(Event $event, EntityInterface $entity, ArrayObject $options)` * Fired before an entity is deleted. By stopping this event you will abort * the delete operation. From 5fcf120d5c61069525f7eab9cdb418ed2d6337f1 Mon Sep 17 00:00:00 2001 From: bravo-kernel Date: Tue, 9 Feb 2016 16:00:57 +0100 Subject: [PATCH 0608/2059] Adds license file for ORM subtree split --- LICENSE.txt | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 LICENSE.txt diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 00000000..c1a562e0 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,22 @@ +The MIT License (MIT) + +CakePHP(tm) : The Rapid Development PHP Framework (http://cakephp.org) +Copyright (c) 2005-2016, Cake Software Foundation, Inc. (http://cakefoundation.org) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From a5e0b2ec4863e41c57becbac09ac94f46746fb00 Mon Sep 17 00:00:00 2001 From: bravo-kernel Date: Tue, 9 Feb 2016 16:02:00 +0100 Subject: [PATCH 0609/2059] Adds packagist and license badges to ORM subtree split --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 24b2a6f8..8a099e25 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +[![Total Downloads](https://img.shields.io/packagist/dt/cakephp/orm.svg?style=flat-square)](https://packagist.org/packages/cakephp/orm) +[![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](LICENSE.txt) + # CakePHP ORM The CakePHP ORM provides a powerful and flexible way to work with relational @@ -34,7 +37,7 @@ mappers if no explicit connection is defined. ## Creating Associations -In your table classes you can define the relations between your tables. CakePHP's ORM +In your table classes you can define the relations between your tables. CakePHP's ORM supports 4 association types out of the box: * belongsTo - E.g. Many articles belong to a user. From a3b664d4d2cc219a4993c4e67d3ce5e77c6c3781 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Wed, 10 Feb 2016 23:06:46 +0100 Subject: [PATCH 0610/2059] Make find(list) not auto-null on a behavior of 2.x where select(keyfield, valuefield) would autopopulate. --- Table.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Table.php b/Table.php index 1b13eb3f..97a2c3ec 100644 --- a/Table.php +++ b/Table.php @@ -1017,6 +1017,24 @@ public function findAll(Query $query, array $options) */ public function findList(Query $query, array $options) { + if (!isset($options['keyField']) && !isset($options['valueField'])) { + $select = $query->clause('select'); + if ($select && count($select) <= 2) { + $keyField = array_shift($select); + + $valueField = array_shift($select) ?: $keyField; + list($model, $keyField) = pluginSplit($keyField); + if (!$model || $model === $this->alias()) { + $options['keyField'] = $keyField; + } + + list($model, $valueField) = pluginSplit($valueField); + if (!$model || $model === $this->alias()) { + $options['valueField'] = $valueField; + } + } + } + $options += [ 'keyField' => $this->primaryKey(), 'valueField' => $this->displayField(), From 32162d7ccc7fc7c6018b8eb8a42f6a2fcea626f6 Mon Sep 17 00:00:00 2001 From: Yevgeny Tomenko Date: Thu, 11 Feb 2016 22:28:06 +0300 Subject: [PATCH 0611/2059] Allow to use the connection name with the option "connectionName" in TableRegistry::get and TableRegistry::config. --- Locator/TableLocator.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 0fe21229..3c035e91 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -116,6 +116,8 @@ public function config($alias = null, $options = null) * If no `table` option is passed, the table name will be the underscored version * of the provided $alias. * + * If no `connection` option is passed and passed `connectionName` string then it's used as original name to instantiate connection. + * * If no `connection` option is passed the table's defaultConnectionName() method * will be called to get the default connection name to use. * @@ -161,7 +163,11 @@ public function get($alias, array $options = []) } if (empty($options['connection'])) { - $connectionName = $options['className']::defaultConnectionName(); + if (!empty($options['connectionName'])) { + $connectionName = $options['connectionName']; + } else { + $connectionName = $options['className']::defaultConnectionName(); + } $options['connection'] = ConnectionManager::get($connectionName); } From 73ad8b2fa26fb9962b34727c85591c282494e218 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Fri, 12 Feb 2016 03:19:32 +0100 Subject: [PATCH 0612/2059] Remove unneeded else. --- Association/HasMany.php | 13 ++++++------- Behavior/TreeBehavior.php | 3 ++- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index b3e36b62..163f46f5 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -442,16 +442,15 @@ protected function _unlink(array $foreignKey, Table $target, array $conditions = $ok = $ok && $target->delete($assoc, $options); } return $ok; - } else { - $target->deleteAll($conditions); - return true; } - } else { - $updateFields = array_fill_keys($foreignKey, null); - $target->updateAll($updateFields, $conditions); - return true; + $target->deleteAll($conditions); + return true; } + + $updateFields = array_fill_keys($foreignKey, null); + $target->updateAll($updateFields, $conditions); + return true; } /** diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 137ce6d5..3c4e3e8c 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -911,7 +911,8 @@ protected function _scope($query) if (is_array($config['scope'])) { return $query->where($config['scope']); - } elseif (is_callable($config['scope'])) { + } + if (is_callable($config['scope'])) { return $config['scope']($query); } From f796a56f9bfb389ec58ccf53c6c993dbfbe98556 Mon Sep 17 00:00:00 2001 From: Walther Lalk Date: Fri, 12 Feb 2016 13:18:59 +0200 Subject: [PATCH 0613/2059] Fix broken tree behaviour --- Behavior/TreeBehavior.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 3c4e3e8c..86e5fdd9 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -846,10 +846,10 @@ protected function _recoverTree($counter = 0, $parentId = null, $level = -1) protected function _getMax() { $field = $this->_config['right']; - $leftField = $this->_config['leftField']; + $rightField = $this->_config['rightField']; $edge = $this->_scope($this->_table->find()) ->select([$field]) - ->orderDesc($leftField) + ->orderDesc($rightField) ->first(); if (empty($edge->{$field})) { From 1cae0c074abdfe104f560f103ab97408ca836e4f Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 12 Feb 2016 22:45:40 -0500 Subject: [PATCH 0614/2059] Fix joinData being lost when belongsToMany is marshalled by id. When patching belongsToMany records by id, the existing joinData record should be retained and not replaced. Refs #8159 --- Marshaller.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index 1c619a7b..2a75d1bd 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -704,19 +704,27 @@ protected function _mergeJoinData($original, $assoc, $value, $options) } $options['accessibleFields'] = ['_joinData' => true]; + $records = $this->mergeMany($original, $value, $options); foreach ($records as $record) { $hash = spl_object_hash($record); $value = $record->get('_joinData'); + // Already an entity, no further marshalling required. + if ($value instanceof EntityInterface) { + continue; + } + + // Scalar data can't be handled if (!is_array($value)) { $record->unsetProperty('_joinData'); continue; } + // Marshal data into the old object, or make a new joinData object. if (isset($extra[$hash])) { $record->set('_joinData', $marshaller->merge($extra[$hash], $value, $nested)); - } else { + } elseif (is_array($value)) { $joinData = $marshaller->one($value, $nested); $record->set('_joinData', $joinData); } From c6282dd1189eee28c5333adfab8c958ed71c340d Mon Sep 17 00:00:00 2001 From: Yevgeny Tomenko Date: Mon, 15 Feb 2016 16:20:45 +0300 Subject: [PATCH 0615/2059] Added link to config documentation for TableRegistry::get --- TableRegistry.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TableRegistry.php b/TableRegistry.php index 8a43fa74..95887f38 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -100,6 +100,8 @@ public static function config($alias = null, $options = null) /** * Get a table instance from the registry. * + * See options specification in {@link TableLocator::get()}. + * * @param string $alias The alias name you want to get. * @param array $options The options you want to build the table with. * @return \Cake\ORM\Table From db142f8d06b708e354aeb62e40b53bea66bcc80d Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 15 Feb 2016 12:48:40 -0500 Subject: [PATCH 0616/2059] Update docs for TableLocator::get(). Make the options documentation more consistent with other methods. --- Locator/TableLocator.php | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 3c035e91..8b37bd7a 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -99,28 +99,28 @@ public function config($alias = null, $options = null) * This is important because table associations are resolved at runtime * and cyclic references need to be handled correctly. * - * The options that can be passed are the same as in `Table::__construct()`, but the - * key `className` is also recognized. + * The options that can be passed are the same as in Cake\ORM\Table::__construct(), but the + * `className` key is also recognized. * - * If $options does not contain `className` CakePHP will attempt to construct the - * class name based on the alias. For example 'Users' would result in - * `App\Model\Table\UsersTable` being attempted. If this class does not exist, - * then the default `Cake\ORM\Table` class will be used. By setting the `className` - * option you can define the specific class to use. This className can - * use a plugin short class reference. + * ### Options * - * If you use a `$name` that uses plugin syntax only the name part will be used as + * - `className` Define the specific class name to use. If undefined, CakePHP will generate the + * class name based on the alias. For example 'Users' would result in + * `App\Model\Table\UsersTable` being used. If this class does not exist, + * then the default `Cake\ORM\Table` class will be used. By setting the `className` + * option you can define the specific class to use. The className option supports + * plugin short class references {@link Cake\Core\App::shortName()}. + * - `table` Define the table name to use. If undefined, this option will default to the underscored + * version of the alias name. + * - `connection` Inject the specific connection object to use. If this option and `connectionName` are undefined, + * The table class' `defaultConnectionName()` method will be invoked to fetch the connection name. + * - `connectionName` Define the connection name to use. The named connection will be fetched from + * Cake\Datasource\ConnectionManager. + * + * *Note* If your `$alias` uses plugin syntax only the name part will be used as * key in the registry. This means that if two plugins, or a plugin and app provide * the same alias, the registry will only store the first instance. * - * If no `table` option is passed, the table name will be the underscored version - * of the provided $alias. - * - * If no `connection` option is passed and passed `connectionName` string then it's used as original name to instantiate connection. - * - * If no `connection` option is passed the table's defaultConnectionName() method - * will be called to get the default connection name to use. - * * @param string $alias The alias name you want to get. * @param array $options The options you want to build the table with. * If a table has already been loaded the options will be ignored. From eb98b3d17514c354035de51637b6132a5e7ee22a Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Wed, 17 Feb 2016 20:30:30 +0100 Subject: [PATCH 0617/2059] Cleaned a few more doc blocks --- LazyEagerLoader.php | 2 +- Query.php | 38 +++++++++++++++++++------------------- RulesChecker.php | 2 +- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index a6de5d1f..117684ca 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -36,7 +36,7 @@ class LazyEagerLoader * * @param \Cake\Datasource\EntityInterface|array $entities a single entity or list of entities * @param array $contain A `contain()` compatible array. - * @see Cake\ORM\Query\contain() + * @see \Cake\ORM\Query\contain() * @param \Cake\ORM\Table $source The table to use for fetching the top level entities * @return \Cake\Datasource\EntityInterface|array */ diff --git a/Query.php b/Query.php index c1f3c9ee..f8b6fe66 100644 --- a/Query.php +++ b/Query.php @@ -33,39 +33,39 @@ * into a specific iterator that will be responsible for hydrating results if * required. * - * @see CollectionInterface For a full description of the collection methods supported by this class - * @method CollectionInterface each(callable $c) Passes each of the query results to the callable - * @method CollectionInterface filter(callable $c = null) Keeps the results using passing the callable test - * @method CollectionInterface reject(callable $c) Removes the results passing the callable test + * @see \Cake\Collection\CollectionInterface For a full description of the collection methods supported by this class + * @method \Cake\Collection\CollectionInterface each(callable $c) Passes each of the query results to the callable + * @method \Cake\Collection\CollectionInterface filter(callable $c = null) Keeps the results using passing the callable test + * @method \Cake\Collection\CollectionInterface reject(callable $c) Removes the results passing the callable test * @method bool every(callable $c) Returns true if all the results pass the callable test * @method bool some(callable $c) Returns true if at least one of the results pass the callable test - * @method CollectionInterface map(callable $c) Modifies each of the results using the callable + * @method \Cake\Collection\CollectionInterface map(callable $c) Modifies each of the results using the callable * @method mixed reduce(callable $c, $zero = null) Folds all the results into a single value using the callable. - * @method CollectionInterface extract($field) Extracts a single column from each row + * @method \Cake\Collection\CollectionInterface extract($field) Extracts a single column from each row * @method mixed max($field, $type = SORT_NUMERIC) Returns the maximum value for a single column in all the results. * @method mixed min($field, $type = SORT_NUMERIC) Returns the minimum value for a single column in all the results. - * @method CollectionInterface groupBy(string|callable $field) In-memory group all results by the value of a column. - * @method CollectionInterface indexBy(string|callable $field) Returns the results indexed by the value of a column. + * @method \Cake\Collection\CollectionInterface groupBy(string|callable $field) In-memory group all results by the value of a column. + * @method \Cake\Collection\CollectionInterface indexBy(string|callable $field) Returns the results indexed by the value of a column. * @method int countBy(string|callable $field) Returns the number of unique values for a column * @method float sumOf(string|callable $field) Returns the sum of all values for a single column - * @method CollectionInterface shuffle() In-memory randomize the order the results are returned - * @method CollectionInterface sample($size = 10) In-memory shuffle the results and return a subset of them. - * @method CollectionInterface take($size = 1, $from = 0) In-memory limit and offset for the query results. - * @method CollectionInterface skip(int $howMany) Skips some rows from the start of the query result. + * @method \Cake\Collection\CollectionInterface shuffle() In-memory randomize the order the results are returned + * @method \Cake\Collection\CollectionInterface sample($size = 10) In-memory shuffle the results and return a subset of them. + * @method \Cake\Collection\CollectionInterface take($size = 1, $from = 0) In-memory limit and offset for the query results. + * @method \Cake\Collection\CollectionInterface skip(int $howMany) Skips some rows from the start of the query result. * @method mixed last() Return the last row of the query result - * @method CollectionInterface append(array|Traversable $items) Appends more rows to the result of the query. - * @method CollectionInterface combine($k, $v, $g = null) Returns the values of the column $v index by column $k, + * @method \Cake\Collection\CollectionInterface append(array|Traversable $items) Appends more rows to the result of the query. + * @method \Cake\Collection\CollectionInterface combine($k, $v, $g = null) Returns the values of the column $v index by column $k, * and grouped by $g. - * @method CollectionInterface nest($k, $p) Creates a tree structure by nesting the values of column $p into that + * @method \Cake\Collection\CollectionInterface nest($k, $p) Creates a tree structure by nesting the values of column $p into that * with the same value for $k. * @method array toArray() Returns a key-value array with the results of this query. * @method array toList() Returns a numerically indexed array with the results of this query. - * @method CollectionInterface stopWhen(callable $c) Returns each row until the callable returns true. - * @method CollectionInterface zip(array|Traversable $c) Returns the first result of both the query and $c in an array, + * @method \Cake\Collection\CollectionInterface stopWhen(callable $c) Returns each row until the callable returns true. + * @method \Cake\Collection\CollectionInterface zip(array|\Traversable $c) Returns the first result of both the query and $c in an array, * then the second results and so on. - * @method CollectionInterface zipWith(...$collections, callable $c) Returns each of the results out of calling $c + * @method \Cake\Collection\CollectionInterface zipWith(...$collections, callable $c) Returns each of the results out of calling $c * with the first rows of the query and each of the items, then the second rows and so on. - * @method CollectionInterface chunk($size) Groups the results in arrays of $size rows each. + * @method \Cake\Collection\CollectionInterface chunk($size) Groups the results in arrays of $size rows each. * @method bool isEmpty($size) Returns true if this query found no results. */ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface diff --git a/RulesChecker.php b/RulesChecker.php index 09eab09f..d8cf1cf3 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -23,7 +23,7 @@ * * Adds ORM related features to the RulesChecker class. * - * @see Cake\Datasource\RulesChecker + * @see \Cake\Datasource\RulesChecker */ class RulesChecker extends BaseRulesChecker { From 9550a139c8126172dc64c2033b7ba0d62e3175d6 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Wed, 17 Feb 2016 21:06:10 +0100 Subject: [PATCH 0618/2059] Fix some more doc block issues. --- Association.php | 2 +- Association/BelongsTo.php | 2 +- Association/BelongsToMany.php | 4 ++-- Association/HasMany.php | 2 +- Association/HasOne.php | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Association.php b/Association.php index ff7e80a8..0732b911 100644 --- a/Association.php +++ b/Association.php @@ -964,7 +964,7 @@ abstract public function isOwningSide(Table $side); * @param array|\ArrayObject $options The options for saving associated data. * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns * the saved entity - * @see Table::save() + * @see \Cake\ORM\Table::save() */ abstract public function saveAssociated(EntityInterface $entity, array $options = []); } diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index bc3d430f..6a15a88f 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -125,7 +125,7 @@ public function type() * the target table * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns * the saved entity - * @see Table::save() + * @see \Cake\ORM\Table::save() */ public function saveAssociated(EntityInterface $entity, array $options = []) { diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 89002702..6bddd870 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -527,8 +527,8 @@ public function saveStrategy($strategy = null) * in the parent entity cannot be traversed * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns * the saved entity - * @see Table::save() - * @see BelongsToMany::replaceLinks() + * @see \Cake\ORM\Table::save() + * @see \Cake\ORM\Association\BelongsToMany::replaceLinks() */ public function saveAssociated(EntityInterface $entity, array $options = []) { diff --git a/Association/HasMany.php b/Association/HasMany.php index 163f46f5..00296bfe 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -123,7 +123,7 @@ public function saveStrategy($strategy = null) * the target table * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns * the saved entity - * @see Table::save() + * @see \Cake\ORM\Table::save() * @throws \InvalidArgumentException when the association data cannot be traversed. */ public function saveAssociated(EntityInterface $entity, array $options = []) diff --git a/Association/HasOne.php b/Association/HasOne.php index 25026da1..a217c5ac 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -110,7 +110,7 @@ public function type() * the target table * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns * the saved entity - * @see Table::save() + * @see \Cake\ORM\Table::save() */ public function saveAssociated(EntityInterface $entity, array $options = []) { From e795e22fc0ada5daaf64372cf2a19feafd5daccc Mon Sep 17 00:00:00 2001 From: antograssiot Date: Mon, 22 Feb 2016 05:43:31 +0100 Subject: [PATCH 0619/2059] remove usage of deprecated ``type()`` method --- Behavior/TreeBehavior.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 86e5fdd9..db14ee85 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -880,15 +880,15 @@ protected function _sync($shift, $dir, $conditions, $mark = false) $exp = $query->newExpr(); $movement = clone $exp; - $movement ->add($field)->add("$shift")->type($dir); + $movement ->add($field)->add("$shift")->tieWith($dir); $inverse = clone $exp; $movement = $mark ? - $inverse->add($movement)->type('*')->add('-1'): + $inverse->add($movement)->tieWith('*')->add('-1'): $movement; $where = clone $exp; - $where->add($field)->add($conditions)->type(''); + $where->add($field)->add($conditions)->tieWith(''); $query->update() ->set($exp->eq($field, $movement)) From 67d55630771191360571dcccb98c78b2aa32698b Mon Sep 17 00:00:00 2001 From: antograssiot Date: Mon, 22 Feb 2016 19:30:02 +0100 Subject: [PATCH 0620/2059] cleanup imports --- Behavior/CounterCacheBehavior.php | 1 - Behavior/TimestampBehavior.php | 1 - Behavior/TreeBehavior.php | 2 -- 3 files changed, 4 deletions(-) diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 79d02a80..ee167f02 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -18,7 +18,6 @@ use Cake\Event\Event; use Cake\ORM\Association; use Cake\ORM\Behavior; -use Cake\ORM\Entity; /** * CounterCache behavior diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 0b6ac637..a166d924 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -18,7 +18,6 @@ use Cake\Event\Event; use Cake\I18n\Time; use Cake\ORM\Behavior; -use Cake\ORM\Entity; use DateTime; use UnexpectedValueException; diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index db14ee85..c7c64bb6 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -19,9 +19,7 @@ use Cake\Datasource\Exception\RecordNotFoundException; use Cake\Event\Event; use Cake\ORM\Behavior; -use Cake\ORM\Entity; use Cake\ORM\Query; -use Cake\ORM\Table; use InvalidArgumentException; use RuntimeException; From 033ecf04e94b5ae776b23ee9666cfb210e038aa4 Mon Sep 17 00:00:00 2001 From: antograssiot Date: Tue, 23 Feb 2016 05:55:17 +0100 Subject: [PATCH 0621/2059] fix the parameter namespace --- Rule/ExistsIn.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 09789287..0c051fd1 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -116,7 +116,7 @@ public function __invoke(EntityInterface $entity, array $options) /** * Check whether or not the entity fields are nullable and null. * - * @param \Cake\ORM\EntityInterface $entity The entity to check. + * @param \Cake\Datasource\EntityInterface $entity The entity to check. * @param \Cake\ORM\Table $source The table to use schema from. * @return bool */ From f26aac3b3d39a6b1b5c18e29011fe588d8fde68f Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 23 Feb 2016 12:43:43 -0500 Subject: [PATCH 0622/2059] Revert "Make find(list) not auto-null on a behavior of 2.x" --- Table.php | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/Table.php b/Table.php index 97a2c3ec..1b13eb3f 100644 --- a/Table.php +++ b/Table.php @@ -1017,24 +1017,6 @@ public function findAll(Query $query, array $options) */ public function findList(Query $query, array $options) { - if (!isset($options['keyField']) && !isset($options['valueField'])) { - $select = $query->clause('select'); - if ($select && count($select) <= 2) { - $keyField = array_shift($select); - - $valueField = array_shift($select) ?: $keyField; - list($model, $keyField) = pluginSplit($keyField); - if (!$model || $model === $this->alias()) { - $options['keyField'] = $keyField; - } - - list($model, $valueField) = pluginSplit($valueField); - if (!$model || $model === $this->alias()) { - $options['valueField'] = $valueField; - } - } - } - $options += [ 'keyField' => $this->primaryKey(), 'valueField' => $this->displayField(), From f55049fbacdedce7c940f69b166af9904c781e34 Mon Sep 17 00:00:00 2001 From: Adam Royle Date: Wed, 24 Feb 2016 12:48:13 +1000 Subject: [PATCH 0623/2059] Add getter for protected $_className value for Association. --- Association.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Association.php b/Association.php index 0732b911..102503c1 100644 --- a/Association.php +++ b/Association.php @@ -260,6 +260,16 @@ public function cascadeCallbacks($cascadeCallbacks = null) return $this->_cascadeCallbacks; } + /** + * The class name of the target table object + * + * @return string + */ + public function className() + { + return $this->_className; + } + /** * Sets the table instance for the source side of the association. If no arguments * are passed, the current configured table instance is returned From 00a5447068076398112940376b4567dd33eb5678 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bene=20Tam=C3=A1s?= Date: Fri, 26 Feb 2016 13:52:27 +0100 Subject: [PATCH 0624/2059] Table mixin added to Association --- Association.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Association.php b/Association.php index 102503c1..c0ac5032 100644 --- a/Association.php +++ b/Association.php @@ -27,6 +27,7 @@ * An Association is a relationship established between two tables and is used * to configure and customize the way interconnected records are retrieved. * + * @mixin \Cake\ORM\Table */ abstract class Association { From cf9ff31a946396973944d6b85fc07d942747a87a Mon Sep 17 00:00:00 2001 From: mark_story Date: Fri, 26 Feb 2016 12:26:03 -0500 Subject: [PATCH 0625/2059] Attempt to clarify behavior of dependent option. Refs #8348 --- Table.php | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Table.php b/Table.php index 1b13eb3f..f064167e 100644 --- a/Table.php +++ b/Table.php @@ -757,9 +757,10 @@ public function belongsTo($associated, array $options = []) * - foreignKey: The name of the field to use as foreign key, if false none * will be used * - dependent: Set to true if you want CakePHP to cascade deletes to the - * associated table when an entity is removed on this table. Set to false - * if you don't want CakePHP to remove associated data, for when you are using - * database constraints. + * associated table when an entity is removed on this table. The delete operation + * on the associated table will not cascade further. To get recursive cascades enable + * `cascadeCallbacks` as well. Set to false if you don't want CakePHP to remove + * associated data, or when you are using database constraints. * - cascadeCallbacks: Set to true if you want CakePHP to fire callbacks on * cascaded deletes. If false the ORM will use deleteAll() to remove data. * When true records will be loaded and then deleted. @@ -799,9 +800,10 @@ public function hasOne($associated, array $options = []) * - foreignKey: The name of the field to use as foreign key, if false none * will be used * - dependent: Set to true if you want CakePHP to cascade deletes to the - * associated table when an entity is removed on this table. Set to false - * if you don't want CakePHP to remove associated data, for when you are using - * database constraints. + * associated table when an entity is removed on this table. The delete operation + * on the associated table will not cascade further. To get recursive cascades enable + * `cascadeCallbacks` as well. Set to false if you don't want CakePHP to remove + * associated data, or when you are using database constraints. * - cascadeCallbacks: Set to true if you want CakePHP to fire callbacks on * cascaded deletes. If false the ORM will use deleteAll() to remove data. * When true records will be loaded and then deleted. @@ -850,6 +852,8 @@ public function hasMany($associated, array $options = []) * - through: If you choose to use an already instantiated link table, set this * key to a configured Table instance containing associations to both the source * and target tables in this association. + * - dependent: Set to false, if you do not want junction table records removed + * when an owning record is removed. * - cascadeCallbacks: Set to true if you want CakePHP to fire callbacks on * cascaded deletes. If false the ORM will use deleteAll() to remove data. * When true join/junction table records will be loaded and then deleted. From 71249ea293ea864654eefe30126d68b74681f1e2 Mon Sep 17 00:00:00 2001 From: antograssiot Date: Sat, 27 Feb 2016 19:20:05 +0100 Subject: [PATCH 0626/2059] remove unused variables/lines detected by scrutinizer --- Query.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Query.php b/Query.php index f8b6fe66..0754111d 100644 --- a/Query.php +++ b/Query.php @@ -977,7 +977,6 @@ protected function _addDefaultFields() protected function _addDefaultSelectTypes() { $typeMap = $this->typeMap()->defaults(); - $selectTypeMap = $this->selectTypeMap(); $select = $this->clause('select'); $types = []; From 6f7d040ce27770d013a8450202080a17b4971ba6 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 27 Feb 2016 11:09:50 +0530 Subject: [PATCH 0627/2059] Show warning if association property name is same as table field. --- Association.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Association.php b/Association.php index c0ac5032..6e4bd3b8 100644 --- a/Association.php +++ b/Association.php @@ -438,6 +438,14 @@ public function property($name = null) if ($name === null && !$this->_propertyName) { list(, $name) = pluginSplit($this->_name); $this->_propertyName = Inflector::underscore($name); + if (in_array($this->_propertyName, $this->_sourceTable->schema()->columns())) { + $msg = 'Association property name "%s" clashes with field of same name of table "%s".' . + "\n" . 'You should explicitly specify the "propertyName" option.'; + trigger_error( + sprintf($msg, $this->_propertyName, $this->_sourceTable->table()), + E_USER_WARNING + ); + } } return $this->_propertyName; } From 0ed40ae8e4f3c7b491c5acb67ed8765f8ddd9cac Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 28 Feb 2016 01:05:48 +0530 Subject: [PATCH 0628/2059] Reduce code duplication. --- Association.php | 14 ++++++++++++-- Association/BelongsTo.php | 17 ++++------------- Association/HasOne.php | 17 ++++------------- 3 files changed, 20 insertions(+), 28 deletions(-) diff --git a/Association.php b/Association.php index 6e4bd3b8..ed3ce990 100644 --- a/Association.php +++ b/Association.php @@ -436,8 +436,7 @@ public function property($name = null) $this->_propertyName = $name; } if ($name === null && !$this->_propertyName) { - list(, $name) = pluginSplit($this->_name); - $this->_propertyName = Inflector::underscore($name); + $this->_propertyName = $this->_propertyName(); if (in_array($this->_propertyName, $this->_sourceTable->schema()->columns())) { $msg = 'Association property name "%s" clashes with field of same name of table "%s".' . "\n" . 'You should explicitly specify the "propertyName" option.'; @@ -450,6 +449,17 @@ public function property($name = null) return $this->_propertyName; } + /** + * Returns default property name based on association name. + * + * @return string + */ + protected function _propertyName() + { + list(, $name) = pluginSplit($this->_name); + return Inflector::underscore($name); + } + /** * Sets the strategy name to be used to fetch associated records. Keep in mind * that some association types might not implement but a default strategy, diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 6a15a88f..a81b8c65 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -72,23 +72,14 @@ public function cascadeDelete(EntityInterface $entity, array $options = []) } /** - * Sets the property name that should be filled with data from the target table - * in the source table record. - * If no arguments are passed, currently configured type is returned. + * Returns default property name based on association name. * - * @param string|null $name The property name, use null to read the current property. * @return string */ - public function property($name = null) + protected function _propertyName() { - if ($name !== null) { - return parent::property($name); - } - if ($name === null && !$this->_propertyName) { - list(, $name) = pluginSplit($this->_name); - $this->_propertyName = Inflector::underscore(Inflector::singularize($name)); - } - return $this->_propertyName; + list(, $name) = pluginSplit($this->_name); + return Inflector::underscore(Inflector::singularize($name)); } /** diff --git a/Association/HasOne.php b/Association/HasOne.php index a217c5ac..c98243fa 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -57,23 +57,14 @@ public function foreignKey($key = null) } /** - * Sets the property name that should be filled with data from the target table - * in the source table record. - * If no arguments are passed, currently configured type is returned. + * Returns default property name based on association name. * - * @param string|null $name The name of the property. Pass null to read the current value. * @return string */ - public function property($name = null) + protected function _propertyName() { - if ($name !== null) { - return parent::property($name); - } - if ($name === null && !$this->_propertyName) { - list(, $name) = pluginSplit($this->_name); - $this->_propertyName = Inflector::underscore(Inflector::singularize($name)); - } - return $this->_propertyName; + list(, $name) = pluginSplit($this->_name); + return Inflector::underscore(Inflector::singularize($name)); } /** From 0ce732a685a232cfa197c206d2ea237752817ecd Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 28 Feb 2016 12:03:56 +0530 Subject: [PATCH 0629/2059] Remov newline from error message. --- Association.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association.php b/Association.php index ed3ce990..c20622ae 100644 --- a/Association.php +++ b/Association.php @@ -439,7 +439,7 @@ public function property($name = null) $this->_propertyName = $this->_propertyName(); if (in_array($this->_propertyName, $this->_sourceTable->schema()->columns())) { $msg = 'Association property name "%s" clashes with field of same name of table "%s".' . - "\n" . 'You should explicitly specify the "propertyName" option.'; + ' You should explicitly specify the "propertyName" option.'; trigger_error( sprintf($msg, $this->_propertyName, $this->_sourceTable->table()), E_USER_WARNING From 25acbf9fb7771275243c94f0e84dc703a78954ae Mon Sep 17 00:00:00 2001 From: Marlin Cremers Date: Wed, 4 Nov 2015 11:04:52 +0100 Subject: [PATCH 0630/2059] Add support for cross schema joins --- Association.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Association.php b/Association.php index 102503c1..1142a5a6 100644 --- a/Association.php +++ b/Association.php @@ -15,6 +15,7 @@ namespace Cake\ORM; use Cake\Core\ConventionsTrait; +use Cake\Database\Expression\CrossSchemaTableExpression; use Cake\Database\Expression\IdentifierExpression; use Cake\Datasource\EntityInterface; use Cake\Datasource\ResultSetDecorator; @@ -524,13 +525,22 @@ public function attachTo(Query $query, array $options = []) { $target = $this->target(); $joinType = empty($options['joinType']) ? $this->joinType() : $options['joinType']; + + $table = $target->table(); + if ($this->source()->connection()->supportsCrossWith($target->connection())) { + $table = new CrossSchemaTableExpression( + $target->connection()->driver()->schema(), + $table + ); + } + $options += [ 'includeFields' => true, 'foreignKey' => $this->foreignKey(), 'conditions' => [], 'fields' => [], 'type' => $joinType, - 'table' => $target->table(), + 'table' => $table, 'finder' => $this->finder() ]; From 313614dcdd216bfbe764689fcefb2fe486c377cc Mon Sep 17 00:00:00 2001 From: mark_story Date: Wed, 2 Mar 2016 12:43:56 -0500 Subject: [PATCH 0631/2059] Resurrect changes from #8131 These changes were lost when we moved to the 3.next branch. This revives those changes and solves #8259 --- Table.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index f064167e..79e6bda3 100644 --- a/Table.php +++ b/Table.php @@ -1030,7 +1030,7 @@ public function findList(Query $query, array $options) if (isset($options['idField'])) { $options['keyField'] = $options['idField']; unset($options['idField']); - trigger_error('Option "idField" is deprecated, use "keyField" instead.', E_USER_WARNING); + trigger_error('Option "idField" is deprecated, use "keyField" instead.', E_USER_DEPRECATED); } if (!$query->clause('select') && @@ -1096,7 +1096,7 @@ public function findThreaded(Query $query, array $options) if (isset($options['idField'])) { $options['keyField'] = $options['idField']; unset($options['idField']); - trigger_error('Option "idField" is deprecated, use "keyField" instead.', E_USER_WARNING); + trigger_error('Option "idField" is deprecated, use "keyField" instead.', E_USER_DEPRECATED); } $options = $this->_setFieldMatchers($options, ['keyField', 'parentField']); From c4519c2fae69360db964ee11f4ab2bdc71ab0c29 Mon Sep 17 00:00:00 2001 From: Jesper Skytte Hansen Date: Thu, 3 Mar 2016 15:20:35 +0100 Subject: [PATCH 0632/2059] Update HasMany to avoid ambigous columns I've changed the conditions from a basic array to a QueryExpression, which I aftewards traverse to add alias to all fields. --- Association/HasMany.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Association/HasMany.php b/Association/HasMany.php index 00296bfe..04cf0345 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -16,6 +16,8 @@ namespace Cake\ORM\Association; use Cake\Collection\Collection; +use Cake\Database\Expression\FieldInterface; +use Cake\Database\Expression\QueryExpression; use Cake\Datasource\EntityInterface; use Cake\ORM\Association; use Cake\ORM\Table; @@ -436,6 +438,12 @@ protected function _unlink(array $foreignKey, Table $target, array $conditions = if ($mustBeDependent) { if ($this->_cascadeCallbacks) { + $conditions = new QueryExpression($conditions); + $conditions->traverse(function ($entry) use ($target) { + if ($entry instanceof FieldInterface) { + $entry->setField($target->aliasField($entry->getField())); + } + }); $query = $this->find('all')->where($conditions); $ok = true; foreach ($query as $assoc) { From 87a2e4701bda31e8a874a75e6d2b4dd6b9e856ed Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sat, 5 Mar 2016 18:29:57 +0100 Subject: [PATCH 0633/2059] Preserving the association conditions when using ExistsIn Closes #7968 --- Association.php | 19 +++++++++++++++++++ Rule/ExistsIn.php | 18 ++++++++---------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/Association.php b/Association.php index f93b3218..9936789c 100644 --- a/Association.php +++ b/Association.php @@ -678,6 +678,25 @@ public function find($type = null, array $options = []) ->where($this->conditions()); } + /** + * Proxies the operation to the target table's exists method after + * appending the default conditions for thisassociation + * + * @param array|callable|ExpressionInterface $conditions The conditions to use + * for checking if any record matches. + * @see \Cake\ORM\Table::exists() + * @return bool + */ + public function exists($conditions) + { + if (!empty($this->_conditions)) { + $conditions = $this + ->find('all', ['conditions' => $conditions]) + ->clause('where'); + } + return $this->target()->exists($conditions); + } + /** * Proxies the update operation to the target table's updateAll method * diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 0c051fd1..286075be 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -77,22 +77,20 @@ public function __invoke(EntityInterface $entity, array $options) } $source = $target = $this->_repository; + $isAssociation = $target instanceof Association; + $bindingKey = $isAssociation ? (array)$target->bindingKey() : (array)$target->primaryKey(); + $realTarget = $isAssociation ? $target->target() : $target; + + if (!empty($options['_sourceTable']) && $realTarget === $options['_sourceTable']) { + return true; + } + if (!empty($options['repository'])) { $source = $options['repository']; } if ($source instanceof Association) { $source = $source->source(); } - if ($target instanceof Association) { - $bindingKey = (array)$target->bindingKey(); - $target = $target->target(); - } else { - $bindingKey = (array)$target->primaryKey(); - } - - if (!empty($options['_sourceTable']) && $target === $options['_sourceTable']) { - return true; - } if (!$entity->extract($this->_fields, true)) { return true; From 3dc114f28578ebe6e807ef93d5d799c7cbf1cfd0 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sat, 5 Mar 2016 19:34:39 +0100 Subject: [PATCH 0634/2059] Avoiding notice when unlinking values using HasMany Fixes #8164 --- Association/HasMany.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index 04cf0345..78980d2b 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -306,7 +306,8 @@ public function unlink(EntityInterface $sourceEntity, array $targetEntities, $op $this->_unlink($foreignKey, $target, $conditions, $options); - if ($options['cleanProperty']) { + $result = $sourceEntity->get($property); + if ($options['cleanProperty'] && $result !== null) { $sourceEntity->set( $property, (new Collection($sourceEntity->get($property))) From c169bb889b09796a3cc385578f76a7cce13d9550 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Lorenzo=20Rodr=C3=ADguez?= Date: Sat, 5 Mar 2016 23:01:25 +0100 Subject: [PATCH 0635/2059] Fixed typo --- Association.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association.php b/Association.php index 9936789c..24bf7c34 100644 --- a/Association.php +++ b/Association.php @@ -680,7 +680,7 @@ public function find($type = null, array $options = []) /** * Proxies the operation to the target table's exists method after - * appending the default conditions for thisassociation + * appending the default conditions for this association * * @param array|callable|ExpressionInterface $conditions The conditions to use * for checking if any record matches. From 668ba93d739f89c2b5e111f655324cd3e1b190f5 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sat, 5 Mar 2016 23:26:04 +0100 Subject: [PATCH 0636/2059] Make sure that the missing fields checks is done after the query is built After this changes, when using the query builder function for a HasMany or BelongsToMany association, failing to select the foreingKey fields will actually trigger the helpful exception for the user. Fixes #6606 --- Association/SelectableAssociationTrait.php | 24 +++++++++++++--------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/Association/SelectableAssociationTrait.php b/Association/SelectableAssociationTrait.php index 5a94e698..ed91e330 100644 --- a/Association/SelectableAssociationTrait.php +++ b/Association/SelectableAssociationTrait.php @@ -82,8 +82,11 @@ protected function _buildQuery($options) $finder = isset($options['finder']) ? $options['finder'] : $this->finder(); list($finder, $opts) = $this->_extractFinder($finder); + $options += ['fields' => []]; + $fetchQuery = $this ->find($finder, $opts) + ->select($options['fields']) ->where($options['conditions']) ->eagerLoaded(true) ->hydrate($options['query']->hydrate()); @@ -95,16 +98,6 @@ protected function _buildQuery($options) $fetchQuery = $this->_addFilteringCondition($fetchQuery, $key, $filter); } - if (!empty($options['fields'])) { - $fields = $fetchQuery->aliasFields($options['fields'], $alias); - if (!in_array($key, $fields)) { - throw new InvalidArgumentException( - sprintf('You are required to select the "%s" field', $key) - ); - } - $fetchQuery->select($fields); - } - if (!empty($options['sort'])) { $fetchQuery->order($options['sort']); } @@ -117,6 +110,17 @@ protected function _buildQuery($options) $fetchQuery = $options['queryBuilder']($fetchQuery); } + $select = $fetchQuery->clause('select'); + if (!empty($select)) { + $select = $fetchQuery->aliasFields($select); + if (array_diff((array)$key, $select) !== []) { + throw new InvalidArgumentException( + sprintf('You are required to select the "%s" field(s)', + implode(', ', (array)$key) + )); + } + } + return $fetchQuery; } From f609b5066db679a4426c092364e78a60beac3932 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 6 Mar 2016 15:26:33 +0100 Subject: [PATCH 0637/2059] Fixed test when using identifier quoting --- Association/SelectableAssociationTrait.php | 40 +++++++++++++++++----- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/Association/SelectableAssociationTrait.php b/Association/SelectableAssociationTrait.php index ed91e330..a90773ea 100644 --- a/Association/SelectableAssociationTrait.php +++ b/Association/SelectableAssociationTrait.php @@ -110,18 +110,40 @@ protected function _buildQuery($options) $fetchQuery = $options['queryBuilder']($fetchQuery); } + $this->_assertFieldsPresent($fetchQuery, (array)$key); + return $fetchQuery; + } + + /** + * Checks that the fetching query either has auto fields on or + * has the foreignKey fields selected. + * If the required fields are missing, throws an exception. + * + * @param \Cake\ORM\Query The association fetching query + * @param array $key The foreign key fields to check + * @return void + * @throws InvalidArgumentException + */ + protected function _assertFieldsPresent($fetchQuery, $key) + { $select = $fetchQuery->clause('select'); - if (!empty($select)) { - $select = $fetchQuery->aliasFields($select); - if (array_diff((array)$key, $select) !== []) { - throw new InvalidArgumentException( - sprintf('You are required to select the "%s" field(s)', - implode(', ', (array)$key) - )); - } + if (empty($select)) { + return; } + $missingFields = array_diff($key, $select) !== []; - return $fetchQuery; + if ($missingFields) { + $driver = $fetchQuery->connection()->driver(); + $key = array_map([$driver, 'quoteIdentifier'], $key); + $missingFields = array_diff($key, $select) !== []; + } + + if ($missingFields) { + throw new InvalidArgumentException( + sprintf('You are required to select the "%s" field(s)', + implode(', ', (array)$key) + )); + } } /** From ecb062289cfc77ff3f1f7eb654985724f2ec721f Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 6 Mar 2016 15:51:15 +0100 Subject: [PATCH 0638/2059] Re-adding line that was removed in previous refactor --- Association/SelectableAssociationTrait.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Association/SelectableAssociationTrait.php b/Association/SelectableAssociationTrait.php index a90773ea..6e221293 100644 --- a/Association/SelectableAssociationTrait.php +++ b/Association/SelectableAssociationTrait.php @@ -126,7 +126,7 @@ protected function _buildQuery($options) */ protected function _assertFieldsPresent($fetchQuery, $key) { - $select = $fetchQuery->clause('select'); + $select = $fetchQuery->aliasFields($fetchQuery->clause('select')); if (empty($select)) { return; } @@ -134,8 +134,8 @@ protected function _assertFieldsPresent($fetchQuery, $key) if ($missingFields) { $driver = $fetchQuery->connection()->driver(); - $key = array_map([$driver, 'quoteIdentifier'], $key); - $missingFields = array_diff($key, $select) !== []; + $quoted = array_map([$driver, 'quoteIdentifier'], $key); + $missingFields = array_diff($quoted, $select) !== []; } if ($missingFields) { From 36694cb926be5f7563b0b76aa79b5035fb990cc9 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 6 Mar 2016 16:47:02 +0100 Subject: [PATCH 0639/2059] Fixing CS errors --- Association/SelectableAssociationTrait.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Association/SelectableAssociationTrait.php b/Association/SelectableAssociationTrait.php index 6e221293..71019bfd 100644 --- a/Association/SelectableAssociationTrait.php +++ b/Association/SelectableAssociationTrait.php @@ -119,7 +119,7 @@ protected function _buildQuery($options) * has the foreignKey fields selected. * If the required fields are missing, throws an exception. * - * @param \Cake\ORM\Query The association fetching query + * @param \Cake\ORM\Query $fetchQuery The association fetching query * @param array $key The foreign key fields to check * @return void * @throws InvalidArgumentException @@ -140,9 +140,11 @@ protected function _assertFieldsPresent($fetchQuery, $key) if ($missingFields) { throw new InvalidArgumentException( - sprintf('You are required to select the "%s" field(s)', - implode(', ', (array)$key) - )); + sprintf( + 'You are required to select the "%s" field(s)', + implode(', ', (array)$key) + ) + ); } } From 3cd2ed5e64e35324f269202515f7aaabcd926227 Mon Sep 17 00:00:00 2001 From: antograssiot Date: Sun, 6 Mar 2016 20:33:41 +0100 Subject: [PATCH 0640/2059] fix some typos and links --- LazyEagerLoader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 117684ca..5a2d8edd 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -61,7 +61,7 @@ public function loadInto($entities, array $contain, Table $source) * Builds a query for loading the passed list of entity objects along with the * associations specified in $contain. * - * @param \Cake\Collection\CollectionInterface $objects The original entitites + * @param \Cake\Collection\CollectionInterface $objects The original entities * @param array $contain The associations to be loaded * @param \Cake\ORM\Table $source The table to use for fetching the top level entities * @return \Cake\ORM\Query From 647c1bb48bc2a64ed60c22640e5247cfec6d3ce1 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Fri, 11 Mar 2016 00:31:51 +0100 Subject: [PATCH 0641/2059] Run sniffer to auto-fix default params into doc blocks. --- Association.php | 4 ++-- Association/BelongsToMany.php | 2 +- Behavior/TimestampBehavior.php | 2 +- EagerLoader.php | 2 +- Query.php | 12 ++++++------ RulesChecker.php | 4 ++-- TableRegistry.php | 2 +- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Association.php b/Association.php index c20622ae..1bee9ce9 100644 --- a/Association.php +++ b/Association.php @@ -411,7 +411,7 @@ public function canBeJoined(array $options = []) * Sets the type of join to be used when adding the association to a query. * If no arguments are passed, the currently configured type is returned. * - * @param string $type the join type to be used (e.g. INNER) + * @param string|null $type the join type to be used (e.g. INNER) * @return string */ public function joinType($type = null) @@ -653,7 +653,7 @@ public function defaultRowValue($row, $joined) * and modifies the query accordingly based of this association * configuration * - * @param string|array $type the type of query to perform, if an array is passed, + * @param string|array|null $type the type of query to perform, if an array is passed, * it will be interpreted as the `$options` parameter * @param array $options The options to for the find * @see \Cake\ORM\Table::find() diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 6bddd870..f44908c2 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -868,7 +868,7 @@ protected function junctionConditions() * If your association includes conditions, the junction table will be * included in the query's contained associations. * - * @param string|array $type the type of query to perform, if an array is passed, + * @param string|array|null $type the type of query to perform, if an array is passed, * it will be interpreted as the `$options` parameter * @param array $options The options to for the find * @see \Cake\ORM\Table::find() diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index a166d924..de101b4d 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -130,7 +130,7 @@ public function implementedEvents() * If an explicit date time is passed, the config option `refreshTimestamp` is * automatically set to false. * - * @param \DateTime $ts Timestamp + * @param \DateTime|null $ts Timestamp * @param bool $refreshTimestamp If true timestamp is refreshed. * @return \Cake\I18n\Time */ diff --git a/EagerLoader.php b/EagerLoader.php index 2ae80dc0..7395358e 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -161,7 +161,7 @@ public function clearContain() /** * Set whether or not contained associations will load fields automatically. * - * @param bool $value The value to set. + * @param bool|null $value The value to set. * @return bool The current value. */ public function autoFields($value = null) diff --git a/Query.php b/Query.php index 0754111d..14c97e22 100644 --- a/Query.php +++ b/Query.php @@ -223,7 +223,7 @@ public function addDefaultTypes(Table $table) * and storing containments. If called with no arguments, it will return the * currently configured instance. * - * @param \Cake\ORM\EagerLoader $instance The eager loader to use. Pass null + * @param \Cake\ORM\EagerLoader|null $instance The eager loader to use. Pass null * to get the current eagerloader. * @return \Cake\ORM\EagerLoader|$this */ @@ -328,7 +328,7 @@ public function eagerLoader(EagerLoader $instance = null) * If called with an empty first argument and $override is set to true, the * previous list will be emptied. * - * @param array|string $associations list of table aliases to be queried + * @param array|string|null $associations list of table aliases to be queried * @param bool $override whether override previous list with the one passed * defaults to merging previous list with the new one. * @return array|$this @@ -424,7 +424,7 @@ protected function _addAssociationsToTypeMap($table, $typeMap, $associations) * add more complex clauses you can do it directly in the main query. * * @param string $assoc The association to filter by - * @param callable $builder a function that will receive a pre-made query object + * @param callable|null $builder a function that will receive a pre-made query object * that can be used to add custom conditions or selecting some fields * @return $this */ @@ -494,7 +494,7 @@ public function matching($assoc, callable $builder = null) * add more complex clauses you can do it directly in the main query. * * @param string $assoc The association to join with - * @param callable $builder a function that will receive a pre-made query object + * @param callable|null $builder a function that will receive a pre-made query object * that can be used to add custom conditions or selecting some fields * @return $this */ @@ -538,7 +538,7 @@ public function leftJoinWith($assoc, callable $builder = null) * will select no fields from the association. * * @param string $assoc The association to join with - * @param callable $builder a function that will receive a pre-made query object + * @param callable|null $builder a function that will receive a pre-made query object * that can be used to add custom conditions or selecting some fields * @return $this * @see \Cake\ORM\Query::matching() @@ -599,7 +599,7 @@ public function innerJoinWith($assoc, callable $builder = null) * add more complex clauses you can do it directly in the main query. * * @param string $assoc The association to filter by - * @param callable $builder a function that will receive a pre-made query object + * @param callable|null $builder a function that will receive a pre-made query object * that can be used to add custom conditions or selecting some fields * @return $this */ diff --git a/RulesChecker.php b/RulesChecker.php index d8cf1cf3..330a658a 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -39,7 +39,7 @@ class RulesChecker extends BaseRulesChecker * ``` * * @param array $fields The list of fields to check for uniqueness. - * @param string $message The error message to show in case the rule does not pass. + * @param string|null $message The error message to show in case the rule does not pass. * @return callable */ public function isUnique(array $fields, $message = null) @@ -73,7 +73,7 @@ public function isUnique(array $fields, $message = null) * @param string|array $field The field or list of fields to check for existence by * primary key lookup in the other table. * @param object|string $table The table name where the fields existence will be checked. - * @param string $message The error message to show in case the rule does not pass. + * @param string|null $message The error message to show in case the rule does not pass. * @return callable */ public function existsIn($field, $table, $message = null) diff --git a/TableRegistry.php b/TableRegistry.php index 95887f38..2b12a6da 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -68,7 +68,7 @@ class TableRegistry /** * Sets and returns a singleton instance of LocatorInterface implementation. * - * @param \Cake\ORM\Locator\LocatorInterface $locator Instance of a locator to use. + * @param \Cake\ORM\Locator\LocatorInterface|null $locator Instance of a locator to use. * @return \Cake\ORM\Locator\LocatorInterface */ public static function locator(LocatorInterface $locator = null) From baec11541f4bc6649bd3d14e45049fe8be480808 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 14 Mar 2016 22:08:55 -0400 Subject: [PATCH 0642/2059] Fix un-aliased query in Marshaller The query used to find possibly existing entities should include the table alias to avoid ambiguous column names. Refs #8463 --- Marshaller.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index 2a75d1bd..f1de596f 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -591,7 +591,8 @@ public function mergeMany($entities, array $data, array $options = []) return count(array_filter($keys, 'strlen')) === count($primary); }) ->reduce(function ($query, $keys) use ($primary) { - return $query->orWhere($query->newExpr()->and_(array_combine($primary, $keys))); + $fields = array_map([$this->_table, 'aliasField'], $primary); + return $query->orWhere($query->newExpr()->and_(array_combine($fields, $keys))); }, $this->_table->find()); if (!empty($indexed) && count($maybeExistentQuery->clause('where'))) { From 8d186cd49f60ec733d4697b1afbef5724f7874da Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 14 Mar 2016 22:10:35 -0400 Subject: [PATCH 0643/2059] Use more efficient array_map invocation. --- Marshaller.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index f1de596f..75fc36d0 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -380,9 +380,7 @@ protected function _loadAssociatedByIds($assoc, $ids) $target = $assoc->target(); $primaryKey = (array)$target->primaryKey(); $multi = count($primaryKey) > 1; - $primaryKey = array_map(function ($key) use ($target) { - return $target->alias() . '.' . $key; - }, $primaryKey); + $primaryKey = array_map([$target, 'aliasField'], $primaryKey); if ($multi) { if (count(current($ids)) !== count($primaryKey)) { From 3cfcad28c38ba898d81b08ded241f7378030c82c Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 15 Mar 2016 21:59:43 -0400 Subject: [PATCH 0644/2059] Fix complex conditions being dropped in BelongsToMany While simple conditions worked fine, operator expressions matched neither the target alias, nor the junction table alias, and thus were excluded from both condition sets. Because munging complex conditions is pretty risky, I'm going to assume that all complex conditions are referencing junction fields. Refs #8436 --- Association/BelongsToMany.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index f44908c2..74a6ec7a 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -853,7 +853,13 @@ protected function junctionConditions() } $alias = $this->_junctionAssociationName() . '.'; foreach ($conditions as $field => $value) { - if (is_string($field) && strpos($field, $alias) === 0) { + $isString = is_string($field); + if ($isString && strpos($field, $alias) === 0) { + $matching[$field] = $value; + } + // Assume that operators contain junction conditions. + // Trying to munge complex conditions could result in incorrect queries. + if ($isString && in_array(strtoupper($field), ['OR', 'NOT', 'AND', 'XOR'])) { $matching[$field] = $value; } } From ae53f969ca3e89af2110c1a69272afe69f715f00 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Wed, 23 Mar 2016 21:29:25 -0400 Subject: [PATCH 0645/2059] Alias conditions in BelongsToMany. Refs #8510 --- Association/BelongsToMany.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 74a6ec7a..8e1c5b6e 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -988,7 +988,7 @@ public function replaceLinks(EntityInterface $sourceEntity, array $targetEntitie return $this->junction()->connection()->transactional( function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { - $foreignKey = (array)$this->foreignKey(); + $foreignKey = array_map([$this->_junctionTable, 'aliasField'], (array)$this->foreignKey()); $hasMany = $this->source()->association($this->_junctionTable->alias()); $existing = $hasMany->find('all') ->where(array_combine($foreignKey, $primaryValue)); From e5522bbe1c32f1cdc1522d42c777d00216254f92 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 26 Mar 2016 22:25:22 -0400 Subject: [PATCH 0646/2059] Fix up phpcs errors and add missing documentation. --- Marshaller.php | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index b61edf05..01f85bf5 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -86,10 +86,15 @@ protected function _buildPropertyMap($options) * * ### Options: * - * * associated: Associations listed here will be marshalled as well. - * * fieldList: A whitelist of fields to be assigned to the entity. If not present, - * the accessible fields list in the entity will be used. - * * accessibleFields: A list of fields to allow or deny in entity accessible fields. + * - validate: Set to false to disable validation. Can also be a string of the validator ruleset to be applied. + * Defaults to true/default. + * - associated: Associations listed here will be marshalled as well. Defaults to null. + * - fieldList: A whitelist of fields to be assigned to the entity. If not present, + * the accessible fields list in the entity will be used. Defaults to null. + * - accessibleFields: A list of fields to allow or deny in entity accessible fields. Defaults to null + * - forceNew: When enabled, belongsToMany associations will have 'new' entities created + * when primary key values are set, and a record does not already exist. Normally primary key + * on missing entities would be ignored. Defaults to false. * * The above options can be used in each nested `associated` array. In addition to the above * options you can also use the `onlyIds` option for HasMany and BelongsToMany associations. @@ -258,10 +263,15 @@ protected function _marshalAssociation($assoc, $value, $options) * * ### Options: * - * * associated: Associations listed here will be marshalled as well. - * * fieldList: A whitelist of fields to be assigned to the entity. If not present, - * the accessible fields list in the entity will be used. - * * accessibleFields: A list of fields to allow or deny in entity accessible fields. + * - validate: Set to false to disable validation. Can also be a string of the validator ruleset to be applied. + * Defaults to true/default. + * - associated: Associations listed here will be marshalled as well. Defaults to null. + * - fieldList: A whitelist of fields to be assigned to the entity. If not present, + * the accessible fields list in the entity will be used. Defaults to null. + * - accessibleFields: A list of fields to allow or deny in entity accessible fields. Defaults to null + * - forceNew: When enabled, belongsToMany associations will have 'new' entities created + * when primary key values are set, and a record does not already exist. Normally primary key + * on missing entities would be ignored. Defaults to false. * * @param array $data The data to hydrate. * @param array $options List of options @@ -315,7 +325,7 @@ protected function _belongsToMany(Association $assoc, array $data, $options = [] foreach ($keys as $key => $value) { $rowConditions[][$target->aliasfield($key)] = $value; } - + if ($forceNew && !$target->exists($rowConditions)) { $records[$i] = $this->one($row, $options); } @@ -431,12 +441,12 @@ protected function _loadBelongsToMany($assoc, $ids) * * ### Options: * - * * associated: Associations listed here will be marshalled as well. - * * validate: Whether or not to validate data before hydrating the entities. Can + * - associated: Associations listed here will be marshalled as well. + * - validate: Whether or not to validate data before hydrating the entities. Can * also be set to a string to use a specific validator. Defaults to true/default. - * * fieldList: A whitelist of fields to be assigned to the entity. If not present + * - fieldList: A whitelist of fields to be assigned to the entity. If not present * the accessible fields list in the entity will be used. - * * accessibleFields: A list of fields to allow or deny in entity accessible fields. + * - accessibleFields: A list of fields to allow or deny in entity accessible fields. * * The above options can be used in each nested `associated` array. In addition to the above * options you can also use the `onlyIds` option for HasMany and BelongsToMany associations. @@ -543,6 +553,8 @@ public function merge(EntityInterface $entity, array $data, array $options = []) * * ### Options: * + * - validate: Whether or not to validate data before hydrating the entities. Can + * also be set to a string to use a specific validator. Defaults to true/default. * - associated: Associations listed here will be marshalled as well. * - fieldList: A whitelist of fields to be assigned to the entity. If not present, * the accessible fields list in the entity will be used. From 0b5a0a1d2acd4d2f346c9ea4fdc413b46b95ed53 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 30 Mar 2016 13:41:41 +0530 Subject: [PATCH 0647/2059] Add info about installing cakehp/cache package. Refs cakephp/orm#8 --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8a099e25..d1d08321 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,8 @@ ConnectionManager::config('default', [ 'driver' => 'Cake\Database\Driver\Mysql', 'database' => 'test', 'username' => 'root', - 'password' => 'secret' + 'password' => 'secret', + 'cacheMetaData' => false // If set to `true` you need to install the optional "cakephp/cache" package. ]); ``` From 5bff1f963830c96f3993e2c168c8b4e0fe225ae8 Mon Sep 17 00:00:00 2001 From: Thinking Media Date: Tue, 5 Apr 2016 11:38:14 -0400 Subject: [PATCH 0648/2059] grammar --- Association/BelongsToMany.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 8e1c5b6e..538adb66 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -673,8 +673,8 @@ protected function _saveLinks(EntityInterface $sourceEntity, $targetEntities, $o /** * Associates the source entity to each of the target entities provided by * creating links in the junction table. Both the source entity and each of - * the target entities are assumed to be already persisted, if the are marked - * as new or their status is unknown, an exception will be thrown. + * the target entities are assumed to be already persisted, if they are marked + * as new or their status is unknown then an exception will be thrown. * * When using this method, all entities in `$targetEntities` will be appended to * the source entity's property corresponding to this association object. From 5ec46a8a73a0b4b50f4990351a3eb3327826538b Mon Sep 17 00:00:00 2001 From: Yves P Date: Sat, 9 Apr 2016 00:10:27 +0200 Subject: [PATCH 0649/2059] Indicate the support for the Oracle driver in the ORM README --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d1d08321..326e3a16 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ The CakePHP ORM provides a powerful and flexible way to work with relational databases. Using a datamapper pattern the ORM allows you to manipulate data as entities allowing you to create expressive domain layers in your applications. -## Connecting to the Database +## Database engines supported The CakePHP ORM is compatible with: @@ -15,6 +15,9 @@ The CakePHP ORM is compatible with: * Postgres 8+ * SQLite3 * SQLServer 2008+ +* Oracle (through a [community plugin](https://github.com/CakeDC/cakephp-oracle-driver)) + +## Connecting to the Database The first thing you need to do when using this library is register a connection object. Before performing any operations with the connection, you need to From 9b94bbd84897e2184a6cb504c2bca3074e1e9d1c Mon Sep 17 00:00:00 2001 From: JayPHP Date: Fri, 8 Apr 2016 23:17:11 +0100 Subject: [PATCH 0650/2059] Added option to check for NULL values on IsUnique --- Rule/IsUnique.php | 11 +++++++++++ RulesChecker.php | 3 ++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index 596168b3..33cd5040 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -53,6 +53,7 @@ public function __invoke(EntityInterface $entity, array $options) return true; } + $checkNull = isset($options['checkNull']) ? true : false; $alias = $options['repository']->alias(); $conditions = $this->_alias($alias, $entity->extract($this->_fields)); if ($entity->isNew() === false) { @@ -63,6 +64,16 @@ public function __invoke(EntityInterface $entity, array $options) } } + if($checkNull) { + // Thanks to ndm + foreach ($conditions as $key => $value) { + if ($value === null) { + $conditions[$key . ' IS'] = $value; + unset($conditions[$key]); + } + } + } + return !$options['repository']->exists($conditions); } diff --git a/RulesChecker.php b/RulesChecker.php index 330a658a..c3dd91dd 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -52,7 +52,8 @@ public function isUnique(array $fields, $message = null) } } - $errorField = current($fields); + $errorField = isset($fields['errorField']) ? $fields['errorField'] : false; + return $this->_addError(new IsUnique($fields), '_isUnique', compact('errorField', 'message')); } From 10d52ddeb228b666f65183ecda25b3871033ad2f Mon Sep 17 00:00:00 2001 From: JayPHP Date: Sat, 9 Apr 2016 21:42:55 +0100 Subject: [PATCH 0651/2059] added tests + corrections --- RulesChecker.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RulesChecker.php b/RulesChecker.php index c3dd91dd..945d7d11 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -52,7 +52,7 @@ public function isUnique(array $fields, $message = null) } } - $errorField = isset($fields['errorField']) ? $fields['errorField'] : false; + $errorField = current($fields); return $this->_addError(new IsUnique($fields), '_isUnique', compact('errorField', 'message')); } From 3ee4288cb256a8904be3aae28998fea12f16296b Mon Sep 17 00:00:00 2001 From: JayPHP Date: Sat, 9 Apr 2016 21:50:33 +0100 Subject: [PATCH 0652/2059] Removed note --- Rule/IsUnique.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index 33cd5040..de76fcc2 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -65,7 +65,6 @@ public function __invoke(EntityInterface $entity, array $options) } if($checkNull) { - // Thanks to ndm foreach ($conditions as $key => $value) { if ($value === null) { $conditions[$key . ' IS'] = $value; From 15988f0d593564615bafd36a85feb7087617a1d5 Mon Sep 17 00:00:00 2001 From: JayPHP Date: Sat, 9 Apr 2016 22:05:49 +0100 Subject: [PATCH 0653/2059] Removed whitespace --- RulesChecker.php | 1 - 1 file changed, 1 deletion(-) diff --git a/RulesChecker.php b/RulesChecker.php index 945d7d11..330a658a 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -53,7 +53,6 @@ public function isUnique(array $fields, $message = null) } $errorField = current($fields); - return $this->_addError(new IsUnique($fields), '_isUnique', compact('errorField', 'message')); } From 8f7c171590f68bafdea42af76f8633a7cfc572e1 Mon Sep 17 00:00:00 2001 From: JayPHP Date: Sat, 9 Apr 2016 22:29:28 +0100 Subject: [PATCH 0654/2059] Corrected uppercase and formatting issues --- Rule/IsUnique.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index de76fcc2..7fefda0a 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -64,7 +64,7 @@ public function __invoke(EntityInterface $entity, array $options) } } - if($checkNull) { + if ($checkNull) { foreach ($conditions as $key => $value) { if ($value === null) { $conditions[$key . ' IS'] = $value; From e132aa86f256ba30b5308acd988f380183825ffb Mon Sep 17 00:00:00 2001 From: "james.byrne" Date: Mon, 11 Apr 2016 15:24:46 +0100 Subject: [PATCH 0655/2059] Changed option name --- Rule/IsUnique.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index 7fefda0a..2897aee8 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -53,7 +53,11 @@ public function __invoke(EntityInterface $entity, array $options) return true; } - $checkNull = isset($options['checkNull']) ? true : false; + $permitMultipleNulls = true; + if(isset($options['permitMultipleNulls'])) { + $permitMultipleNulls = $options['permitMultipleNulls'] === true ? true : false; + } + $alias = $options['repository']->alias(); $conditions = $this->_alias($alias, $entity->extract($this->_fields)); if ($entity->isNew() === false) { @@ -64,7 +68,7 @@ public function __invoke(EntityInterface $entity, array $options) } } - if ($checkNull) { + if ($permitMultipleNulls) { foreach ($conditions as $key => $value) { if ($value === null) { $conditions[$key . ' IS'] = $value; From 3e289b92032d14559613c9de852c7fd9dd7ab77f Mon Sep 17 00:00:00 2001 From: "james.byrne" Date: Mon, 11 Apr 2016 16:13:09 +0100 Subject: [PATCH 0656/2059] Using pre-existing fixture + CS Fix --- Rule/IsUnique.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index 2897aee8..b2398f01 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -54,7 +54,7 @@ public function __invoke(EntityInterface $entity, array $options) } $permitMultipleNulls = true; - if(isset($options['permitMultipleNulls'])) { + if (isset($options['permitMultipleNulls'])) { $permitMultipleNulls = $options['permitMultipleNulls'] === true ? true : false; } @@ -68,7 +68,7 @@ public function __invoke(EntityInterface $entity, array $options) } } - if ($permitMultipleNulls) { + if (!$permitMultipleNulls) { foreach ($conditions as $key => $value) { if ($value === null) { $conditions[$key . ' IS'] = $value; From 1cc4349c7da21dd98072b39074eea92a4ae4d6ad Mon Sep 17 00:00:00 2001 From: "james.byrne" Date: Tue, 12 Apr 2016 08:41:26 +0100 Subject: [PATCH 0657/2059] Changed name to allow instead of permit --- Rule/IsUnique.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index b2398f01..2d11eda5 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -53,9 +53,9 @@ public function __invoke(EntityInterface $entity, array $options) return true; } - $permitMultipleNulls = true; - if (isset($options['permitMultipleNulls'])) { - $permitMultipleNulls = $options['permitMultipleNulls'] === true ? true : false; + $allowMultipleNulls = true; + if (isset($options['allowMultipleNulls'])) { + $allowMultipleNulls = $options['allowMultipleNulls'] === true ? true : false; } $alias = $options['repository']->alias(); @@ -68,7 +68,7 @@ public function __invoke(EntityInterface $entity, array $options) } } - if (!$permitMultipleNulls) { + if (!$allowMultipleNulls) { foreach ($conditions as $key => $value) { if ($value === null) { $conditions[$key . ' IS'] = $value; From b08b52f16c5fcb86118125f669fc5c5164b8ab1c Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 13 Apr 2016 00:02:56 +0530 Subject: [PATCH 0658/2059] Add Table::saveMany(). This allows saving multiple records in a transaction. --- Table.php | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Table.php b/Table.php index 79e6bda3..a45d8708 100644 --- a/Table.php +++ b/Table.php @@ -1618,6 +1618,32 @@ protected function _update($entity, $data) return $success; } + /** + * Saves multiple records for a table. + * + * @param array $entities Entities to save. + * @param array $options Options used when calling Table::save() for each entity. + * @return bool|array False on failure, entities list on succcess. + */ + public function saveMany($entities, $options = []) + { + $return = $this->connection()->transactional( + function () use ($entities, $options) { + foreach ($entities as $entity) { + if ($this->save($entity, $options) === false) { + return false; + } + } + } + ); + + if ($return === false) { + return false; + } + + return $entities; + } + /** * {@inheritDoc} * From 45bcec9c23e2714ca049d9a39804a02ca5006e6b Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 17 Apr 2016 20:54:08 +0530 Subject: [PATCH 0659/2059] Add test showing saveMany() also works with ResultSet --- Table.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index a45d8708..fe5d79c4 100644 --- a/Table.php +++ b/Table.php @@ -1621,9 +1621,9 @@ protected function _update($entity, $data) /** * Saves multiple records for a table. * - * @param array $entities Entities to save. + * @param array|\Cake\ORM\ResultSet $entities Entities to save. * @param array $options Options used when calling Table::save() for each entity. - * @return bool|array False on failure, entities list on succcess. + * @return bool|array|\Cake\ORM\ResultSet False on failure, entities list on succcess. */ public function saveMany($entities, $options = []) { From 62fa2f929b7a4871d0079a06855c6af12182f816 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 17 Apr 2016 23:24:05 +0530 Subject: [PATCH 0660/2059] Restore state of entities if transaction is rollbacked. --- Table.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index fe5d79c4..391447fa 100644 --- a/Table.php +++ b/Table.php @@ -1627,9 +1627,12 @@ protected function _update($entity, $data) */ public function saveMany($entities, $options = []) { + $isNew = []; + $return = $this->connection()->transactional( - function () use ($entities, $options) { - foreach ($entities as $entity) { + function () use ($entities, $options, &$isNew) { + foreach ($entities as $key => $entity) { + $isNew[$key] = $entity->isNew(); if ($this->save($entity, $options) === false) { return false; } @@ -1638,6 +1641,12 @@ function () use ($entities, $options) { ); if ($return === false) { + foreach ($entities as $key => $entity) { + if (isset($isNew[$key]) && $isNew[$key]) { + $entity->unsetProperty($this->primaryKey()); + $entity->isNew(true); + } + } return false; } From 847c6657b669adb22aa35475a45f3129a42a61f3 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 18 Apr 2016 12:00:46 +0530 Subject: [PATCH 0661/2059] update docblock --- Table.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index 391447fa..27434adb 100644 --- a/Table.php +++ b/Table.php @@ -1619,10 +1619,14 @@ protected function _update($entity, $data) } /** - * Saves multiple records for a table. + * Persists multiple entities of a table. + * + * The records will be saved in a transaction which will be rolled back if + * any one of the records fails to save due to failed validation or datbase + * error. * * @param array|\Cake\ORM\ResultSet $entities Entities to save. - * @param array $options Options used when calling Table::save() for each entity. + * @param array|\ArrayAccess $options Options used when calling Table::save() for each entity. * @return bool|array|\Cake\ORM\ResultSet False on failure, entities list on succcess. */ public function saveMany($entities, $options = []) From 77db8c4b24b0af3fddab35f9d36822f363f1978b Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 19 Apr 2016 11:13:22 +0530 Subject: [PATCH 0662/2059] fix typo --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 27434adb..2ed26d18 100644 --- a/Table.php +++ b/Table.php @@ -1622,7 +1622,7 @@ protected function _update($entity, $data) * Persists multiple entities of a table. * * The records will be saved in a transaction which will be rolled back if - * any one of the records fails to save due to failed validation or datbase + * any one of the records fails to save due to failed validation or database * error. * * @param array|\Cake\ORM\ResultSet $entities Entities to save. From 07f3e7065ed5f1b42d00ba838555f7bf8c5b125f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Thu, 21 Apr 2016 10:13:19 +0200 Subject: [PATCH 0663/2059] #8671 Improving Table::findOrCreate() --- Table.php | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index 2ed26d18..92fdf60e 100644 --- a/Table.php +++ b/Table.php @@ -1213,7 +1213,7 @@ public function get($primaryKey, $options = []) * called allowing you to define additional default values. The new * entity will be saved and returned. * - * @param array $search The criteria to find existing records by. + * @param array|\Cake\ORM\Query|string $search The criteria to find existing records by. * @param callable|null $callback A callback that will be invoked for newly * created entities. This callback will be called *before* the entity * is persisted. @@ -1221,7 +1221,7 @@ public function get($primaryKey, $options = []) */ public function findOrCreate($search, callable $callback = null) { - $query = $this->find()->where($search); + $query = $this->_getFindOrCreateQuery($search); $row = $query->first(); if ($row) { return $row; @@ -1234,6 +1234,25 @@ public function findOrCreate($search, callable $callback = null) return $this->save($entity) ?: $entity; } + /** + * Gets the query object for findOrCreate(). + * + * @param array|\Cake\ORM\Query|string $search The criteria to find existing records by. + * @return \Cake\ORM\Query + */ + protected function _getFindOrCreateQuery($search) { + if ($search instanceof Query) { + return $search; + } elseif (is_string($search)) { + if (method_exists($this, $search)) { + return $this->{$search}(); + } else { + throw new InvalidArgumentException('Method `' . $search . '` does not exist!'); + } + } + return $this->find()->where($search); + } + /** * {@inheritDoc} */ From 636e38903056c31fcc6ba9db6835763cf56a14c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Thu, 21 Apr 2016 10:26:51 +0200 Subject: [PATCH 0664/2059] #8687 Minor code improvements --- Table.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Table.php b/Table.php index 92fdf60e..0705acac 100644 --- a/Table.php +++ b/Table.php @@ -1240,15 +1240,16 @@ public function findOrCreate($search, callable $callback = null) * @param array|\Cake\ORM\Query|string $search The criteria to find existing records by. * @return \Cake\ORM\Query */ - protected function _getFindOrCreateQuery($search) { + protected function _getFindOrCreateQuery($search) + { if ($search instanceof Query) { return $search; - } elseif (is_string($search)) { + } + if (is_string($search)) { if (method_exists($this, $search)) { return $this->{$search}(); - } else { - throw new InvalidArgumentException('Method `' . $search . '` does not exist!'); } + throw new InvalidArgumentException('Method `' . $search . '` does not exist!'); } return $this->find()->where($search); } From ca459f7ea808228cd12170d0fa4f5383ce161453 Mon Sep 17 00:00:00 2001 From: Marc Ypes Date: Fri, 22 Apr 2016 14:43:22 +0200 Subject: [PATCH 0665/2059] Update minimum requirements for cakephp/orm. Fixes cakephp/orm#10 --- composer.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 2fad694e..66983e76 100644 --- a/composer.json +++ b/composer.json @@ -4,9 +4,9 @@ "license": "MIT", "authors": [ { - "name": "CakePHP Community", - "homepage": "http://cakephp.org" - } + "name": "CakePHP Community", + "homepage": "http://cakephp.org" + } ], "autoload": { "psr-4": { @@ -16,8 +16,8 @@ "require": { "cakephp/collection": "~3.0", "cakephp/core": "~3.0", - "cakephp/datasource": "~3.0", - "cakephp/database": "~3.0", + "cakephp/datasource": "^3.1.2", + "cakephp/database": "^3.1.4", "cakephp/event": "~3.0", "cakephp/utility": "~3.0", "cakephp/validation": "~3.0" From fe0f87ba3e6d6b8e37f8980f3c5c21319af0142b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Mon, 25 Apr 2016 12:48:52 +0200 Subject: [PATCH 0666/2059] #8686 Adding a method to validate hasMany and HABTM count --- Table.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Table.php b/Table.php index 2ed26d18..db5f5887 100644 --- a/Table.php +++ b/Table.php @@ -2304,4 +2304,24 @@ public function __debugInfo() 'connectionName' => $conn ? $conn->configName() : null ]; } + + /** + * Validates that a belongsToMany or hasMany assoc has at least one entry. + * + * @param mixed $value + * @return boolean + */ + public function validateAtLeastOne($value) + { + if (!is_array($value)) { + return false; + } + if (isset($value['_ids'])) { + if (!is_array($value['_ids'])) { + return false; + } + return (count($value['_ids']) > 0); + } + return (count($value) > 0); + } } From 4dd3eac7196606a6e28d7b93487b3fd9f76f1e61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Mon, 25 Apr 2016 13:56:46 +0200 Subject: [PATCH 0667/2059] #8686 Validate min/max/equal count for associated records --- Table.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Table.php b/Table.php index db5f5887..a61ceec1 100644 --- a/Table.php +++ b/Table.php @@ -35,6 +35,7 @@ use Cake\ORM\Exception\MissingEntityException; use Cake\ORM\Rule\IsUnique; use Cake\Utility\Inflector; +use Cake\Validation\Validation; use Cake\Validation\ValidatorAwareTrait; use InvalidArgumentException; use RuntimeException; @@ -2311,7 +2312,7 @@ public function __debugInfo() * @param mixed $value * @return boolean */ - public function validateAtLeastOne($value) + public function validateAtLeast($value, $expectedCount = 0, $operator = '>') { if (!is_array($value)) { return false; @@ -2320,8 +2321,10 @@ public function validateAtLeastOne($value) if (!is_array($value['_ids'])) { return false; } - return (count($value['_ids']) > 0); + $count = count($value['_ids']); + } else { + $count = count($value); } - return (count($value) > 0); + return Validation::comparison($count, $operator, $expectedCount); } } From 36c6ade38b3502ad0a453ba5af1eac66505ed2ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Mon, 25 Apr 2016 22:26:06 +0200 Subject: [PATCH 0668/2059] #8686 Changing the method name of the new validation method --- Table.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Table.php b/Table.php index a61ceec1..da80501d 100644 --- a/Table.php +++ b/Table.php @@ -2309,10 +2309,12 @@ public function __debugInfo() /** * Validates that a belongsToMany or hasMany assoc has at least one entry. * + * This can be used to validate any other array as well. + * * @param mixed $value * @return boolean */ - public function validateAtLeast($value, $expectedCount = 0, $operator = '>') + public function validateCount($value, $expectedCount = 0, $operator = '>') { if (!is_array($value)) { return false; From 4ae9dde9c7f73512f847db4785789c5e1762f994 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Mon, 25 Apr 2016 23:23:13 +0200 Subject: [PATCH 0669/2059] Removing the useless string callback from the findOrCreate --- Table.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Table.php b/Table.php index 0705acac..029e54f4 100644 --- a/Table.php +++ b/Table.php @@ -1245,12 +1245,6 @@ protected function _getFindOrCreateQuery($search) if ($search instanceof Query) { return $search; } - if (is_string($search)) { - if (method_exists($this, $search)) { - return $this->{$search}(); - } - throw new InvalidArgumentException('Method `' . $search . '` does not exist!'); - } return $this->find()->where($search); } From 196a82b456237e24258a31d745142dbca9928a65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Mon, 25 Apr 2016 23:24:04 +0200 Subject: [PATCH 0670/2059] Updating the doc block for findOrCreate --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 029e54f4..883c5607 100644 --- a/Table.php +++ b/Table.php @@ -1213,7 +1213,7 @@ public function get($primaryKey, $options = []) * called allowing you to define additional default values. The new * entity will be saved and returned. * - * @param array|\Cake\ORM\Query|string $search The criteria to find existing records by. + * @param array|\Cake\ORM\Query $search The criteria to find existing records by. * @param callable|null $callback A callback that will be invoked for newly * created entities. This callback will be called *before* the entity * is persisted. From fa4a3953eb11529f65ba46ae952f6a4e7acd4563 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Wed, 27 Apr 2016 00:53:56 +0200 Subject: [PATCH 0671/2059] #8671 Adding documentation and another assert. --- Table.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 883c5607..33def7eb 100644 --- a/Table.php +++ b/Table.php @@ -1213,7 +1213,9 @@ public function get($primaryKey, $options = []) * called allowing you to define additional default values. The new * entity will be saved and returned. * - * @param array|\Cake\ORM\Query $search The criteria to find existing records by. + * @param array|\Cake\ORM\Query $search The criteria to find existing + * records by. Note that when you pass a query object you'll have to use + * the 2nd arg of the method to modify the entity data before saving. * @param callable|null $callback A callback that will be invoked for newly * created entities. This callback will be called *before* the entity * is persisted. From ee28b9ff46b19eebb86a0404de8c2fdb17f7327a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Wed, 27 Apr 2016 01:55:35 +0200 Subject: [PATCH 0672/2059] #8703 Adding a validCount rule. --- RulesChecker.php | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/RulesChecker.php b/RulesChecker.php index 330a658a..86ab983a 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -16,6 +16,7 @@ use Cake\Datasource\RulesChecker as BaseRulesChecker; use Cake\ORM\Rule\ExistsIn; +use Cake\ORM\Rule\ValidCount; use Cake\ORM\Rule\IsUnique; /** @@ -89,4 +90,27 @@ public function existsIn($field, $table, $message = null) $errorField = is_string($field) ? $field : current($field); return $this->_addError(new ExistsIn($field, $table), '_existsIn', compact('errorField', 'message')); } + + /** + * Validates the count of associated records. + * + * @param The entity property that contains the associations. + * @param int The expected count. + * @param string The operator for the count comparison. + * @param string|null $message The error message to show in case the rule does not pass. + * @return callable + */ + public function validCount($field, $count = 0, $operator = '>', $message = null) + { + if (!$message) { + if ($this->_useI18n) { + $message = __d('cake', 'The count does not match {0}{1}', [$operator, $count]); + } else { + $message = 'This value does not exist'; + } + } + + $errorField = $field; + return $this->_addError(new ValidCount($field, $count, $operator), '_validCount', compact('count', 'operator', 'errorField', 'message')); + } } From 022e8ab97c12e161cf3824391569dfdd1e5a6e29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Wed, 27 Apr 2016 10:12:29 +0200 Subject: [PATCH 0673/2059] Adding the ValidCount rule class. --- Rule/ValidCount.php | 70 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 Rule/ValidCount.php diff --git a/Rule/ValidCount.php b/Rule/ValidCount.php new file mode 100644 index 00000000..f0d08873 --- /dev/null +++ b/Rule/ValidCount.php @@ -0,0 +1,70 @@ +_field = $field; + } + + /** + * Performs the count check + * + * @param \Cake\Datasource\EntityInterface $entity The entity from where to extract the fields + * @param array $options Options passed to the check. + * @return bool + */ + public function __invoke(EntityInterface $entity, array $options) + { + $value = $entity->{$this->_field}; + if (!is_array($value)) { + return false; + } + + if (isset($value['_ids'])) { + if (!is_array($value['_ids'])) { + return false; + } + $count = count($value['_ids']); + } else { + $count = count($value); + } + + return Validation::comparison($count, $options['operator'], $options['count']); + } +} From 2a69a6a60eda304dbdf9b3d2210ecfa5b0bde516 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Wed, 27 Apr 2016 10:39:19 +0200 Subject: [PATCH 0674/2059] Changing the non-i18n validation message for validCount() --- RulesChecker.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RulesChecker.php b/RulesChecker.php index 86ab983a..08f805cb 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -106,7 +106,7 @@ public function validCount($field, $count = 0, $operator = '>', $message = null) if ($this->_useI18n) { $message = __d('cake', 'The count does not match {0}{1}', [$operator, $count]); } else { - $message = 'This value does not exist'; + $message = sprintf('The count does not match %s%d', $operator, $count); } } From 3fa578385245dc2d31ae8546dd8c4774d2b19dfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Wed, 27 Apr 2016 12:43:39 +0200 Subject: [PATCH 0675/2059] Removing the count validation from the table object It's now in a rule. --- Table.php | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/Table.php b/Table.php index da80501d..62b02388 100644 --- a/Table.php +++ b/Table.php @@ -2305,28 +2305,4 @@ public function __debugInfo() 'connectionName' => $conn ? $conn->configName() : null ]; } - - /** - * Validates that a belongsToMany or hasMany assoc has at least one entry. - * - * This can be used to validate any other array as well. - * - * @param mixed $value - * @return boolean - */ - public function validateCount($value, $expectedCount = 0, $operator = '>') - { - if (!is_array($value)) { - return false; - } - if (isset($value['_ids'])) { - if (!is_array($value['_ids'])) { - return false; - } - $count = count($value['_ids']); - } else { - $count = count($value); - } - return Validation::comparison($count, $operator, $expectedCount); - } } From 0dee40ffec937debcd163b5dc9e63bf0076876f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Wed, 27 Apr 2016 12:43:59 +0200 Subject: [PATCH 0676/2059] phpcs and doc block fixes. --- Rule/ValidCount.php | 4 ++-- RulesChecker.php | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Rule/ValidCount.php b/Rule/ValidCount.php index f0d08873..d5ce124f 100644 --- a/Rule/ValidCount.php +++ b/Rule/ValidCount.php @@ -26,7 +26,7 @@ class ValidCount { /** - * The list of fields to check + * The field to check * * @var array */ @@ -35,7 +35,7 @@ class ValidCount /** * Constructor. * - * @param string|array $fields The field to check the count on. + * @param string $field The field to check the count on. */ public function __construct($field) { diff --git a/RulesChecker.php b/RulesChecker.php index 08f805cb..e46c8f07 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -16,8 +16,8 @@ use Cake\Datasource\RulesChecker as BaseRulesChecker; use Cake\ORM\Rule\ExistsIn; -use Cake\ORM\Rule\ValidCount; use Cake\ORM\Rule\IsUnique; +use Cake\ORM\Rule\ValidCount; /** * ORM flavoured rules checker. @@ -94,9 +94,9 @@ public function existsIn($field, $table, $message = null) /** * Validates the count of associated records. * - * @param The entity property that contains the associations. - * @param int The expected count. - * @param string The operator for the count comparison. + * @param string $field The field to check the count on. + * @param int $count The expected count. + * @param string $operator The operator for the count comparison. * @param string|null $message The error message to show in case the rule does not pass. * @return callable */ From fe747528b8bb8839970002d351a2c520ced28b0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Fri, 29 Apr 2016 22:22:30 +0200 Subject: [PATCH 0677/2059] Removing the check for _ids from the ValidCount rule. --- Rule/ValidCount.php | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/Rule/ValidCount.php b/Rule/ValidCount.php index d5ce124f..b7af246c 100644 --- a/Rule/ValidCount.php +++ b/Rule/ValidCount.php @@ -9,7 +9,7 @@ * * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) * @link http://cakephp.org CakePHP(tm) Project - * @since 3.0.0 + * @since 3.2.9 * @license http://www.opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM\Rule; @@ -17,7 +17,6 @@ use Cake\Datasource\EntityInterface; use Cake\ORM\Association; use Cake\Validation\Validation; -use RuntimeException; /** * Validates the count of associated records. @@ -56,15 +55,6 @@ public function __invoke(EntityInterface $entity, array $options) return false; } - if (isset($value['_ids'])) { - if (!is_array($value['_ids'])) { - return false; - } - $count = count($value['_ids']); - } else { - $count = count($value); - } - - return Validation::comparison($count, $options['operator'], $options['count']); + return Validation::comparison(count($value), $options['operator'], $options['count']); } } From 4f78a43abff215b8850fea010b024d2f1d133223 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sat, 7 May 2016 18:50:01 +0200 Subject: [PATCH 0678/2059] Adding more tests and a check on \Countable for ValidCount --- Rule/ValidCount.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Rule/ValidCount.php b/Rule/ValidCount.php index b7af246c..5e7779da 100644 --- a/Rule/ValidCount.php +++ b/Rule/ValidCount.php @@ -14,6 +14,7 @@ */ namespace Cake\ORM\Rule; +use Countable; use Cake\Datasource\EntityInterface; use Cake\ORM\Association; use Cake\Validation\Validation; @@ -44,14 +45,14 @@ public function __construct($field) /** * Performs the count check * - * @param \Cake\Datasource\EntityInterface $entity The entity from where to extract the fields + * @param \Cake\Datasource\EntityInterface $entity The entity from where to extract the fields. * @param array $options Options passed to the check. - * @return bool + * @return bool True if successful, else false. */ public function __invoke(EntityInterface $entity, array $options) { $value = $entity->{$this->_field}; - if (!is_array($value)) { + if (!is_array($value) && !$value instanceof Countable) { return false; } From d04e08efcfebf899e7210886d7fa86b17c9e6b01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Arellano?= Date: Sun, 8 May 2016 15:12:34 -0300 Subject: [PATCH 0679/2059] Force ExistsIn rule in new Entiy even with a not dirty field --- Rule/ExistsIn.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 0c051fd1..e41d8805 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -94,7 +94,7 @@ public function __invoke(EntityInterface $entity, array $options) return true; } - if (!$entity->extract($this->_fields, true)) { + if (!$entity->extract($this->_fields, true) && !$entity->isNew()) { return true; } From ded42560431ed650017a84ff97574de16c2479d5 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 9 May 2016 22:13:55 -0400 Subject: [PATCH 0680/2059] Fix PHPCS error. --- Rule/ValidCount.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rule/ValidCount.php b/Rule/ValidCount.php index 5e7779da..4a47d7f5 100644 --- a/Rule/ValidCount.php +++ b/Rule/ValidCount.php @@ -14,10 +14,10 @@ */ namespace Cake\ORM\Rule; -use Countable; use Cake\Datasource\EntityInterface; use Cake\ORM\Association; use Cake\Validation\Validation; +use Countable; /** * Validates the count of associated records. From 3e26730868b345057262f54a61dc74cf6fbbe11d Mon Sep 17 00:00:00 2001 From: Jesper Skytte Hansen Date: Tue, 10 May 2016 09:26:26 +0200 Subject: [PATCH 0681/2059] Reset ValueBinder in SubQuery --- Association/SelectableAssociationTrait.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Association/SelectableAssociationTrait.php b/Association/SelectableAssociationTrait.php index 5a94e698..23042a01 100644 --- a/Association/SelectableAssociationTrait.php +++ b/Association/SelectableAssociationTrait.php @@ -222,6 +222,7 @@ protected function _buildSubquery($query) $filterQuery->mapReduce(null, null, true); $filterQuery->formatResults(null, true); $filterQuery->contain([], true); + $filterQuery->valueBinder()->resetCount(); if (!$filterQuery->clause('limit')) { $filterQuery->limit(null); From b62ec3ca1e630e5abc7f91d366af26f5932e4e31 Mon Sep 17 00:00:00 2001 From: Jesper Skytte Hansen Date: Tue, 10 May 2016 11:24:57 +0200 Subject: [PATCH 0682/2059] Change ValueBinder reset + add unit test I've changed the ValueBinder reset from using ->resetCount() to setting a completely new ValueBinder, as the resetCount was only respected when the select sub function was on another query than the master query. I've also added unit tests, that fails before adding the fix and works now. --- Association/SelectableAssociationTrait.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Association/SelectableAssociationTrait.php b/Association/SelectableAssociationTrait.php index 23042a01..f77e9b2e 100644 --- a/Association/SelectableAssociationTrait.php +++ b/Association/SelectableAssociationTrait.php @@ -16,6 +16,7 @@ use Cake\Database\Expression\IdentifierExpression; use Cake\Database\Expression\TupleComparison; +use Cake\Database\ValueBinder; use InvalidArgumentException; /** @@ -222,7 +223,7 @@ protected function _buildSubquery($query) $filterQuery->mapReduce(null, null, true); $filterQuery->formatResults(null, true); $filterQuery->contain([], true); - $filterQuery->valueBinder()->resetCount(); + $filterQuery->valueBinder(new ValueBinder()); if (!$filterQuery->clause('limit')) { $filterQuery->limit(null); From cb9b5fa1ff01588ee230add2e1205147f5a96e29 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 10 May 2016 22:44:51 -0400 Subject: [PATCH 0683/2059] Fix IsUnique rule & null values IsUnique rules across multiple fields with nulls should be detected correctly. With this change `IS NULL` is emitted instead of `= NULL`. Refs #8803 --- Rule/IsUnique.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index 596168b3..92353306 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -77,7 +77,7 @@ protected function _alias($alias, $conditions) { $aliased = []; foreach ($conditions as $key => $value) { - $aliased["$alias.$key"] = $value; + $aliased["$alias.$key IS"] = $value; } return $aliased; } From 4d4b6bcc474bc255b8856fd89e3daadbfe3ca3c4 Mon Sep 17 00:00:00 2001 From: Sebastian Choina Date: Sat, 14 May 2016 12:52:07 +0200 Subject: [PATCH 0684/2059] Throw exception if marshaller encounter missing/invalid associations during entity marshalling --- Exception/MissingAssociationException.php | 24 +++++++++++++++++++++++ Marshaller.php | 6 ++++++ 2 files changed, 30 insertions(+) create mode 100644 Exception/MissingAssociationException.php diff --git a/Exception/MissingAssociationException.php b/Exception/MissingAssociationException.php new file mode 100644 index 00000000..dea6795b --- /dev/null +++ b/Exception/MissingAssociationException.php @@ -0,0 +1,24 @@ +_table->association($key); if ($assoc) { $map[$assoc->property()] = ['association' => $assoc] + $nested + ['associated' => []]; + } else { + if (substr($key, 0, 1) !== "_") { // if $key is a special underscored field eg _ids or _joinData + throw new MissingAssociationException([$this->_table->alias(), $key]); + } + } } return $map; From 57f0297d8a147adc7e9bdd308a204608e246fb87 Mon Sep 17 00:00:00 2001 From: Sebastian Choina Date: Sun, 15 May 2016 18:09:06 +0200 Subject: [PATCH 0685/2059] Code refactoring due to comments @ https://github.com/cakephp/cakephp/pull/8824#discussion_r63280504 --- Exception/MissingAssociationException.php | 24 ----------------------- Marshaller.php | 10 +++++----- 2 files changed, 5 insertions(+), 29 deletions(-) delete mode 100644 Exception/MissingAssociationException.php diff --git a/Exception/MissingAssociationException.php b/Exception/MissingAssociationException.php deleted file mode 100644 index dea6795b..00000000 --- a/Exception/MissingAssociationException.php +++ /dev/null @@ -1,24 +0,0 @@ - association names. * * @param array $options List of options containing the 'associated' key. + * @throws \InvalidArgumentException * @return array */ protected function _buildPropertyMap($options) @@ -78,11 +79,10 @@ protected function _buildPropertyMap($options) $assoc = $this->_table->association($key); if ($assoc) { $map[$assoc->property()] = ['association' => $assoc] + $nested + ['associated' => []]; - } else { - if (substr($key, 0, 1) !== "_") { // if $key is a special underscored field eg _ids or _joinData - throw new MissingAssociationException([$this->_table->alias(), $key]); - } - + continue; + } + if (substr($key, 0, 1) !== "_") { // if $key is not a special underscored field eg _ids or _joinData + throw new \InvalidArgumentException(vsprintf("%s is not associated with %s",[$this->_table->alias(), $key])); } } return $map; From 0cfb9b0a33a91e5a6669e0dc3153eca3ccb7df7b Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 15 May 2016 22:21:50 -0400 Subject: [PATCH 0686/2059] Remove duplicate code. This code was causing conditions ending in `IS IS`. Remove it as we don't need it now. --- Rule/IsUnique.php | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index 3450d387..5484a24d 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -57,7 +57,7 @@ public function __invoke(EntityInterface $entity, array $options) if (isset($options['allowMultipleNulls'])) { $allowMultipleNulls = $options['allowMultipleNulls'] === true ? true : false; } - + $alias = $options['repository']->alias(); $conditions = $this->_alias($alias, $entity->extract($this->_fields)); if ($entity->isNew() === false) { @@ -68,15 +68,6 @@ public function __invoke(EntityInterface $entity, array $options) } } - if (!$allowMultipleNulls) { - foreach ($conditions as $key => $value) { - if ($value === null) { - $conditions[$key . ' IS'] = $value; - unset($conditions[$key]); - } - } - } - return !$options['repository']->exists($conditions); } From 69ee0461cce6445afe2af5767eb768ecdab42d98 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 17 May 2016 21:50:38 -0400 Subject: [PATCH 0687/2059] Fix formatting and spacing. Refs #8824 --- Marshaller.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index b8c2537f..1a03e872 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -20,7 +20,6 @@ use Cake\Database\Type; use Cake\Datasource\EntityInterface; use Cake\Datasource\InvalidPropertyInterface; -use Cake\ORM\Exception\MissingAssociationException; use RuntimeException; /** @@ -81,8 +80,14 @@ protected function _buildPropertyMap($options) $map[$assoc->property()] = ['association' => $assoc] + $nested + ['associated' => []]; continue; } - if (substr($key, 0, 1) !== "_") { // if $key is not a special underscored field eg _ids or _joinData - throw new \InvalidArgumentException(vsprintf("%s is not associated with %s",[$this->_table->alias(), $key])); + // If the key is not a special field like _ids or _joinData + // it is a missing association that we should error on. + if (substr($key, 0, 1) !== "_") { + throw new \InvalidArgumentException(sprintf( + "'%s' is not associated with '%s'", + $this->_table->alias(), + $key + )); } } return $map; From f995021f851a6cab61f2fdec6857fe9a6c7fab42 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 19 May 2016 19:34:49 -0400 Subject: [PATCH 0688/2059] Revert "Force ExistsIn rule in new Entity even with a not dirty field" --- Rule/ExistsIn.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index e41d8805..0c051fd1 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -94,7 +94,7 @@ public function __invoke(EntityInterface $entity, array $options) return true; } - if (!$entity->extract($this->_fields, true) && !$entity->isNew()) { + if (!$entity->extract($this->_fields, true)) { return true; } From 3f19952193ae0a3a776a36323ada097540d23c6d Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 22 May 2016 21:52:35 -0400 Subject: [PATCH 0689/2059] Revert changes made in #8807 While the generated SQL looks incorrect, it is actually what we want. Because SQL always treats NULL != NULL, the generated `field = NULL` conditions emulate how a SQL index would actually work. We'll need a separate rule to handle more strict unique checks. Refs #8873 --- Rule/IsUnique.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index 92353306..1edd8927 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -69,6 +69,9 @@ public function __invoke(EntityInterface $entity, array $options) /** * Add a model alias to all the keys in a set of conditions. * + * Null values will be omitted from the generated conditions, + * as SQL UNIQUE indexes treat `NULL != NULL` + * * @param string $alias The alias to add. * @param array $conditions The conditions to alias. * @return array @@ -77,7 +80,7 @@ protected function _alias($alias, $conditions) { $aliased = []; foreach ($conditions as $key => $value) { - $aliased["$alias.$key IS"] = $value; + $aliased["$alias.$key"] = $value; } return $aliased; } From b38e2dfed8f1ce2fb7915539b8c1dae2316369dd Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 24 May 2016 13:12:51 +0200 Subject: [PATCH 0690/2059] Merge prefixed data in instead of overwriting. By merging we can retain any top level data that the developer added before marshalling happened. This helps prevent scenarios where a bad person adds prefixed data to a payload to circumvent manually assigned data before marshalling. --- Marshaller.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index 7f6e1707..a4e065d4 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -216,7 +216,8 @@ protected function _prepareDataAndOptions($data, $options) $tableName = $this->_table->alias(); if (isset($data[$tableName])) { - $data = $data[$tableName]; + $data += $data[$tableName]; + unset($data[$tableName]); } $data = new ArrayObject($data); From 305f6d33f12719058d668e3f5684b682c3dc207c Mon Sep 17 00:00:00 2001 From: Frederik Bauer Date: Tue, 24 May 2016 18:21:28 +0200 Subject: [PATCH 0691/2059] fixes wrong foreignKey to find the association was using the foreignKey for the source table ("foreignKey") and not the foreignKey for the target table ("targetForeignKey") --- Association/BelongsToMany.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 538adb66..337f7cb3 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -894,7 +894,7 @@ public function find($type = null, array $options = []) $belongsTo = $this->junction()->association($this->target()->alias()); $conditions = $belongsTo->_joinCondition([ - 'foreignKey' => $this->foreignKey() + 'foreignKey' => $this->targetForeignKey() ]); $conditions += $this->junctionConditions(); return $this->_appendJunctionJoin($query, $conditions); From f45d5d99123c888b90df380eb74d5cbfddc89fbb Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 27 May 2016 11:25:26 +0200 Subject: [PATCH 0692/2059] Fix failing tests in IsUnique rule. A while back the default behavior was changed in 3.2.x to make IsUnique strict about nulls. When that code was merged into `3.next` I deleted the 'duplicate' code. Then that change was reverted in `master` leaving these tests failing. This restores the behavior and fixes the failing tests. --- Rule/IsUnique.php | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index b356b1c6..663e78f9 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -52,17 +52,14 @@ public function __invoke(EntityInterface $entity, array $options) if (!$entity->extract($this->_fields, true)) { return true; } - - $allowMultipleNulls = true; - if (isset($options['allowMultipleNulls'])) { - $allowMultipleNulls = $options['allowMultipleNulls'] === true ? true : false; - } + $options += ['allowMultipleNulls' => true]; + $allowMultipleNulls = $options['allowMultipleNulls']; $alias = $options['repository']->alias(); - $conditions = $this->_alias($alias, $entity->extract($this->_fields)); + $conditions = $this->_alias($alias, $entity->extract($this->_fields), $allowMultipleNulls); if ($entity->isNew() === false) { $keys = (array)$options['repository']->primaryKey(); - $keys = $this->_alias($alias, $entity->extract($keys)); + $keys = $this->_alias($alias, $entity->extract($keys), $allowMultipleNulls); if (array_filter($keys, 'strlen')) { $conditions['NOT'] = $keys; } @@ -79,13 +76,18 @@ public function __invoke(EntityInterface $entity, array $options) * * @param string $alias The alias to add. * @param array $conditions The conditions to alias. + * @param bool $multipleNulls Whether or not to allow multiple nulls. * @return array */ - protected function _alias($alias, $conditions) + protected function _alias($alias, $conditions, $multipleNulls) { $aliased = []; foreach ($conditions as $key => $value) { - $aliased["$alias.$key"] = $value; + if ($multipleNulls) { + $aliased["$alias.$key"] = $value; + } else { + $aliased["$alias.$key IS"] = $value; + } } return $aliased; } From 4d7fdeb7d8d9cf499f5f2b8aa519b57a83c302f2 Mon Sep 17 00:00:00 2001 From: Jonas Date: Mon, 30 May 2016 08:13:36 +0000 Subject: [PATCH 0693/2059] Add allowPartialNulls flag to existsIn that matches SQLs behavior of composite foreign keys with nullable nulls - set 'allowPartialNulls' true to accept composite foreign keys where one or more nullable columns are null. Ths Retargets #8903 to 3.next - in a clean way --- Rule/ExistsIn.php | 39 ++++++++++++++++++++++++++++++++++++++- RulesChecker.php | 18 +++++++++++++++--- 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 286075be..267a6b12 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -39,15 +39,29 @@ class ExistsIn */ protected $_repository; + /** + * Options for the constructor + * + * @var array + */ + protected $_options = []; + /** * Constructor. * + * Available option for $options is 'allowPartialNulls' flag. + * Set to true to accept composite foreign keys where one or more nullable columns are null.' + * * @param string|array $fields The field or fields to check existence as primary key. * @param object|string $repository The repository where the field will be looked for, * or the association name for the repository. + * @param array $options The options that modify the rules behavior. */ - public function __construct($fields, $repository) + public function __construct($fields, $repository, array $options = []) { + $options += ['allowPartialNulls' => false]; + $this->_options = $options; + $this->_fields = (array)$fields; $this->_repository = $repository; } @@ -96,6 +110,11 @@ public function __invoke(EntityInterface $entity, array $options) return true; } + if ($this->_options['allowPartialNulls'] === true + && $this->_checkPartialSchemaNulls($entity, $source) === true + ) { + return true; + } if ($this->_fieldsAreNull($entity, $source)) { return true; } @@ -129,4 +148,22 @@ protected function _fieldsAreNull($entity, $source) } return $nulls === count($this->_fields); } + + /** + * Check whether there are nullable nulls in at least one part of the foreign key. + * + * @param \Cake\Datasource\EntityInterface $entity The entity to check. + * @param \Cake\ORM\Table $source The table to use schema from. + * @return bool + */ + protected function _checkPartialSchemaNulls($entity, $source) + { + $schema = $source->schema(); + foreach ($this->_fields as $field) { + if ($schema->isNullable($field) === true && $entity->get($field) === null) { + return true; + } + } + return false; + } } diff --git a/RulesChecker.php b/RulesChecker.php index e46c8f07..2eadba6e 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -71,14 +71,26 @@ public function isUnique(array $fields, $message = null) * $rules->add($rules->existsIn('site_id', new SitesTable(), 'Invalid Site')); * ``` * + * Available $options are error 'message' and 'allowPartialNulls' flag. + * 'message' sets a custom error message. + * Set 'allowPartialNulls' to true to accept composite foreign keys where one or more nullable columns are null.' + * * @param string|array $field The field or list of fields to check for existence by * primary key lookup in the other table. * @param object|string $table The table name where the fields existence will be checked. - * @param string|null $message The error message to show in case the rule does not pass. + * @param array|string|null $options List of options or error message string to show in case the rule does not pass. * @return callable */ - public function existsIn($field, $table, $message = null) + public function existsIn($field, $table, $options = null) { + if (is_string($options)) { + $options = ['message' => $options]; + } + + $options = (array)$options + ['message' => null]; + $message = $options['message']; + unset($options['message']); + if (!$message) { if ($this->_useI18n) { $message = __d('cake', 'This value does not exist'); @@ -88,7 +100,7 @@ public function existsIn($field, $table, $message = null) } $errorField = is_string($field) ? $field : current($field); - return $this->_addError(new ExistsIn($field, $table), '_existsIn', compact('errorField', 'message')); + return $this->_addError(new ExistsIn($field, $table, $options), '_existsIn', compact('errorField', 'message')); } /** From 91c735f3e8b224dc217f0a5f0768dc3615cd8a59 Mon Sep 17 00:00:00 2001 From: Jonas Date: Mon, 30 May 2016 08:16:51 +0000 Subject: [PATCH 0694/2059] remove typo --- Rule/ExistsIn.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 267a6b12..8a2bc914 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -50,7 +50,7 @@ class ExistsIn * Constructor. * * Available option for $options is 'allowPartialNulls' flag. - * Set to true to accept composite foreign keys where one or more nullable columns are null.' + * Set to true to accept composite foreign keys where one or more nullable columns are null. * * @param string|array $fields The field or fields to check existence as primary key. * @param object|string $repository The repository where the field will be looked for, From e749411ac5f820554711d527351f08ee32493aa0 Mon Sep 17 00:00:00 2001 From: Jonas Date: Mon, 30 May 2016 08:41:00 +0000 Subject: [PATCH 0695/2059] shorter boolean check --- Rule/ExistsIn.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 8a2bc914..cbcdfeb6 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -160,7 +160,7 @@ protected function _checkPartialSchemaNulls($entity, $source) { $schema = $source->schema(); foreach ($this->_fields as $field) { - if ($schema->isNullable($field) === true && $entity->get($field) === null) { + if ($schema->isNullable($field) && $entity->get($field) === null) { return true; } } From 66911d64eb112828bb98fffc4952029476dc00e9 Mon Sep 17 00:00:00 2001 From: Jonas Date: Mon, 30 May 2016 08:49:56 +0000 Subject: [PATCH 0696/2059] shorter boolean checks --- Rule/ExistsIn.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index cbcdfeb6..17fd9e5f 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -110,9 +110,7 @@ public function __invoke(EntityInterface $entity, array $options) return true; } - if ($this->_options['allowPartialNulls'] === true - && $this->_checkPartialSchemaNulls($entity, $source) === true - ) { + if ($this->_options['allowPartialNulls'] && $this->_checkPartialSchemaNulls($entity, $source)) { return true; } if ($this->_fieldsAreNull($entity, $source)) { From 80eb4b5b11e6a87170ad13d96c06092be084756d Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 30 May 2016 20:04:47 +0200 Subject: [PATCH 0697/2059] Make allowMultipleNulls a constructor argument. Previously this option was implemented as an additional context parameter. That seemed a bit strange as the option could and probably should be a constructor argument. Given that the option is only relevant to the IsUnique rule there is no reason for the RulesChecker to be handling it. --- Rule/IsUnique.php | 13 ++++++++++--- RulesChecker.php | 11 +++++++++-- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index 663e78f9..a156c2e9 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -29,14 +29,22 @@ class IsUnique */ protected $_fields; + /** + * The options to use. + * + * @var array + */ + protected $_options; + /** * Constructor. * * @param array $fields The list of fields to check uniqueness for */ - public function __construct(array $fields) + public function __construct(array $fields, array $options = []) { $this->_fields = $fields; + $this->_options = $options + ['allowMultipleNulls' => true]; } /** @@ -52,8 +60,7 @@ public function __invoke(EntityInterface $entity, array $options) if (!$entity->extract($this->_fields, true)) { return true; } - $options += ['allowMultipleNulls' => true]; - $allowMultipleNulls = $options['allowMultipleNulls']; + $allowMultipleNulls = $this->_options['allowMultipleNulls']; $alias = $options['repository']->alias(); $conditions = $this->_alias($alias, $entity->extract($this->_fields), $allowMultipleNulls); diff --git a/RulesChecker.php b/RulesChecker.php index e46c8f07..ec658c35 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -40,11 +40,18 @@ class RulesChecker extends BaseRulesChecker * ``` * * @param array $fields The list of fields to check for uniqueness. - * @param string|null $message The error message to show in case the rule does not pass. + * @param string|array|null $message The error message to show in case the rule does not pass. Can + * also be an array of options. When an array, the 'message' key can be used to provide a message. * @return callable */ public function isUnique(array $fields, $message = null) { + $options = []; + if (is_array($message)) { + $options = $message + ['message' => null]; + $message = $options['message']; + unset($options['message']); + } if (!$message) { if ($this->_useI18n) { $message = __d('cake', 'This value is already in use'); @@ -54,7 +61,7 @@ public function isUnique(array $fields, $message = null) } $errorField = current($fields); - return $this->_addError(new IsUnique($fields), '_isUnique', compact('errorField', 'message')); + return $this->_addError(new IsUnique($fields, $options), '_isUnique', compact('errorField', 'message')); } /** From ae196ec1ef155e2aacdc319ffccb5b183dee6bc8 Mon Sep 17 00:00:00 2001 From: ndm2 Date: Tue, 31 May 2016 16:12:57 +0200 Subject: [PATCH 0698/2059] Consistently read the PK from the table (instead of the schema). --- Marshaller.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index a4e065d4..9c2800e8 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -119,7 +119,7 @@ public function one(array $data, array $options = []) $propertyMap = $this->_buildPropertyMap($options); $schema = $this->_table->schema(); - $primaryKey = $schema->primaryKey(); + $primaryKey = (array)$this->_table->primaryKey(); $entityClass = $this->_table->entityClass(); $entity = new $entityClass(); $entity->source($this->_table->registryAlias()); @@ -314,7 +314,7 @@ protected function _belongsToMany(Association $assoc, array $data, $options = [] $data = array_values($data); $target = $assoc->target(); - $primaryKey = array_flip($target->schema()->primaryKey()); + $primaryKey = array_flip((array)$target->primaryKey()); $records = $conditions = []; $primaryCount = count($primaryKey); $conditions = []; From 226c97b690edcf2a110e8448df726cf5a403d1ad Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 31 May 2016 20:21:31 -0400 Subject: [PATCH 0699/2059] Fix PHPCS / docs errors. --- Rule/IsUnique.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index a156c2e9..1cb1cc7f 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -39,7 +39,14 @@ class IsUnique /** * Constructor. * + * ### Options + * + * - `allowMultipleNulls` Set to false to disallow multiple null values in + * multi-column unique rules. By default this is `true` to emulate how SQL UNIQUE + * keys work. + * * @param array $fields The list of fields to check uniqueness for + * @param array $options The additional options for this rule. */ public function __construct(array $fields, array $options = []) { @@ -51,8 +58,8 @@ public function __construct(array $fields, array $options = []) * Performs the uniqueness check * * @param \Cake\Datasource\EntityInterface $entity The entity from where to extract the fields + * where the `repository` key is required. * @param array $options Options passed to the check, - * where the `repository` key is required. * @return bool */ public function __invoke(EntityInterface $entity, array $options) From a81127be3f7c398cce197cc0a479551d55bc27d2 Mon Sep 17 00:00:00 2001 From: Jonas Date: Fri, 3 Jun 2016 19:03:03 +0000 Subject: [PATCH 0700/2059] make sure error messages are tested, make sure that object mutation does not screw up tests --- RulesChecker.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/RulesChecker.php b/RulesChecker.php index 2eadba6e..c5802ef1 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -78,19 +78,19 @@ public function isUnique(array $fields, $message = null) * @param string|array $field The field or list of fields to check for existence by * primary key lookup in the other table. * @param object|string $table The table name where the fields existence will be checked. - * @param array|string|null $options List of options or error message string to show in case the rule does not pass. + * @param @param string|array|null $message The error message to show in case the rule does not pass. Can + * also be an array of options. When an array, the 'message' key can be used to provide a message. * @return callable */ - public function existsIn($field, $table, $options = null) + public function existsIn($field, $table, $message = null) { - if (is_string($options)) { - $options = ['message' => $options]; + $options = []; + if (is_array($message)) { + $options = $message + ['message' => null]; + $message = $options['message']; + unset($options['message']); } - $options = (array)$options + ['message' => null]; - $message = $options['message']; - unset($options['message']); - if (!$message) { if ($this->_useI18n) { $message = __d('cake', 'This value does not exist'); From e33b097ae52037dd0de1e327cef3290f276c8e70 Mon Sep 17 00:00:00 2001 From: ndm2 Date: Sat, 11 Jun 2016 15:34:07 +0200 Subject: [PATCH 0701/2059] Mention `finder` option in the `Query::contain` docs. --- Query.php | 47 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/Query.php b/Query.php index 14c97e22..c21ffd0e 100644 --- a/Query.php +++ b/Query.php @@ -289,20 +289,37 @@ public function eagerLoader(EagerLoader $instance = null) * Each association might define special options when eager loaded, the allowed * options that can be set per association are: * - * - foreignKey: Used to set a different field to match both tables, if set to false + * - `foreignKey`: Used to set a different field to match both tables, if set to false * no join conditions will be generated automatically. `false` can only be used on * joinable associations and cannot be used with hasMany or belongsToMany associations. - * - fields: An array with the fields that should be fetched from the association - * - queryBuilder: Equivalent to passing a callable instead of an options array + * - `fields`: An array with the fields that should be fetched from the association. + * - `finder`: The finder to use when loading associated records. Either the name of the + * finder as a string, or an array to define options to pass to the finder. + * - `queryBuilder`: Equivalent to passing a callable instead of an options array. * * ### Example: * * ``` * // Set options for the hasMany articles that will be eagerly loaded for an author * $query->contain([ - * 'Articles' => [ - * 'fields' => ['title', 'author_id'] - * ] + * 'Articles' => [ + * 'fields' => ['title', 'author_id'] + * ] + * ]); + * ``` + * + * Finders can be configured to use options. + * + * ``` + * // Retrieve translations for the articles, but only those for the `en` and `es` locales + * $query->contain([ + * 'Articles' => [ + * 'finder' => [ + * 'translations' => [ + * 'locales' => ['en', 'es'] + * ] + * ] + * ] * ]); * ``` * @@ -312,12 +329,12 @@ public function eagerLoader(EagerLoader $instance = null) * ``` * // Use special join conditions for getting an Articles's belongsTo 'authors' * $query->contain([ - * 'Authors' => [ - * 'foreignKey' => false, - * 'queryBuilder' => function ($q) { - * return $q->where(...); // Add full filtering conditions - * } - * ] + * 'Authors' => [ + * 'foreignKey' => false, + * 'queryBuilder' => function ($q) { + * return $q->where(...); // Add full filtering conditions + * } + * ] * ]); * ``` * @@ -325,11 +342,11 @@ public function eagerLoader(EagerLoader $instance = null) * with the list of previously configured associations to be contained in the * result. * - * If called with an empty first argument and $override is set to true, the + * If called with an empty first argument and `$override` is set to true, the * previous list will be emptied. * - * @param array|string|null $associations list of table aliases to be queried - * @param bool $override whether override previous list with the one passed + * @param array|string|null $associations List of table aliases to be queried. + * @param bool $override Whether override previous list with the one passed * defaults to merging previous list with the new one. * @return array|$this */ From 46f4b171074b0a9d3bf23cce768808fd2843d972 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Mon, 13 Jun 2016 12:10:52 +0200 Subject: [PATCH 0702/2059] Improving the error message for associations with no PK. This makes it more clear what needs to be done to fix the issue. --- Association.php | 5 +++++ Association/BelongsTo.php | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/Association.php b/Association.php index 1bee9ce9..29cffdca 100644 --- a/Association.php +++ b/Association.php @@ -841,6 +841,11 @@ protected function _joinCondition($options) $bindingKey = (array)$this->bindingKey(); if (count($foreignKey) !== count($bindingKey)) { + if (empty($bindingKey)) { + $msg = 'The "%s" table does not define a primary key. Please set one.'; + throw new RuntimeException(sprintf($msg, $this->target()->table())); + } + $msg = 'Cannot match provided foreignKey for "%s", got "(%s)" but expected foreign key for "(%s)"'; throw new RuntimeException(sprintf( $msg, diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index a81b8c65..2eec4a24 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -157,6 +157,11 @@ protected function _joinCondition($options) $bindingKey = (array)$this->bindingKey(); if (count($foreignKey) !== count($bindingKey)) { + if (empty($bindingKey)) { + $msg = 'The "%s" table does not define a primary key. Please set one.'; + throw new RuntimeException(sprintf($msg, $this->target()->table())); + } + $msg = 'Cannot match provided foreignKey for "%s", got "(%s)" but expected foreign key for "(%s)"'; throw new RuntimeException(sprintf( $msg, From c2e44a3dcacdd52bb099217c2d47c82f2c3985e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20W=C3=BCrth?= Date: Wed, 15 Jun 2016 11:14:01 +0200 Subject: [PATCH 0703/2059] Remove space before object operator --- Behavior/TreeBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index c7c64bb6..9fd420e2 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -878,7 +878,7 @@ protected function _sync($shift, $dir, $conditions, $mark = false) $exp = $query->newExpr(); $movement = clone $exp; - $movement ->add($field)->add("$shift")->tieWith($dir); + $movement->add($field)->add("$shift")->tieWith($dir); $inverse = clone $exp; $movement = $mark ? From 3d027d3d1d769c7ff27f52ef92dc18fa886a997e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20W=C3=BCrth?= Date: Wed, 15 Jun 2016 12:36:29 +0200 Subject: [PATCH 0704/2059] Add space before operators --- Behavior/TreeBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 9fd420e2..2986e4b7 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -882,7 +882,7 @@ protected function _sync($shift, $dir, $conditions, $mark = false) $inverse = clone $exp; $movement = $mark ? - $inverse->add($movement)->tieWith('*')->add('-1'): + $inverse->add($movement)->tieWith('*')->add('-1') : $movement; $where = clone $exp; From 72827a9fa7bc97047bd2434269725628606681de Mon Sep 17 00:00:00 2001 From: Jonas Date: Fri, 17 Jun 2016 12:50:30 +0000 Subject: [PATCH 0705/2059] add convenient checks for not-nullable nulls for "allowPartialNulls" flag --- Rule/ExistsIn.php | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 17fd9e5f..6e2fb6ce 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -129,7 +129,7 @@ public function __invoke(EntityInterface $entity, array $options) } /** - * Check whether or not the entity fields are nullable and null. + * Checks whether or not the given entity fields are nullable and null. * * @param \Cake\Datasource\EntityInterface $entity The entity to check. * @param \Cake\ORM\Table $source The table to use schema from. @@ -148,7 +148,8 @@ protected function _fieldsAreNull($entity, $source) } /** - * Check whether there are nullable nulls in at least one part of the foreign key. + * Checks whether or not the give entity fields are null and map to schema NULL + * or are not null and map to schema NOT NULL. * * @param \Cake\Datasource\EntityInterface $entity The entity to check. * @param \Cake\ORM\Table $source The table to use schema from. @@ -158,10 +159,15 @@ protected function _checkPartialSchemaNulls($entity, $source) { $schema = $source->schema(); foreach ($this->_fields as $field) { - if ($schema->isNullable($field) && $entity->get($field) === null) { - return true; + $isNullable = $schema->isNullable($field); + $value = $entity->get($field); + if (!$isNullable && $value === null) { + return false; + } + if ($isNullable && $value !== null) { + return false; } } - return false; + return true; } } From d2b13a0947a82f5e6926b20c1a0e029772478d29 Mon Sep 17 00:00:00 2001 From: Jonas Date: Fri, 17 Jun 2016 12:54:36 +0000 Subject: [PATCH 0706/2059] fix docblocks --- RulesChecker.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RulesChecker.php b/RulesChecker.php index c5802ef1..6aa70ec4 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -73,12 +73,12 @@ public function isUnique(array $fields, $message = null) * * Available $options are error 'message' and 'allowPartialNulls' flag. * 'message' sets a custom error message. - * Set 'allowPartialNulls' to true to accept composite foreign keys where one or more nullable columns are null.' + * Set 'allowPartialNulls' to true to accept composite foreign keys where one or more nullable columns are null. * * @param string|array $field The field or list of fields to check for existence by * primary key lookup in the other table. * @param object|string $table The table name where the fields existence will be checked. - * @param @param string|array|null $message The error message to show in case the rule does not pass. Can + * @param string|array|null $message The error message to show in case the rule does not pass. Can * also be an array of options. When an array, the 'message' key can be used to provide a message. * @return callable */ From 46f437840e4a7f9f3b015c9c659380cb2936a6e7 Mon Sep 17 00:00:00 2001 From: Thinking Media Date: Mon, 20 Jun 2016 14:31:33 -0400 Subject: [PATCH 0707/2059] wrap findOrCreate with transactional --- Table.php | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/Table.php b/Table.php index 62b02388..d5b33591 100644 --- a/Table.php +++ b/Table.php @@ -1222,17 +1222,19 @@ public function get($primaryKey, $options = []) */ public function findOrCreate($search, callable $callback = null) { - $query = $this->find()->where($search); - $row = $query->first(); - if ($row) { - return $row; - } - $entity = $this->newEntity(); - $entity->set($search, ['guard' => false]); - if ($callback) { - $callback($entity); - } - return $this->save($entity) ?: $entity; + $this->connection()->transactional(function() use($search, $callback) { + $query = $this->find()->where($search); + $row = $query->first(); + if ($row) { + return $row; + } + $entity = $this->newEntity(); + $entity->set($search, ['guard' => false]); + if ($callback) { + $callback($entity); + } + return $this->save($entity) ?: $entity; + }); } /** From 5d1c6d4c9348b47761c0d0dc7042a52fe25514ae Mon Sep 17 00:00:00 2001 From: Thinking Media Date: Mon, 20 Jun 2016 14:33:51 -0400 Subject: [PATCH 0708/2059] Update Table.php --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index d5b33591..25f15488 100644 --- a/Table.php +++ b/Table.php @@ -1222,7 +1222,7 @@ public function get($primaryKey, $options = []) */ public function findOrCreate($search, callable $callback = null) { - $this->connection()->transactional(function() use($search, $callback) { + $this->connection()->transactional(function () use ($search, $callback) { $query = $this->find()->where($search); $row = $query->first(); if ($row) { From 5ea951bb5d6df82e40e08d267464d63e7a2763ba Mon Sep 17 00:00:00 2001 From: Thinking Media Date: Mon, 20 Jun 2016 17:47:32 -0400 Subject: [PATCH 0709/2059] don't update private variable unless repo exists. (#9009) --- Rule/ExistsIn.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 0c051fd1..ec96838a 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -64,16 +64,15 @@ public function __construct($fields, $repository) public function __invoke(EntityInterface $entity, array $options) { if (is_string($this->_repository)) { - $alias = $this->_repository; - $this->_repository = $options['repository']->association($alias); - - if (empty($this->_repository)) { + $repository = $options['repository']->association($this->_repository); + if (empty($repository)) { throw new RuntimeException(sprintf( "ExistsIn rule for '%s' is invalid. The '%s' association is not defined.", implode(', ', $this->_fields), - $alias + $this->_repository )); } + $this->_repository = $repository; } $source = $target = $this->_repository; From 1ecee37c30c22a79545cb64bc8cffa37c2d78995 Mon Sep 17 00:00:00 2001 From: mark_story Date: Mon, 20 Jun 2016 17:48:16 -0400 Subject: [PATCH 0710/2059] Don't use empty() Refs #9009 --- Rule/ExistsIn.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index ec96838a..7b40f490 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -65,7 +65,7 @@ public function __invoke(EntityInterface $entity, array $options) { if (is_string($this->_repository)) { $repository = $options['repository']->association($this->_repository); - if (empty($repository)) { + if (!$repository) { throw new RuntimeException(sprintf( "ExistsIn rule for '%s' is invalid. The '%s' association is not defined.", implode(', ', $this->_fields), From 01b9f0ef1203e33e9372048b74916d30587f22de Mon Sep 17 00:00:00 2001 From: Uli Staerk Date: Tue, 21 Jun 2016 13:31:23 +0200 Subject: [PATCH 0711/2059] bug: target and junction conditions not reset properly --- Association/BelongsToMany.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 337f7cb3..e4809a85 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -799,7 +799,7 @@ public function conditions($conditions = null) { if ($conditions !== null) { $this->_conditions = $conditions; - $this->_targetConditions = $this->_junctionConditions = []; + $this->_targetConditions = $this->_junctionConditions = null; } return $this->_conditions; } From e722e846cb0c561ff621035cd7db89e3c5da3e98 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 21 Jun 2016 20:28:23 -0400 Subject: [PATCH 0712/2059] Forward the current connection to the generated table. This helps make belongsToMany associations in plugins easier to work with, as the generated table will inherit the source table's connection, which is _generally_ what people will expect. Refs #9016 --- Association/BelongsToMany.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index e4809a85..dd28bf58 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -191,7 +191,10 @@ public function junction($table = null) $config = []; if (!$tableLocator->exists($tableAlias)) { - $config = ['table' => $tableName]; + $config = [ + 'table' => $tableName, + 'connection' => $this->source()->connection() + ]; } $table = $tableLocator->get($tableAlias, $config); } @@ -200,8 +203,8 @@ public function junction($table = null) if (is_string($table)) { $table = $tableLocator->get($table); } - $target = $this->target(); $source = $this->source(); + $target = $this->target(); $this->_generateSourceAssociations($table, $source); $this->_generateTargetAssociations($table, $source, $target); From 8dd6776a9a67b021d148351cef80533f40f616fb Mon Sep 17 00:00:00 2001 From: Mark Story Date: Wed, 22 Jun 2016 09:13:08 -0400 Subject: [PATCH 0713/2059] Only propagate the connection on automodels. Do some refactoring in BelongsToMany::junction() to reduce nesting depth. Refs #9018 --- Association/BelongsToMany.php | 38 +++++++++++++++++------------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index dd28bf58..56a3110b 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -14,6 +14,7 @@ */ namespace Cake\ORM\Association; +use Cake\Core\App; use Cake\Database\ExpressionInterface; use Cake\Datasource\EntityInterface; use Cake\ORM\Association; @@ -176,28 +177,27 @@ public function targetForeignKey($key = null) */ public function junction($table = null) { - $tableLocator = $this->tableLocator(); - - if ($table === null) { - if (!empty($this->_junctionTable)) { - return $this->_junctionTable; - } + if ($table === null && $this->_junctionTable) { + return $this->_junctionTable; + } - if (!empty($this->_through)) { - $table = $this->_through; - } else { - $tableName = $this->_junctionTableName(); - $tableAlias = Inflector::camelize($tableName); - - $config = []; - if (!$tableLocator->exists($tableAlias)) { - $config = [ - 'table' => $tableName, - 'connection' => $this->source()->connection() - ]; + $tableLocator = $this->tableLocator(); + if ($table === null && $this->_through) { + $table = $this->_through; + } elseif ($table === null) { + $tableName = $this->_junctionTableName(); + $tableAlias = Inflector::camelize($tableName); + + $config = []; + if (!$tableLocator->exists($tableAlias)) { + $config = ['table' => $tableName]; + + // Propagate the connection if we'll get an auto-model + if (!App::className($tableAlias, 'Model/Table', 'Table')) { + $config['connection'] = $this->source()->connection(); } - $table = $tableLocator->get($tableAlias, $config); } + $table = $tableLocator->get($tableAlias, $config); } if (is_string($table)) { From ba350013844319ebc460329217ba8be3ac765405 Mon Sep 17 00:00:00 2001 From: Walther Lalk Date: Wed, 22 Jun 2016 19:00:15 +0200 Subject: [PATCH 0714/2059] Association formatter should not invoke the entity get method --- Association.php | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Association.php b/Association.php index 29cffdca..be7e40c2 100644 --- a/Association.php +++ b/Association.php @@ -14,6 +14,7 @@ */ namespace Cake\ORM; +use Cake\Collection\Collection; use Cake\Core\ConventionsTrait; use Cake\Database\Expression\IdentifierExpression; use Cake\Datasource\EntityInterface; @@ -774,8 +775,20 @@ protected function _formatAssociationResults($query, $surrogate, $options) } $property = $options['propertyPath']; - $query->formatResults(function ($results) use ($formatters, $property) { - $extracted = $results->extract($property)->compile(); + $propertyPath = explode('.', $property); + $query->formatResults(function ($results) use ($formatters, $property, $propertyPath) { + $extracted = []; + foreach ($results as $result) { + foreach ($propertyPath as $propertyPathItem) { + if (!isset($result[$propertyPathItem])) { + $result = null; + break; + } + $result = $result[$propertyPathItem]; + } + $extracted[] = $result; + } + $extracted = new Collection($extracted); foreach ($formatters as $callable) { $extracted = new ResultSetDecorator($callable($extracted)); } From 018a5c667d644f5f49649913c335f27bdb3bb935 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sat, 30 Apr 2016 21:54:22 +0200 Subject: [PATCH 0715/2059] Inverted the order in which the joins for belongsToMany are added --- Association/BelongsToMany.php | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 56a3110b..c6e58d35 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -330,8 +330,6 @@ protected function _generateJunctionAssociations($junction, $source, $target) */ public function attachTo(Query $query, array $options = []) { - parent::attachTo($query, $options); - $junction = $this->junction(); $belongsTo = $junction->association($this->source()->alias()); $cond = $belongsTo->_joinCondition(['foreignKey' => $belongsTo->foreignKey()]); @@ -344,14 +342,20 @@ public function attachTo(Query $query, array $options = []) // Attach the junction table as well we need it to populate _joinData. $assoc = $this->_targetTable->association($junction->alias()); $query->removeJoin($assoc->name()); - $options = array_intersect_key($options, ['joinType' => 1, 'fields' => 1]); - $options += [ + $newOptions = array_intersect_key($options, ['joinType' => 1, 'fields' => 1]); + $newOptions += [ 'conditions' => $cond, 'includeFields' => $includeFields, - 'foreignKey' => $this->targetForeignKey(), + 'foreignKey' => false, ]; - $assoc->attachTo($query, $options); + $assoc->attachTo($query, $newOptions); $query->eagerLoader()->addToJoinsMap($junction->alias(), $assoc, true); + + parent::attachTo($query, $options); + + $foreignKey = $this->targetForeignKey(); + $thisJoin = $query->clause('join')[$this->name()]; + $thisJoin['conditions']->add($assoc->_joinCondition(['foreignKey' => $foreignKey])); } /** From 98fee81d76903772235c24b996de522e7e2b0a15 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Thu, 23 Jun 2016 21:32:46 +0200 Subject: [PATCH 0716/2059] Using another strategy for implementing notMatching in belongsToMany After chaning the joins order, it was impossible to keep using the old way, now it uses a NOT IN (subequery) --- Association.php | 1 + Association/BelongsToMany.php | 43 +++++++++++++++++++++++++++++------ 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/Association.php b/Association.php index cfe1dfd8..a73bb517 100644 --- a/Association.php +++ b/Association.php @@ -575,6 +575,7 @@ public function attachTo(Query $query, array $options = []) $dummy = $this ->find($finder, $opts) ->eagerLoaded(true); + if (!empty($options['queryBuilder'])) { $dummy = $options['queryBuilder']($dummy); if (!($dummy instanceof Query)) { diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index c6e58d35..58ae72fb 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -330,6 +330,11 @@ protected function _generateJunctionAssociations($junction, $source, $target) */ public function attachTo(Query $query, array $options = []) { + if (!empty($options['negateMatch'])) { + $this->_appendNotMatching($query, $options); + return; + } + $junction = $this->junction(); $belongsTo = $junction->association($this->source()->alias()); $cond = $belongsTo->_joinCondition(['foreignKey' => $belongsTo->foreignKey()]); @@ -363,14 +368,38 @@ public function attachTo(Query $query, array $options = []) */ protected function _appendNotMatching($query, $options) { - $target = $this->junction(); - if (!empty($options['negateMatch'])) { - $primaryKey = $query->aliasFields((array)$target->primaryKey(), $target->alias()); - $query->andWhere(function ($exp) use ($primaryKey) { - array_map([$exp, 'isNull'], $primaryKey); - return $exp; - }); + if (empty($options['negateMatch'])) { + return; } + + $options += ['conditions' => []]; + $junction = $this->junction(); + $belongsTo = $junction->association($this->source()->alias()); + $conds = $belongsTo->_joinCondition(['foreignKey' => $belongsTo->foreignKey()]); + + $subquery = $this->find() + ->select(array_values($conds)) + ->where($options['conditions']); + + $subquery = $options['queryBuilder']($subquery); + + $assoc = $this->junction()->association($this->target()->alias()); + $conditions = $assoc->_joinCondition([ + 'foreignKey' => $this->targetForeignKey() + ]); + $subquery = $this->_appendJunctionJoin($subquery, $conditions); + + $query->andWhere(function ($exp) use ($subquery, $conds) { + if (count($conds) === 1) { + return $exp->notIn(key($conds), $subquery); + } + return $this->_createTupleCondition( + $subquery, + array_keys($conds), + $subquery, + 'NOT IN' + ); + }); } /** From 80e06083aa1fb7f469ca0d43db7fa27988131bd9 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sat, 25 Jun 2016 19:18:48 +0200 Subject: [PATCH 0717/2059] Fixed the remaining failing tests --- Association/BelongsToMany.php | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 58ae72fb..0d30022c 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -16,6 +16,7 @@ use Cake\Core\App; use Cake\Database\ExpressionInterface; +use Cake\Database\Expression\IdentifierExpression; use Cake\Datasource\EntityInterface; use Cake\ORM\Association; use Cake\ORM\Query; @@ -346,7 +347,6 @@ public function attachTo(Query $query, array $options = []) // Attach the junction table as well we need it to populate _joinData. $assoc = $this->_targetTable->association($junction->alias()); - $query->removeJoin($assoc->name()); $newOptions = array_intersect_key($options, ['joinType' => 1, 'fields' => 1]); $newOptions += [ 'conditions' => $cond, @@ -371,7 +371,6 @@ protected function _appendNotMatching($query, $options) if (empty($options['negateMatch'])) { return; } - $options += ['conditions' => []]; $junction = $this->junction(); $belongsTo = $junction->association($this->source()->alias()); @@ -389,17 +388,20 @@ protected function _appendNotMatching($query, $options) ]); $subquery = $this->_appendJunctionJoin($subquery, $conditions); - $query->andWhere(function ($exp) use ($subquery, $conds) { - if (count($conds) === 1) { - return $exp->notIn(key($conds), $subquery); - } - return $this->_createTupleCondition( - $subquery, - array_keys($conds), - $subquery, - 'NOT IN' - ); - }); + $query + ->andWhere(function ($exp) use ($subquery, $conds) { + $identifiers = []; + foreach (array_keys($conds) as $field) { + $identifiers = new IdentifierExpression($field); + } + $identifiers = $subquery->newExpr()->add($identifiers)->tieWith(','); + $nullExp = clone $exp; + return $exp + ->or_([ + $exp->notIn($identifiers, $subquery), + $nullExp->and(array_map([$nullExp, 'isNull'], array_keys($conds))), + ]); + }); } /** From e5a7f6471d5c10ac41c8d027163dd2180e6be3aa Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sat, 25 Jun 2016 19:55:30 +0200 Subject: [PATCH 0718/2059] Simplifying code a bit --- Association/BelongsToMany.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 0d30022c..cac4d5d8 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -382,7 +382,7 @@ protected function _appendNotMatching($query, $options) $subquery = $options['queryBuilder']($subquery); - $assoc = $this->junction()->association($this->target()->alias()); + $assoc = $junction->association($this->target()->alias()); $conditions = $assoc->_joinCondition([ 'foreignKey' => $this->targetForeignKey() ]); From fad176d76f86589f4b11a81263bddacbd83931ea Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 26 Jun 2016 14:38:14 +0200 Subject: [PATCH 0719/2059] Making test more clear and re-adding missing junction conditions in subquery for `notMatching` --- Association/BelongsToMany.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index cac4d5d8..ee4554fb 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -378,7 +378,8 @@ protected function _appendNotMatching($query, $options) $subquery = $this->find() ->select(array_values($conds)) - ->where($options['conditions']); + ->where($options['conditions']) + ->andWhere($this->junctionConditions()); $subquery = $options['queryBuilder']($subquery); From 7bd6ee8334042065c82eceadd029987ef5673a91 Mon Sep 17 00:00:00 2001 From: thinkingmedia Date: Mon, 27 Jun 2016 10:14:10 -0400 Subject: [PATCH 0720/2059] I forgot to return the result in findOrCreate, thank goodness for unit tests --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 25f15488..1d6f9bff 100644 --- a/Table.php +++ b/Table.php @@ -1222,7 +1222,7 @@ public function get($primaryKey, $options = []) */ public function findOrCreate($search, callable $callback = null) { - $this->connection()->transactional(function () use ($search, $callback) { + return $this->connection()->transactional(function () use ($search, $callback) { $query = $this->find()->where($search); $row = $query->first(); if ($row) { From 715e27985452db189709be5a591ad544d0068cc5 Mon Sep 17 00:00:00 2001 From: thinkingmedia Date: Mon, 27 Jun 2016 11:56:56 -0400 Subject: [PATCH 0721/2059] adds unit tests, callable search argument and optional transactions --- Table.php | 86 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 69 insertions(+), 17 deletions(-) diff --git a/Table.php b/Table.php index 1d6f9bff..c8b2fdc4 100644 --- a/Table.php +++ b/Table.php @@ -1205,8 +1205,8 @@ public function get($primaryKey, $options = []) /** * Finds an existing record or creates a new one. * - * Using the attributes defined in $search a find() will be done to locate - * an existing record. If records matches the conditions, the first record + * A find() will be done to locate an existing record using the attributes + * defined in $search. If records matches the conditions, the first record * will be returned. * * If no record can be found, a new entity will be created @@ -1214,27 +1214,79 @@ public function get($primaryKey, $options = []) * called allowing you to define additional default values. The new * entity will be saved and returned. * - * @param array $search The criteria to find existing records by. + * If the $search properties do not match the properties of the entity, then you + * should disable defaults and define all default values using the callback. + * + * If your find conditions require custom order, associations or conditions. The $search + * parameter can be a callable that takes the Query as the argument. Allowing you to + * customize the find results. + * + * ### Options + * + * The options array is passed to the save method with exception to the following keys: + * + * - atomic: Whether to execute the methods for find, save and callbacks inside a database + * transaction (default: true) + * - defaults: Whether to use the search criteria as default values for the new entity (default: true) + * + * @param array|callable $search The criteria to find an existing record by, or a callable tha will + * customize the find query. * @param callable|null $callback A callback that will be invoked for newly * created entities. This callback will be called *before* the entity * is persisted. - * @return \Cake\Datasource\EntityInterface An entity. + * @param array $options The options to use when saving. + * @return EntityInterface An entity. */ - public function findOrCreate($search, callable $callback = null) + public function findOrCreate($search, callable $callback = null, $options = []) { - return $this->connection()->transactional(function () use ($search, $callback) { - $query = $this->find()->where($search); - $row = $query->first(); - if ($row) { - return $row; - } - $entity = $this->newEntity(); + $options = array_merge([ + 'atomic' => true, + 'defaults' => true + ], $options); + + if ($options['atomic']) { + return $this->connection()->transactional(function () use ($search, $callback, $options) { + return $this->_processFindOrCreate($search, $callback, $options); + }); + } else { + return $this->_processFindOrCreate($search, $callback, $options); + } + } + + /** + * Performs the actual find and/or create of an entity based on the passed options. + * + * @param array|callable $search The criteria to find an existing record by, or a callable tha will + * customize the find query. + * @param callable|null $callback A callback that will be invoked for newly + * created entities. This callback will be called *before* the entity + * is persisted. + * @param array $options The options to use when saving. + * @return EntityInterface An entity. + */ + protected function _processFindOrCreate($search, callable $callback = null, $options) + { + $query = $this->find(); + if(is_callable($search)) { + call_user_func($search, $query); + } else if(is_array($search)) { + $query->where($search); + } else { + throw new InvalidArgumentException('Search criteria must be an array or callable'); + } + $row = $query->first(); + if ($row) { + return $row; + } + $entity = $this->newEntity(); + if($options['defaults'] && is_array($search)) { $entity->set($search, ['guard' => false]); - if ($callback) { - $callback($entity); - } - return $this->save($entity) ?: $entity; - }); + } + if ($callback) { + $callback($entity); + } + unset($options['defaults']); + return $this->save($entity, $options) ?: $entity; } /** From 2c962b48c917a92adac3958b514c8f63a0b5c039 Mon Sep 17 00:00:00 2001 From: thinkingmedia Date: Mon, 27 Jun 2016 13:10:23 -0400 Subject: [PATCH 0722/2059] fixes phpcs issues --- Table.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Table.php b/Table.php index c8b2fdc4..9e05eb28 100644 --- a/Table.php +++ b/Table.php @@ -1217,7 +1217,7 @@ public function get($primaryKey, $options = []) * If the $search properties do not match the properties of the entity, then you * should disable defaults and define all default values using the callback. * - * If your find conditions require custom order, associations or conditions. The $search + * If your find conditions require custom order, associations or conditions, then the $search * parameter can be a callable that takes the Query as the argument. Allowing you to * customize the find results. * @@ -1229,7 +1229,7 @@ public function get($primaryKey, $options = []) * transaction (default: true) * - defaults: Whether to use the search criteria as default values for the new entity (default: true) * - * @param array|callable $search The criteria to find an existing record by, or a callable tha will + * @param array|callable $search The criteria to find an existing record by, or a callable that will * customize the find query. * @param callable|null $callback A callback that will be invoked for newly * created entities. This callback will be called *before* the entity @@ -1240,9 +1240,9 @@ public function get($primaryKey, $options = []) public function findOrCreate($search, callable $callback = null, $options = []) { $options = array_merge([ - 'atomic' => true, - 'defaults' => true - ], $options); + 'atomic' => true, + 'defaults' => true + ], $options); if ($options['atomic']) { return $this->connection()->transactional(function () use ($search, $callback, $options) { @@ -1264,12 +1264,12 @@ public function findOrCreate($search, callable $callback = null, $options = []) * @param array $options The options to use when saving. * @return EntityInterface An entity. */ - protected function _processFindOrCreate($search, callable $callback = null, $options) + protected function _processFindOrCreate($search, callable $callback = null, $options = []) { $query = $this->find(); - if(is_callable($search)) { + if (is_callable($search)) { call_user_func($search, $query); - } else if(is_array($search)) { + } elseif (is_array($search)) { $query->where($search); } else { throw new InvalidArgumentException('Search criteria must be an array or callable'); @@ -1279,7 +1279,7 @@ protected function _processFindOrCreate($search, callable $callback = null, $opt return $row; } $entity = $this->newEntity(); - if($options['defaults'] && is_array($search)) { + if ($options['defaults'] && is_array($search)) { $entity->set($search, ['guard' => false]); } if ($callback) { From c67072ee41e3a6053343b82921851aed44303820 Mon Sep 17 00:00:00 2001 From: thinkingmedia Date: Mon, 27 Jun 2016 14:28:32 -0400 Subject: [PATCH 0723/2059] fixes if conditions, and uses optional return value of --- Table.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Table.php b/Table.php index 9e05eb28..5fb5c48a 100644 --- a/Table.php +++ b/Table.php @@ -1268,22 +1268,22 @@ protected function _processFindOrCreate($search, callable $callback = null, $opt { $query = $this->find(); if (is_callable($search)) { - call_user_func($search, $query); + $search($query); } elseif (is_array($search)) { $query->where($search); } else { throw new InvalidArgumentException('Search criteria must be an array or callable'); } $row = $query->first(); - if ($row) { + if ($row !== null) { return $row; } $entity = $this->newEntity(); if ($options['defaults'] && is_array($search)) { $entity->set($search, ['guard' => false]); } - if ($callback) { - $callback($entity); + if ($callback !== null) { + $entity = $callback($entity) ?: $entity; } unset($options['defaults']); return $this->save($entity, $options) ?: $entity; From 9e899bbf63f6aa161bf9126c7843d59217aa8e16 Mon Sep 17 00:00:00 2001 From: thinkingmedia Date: Mon, 27 Jun 2016 14:37:49 -0400 Subject: [PATCH 0724/2059] removed redundant else --- Table.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index 5fb5c48a..5aee623f 100644 --- a/Table.php +++ b/Table.php @@ -1248,9 +1248,9 @@ public function findOrCreate($search, callable $callback = null, $options = []) return $this->connection()->transactional(function () use ($search, $callback, $options) { return $this->_processFindOrCreate($search, $callback, $options); }); - } else { - return $this->_processFindOrCreate($search, $callback, $options); } + $redundant = 0; + return $this->_processFindOrCreate($search, $callback, $options); } /** From 67342d296985b25f81f366702ddbf79c94909e0c Mon Sep 17 00:00:00 2001 From: thinkingmedia Date: Mon, 27 Jun 2016 14:38:05 -0400 Subject: [PATCH 0725/2059] removed redundant else --- Table.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Table.php b/Table.php index 5aee623f..6441e00d 100644 --- a/Table.php +++ b/Table.php @@ -1249,7 +1249,6 @@ public function findOrCreate($search, callable $callback = null, $options = []) return $this->_processFindOrCreate($search, $callback, $options); }); } - $redundant = 0; return $this->_processFindOrCreate($search, $callback, $options); } From 9f778c8f22ea1fce8fbdf5eb518ae0e34ffd1077 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Lorenzo=20Rodr=C3=ADguez?= Date: Tue, 3 May 2016 23:15:08 +0200 Subject: [PATCH 0726/2059] cherry picked from merge #8707 --- Table.php | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Table.php b/Table.php index 6441e00d..13addcb9 100644 --- a/Table.php +++ b/Table.php @@ -1214,9 +1214,6 @@ public function get($primaryKey, $options = []) * called allowing you to define additional default values. The new * entity will be saved and returned. * - * If the $search properties do not match the properties of the entity, then you - * should disable defaults and define all default values using the callback. - * * If your find conditions require custom order, associations or conditions, then the $search * parameter can be a callable that takes the Query as the argument. Allowing you to * customize the find results. @@ -1229,6 +1226,9 @@ public function get($primaryKey, $options = []) * transaction (default: true) * - defaults: Whether to use the search criteria as default values for the new entity (default: true) * + * @param array|\Cake\ORM\Query $search The criteria to find existing + * records by. Note that when you pass a query object you'll have to use + * the 2nd arg of the method to modify the entity data before saving. * @param array|callable $search The criteria to find an existing record by, or a callable that will * customize the find query. * @param callable|null $callback A callback that will be invoked for newly @@ -1288,6 +1288,20 @@ protected function _processFindOrCreate($search, callable $callback = null, $opt return $this->save($entity, $options) ?: $entity; } + /** + * Gets the query object for findOrCreate(). + * + * @param array|\Cake\ORM\Query|string $search The criteria to find existing records by. + * @return \Cake\ORM\Query + */ + protected function _getFindOrCreateQuery($search) + { + if ($search instanceof Query) { + return $search; + } + return $this->find()->where($search); + } + /** * {@inheritDoc} */ From 7dc12ad0379da8b593b4e8251c090c6a3d4534a4 Mon Sep 17 00:00:00 2001 From: thinkingmedia Date: Mon, 27 Jun 2016 17:42:13 -0400 Subject: [PATCH 0727/2059] finished merging changes from 3.next --- Table.php | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/Table.php b/Table.php index 13addcb9..0c0404d3 100644 --- a/Table.php +++ b/Table.php @@ -1215,8 +1215,8 @@ public function get($primaryKey, $options = []) * entity will be saved and returned. * * If your find conditions require custom order, associations or conditions, then the $search - * parameter can be a callable that takes the Query as the argument. Allowing you to - * customize the find results. + * parameter can be a callable that takes the Query as the argument, or a \Cake\ORM\Query object passed + * as the $search parameter. Allowing you to customize the find results. * * ### Options * @@ -1226,11 +1226,8 @@ public function get($primaryKey, $options = []) * transaction (default: true) * - defaults: Whether to use the search criteria as default values for the new entity (default: true) * - * @param array|\Cake\ORM\Query $search The criteria to find existing - * records by. Note that when you pass a query object you'll have to use - * the 2nd arg of the method to modify the entity data before saving. - * @param array|callable $search The criteria to find an existing record by, or a callable that will - * customize the find query. + * @param array|callable|\Cake\ORM\Query $search The criteria to find an existing record by, or a + * callable that will customize the find query. * @param callable|null $callback A callback that will be invoked for newly * created entities. This callback will be called *before* the entity * is persisted. @@ -1265,7 +1262,7 @@ public function findOrCreate($search, callable $callback = null, $options = []) */ protected function _processFindOrCreate($search, callable $callback = null, $options = []) { - $query = $this->find(); + $query = ($search instanceof Query) ? $search : $this->find(); if (is_callable($search)) { $search($query); } elseif (is_array($search)) { @@ -1288,20 +1285,6 @@ protected function _processFindOrCreate($search, callable $callback = null, $opt return $this->save($entity, $options) ?: $entity; } - /** - * Gets the query object for findOrCreate(). - * - * @param array|\Cake\ORM\Query|string $search The criteria to find existing records by. - * @return \Cake\ORM\Query - */ - protected function _getFindOrCreateQuery($search) - { - if ($search instanceof Query) { - return $search; - } - return $this->find()->where($search); - } - /** * {@inheritDoc} */ From 1086ce3793dc7e271b6da27d518693cc78ee3741 Mon Sep 17 00:00:00 2001 From: thinkingmedia Date: Mon, 27 Jun 2016 20:57:40 -0400 Subject: [PATCH 0728/2059] fixes unit tests for Query search parameter --- Table.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Table.php b/Table.php index 0c0404d3..c07f8cd1 100644 --- a/Table.php +++ b/Table.php @@ -1262,13 +1262,15 @@ public function findOrCreate($search, callable $callback = null, $options = []) */ protected function _processFindOrCreate($search, callable $callback = null, $options = []) { - $query = ($search instanceof Query) ? $search : $this->find(); if (is_callable($search)) { + $query = $this->find(); $search($query); } elseif (is_array($search)) { - $query->where($search); + $query = $this->find()->where($search); + } elseif ($search instanceof Query) { + $query = $search; } else { - throw new InvalidArgumentException('Search criteria must be an array or callable'); + throw new InvalidArgumentException('Search criteria must be an array, callable or Query'); } $row = $query->first(); if ($row !== null) { From 37c46ee588a8b85b1f6270d809308dc423acd8ce Mon Sep 17 00:00:00 2001 From: thinkingmedia Date: Tue, 28 Jun 2016 10:30:28 -0400 Subject: [PATCH 0729/2059] use plus operator --- Table.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index c07f8cd1..51c2e25f 100644 --- a/Table.php +++ b/Table.php @@ -1236,10 +1236,10 @@ public function get($primaryKey, $options = []) */ public function findOrCreate($search, callable $callback = null, $options = []) { - $options = array_merge([ + $options = $options + [ 'atomic' => true, 'defaults' => true - ], $options); + ]; if ($options['atomic']) { return $this->connection()->transactional(function () use ($search, $callback, $options) { From e5e904a68c103e961bcb8bc8f4c916b507f12f13 Mon Sep 17 00:00:00 2001 From: Jonas Date: Tue, 28 Jun 2016 15:06:23 +0000 Subject: [PATCH 0730/2059] 3rd allowPartialNulls implementation --- Rule/ExistsIn.php | 38 +++++++++++--------------------------- 1 file changed, 11 insertions(+), 27 deletions(-) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 6e2fb6ce..8440e48c 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -110,13 +110,20 @@ public function __invoke(EntityInterface $entity, array $options) return true; } - if ($this->_options['allowPartialNulls'] && $this->_checkPartialSchemaNulls($entity, $source)) { - return true; - } if ($this->_fieldsAreNull($entity, $source)) { return true; } + if ($this->_options['allowPartialNulls']) { + $schema = $source->schema(); + foreach ($this->_fields as $i => $field) { + if ($schema->column($field) && $schema->isNullable($field) && $entity->get($field) === null) { + unset($bindingKey[$i]); + unset($this->_fields[$i]); + } + } + } + $primary = array_map( [$target, 'aliasField'], $bindingKey @@ -125,6 +132,7 @@ public function __invoke(EntityInterface $entity, array $options) $primary, $entity->extract($this->_fields) ); + return $target->exists($conditions); } @@ -146,28 +154,4 @@ protected function _fieldsAreNull($entity, $source) } return $nulls === count($this->_fields); } - - /** - * Checks whether or not the give entity fields are null and map to schema NULL - * or are not null and map to schema NOT NULL. - * - * @param \Cake\Datasource\EntityInterface $entity The entity to check. - * @param \Cake\ORM\Table $source The table to use schema from. - * @return bool - */ - protected function _checkPartialSchemaNulls($entity, $source) - { - $schema = $source->schema(); - foreach ($this->_fields as $field) { - $isNullable = $schema->isNullable($field); - $value = $entity->get($field); - if (!$isNullable && $value === null) { - return false; - } - if ($isNullable && $value !== null) { - return false; - } - } - return true; - } } From b8720dcbe9532f6c774ec78e92b8b81e4fcfeae3 Mon Sep 17 00:00:00 2001 From: thinkingmedia Date: Tue, 28 Jun 2016 11:09:31 -0400 Subject: [PATCH 0731/2059] changed to += --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 51c2e25f..9e3c922e 100644 --- a/Table.php +++ b/Table.php @@ -1236,7 +1236,7 @@ public function get($primaryKey, $options = []) */ public function findOrCreate($search, callable $callback = null, $options = []) { - $options = $options + [ + $options += [ 'atomic' => true, 'defaults' => true ]; From 3bd2194887924e54429de71bb14c3c1e2bd1f29e Mon Sep 17 00:00:00 2001 From: thinkingmedia Date: Thu, 30 Jun 2016 11:32:10 -0400 Subject: [PATCH 0732/2059] adds incase lookup for type --- AssociationCollection.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AssociationCollection.php b/AssociationCollection.php index 41bad45a..0edca8ab 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -114,11 +114,11 @@ public function keys() */ public function type($class) { - $class = (array)$class; + $class = array_map('strtolower', (array)$class); $out = array_filter($this->_items, function ($assoc) use ($class) { list(, $name) = namespaceSplit(get_class($assoc)); - return in_array($name, $class, true); + return in_array(strtolower($name), $class, true); }); return array_values($out); } From ed7cde16222fa3e6582161038cf25d98809a10b9 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 10 Jul 2016 11:00:44 -0400 Subject: [PATCH 0733/2059] Fix up formatting in code snippets in API docs. The new version of ApiGen does not like leading spaces inside code blocks. --- Query.php | 160 +++++++++++++++++++++++++++--------------------------- 1 file changed, 80 insertions(+), 80 deletions(-) diff --git a/Query.php b/Query.php index c21ffd0e..c4ad8cf0 100644 --- a/Query.php +++ b/Query.php @@ -247,11 +247,11 @@ public function eagerLoader(EagerLoader $instance = null) * ### Example: * * ``` - * // Bring articles' author information - * $query->contain('Author'); + * // Bring articles' author information + * $query->contain('Author'); * - * // Also bring the category and tags associated to each article - * $query->contain(['Category', 'Tag']); + * // Also bring the category and tags associated to each article + * $query->contain(['Category', 'Tag']); * ``` * * Associations can be arbitrarily nested using dot notation or nested arrays, @@ -261,14 +261,14 @@ public function eagerLoader(EagerLoader $instance = null) * ### Example: * * ``` - * // Eager load the product info, and for each product load other 2 associations - * $query->contain(['Product' => ['Manufacturer', 'Distributor']); + * // Eager load the product info, and for each product load other 2 associations + * $query->contain(['Product' => ['Manufacturer', 'Distributor']); * - * // Which is equivalent to calling - * $query->contain(['Products.Manufactures', 'Products.Distributors']); + * // Which is equivalent to calling + * $query->contain(['Products.Manufactures', 'Products.Distributors']); * - * // For an author query, load his region, state and country - * $query->contain('Regions.States.Countries'); + * // For an author query, load his region, state and country + * $query->contain('Regions.States.Countries'); * ``` * * It is possible to control the conditions and fields selected for each of the @@ -277,13 +277,13 @@ public function eagerLoader(EagerLoader $instance = null) * ### Example: * * ``` - * $query->contain(['Tags' => function ($q) { - * return $q->where(['Tags.is_popular' => true]); - * }]); + * $query->contain(['Tags' => function ($q) { + * return $q->where(['Tags.is_popular' => true]); + * }]); * - * $query->contain(['Products.Manufactures' => function ($q) { - * return $q->select(['name'])->where(['Manufactures.active' => true]); - * }]); + * $query->contain(['Products.Manufactures' => function ($q) { + * return $q->select(['name'])->where(['Manufactures.active' => true]); + * }]); * ``` * * Each association might define special options when eager loaded, the allowed @@ -404,10 +404,10 @@ protected function _addAssociationsToTypeMap($table, $typeMap, $associations) * ### Example: * * ``` - * // Bring only articles that were tagged with 'cake' - * $query->matching('Tags', function ($q) { - * return $q->where(['name' => 'cake']); - * ); + * // Bring only articles that were tagged with 'cake' + * $query->matching('Tags', function ($q) { + * return $q->where(['name' => 'cake']); + * ); * ``` * * It is possible to filter by deep associations by using dot notation: @@ -415,10 +415,10 @@ protected function _addAssociationsToTypeMap($table, $typeMap, $associations) * ### Example: * * ``` - * // Bring only articles that were commented by 'markstory' - * $query->matching('Comments.Users', function ($q) { - * return $q->where(['username' => 'markstory']); - * ); + * // Bring only articles that were commented by 'markstory' + * $query->matching('Comments.Users', function ($q) { + * return $q->where(['username' => 'markstory']); + * ); * ``` * * As this function will create `INNER JOIN`, you might want to consider @@ -429,11 +429,11 @@ protected function _addAssociationsToTypeMap($table, $typeMap, $associations) * ### Example: * * ``` - * // Bring unique articles that were commented by 'markstory' - * $query->distinct(['Articles.id']) - * ->matching('Comments.Users', function ($q) { - * return $q->where(['username' => 'markstory']); - * ); + * // Bring unique articles that were commented by 'markstory' + * $query->distinct(['Articles.id']) + * ->matching('Comments.Users', function ($q) { + * return $q->where(['username' => 'markstory']); + * ); * ``` * * Please note that the query passed to the closure will only accept calling @@ -462,34 +462,34 @@ public function matching($assoc, callable $builder = null) * ### Example: * * ``` - * // Get the count of articles per user - * $usersQuery - * ->select(['total_articles' => $query->func()->count('Articles.id')]) - * ->leftJoinWith('Articles') - * ->group(['Users.id']) - * ->autoFields(true); + * // Get the count of articles per user + * $usersQuery + * ->select(['total_articles' => $query->func()->count('Articles.id')]) + * ->leftJoinWith('Articles') + * ->group(['Users.id']) + * ->autoFields(true); * ``` * * You can also customize the conditions passed to the LEFT JOIN: * * ``` - * // Get the count of articles per user with at least 5 votes - * $usersQuery - * ->select(['total_articles' => $query->func()->count('Articles.id')]) - * ->leftJoinWith('Articles', function ($q) { - * return $q->where(['Articles.votes >=' => 5]); - * }) - * ->group(['Users.id']) - * ->autoFields(true); + * // Get the count of articles per user with at least 5 votes + * $usersQuery + * ->select(['total_articles' => $query->func()->count('Articles.id')]) + * ->leftJoinWith('Articles', function ($q) { + * return $q->where(['Articles.votes >=' => 5]); + * }) + * ->group(['Users.id']) + * ->autoFields(true); * ``` * * This will create the following SQL: * * ``` - * SELECT COUNT(Articles.id) AS total_articles, Users.* - * FROM users Users - * LEFT JOIN articles Articles ON Articles.user_id = Users.id AND Articles.votes >= 5 - * GROUP BY USers.id + * SELECT COUNT(Articles.id) AS total_articles, Users.* + * FROM users Users + * LEFT JOIN articles Articles ON Articles.user_id = Users.id AND Articles.votes >= 5 + * GROUP BY USers.id * ``` * * It is possible to left join deep associations by using dot notation @@ -497,13 +497,13 @@ public function matching($assoc, callable $builder = null) * ### Example: * * ``` - * // Total comments in articles by 'markstory' - * $query - * ->select(['total_comments' => $query->func()->count('Comments.id')]) - * ->leftJoinWith('Comments.Users', function ($q) { - * return $q->where(['username' => 'markstory']); - * ) - * ->group(['Users.id']); + * // Total comments in articles by 'markstory' + * $query + * ->select(['total_comments' => $query->func()->count('Comments.id')]) + * ->leftJoinWith('Comments.Users', function ($q) { + * return $q->where(['username' => 'markstory']); + * ) + * ->group(['Users.id']); * ``` * * Please note that the query passed to the closure will only accept calling @@ -535,20 +535,20 @@ public function leftJoinWith($assoc, callable $builder = null) * ### Example: * * ``` - * // Bring only articles that were tagged with 'cake' - * $query->innerJoinWith('Tags', function ($q) { - * return $q->where(['name' => 'cake']); - * ); + * // Bring only articles that were tagged with 'cake' + * $query->innerJoinWith('Tags', function ($q) { + * return $q->where(['name' => 'cake']); + * ); * ``` * * This will create the following SQL: * * ``` - * SELECT Articles.* - * FROM articles Articles - * INNER JOIN tags Tags ON Tags.name = 'cake' - * INNER JOIN articles_tags ArticlesTags ON ArticlesTags.tag_id = Tags.id - * AND ArticlesTags.articles_id = Articles.id + * SELECT Articles.* + * FROM articles Articles + * INNER JOIN tags Tags ON Tags.name = 'cake' + * INNER JOIN articles_tags ArticlesTags ON ArticlesTags.tag_id = Tags.id + * AND ArticlesTags.articles_id = Articles.id * ``` * * This function works the same as `matching()` with the difference that it @@ -579,10 +579,10 @@ public function innerJoinWith($assoc, callable $builder = null) * ### Example: * * ``` - * // Bring only articles that were not tagged with 'cake' - * $query->notMatching('Tags', function ($q) { - * return $q->where(['name' => 'cake']); - * ); + * // Bring only articles that were not tagged with 'cake' + * $query->notMatching('Tags', function ($q) { + * return $q->where(['name' => 'cake']); + * ); * ``` * * It is possible to filter by deep associations by using dot notation: @@ -590,10 +590,10 @@ public function innerJoinWith($assoc, callable $builder = null) * ### Example: * * ``` - * // Bring only articles that weren't commented by 'markstory' - * $query->notMatching('Comments.Users', function ($q) { - * return $q->where(['username' => 'markstory']); - * ); + * // Bring only articles that weren't commented by 'markstory' + * $query->notMatching('Comments.Users', function ($q) { + * return $q->where(['username' => 'markstory']); + * ); * ``` * * As this function will create a `LEFT JOIN`, you might want to consider @@ -604,11 +604,11 @@ public function innerJoinWith($assoc, callable $builder = null) * ### Example: * * ``` - * // Bring unique articles that were commented by 'markstory' - * $query->distinct(['Articles.id']) - * ->notMatching('Comments.Users', function ($q) { - * return $q->where(['username' => 'markstory']); - * ); + * // Bring unique articles that were commented by 'markstory' + * $query->distinct(['Articles.id']) + * ->notMatching('Comments.Users', function ($q) { + * return $q->where(['username' => 'markstory']); + * ); * ``` * * Please note that the query passed to the closure will only accept calling @@ -663,10 +663,10 @@ public function notMatching($assoc, callable $builder = null) * Is equivalent to: * * ``` - * $query - * ->select(['id', 'name']) - * ->where(['created >=' => '2013-01-01']) - * ->limit(10) + * $query + * ->select(['id', 'name']) + * ->where(['created >=' => '2013-01-01']) + * ->limit(10) * ``` */ public function applyOptions(array $options) From da25fe6d7761b0142228714b3e37cd667157bcc2 Mon Sep 17 00:00:00 2001 From: Jonas Date: Sun, 10 Jul 2016 17:22:16 +0000 Subject: [PATCH 0734/2059] rename allowPartialNulls to allowNullableNulls --- Rule/ExistsIn.php | 6 +++--- RulesChecker.php | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 8440e48c..f05a687c 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -49,7 +49,7 @@ class ExistsIn /** * Constructor. * - * Available option for $options is 'allowPartialNulls' flag. + * Available option for $options is 'allowNullableNulls' flag. * Set to true to accept composite foreign keys where one or more nullable columns are null. * * @param string|array $fields The field or fields to check existence as primary key. @@ -59,7 +59,7 @@ class ExistsIn */ public function __construct($fields, $repository, array $options = []) { - $options += ['allowPartialNulls' => false]; + $options += ['allowNullableNulls' => false]; $this->_options = $options; $this->_fields = (array)$fields; @@ -114,7 +114,7 @@ public function __invoke(EntityInterface $entity, array $options) return true; } - if ($this->_options['allowPartialNulls']) { + if ($this->_options['allowNullableNulls']) { $schema = $source->schema(); foreach ($this->_fields as $i => $field) { if ($schema->column($field) && $schema->isNullable($field) && $entity->get($field) === null) { diff --git a/RulesChecker.php b/RulesChecker.php index 6aa70ec4..f678ce68 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -71,9 +71,9 @@ public function isUnique(array $fields, $message = null) * $rules->add($rules->existsIn('site_id', new SitesTable(), 'Invalid Site')); * ``` * - * Available $options are error 'message' and 'allowPartialNulls' flag. + * Available $options are error 'message' and 'allowNullableNulls' flag. * 'message' sets a custom error message. - * Set 'allowPartialNulls' to true to accept composite foreign keys where one or more nullable columns are null. + * Set 'allowNullableNulls' to true to accept composite foreign keys where one or more nullable columns are null. * * @param string|array $field The field or list of fields to check for existence by * primary key lookup in the other table. From b9520cfa08b0c76265e230b1d089b68f4369bb91 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 11 Jul 2016 22:19:14 -0400 Subject: [PATCH 0735/2059] Disallow nodes being their own parents. A node can never be its own parent. Disallow this state in both the update and create scenarios. Refs #9080 --- Behavior/TreeBehavior.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 2986e4b7..986ec5df 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -103,11 +103,11 @@ public function beforeSave(Event $event, EntityInterface $entity) $dirty = $entity->dirty($config['parent']); $level = $config['level']; - if ($isNew && $parent) { - if ($entity->get($primaryKey[0]) == $parent) { - throw new RuntimeException("Cannot set a node's parent as itself"); - } + if ($parent && $entity->get($primaryKey) == $parent) { + throw new RuntimeException("Cannot set a node's parent as itself"); + } + if ($isNew && $parent) { $parentNode = $this->_getNode($parent); $edge = $parentNode->get($config['right']); $entity->set($config['left'], $edge); From c90fab5ab9648f367570ca2b361e795cbe084b5d Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 12 Jul 2016 22:32:18 -0400 Subject: [PATCH 0736/2059] Add buildValidator event hook to Table. While I don't think this will be frequently used, it does round out the method listener options at the Table class. I've included some more tests as well. Refs #9099 --- Table.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Table.php b/Table.php index 9e3c922e..aee95dff 100644 --- a/Table.php +++ b/Table.php @@ -2252,6 +2252,7 @@ public function validateUnique($value, array $options, array $context = null) * The conventional method map is: * * - Model.beforeMarshal => beforeMarshal + * - Model.buildValidator => buildValidator * - Model.beforeFind => beforeFind * - Model.beforeSave => beforeSave * - Model.afterSave => afterSave @@ -2268,6 +2269,7 @@ public function implementedEvents() { $eventMap = [ 'Model.beforeMarshal' => 'beforeMarshal', + 'Model.buildValidator' => 'buildValidator', 'Model.beforeFind' => 'beforeFind', 'Model.beforeSave' => 'beforeSave', 'Model.afterSave' => 'afterSave', From 3f443f2d8626a2c4e0a50b81c3aa809e1af112cf Mon Sep 17 00:00:00 2001 From: Mark Story Date: Wed, 13 Jul 2016 13:38:37 -0400 Subject: [PATCH 0737/2059] Fix up API doc example. replaceLinks() is not a method on this class. --- Association/HasMany.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index 78980d2b..5f349152 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -352,7 +352,7 @@ function ($assoc) use ($targetEntities) { * $author->articles = [$article1, $article2, $article3, $article4]; * $authors->save($author); * $articles = [$article1, $article3]; - * $authors->association('articles')->replaceLinks($author, $articles); + * $authors->association('articles')->replace($author, $articles); * ``` * * `$author->get('articles')` will contain only `[$article1, $article3]` at the end From 9b9d47eae8e4743609ee6d471b78364aa17f7241 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 14 Jul 2016 01:21:58 +0530 Subject: [PATCH 0738/2059] Fix CS error. New "BlankLineBeforeReturn" sniff was added to our ruleset. --- Association.php | 19 ++++++++++++ Association/BelongsTo.php | 5 ++++ Association/BelongsToMany.php | 21 +++++++++++++ Association/DependentDeleteTrait.php | 2 ++ Association/ExternalAssociationTrait.php | 5 ++++ Association/HasMany.php | 7 +++++ Association/HasOne.php | 5 ++++ Association/SelectableAssociationTrait.php | 10 +++++++ AssociationCollection.php | 9 ++++++ Behavior.php | 3 ++ Behavior/Translate/TranslateTrait.php | 1 + Behavior/TranslateBehavior.php | 4 +++ Behavior/TreeBehavior.php | 13 +++++++++ BehaviorRegistry.php | 6 ++++ EagerLoadable.php | 1 + EagerLoader.php | 9 ++++++ Entity.php | 1 + LazyEagerLoader.php | 3 ++ Locator/LocatorAwareTrait.php | 1 + Locator/TableLocator.php | 3 ++ Marshaller.php | 11 +++++++ Query.php | 22 ++++++++++++++ ResultSet.php | 5 ++++ Rule/ExistsIn.php | 2 ++ Rule/IsUnique.php | 1 + RulesChecker.php | 3 ++ Table.php | 34 ++++++++++++++++++++++ 27 files changed, 206 insertions(+) diff --git a/Association.php b/Association.php index be7e40c2..93d638a3 100644 --- a/Association.php +++ b/Association.php @@ -244,6 +244,7 @@ public function name($name = null) if ($name !== null) { $this->_name = $name; } + return $this->_name; } @@ -259,6 +260,7 @@ public function cascadeCallbacks($cascadeCallbacks = null) if ($cascadeCallbacks !== null) { $this->_cascadeCallbacks = $cascadeCallbacks; } + return $this->_cascadeCallbacks; } @@ -284,6 +286,7 @@ public function source(Table $table = null) if ($table === null) { return $this->_sourceTable; } + return $this->_sourceTable = $table; } @@ -335,6 +338,7 @@ public function conditions($conditions = null) if ($conditions !== null) { $this->_conditions = $conditions; } + return $this->_conditions; } @@ -374,6 +378,7 @@ public function foreignKey($key = null) if ($key !== null) { $this->_foreignKey = $key; } + return $this->_foreignKey; } @@ -393,6 +398,7 @@ public function dependent($dependent = null) if ($dependent !== null) { $this->_dependent = $dependent; } + return $this->_dependent; } @@ -405,6 +411,7 @@ public function dependent($dependent = null) public function canBeJoined(array $options = []) { $strategy = isset($options['strategy']) ? $options['strategy'] : $this->strategy(); + return $strategy == $this::STRATEGY_JOIN; } @@ -420,6 +427,7 @@ public function joinType($type = null) if ($type === null) { return $this->_joinType; } + return $this->_joinType = $type; } @@ -447,6 +455,7 @@ public function property($name = null) ); } } + return $this->_propertyName; } @@ -458,6 +467,7 @@ public function property($name = null) protected function _propertyName() { list(, $name) = pluginSplit($this->_name); + return Inflector::underscore($name); } @@ -481,6 +491,7 @@ public function strategy($name = null) } $this->_strategy = $name; } + return $this->_strategy; } @@ -497,6 +508,7 @@ public function finder($finder = null) if ($finder !== null) { $this->_finder = $finder; } + return $this->_finder; } @@ -603,6 +615,7 @@ protected function _appendNotMatching($query, $options) $primaryKey = $query->aliasFields((array)$target->primaryKey(), $this->_name); $query->andWhere(function ($exp) use ($primaryKey) { array_map([$exp, 'isNull'], $primaryKey); + return $exp; }); } @@ -627,6 +640,7 @@ public function transformRow($row, $nestKey, $joined) $row[$sourceAlias][$this->property()] = $row[$nestKey]; unset($row[$nestKey]); } + return $row; } @@ -646,6 +660,7 @@ public function defaultRowValue($row, $joined) if (isset($row[$sourceAlias])) { $row[$sourceAlias][$this->property()] = null; } + return $row; } @@ -664,6 +679,7 @@ public function find($type = null, array $options = []) { $type = $type ?: $this->finder(); list($type, $opts) = $this->_extractFinder($type); + return $this->target() ->find($type, $options + $opts) ->where($this->conditions()); @@ -685,6 +701,7 @@ public function updateAll($fields, $conditions) ->where($this->conditions()) ->where($conditions) ->clause('where'); + return $target->updateAll($fields, $expression); } @@ -703,6 +720,7 @@ public function deleteAll($conditions) ->where($this->conditions()) ->where($conditions) ->clause('where'); + return $target->deleteAll($expression); } @@ -792,6 +810,7 @@ protected function _formatAssociationResults($query, $surrogate, $options) foreach ($formatters as $callable) { $extracted = new ResultSetDecorator($callable($extracted)); } + return $results->insert($property, $extracted); }, Query::PREPEND); } diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 2eec4a24..ddf5f67c 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -52,8 +52,10 @@ public function foreignKey($key = null) if ($this->_foreignKey === null) { $this->_foreignKey = $this->_modelKey($this->target()->alias()); } + return $this->_foreignKey; } + return parent::foreignKey($key); } @@ -79,6 +81,7 @@ public function cascadeDelete(EntityInterface $entity, array $options = []) protected function _propertyName() { list(, $name) = pluginSplit($this->_name); + return Inflector::underscore(Inflector::singularize($name)); } @@ -136,6 +139,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) $targetEntity->extract((array)$this->bindingKey()) ); $entity->set($properties, ['guard' => false]); + return $entity; } @@ -214,6 +218,7 @@ protected function _buildResultMap($fetchQuery, $options) } $resultMap[implode(';', $values)] = $result; } + return $resultMap; } } diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 56a3110b..d5f191d5 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -163,8 +163,10 @@ public function targetForeignKey($key = null) if ($this->_targetForeignKey === null) { $this->_targetForeignKey = $this->_modelKey($this->target()->alias()); } + return $this->_targetForeignKey; } + return $this->_targetForeignKey = $key; } @@ -209,6 +211,7 @@ public function junction($table = null) $this->_generateSourceAssociations($table, $source); $this->_generateTargetAssociations($table, $source, $target); $this->_generateJunctionAssociations($table, $source, $target); + return $this->_junctionTable = $table; } @@ -364,6 +367,7 @@ protected function _appendNotMatching($query, $options) $primaryKey = $query->aliasFields((array)$target->primaryKey(), $target->alias()); $query->andWhere(function ($exp) use ($primaryKey) { array_map([$exp, 'isNull'], $primaryKey); + return $exp; }); } @@ -440,6 +444,7 @@ protected function _buildResultMap($fetchQuery, $options) } $resultMap[implode(';', $values)][] = $result; } + return $resultMap; } @@ -469,10 +474,12 @@ public function cascadeDelete(EntityInterface $entity, array $options = []) foreach ($hasMany->find('all')->where($conditions)->toList() as $related) { $table->delete($related, $options); } + return true; } $conditions = array_merge($conditions, $hasMany->conditions()); + return $table->deleteAll($conditions); } @@ -505,6 +512,7 @@ public function saveStrategy($strategy = null) $msg = sprintf('Invalid save strategy "%s"', $strategy); throw new InvalidArgumentException($msg); } + return $this->_saveStrategy = $strategy; } @@ -606,6 +614,7 @@ protected function _saveTarget(EntityInterface $parentEntity, $entities, $option if (!empty($options['atomic'])) { $original[$k]->errors($entity->errors()); + return false; } } @@ -614,10 +623,12 @@ protected function _saveTarget(EntityInterface $parentEntity, $entities, $option $success = $this->_saveLinks($parentEntity, $persisted, $options); if (!$success && !empty($options['atomic'])) { $parentEntity->set($this->property(), $original); + return false; } $parentEntity->set($this->property(), $entities); + return $parentEntity; } @@ -804,6 +815,7 @@ public function conditions($conditions = null) $this->_conditions = $conditions; $this->_targetConditions = $this->_junctionConditions = null; } + return $this->_conditions; } @@ -835,6 +847,7 @@ protected function targetConditions() $matching[$field] = $value; } } + return $this->_targetConditions = $matching; } @@ -866,6 +879,7 @@ protected function junctionConditions() $matching[$field] = $value; } } + return $this->_junctionConditions = $matching; } @@ -900,6 +914,7 @@ public function find($type = null, array $options = []) 'foreignKey' => $this->targetForeignKey() ]); $conditions += $this->junctionConditions(); + return $this->_appendJunctionJoin($query, $conditions); } @@ -927,6 +942,7 @@ protected function _appendJunctionJoin($query, $conditions) ->addDefaultTypes($assoc->target()) ->join($matching + $joins, [], true); $query->eagerLoader()->addToJoinsMap($name, $assoc); + return $query; } @@ -1022,6 +1038,7 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { ksort($targetEntities); $sourceEntity->set($property, array_values($targetEntities)); $sourceEntity->dirty($property, false); + return true; } ); @@ -1225,6 +1242,7 @@ protected function _buildQuery($options) ->select($query->aliasFields((array)$assoc->foreignKey(), $name)); $assoc->attachTo($query); + return $query; } @@ -1265,6 +1283,7 @@ protected function _junctionAssociationName() ->association($this->junction()->alias()) ->name(); } + return $this->_junctionAssociationName; } @@ -1287,8 +1306,10 @@ protected function _junctionTableName($name = null) sort($tablesNames); $this->_junctionTableName = implode('_', $tablesNames); } + return $this->_junctionTableName; } + return $this->_junctionTableName = $name; } diff --git a/Association/DependentDeleteTrait.php b/Association/DependentDeleteTrait.php index ee68f2b2..1c2424b6 100644 --- a/Association/DependentDeleteTrait.php +++ b/Association/DependentDeleteTrait.php @@ -47,10 +47,12 @@ public function cascadeDelete(EntityInterface $entity, array $options = []) foreach ($this->find()->where($conditions)->toList() as $related) { $table->delete($related, $options); } + return true; } $conditions = array_merge($conditions, $this->conditions()); + return $table->deleteAll($conditions); } } diff --git a/Association/ExternalAssociationTrait.php b/Association/ExternalAssociationTrait.php index 90ad6be9..385f9f88 100644 --- a/Association/ExternalAssociationTrait.php +++ b/Association/ExternalAssociationTrait.php @@ -57,8 +57,10 @@ public function foreignKey($key = null) if ($this->_foreignKey === null) { $this->_foreignKey = $this->_modelKey($this->source()->table()); } + return $this->_foreignKey; } + return parent::foreignKey($key); } @@ -74,6 +76,7 @@ public function sort($sort = null) if ($sort !== null) { $this->_sort = $sort; } + return $this->_sort; } @@ -86,6 +89,7 @@ public function defaultRowValue($row, $joined) if (isset($row[$sourceAlias])) { $row[$sourceAlias][$this->property()] = $joined ? null : []; } + return $row; } @@ -116,6 +120,7 @@ protected function _buildResultMap($fetchQuery, $options) } $resultMap[implode(';', $values)][] = $result; } + return $resultMap; } diff --git a/Association/HasMany.php b/Association/HasMany.php index 5f349152..5ea3fbae 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -111,6 +111,7 @@ public function saveStrategy($strategy = null) $msg = sprintf('Invalid save strategy "%s"', $strategy); throw new InvalidArgumentException($msg); } + return $this->_saveStrategy = $strategy; } @@ -180,11 +181,13 @@ public function saveAssociated(EntityInterface $entity, array $options = []) if (!empty($options['atomic'])) { $original[$k]->errors($targetEntity->errors()); $entity->set($this->property(), $original); + return false; } } $entity->set($this->property(), $targetEntities); + return $entity; } @@ -379,6 +382,7 @@ public function replace(EntityInterface $sourceEntity, array $targetEntities, ar $sourceEntity = $result; } $this->saveStrategy($saveStrategy); + return $ok; } @@ -450,15 +454,18 @@ protected function _unlink(array $foreignKey, Table $target, array $conditions = foreach ($query as $assoc) { $ok = $ok && $target->delete($assoc, $options); } + return $ok; } $target->deleteAll($conditions); + return true; } $updateFields = array_fill_keys($foreignKey, null); $target->updateAll($updateFields, $conditions); + return true; } diff --git a/Association/HasOne.php b/Association/HasOne.php index c98243fa..4eea06db 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -51,8 +51,10 @@ public function foreignKey($key = null) if ($this->_foreignKey === null) { $this->_foreignKey = $this->_modelKey($this->source()->alias()); } + return $this->_foreignKey; } + return parent::foreignKey($key); } @@ -64,6 +66,7 @@ public function foreignKey($key = null) protected function _propertyName() { list(, $name) = pluginSplit($this->_name); + return Inflector::underscore(Inflector::singularize($name)); } @@ -118,6 +121,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) if (!$this->target()->save($targetEntity, $options)) { $targetEntity->unsetProperty(array_keys($properties)); + return false; } @@ -158,6 +162,7 @@ protected function _buildResultMap($fetchQuery, $options) } $resultMap[implode(';', $values)] = $result; } + return $resultMap; } } diff --git a/Association/SelectableAssociationTrait.php b/Association/SelectableAssociationTrait.php index f77e9b2e..cf2c517d 100644 --- a/Association/SelectableAssociationTrait.php +++ b/Association/SelectableAssociationTrait.php @@ -35,6 +35,7 @@ trait SelectableAssociationTrait public function requiresKeys(array $options = []) { $strategy = isset($options['strategy']) ? $options['strategy'] : $this->strategy(); + return $strategy === $this::STRATEGY_SELECT; } @@ -46,6 +47,7 @@ public function eagerLoader(array $options) $options += $this->_defaultOptions(); $fetchQuery = $this->_buildQuery($options); $resultMap = $this->_buildResultMap($fetchQuery, $options); + return $this->_resultInjector($fetchQuery, $resultMap, $options); } @@ -152,6 +154,7 @@ public function _addFilteringJoin($query, $key, $subquery) } $conditions = isset($conditions) ? $conditions : $query->newExpr([$key => $filter]); + return $query->innerJoin( [$aliasedTable => $subquery], $conditions @@ -174,6 +177,7 @@ protected function _addFilteringCondition($query, $key, $filter) } $conditions = isset($conditions) ? $conditions : [$key . ' IN' => $filter]; + return $query->andWhere($conditions); } @@ -196,6 +200,7 @@ protected function _createTupleCondition($query, $keys, $filter, $operator) $types[] = $defaults[$k]; } } + return new TupleComparison($keys, $filter, $types, $operator); } @@ -233,6 +238,7 @@ protected function _buildSubquery($query) $fields = $this->_subqueryFields($query); $filterQuery->select($fields['select'], true)->group($fields['group']); + return $filterQuery; } @@ -264,6 +270,7 @@ protected function _subqueryFields($query) } }); } + return ['select' => $fields, 'group' => $group]; } @@ -307,10 +314,12 @@ protected function _resultInjector($fetchQuery, $resultMap, $options) } $sourceKey = $sourceKeys[0]; + return function ($row) use ($resultMap, $sourceKey, $nestKey) { if (isset($row[$sourceKey], $resultMap[$row[$sourceKey]])) { $row[$nestKey] = $resultMap[$row[$sourceKey]]; } + return $row; }; } @@ -337,6 +346,7 @@ protected function _multiKeysInjector($resultMap, $sourceKeys, $nestKey) if (isset($resultMap[$key])) { $row[$nestKey] = $resultMap[$key]; } + return $row; }; } diff --git a/AssociationCollection.php b/AssociationCollection.php index 0edca8ab..3a6d521f 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -50,6 +50,7 @@ class AssociationCollection implements IteratorAggregate public function add($alias, Association $association) { list(, $alias) = pluginSplit($alias); + return $this->_items[strtolower($alias)] = $association; } @@ -65,6 +66,7 @@ public function get($alias) if (isset($this->_items[$alias])) { return $this->_items[$alias]; } + return null; } @@ -81,6 +83,7 @@ public function getByProperty($prop) return $assoc; } } + return null; } @@ -118,8 +121,10 @@ public function type($class) $out = array_filter($this->_items, function ($assoc) use ($class) { list(, $name) = namespaceSplit(get_class($assoc)); + return in_array(strtolower($name), $class, true); }); + return array_values($out); } @@ -168,6 +173,7 @@ public function saveParents(Table $table, EntityInterface $entity, $associations if (empty($associations)) { return true; } + return $this->_saveAssociations($table, $entity, $associations, $options, false); } @@ -189,6 +195,7 @@ public function saveChildren(Table $table, EntityInterface $entity, array $assoc if (empty($associations)) { return true; } + return $this->_saveAssociations($table, $entity, $associations, $options, true); } @@ -228,6 +235,7 @@ protected function _saveAssociations($table, $entity, $associations, $options, $ return false; } } + return true; } @@ -248,6 +256,7 @@ protected function _save($association, $entity, $nested, $options) if (!empty($nested)) { $options = (array)$nested + $options; } + return (bool)$association->saveAssociated($entity, $options); } diff --git a/Behavior.php b/Behavior.php index f749ac15..5a835da5 100644 --- a/Behavior.php +++ b/Behavior.php @@ -195,6 +195,7 @@ protected function _resolveMethodAliases($key, $defaults, $config) if (isset($config[$key]) && $config[$key] === []) { $this->config($key, [], false); unset($config[$key]); + return $config; } @@ -207,6 +208,7 @@ protected function _resolveMethodAliases($key, $defaults, $config) } $this->config($key, array_flip($indexedCustom), false); unset($config[$key]); + return $config; } @@ -278,6 +280,7 @@ public function implementedEvents() ]; } } + return $events; } diff --git a/Behavior/Translate/TranslateTrait.php b/Behavior/Translate/TranslateTrait.php index df56bdef..b98738d4 100644 --- a/Behavior/Translate/TranslateTrait.php +++ b/Behavior/Translate/TranslateTrait.php @@ -59,6 +59,7 @@ public function translation($language) // Assume the user will modify any of the internal translations, helps with saving $this->dirty('_translations', true); + return $i18n[$language]; } } diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 0c7ce98a..5d32ee88 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -339,6 +339,7 @@ public function locale($locale = null) if ($locale === null) { return $this->_locale ?: I18n::locale(); } + return $this->_locale = (string)$locale; } @@ -368,11 +369,13 @@ public function findTranslations(Query $query, array $options) { $locales = isset($options['locales']) ? $options['locales'] : []; $targetAlias = $this->_translationTable->alias(); + return $query ->contain([$targetAlias => function ($q) use ($locales, $targetAlias) { if ($locales) { $q->where(["$targetAlias.locale IN" => $locales]); } + return $q; }]) ->formatResults([$this, 'groupTranslations'], $query::PREPEND); @@ -477,6 +480,7 @@ public function groupTranslations($results) $row->set('_translations', $result, $options); unset($row['_i18n']); $row->clean(); + return $row; }); } diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 986ec5df..4726a558 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -117,6 +117,7 @@ public function beforeSave(Event $event, EntityInterface $entity) if ($level) { $entity->set($level, $parentNode[$level] + 1); } + return; } @@ -128,6 +129,7 @@ public function beforeSave(Event $event, EntityInterface $entity) if ($level) { $entity->set($level, 0); } + return; } @@ -138,6 +140,7 @@ public function beforeSave(Event $event, EntityInterface $entity) $parentNode = $this->_getNode($parent); $entity->set($level, $parentNode[$level] + 1); } + return; } @@ -341,6 +344,7 @@ function ($exp) use ($config) { $leftInverse = clone $exp; $leftInverse->type('*')->add('-1'); $rightInverse = clone $leftInverse; + return $exp ->eq($config['leftField'], $leftInverse->add($config['leftField'])) ->eq($config['rightField'], $rightInverse->add($config['rightField'])); @@ -405,6 +409,7 @@ public function childCount(EntityInterface $node, $direct = false) } $this->_ensureFields($node); + return ($node->get($config['right']) - $node->get($config['left']) - 1) / 2; } @@ -450,6 +455,7 @@ function ($field) { } $node = $this->_getNode($for); + return $this->_scope($query) ->where([ "{$right} <" => $node->get($config['right']), @@ -481,6 +487,7 @@ public function findTreeList(Query $query, array $options) 'parentField' => $this->config('parent'), 'order' => [$this->config('left') => 'ASC'] ]); + return $this->formatTreeList($results, $options); } @@ -509,6 +516,7 @@ public function formatTreeList(Query $query, array $options = []) 'valuePath' => $this->_table->displayField(), 'spacer' => '_' ]; + return $results ->listNested() ->printer($options['valuePath'], $options['keyPath'], $options['spacer']); @@ -530,6 +538,7 @@ public function removeFromTree(EntityInterface $node) { return $this->_table->connection()->transactional(function () use ($node) { $this->_ensureFields($node); + return $this->_removeFromTree($node); }); } @@ -571,6 +580,7 @@ protected function _removeFromTree($node) foreach ($fields as $field) { $node->dirty($field, false); } + return $node; } @@ -593,6 +603,7 @@ public function moveUp(EntityInterface $node, $number = 1) return $this->_table->connection()->transactional(function () use ($node, $number) { $this->_ensureFields($node); + return $this->_moveUp($node, $number); }); } @@ -680,6 +691,7 @@ public function moveDown(EntityInterface $node, $number = 1) return $this->_table->connection()->transactional(function () use ($node, $number) { $this->_ensureFields($node); + return $this->_moveDown($node, $number); }); } @@ -952,6 +964,7 @@ protected function _getPrimaryKey() $this->_primaryKey = (array)$this->_table->primaryKey(); $this->_primaryKey = $this->_primaryKey[0]; } + return $this->_primaryKey; } diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index ebe5c7a4..36965f78 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -92,6 +92,7 @@ protected function _resolveClassName($class) if (!$result) { $result = App::className($class, 'ORM/Behavior', 'Behavior'); } + return $result; } @@ -134,6 +135,7 @@ protected function _create($class, $alias, $config) $methods = $this->_getMethods($instance, $class, $alias); $this->_methodMap += $methods['methods']; $this->_finderMap += $methods['finders']; + return $instance; } @@ -198,6 +200,7 @@ protected function _getMethods(Behavior $instance, $class, $alias) public function hasMethod($method) { $method = strtolower($method); + return isset($this->_methodMap[$method]); } @@ -213,6 +216,7 @@ public function hasMethod($method) public function hasFinder($method) { $method = strtolower($method); + return isset($this->_finderMap[$method]); } @@ -229,6 +233,7 @@ public function call($method, array $args = []) $method = strtolower($method); if ($this->hasMethod($method) && $this->has($this->_methodMap[$method][0])) { list($behavior, $callMethod) = $this->_methodMap[$method]; + return call_user_func_array([$this->_loaded[$behavior], $callMethod], $args); } @@ -251,6 +256,7 @@ public function callFinder($type, array $args = []) if ($this->hasFinder($type) && $this->has($this->_finderMap[$type][0])) { list($behavior, $callMethod) = $this->_finderMap[$type]; + return call_user_func_array([$this->_loaded[$behavior], $callMethod], $args); } diff --git a/EagerLoadable.php b/EagerLoadable.php index 555804eb..26725934 100644 --- a/EagerLoadable.php +++ b/EagerLoadable.php @@ -231,6 +231,7 @@ public function asContainArray() if ($this->_forMatching !== null) { $config = ['matching' => $this->_forMatching] + $config; } + return [ $this->_name => [ 'associations' => $associations, diff --git a/EagerLoader.php b/EagerLoader.php index 7395358e..d6cfbd9c 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -139,6 +139,7 @@ public function contain($associations = []) $this->_normalized = null; $this->_loadExternal = []; $this->_aliasList = []; + return $this->_containments = $associations; } @@ -169,6 +170,7 @@ public function autoFields($value = null) if ($value !== null) { $this->_autoFields = (bool)$value; } + return $this->_autoFields; } @@ -213,6 +215,7 @@ public function matching($assoc = null, callable $builder = null, $options = []) } $pointer[$last] = ['queryBuilder' => $builder, 'matching' => true] + $options; + return $this->_matching->contain($containments); } @@ -378,6 +381,7 @@ public function attachableAssociations(Table $repository) $matching = $this->_matching ? $this->_matching->normalized($repository) : []; $this->_fixStrategies(); $this->_loadExternal = []; + return $this->_resolveJoins($contain, $matching); } @@ -396,6 +400,7 @@ public function externalAssociations(Table $repository) } $this->attachableAssociations($repository); + return $this->_loadExternal; } @@ -541,6 +546,7 @@ protected function _resolveJoins($associations, $matching = []) $loadable->canBeJoined(false); $this->_loadExternal[] = $loadable; } + return $result; } @@ -586,6 +592,7 @@ public function loadExternal($query, $statement) ); $statement = new CallbackStatement($statement, $driver, $f); } + return $statement; } @@ -634,6 +641,7 @@ public function associationsMap($table) $visitor($this->_matching->normalized($table), true); $visitor($this->normalized($table)); $visitor($this->_joinsMap); + return $map; } @@ -734,6 +742,7 @@ protected function _groupKeys($statement, $collectKeys) } $statement->rewind(); + return $keys; } } diff --git a/Entity.php b/Entity.php index 2077f0a6..ce96056d 100644 --- a/Entity.php +++ b/Entity.php @@ -65,6 +65,7 @@ public function __construct(array $properties = [], array $options = []) if (!empty($properties) && $options['markClean'] && !$options['useSetters']) { $this->_properties = $properties; + return; } diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 5a2d8edd..a7bc4d2c 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -54,6 +54,7 @@ public function loadInto($entities, array $contain, Table $source) $associations = array_keys($query->contain()); $entities = $this->_injectResults($entities, $query, $associations, $source); + return $returnSingle ? array_shift($entities) : $entities; } @@ -89,6 +90,7 @@ protected function _getQuery($objects, $contain, $source) $types = array_intersect_key($q->defaultTypes(), array_flip($primaryKey)); $primaryKey = array_map([$source, 'aliasField'], $primaryKey); + return new TupleComparison($primaryKey, $keys->toList(), $types, 'IN'); }) ->contain($contain); @@ -117,6 +119,7 @@ protected function _getPropertyMap($source, $associations) foreach ($associations as $assoc) { $map[$assoc] = $container->get($assoc)->property(); } + return $map; } diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index 7a2fdd03..eaad3340 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -44,6 +44,7 @@ public function tableLocator(LocatorInterface $tableLocator = null) if (!$this->_tableLocator) { $this->_tableLocator = TableRegistry::locator(); } + return $this->_tableLocator; } } diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 8b37bd7a..08c90a56 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -88,6 +88,7 @@ public function config($alias = null, $options = null) $alias )); } + return $this->_config[$alias] = $options; } @@ -136,6 +137,7 @@ public function get($alias, array $options = []) $alias )); } + return $this->_instances[$alias]; } @@ -193,6 +195,7 @@ protected function _getClassName($alias, array $options = []) if (empty($options['className'])) { $options['className'] = Inflector::camelize($alias); } + return App::className($options['className'], 'Model/Table', 'Table'); } diff --git a/Marshaller.php b/Marshaller.php index 9c2800e8..7e46a98a 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -79,6 +79,7 @@ protected function _buildPropertyMap($options) $map[$assoc->property()] = ['association' => $assoc] + $nested + ['associated' => []]; } } + return $map; } @@ -161,6 +162,7 @@ public function one(array $data, array $options = []) if (!isset($options['fieldList'])) { $entity->set($properties); $entity->errors($errors); + return $entity; } @@ -171,6 +173,7 @@ public function one(array $data, array $options = []) } $entity->errors($errors); + return $entity; } @@ -260,6 +263,7 @@ protected function _marshalAssociation($assoc, $value, $options) if ($assoc->type() === Association::MANY_TO_MANY) { return $marshaller->_belongsToMany($assoc, $value, (array)$options); } + return $marshaller->many($value, (array)$options); } @@ -292,6 +296,7 @@ public function many(array $data, array $options = []) } $output[] = $this->one($record, $options); } + return $output; } @@ -386,6 +391,7 @@ protected function _belongsToMany(Association $assoc, array $data, $options = [] $record->set('_joinData', $joinData); } } + return $records; } @@ -527,6 +533,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) $entity->dirty($field, $properties[$field]->dirty()); } } + return $entity; } @@ -540,6 +547,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) } $entity->errors($errors); + return $entity; } @@ -583,6 +591,7 @@ public function mergeMany($entities, array $data, array $options = []) foreach ($primary as $key) { $keys[] = isset($el[$key]) ? $el[$key] : ''; } + return implode(';', $keys); }) ->map(function ($element, $key) { @@ -617,6 +626,7 @@ public function mergeMany($entities, array $data, array $options = []) }) ->reduce(function ($query, $keys) use ($primary) { $fields = array_map([$this->_table, 'aliasField'], $primary); + return $query->orWhere($query->newExpr()->and_(array_combine($fields, $keys))); }, $this->_table->find()); @@ -664,6 +674,7 @@ protected function _mergeAssociation($original, $assoc, $value, $options) if ($assoc->type() === Association::MANY_TO_MANY) { return $marshaller->_mergeBelongsToMany($original, $assoc, $value, (array)$options); } + return $marshaller->mergeMany($original, $value, (array)$options); } diff --git a/Query.php b/Query.php index c4ad8cf0..1993b023 100644 --- a/Query.php +++ b/Query.php @@ -233,9 +233,11 @@ public function eagerLoader(EagerLoader $instance = null) if ($this->_eagerLoader === null) { $this->_eagerLoader = new EagerLoader; } + return $this->_eagerLoader; } $this->_eagerLoader = $instance; + return $this; } @@ -364,6 +366,7 @@ public function contain($associations = null, $override = false) $result = $loader->contain($associations); $this->_addAssociationsToTypeMap($this->repository(), $this->typeMap(), $result); + return $this; } @@ -449,6 +452,7 @@ public function matching($assoc, callable $builder = null) { $this->eagerLoader()->matching($assoc, $builder); $this->_dirty(); + return $this; } @@ -522,6 +526,7 @@ public function leftJoinWith($assoc, callable $builder = null) 'fields' => false ]); $this->_dirty(); + return $this; } @@ -567,6 +572,7 @@ public function innerJoinWith($assoc, callable $builder = null) 'fields' => false ]); $this->_dirty(); + return $this; } @@ -628,6 +634,7 @@ public function notMatching($assoc, callable $builder = null) 'negateMatch' => true ]); $this->_dirty(); + return $this; } @@ -725,6 +732,7 @@ public function cleanCopy() $clone->formatResults(null, true); $clone->selectTypeMap(new TypeMap()); $clone->decorateResults(null, true); + return $clone; } @@ -770,6 +778,7 @@ protected function _performCount() $counter = $this->_counter; if ($counter) { $query->counter(null); + return (int)$counter($query); } @@ -812,6 +821,7 @@ protected function _performCount() $result = $statement->fetch('assoc')['count']; $statement->closeCursor(); + return (int)$result; } @@ -836,6 +846,7 @@ protected function _performCount() public function counter($counter) { $this->_counter = $counter; + return $this; } @@ -856,6 +867,7 @@ public function hydrate($enable = null) $this->_dirty(); $this->_hydrate = (bool)$enable; + return $this; } @@ -870,6 +882,7 @@ public function cache($key, $config = 'default') if ($this->_type !== 'select' && $this->_type !== null) { throw new RuntimeException('You cannot cache the results of non-select queries.'); } + return $this->_cache($key, $config); } @@ -885,6 +898,7 @@ public function all() 'You cannot call all() on a non-select query. Use execute() instead.' ); } + return $this->_all(); } @@ -917,6 +931,7 @@ public function sql(ValueBinder $binder = null) $this->_transformQuery(); $sql = parent::sql($binder); + return $sql; } @@ -932,10 +947,12 @@ protected function _execute() $this->triggerBeforeFind(); if ($this->_results) { $decorator = $this->_decoratorClass(); + return new $decorator($this->_results); } $statement = $this->eagerLoader()->loadExternal($this, $this->execute()); + return new ResultSet($this, $statement); } @@ -1044,6 +1061,7 @@ protected function _dirty() public function update($table = null) { $table = $table ?: $this->repository()->table(); + return parent::update($table); } @@ -1060,6 +1078,7 @@ public function delete($table = null) { $repo = $this->repository(); $this->from([$repo->alias() => $repo->table()]); + return parent::delete(); } @@ -1080,6 +1099,7 @@ public function insert(array $columns, array $types = []) { $table = $this->repository()->table(); $this->into($table); + return parent::insert($columns, $types); } @@ -1105,6 +1125,7 @@ public function __call($method, $arguments) public function __debugInfo() { $eagerLoader = $this->eagerLoader(); + return parent::__debugInfo() + [ 'hydrate' => $this->_hydrate, 'buffered' => $this->_useBufferedResults, @@ -1144,6 +1165,7 @@ public function autoFields($value = null) return $this->_autoFields; } $this->_autoFields = (bool)$value; + return $this; } diff --git a/ResultSet.php b/ResultSet.php index c806cc48..06f6d515 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -258,6 +258,7 @@ public function valid() $valid = $this->_index < $this->_count; if ($valid && $this->_results[$this->_index] !== null) { $this->_current = $this->_results[$this->_index]; + return true; } if (!$valid) { @@ -291,6 +292,7 @@ public function first() if ($this->_statement && !$this->_useBuffering) { $this->_statement->closeCursor(); } + return $result; } } @@ -307,6 +309,7 @@ public function serialize() while ($this->valid()) { $this->next(); } + return serialize($this->_results); } @@ -340,6 +343,7 @@ public function count() if ($this->_statement) { return $this->_count = $this->_statement->rowCount(); } + return $this->_count = count($this->_results); } @@ -456,6 +460,7 @@ protected function _fetchResult() if ($row === false) { return $row; } + return $this->_groupResult($row); } diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 7b40f490..47fcdcaf 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -109,6 +109,7 @@ public function __invoke(EntityInterface $entity, array $options) $primary, $entity->extract($this->_fields) ); + return $target->exists($conditions); } @@ -128,6 +129,7 @@ protected function _fieldsAreNull($entity, $source) $nulls++; } } + return $nulls === count($this->_fields); } } diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index 1edd8927..87843f13 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -82,6 +82,7 @@ protected function _alias($alias, $conditions) foreach ($conditions as $key => $value) { $aliased["$alias.$key"] = $value; } + return $aliased; } } diff --git a/RulesChecker.php b/RulesChecker.php index e46c8f07..c847fe1f 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -54,6 +54,7 @@ public function isUnique(array $fields, $message = null) } $errorField = current($fields); + return $this->_addError(new IsUnique($fields), '_isUnique', compact('errorField', 'message')); } @@ -88,6 +89,7 @@ public function existsIn($field, $table, $message = null) } $errorField = is_string($field) ? $field : current($field); + return $this->_addError(new ExistsIn($field, $table), '_existsIn', compact('errorField', 'message')); } @@ -111,6 +113,7 @@ public function validCount($field, $count = 0, $operator = '>', $message = null) } $errorField = $field; + return $this->_addError(new ValidCount($field, $count, $operator), '_validCount', compact('count', 'operator', 'errorField', 'message')); } } diff --git a/Table.php b/Table.php index 9e3c922e..8ab125cd 100644 --- a/Table.php +++ b/Table.php @@ -346,6 +346,7 @@ public function table($table = null) } $this->_table = Inflector::underscore($table); } + return $this->_table; } @@ -362,6 +363,7 @@ public function alias($alias = null) $alias = substr(end($alias), 0, -5) ?: $this->_table; $this->_alias = $alias; } + return $this->_alias; } @@ -390,6 +392,7 @@ public function registryAlias($registryAlias = null) if ($this->_registryAlias === null) { $this->_registryAlias = $this->alias(); } + return $this->_registryAlias; } @@ -430,6 +433,7 @@ public function schema($schema = null) ->describe($this->table()) ); } + return $this->_schema; } @@ -488,6 +492,7 @@ protected function _initializeSchema(Schema $table) public function hasField($field) { $schema = $this->schema(); + return $schema->column($field) !== null; } @@ -509,6 +514,7 @@ public function primaryKey($key = null) } $this->_primaryKey = $key; } + return $this->_primaryKey; } @@ -534,6 +540,7 @@ public function displayField($key = null) $this->_displayField = 'name'; } } + return $this->_displayField; } @@ -740,6 +747,7 @@ public function belongsTo($associated, array $options = []) { $options += ['sourceTable' => $this]; $association = new BelongsTo($associated, $options); + return $this->_associations->add($association->name(), $association); } @@ -783,6 +791,7 @@ public function hasOne($associated, array $options = []) { $options += ['sourceTable' => $this]; $association = new HasOne($associated, $options); + return $this->_associations->add($association->name(), $association); } @@ -832,6 +841,7 @@ public function hasMany($associated, array $options = []) { $options += ['sourceTable' => $this]; $association = new HasMany($associated, $options); + return $this->_associations->add($association->name(), $association); } @@ -883,6 +893,7 @@ public function belongsToMany($associated, array $options = []) { $options += ['sourceTable' => $this]; $association = new BelongsToMany($associated, $options); + return $this->_associations->add($association->name(), $association); } @@ -945,6 +956,7 @@ public function find($type = 'all', $options = []) { $query = $this->query(); $query->select(); + return $this->callFinder($type, $query, $options); } @@ -1138,6 +1150,7 @@ protected function _setFieldMatchers($options, $keys) foreach ($fields as $field) { $matches[] = $row[$field]; } + return implode(';', $matches); }; } @@ -1199,6 +1212,7 @@ public function get($primaryKey, $options = []) } $query->cache($cacheKey, $cacheConfig); } + return $query->firstOrFail(); } @@ -1246,6 +1260,7 @@ public function findOrCreate($search, callable $callback = null, $options = []) return $this->_processFindOrCreate($search, $callback, $options); }); } + return $this->_processFindOrCreate($search, $callback, $options); } @@ -1284,6 +1299,7 @@ protected function _processFindOrCreate($search, callable $callback = null, $opt $entity = $callback($entity) ?: $entity; } unset($options['defaults']); + return $this->save($entity, $options) ?: $entity; } @@ -1306,6 +1322,7 @@ public function updateAll($fields, $conditions) ->where($conditions); $statement = $query->execute(); $statement->closeCursor(); + return $statement->rowCount(); } @@ -1319,6 +1336,7 @@ public function deleteAll($conditions) ->where($conditions); $statement = $query->execute(); $statement->closeCursor(); + return $statement->rowCount(); } @@ -1540,6 +1558,7 @@ protected function _processSave($entity, $options) if ($success) { return $entity; } + return false; } @@ -1611,6 +1630,7 @@ protected function _insert($entity, $data) } } $statement->closeCursor(); + return $success; } @@ -1631,6 +1651,7 @@ protected function _newId($primary) } $typeName = $this->schema()->columnType($primary[0]); $type = Type::build($typeName); + return $type->newId(); } @@ -1668,6 +1689,7 @@ protected function _update($entity, $data) $success = $entity; } $statement->closeCursor(); + return $success; } @@ -1704,6 +1726,7 @@ function () use ($entities, $options, &$isNew) { $entity->isNew(true); } } + return false; } @@ -1765,6 +1788,7 @@ public function delete(EntityInterface $entity, $options = []) 'options' => $options ]); } + return $success; } @@ -1907,6 +1931,7 @@ protected function _dynamicFinder($method, $args) foreach ($fields as $field) { $conditions[$this->aliasField($field)] = array_shift($args); } + return $conditions; }; @@ -1977,6 +2002,7 @@ public function __get($property) $property )); } + return $association; } @@ -2065,12 +2091,14 @@ public function newEntity($data = null, array $options = []) if ($data === null) { $class = $this->entityClass(); $entity = new $class([], ['source' => $this->registryAlias()]); + return $entity; } if (!isset($options['associated'])) { $options['associated'] = $this->_associations->keys(); } $marshaller = $this->marshaller(); + return $marshaller->one($data, $options); } @@ -2108,6 +2136,7 @@ public function newEntities(array $data, array $options = []) $options['associated'] = $this->_associations->keys(); } $marshaller = $this->marshaller(); + return $marshaller->many($data, $options); } @@ -2148,6 +2177,7 @@ public function patchEntity(EntityInterface $entity, array $data, array $options $options['associated'] = $this->_associations->keys(); } $marshaller = $this->marshaller(); + return $marshaller->merge($entity, $data, $options); } @@ -2182,6 +2212,7 @@ public function patchEntities($entities, array $data, array $options = []) $options['associated'] = $this->_associations->keys(); } $marshaller = $this->marshaller(); + return $marshaller->mergeMany($entities, $data, $options); } @@ -2237,6 +2268,7 @@ public function validateUnique($value, array $options, array $context = null) isset($options['scope']) ? (array)$options['scope'] : [] ); $rule = new IsUnique($fields); + return $rule($entity, ['repository' => $this]); } @@ -2286,6 +2318,7 @@ public function implementedEvents() } $events[$event] = $method; } + return $events; } @@ -2346,6 +2379,7 @@ public function __debugInfo() $conn = $this->connection(); $associations = $this->_associations ?: false; $behaviors = $this->_behaviors ?: false; + return [ 'registryAlias' => $this->registryAlias(), 'table' => $this->table(), From 089149613b8c2efe4727a17c19121d68cda83cf2 Mon Sep 17 00:00:00 2001 From: antograssiot Date: Thu, 14 Jul 2016 07:11:41 +0200 Subject: [PATCH 0739/2059] fix CS --- Association.php | 1 + Association/BelongsToMany.php | 1 + Association/SelectableAssociationTrait.php | 1 + Table.php | 1 + 4 files changed, 4 insertions(+) diff --git a/Association.php b/Association.php index 42a639f7..341f4463 100644 --- a/Association.php +++ b/Association.php @@ -712,6 +712,7 @@ public function exists($conditions) ->find('all', ['conditions' => $conditions]) ->clause('where'); } + return $this->target()->exists($conditions); } diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 13f8c973..2010e2f2 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -336,6 +336,7 @@ public function attachTo(Query $query, array $options = []) { if (!empty($options['negateMatch'])) { $this->_appendNotMatching($query, $options); + return; } diff --git a/Association/SelectableAssociationTrait.php b/Association/SelectableAssociationTrait.php index 328de6ef..d8ef703a 100644 --- a/Association/SelectableAssociationTrait.php +++ b/Association/SelectableAssociationTrait.php @@ -114,6 +114,7 @@ protected function _buildQuery($options) } $this->_assertFieldsPresent($fetchQuery, (array)$key); + return $fetchQuery; } diff --git a/Table.php b/Table.php index 5424962b..728ce4e0 100644 --- a/Table.php +++ b/Table.php @@ -1315,6 +1315,7 @@ protected function _getFindOrCreateQuery($search) if ($search instanceof Query) { return $search; } + return $this->find()->where($search); } From 77bc0433085ceceffaa07c6a83f9e40a76f3250a Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 14 Jul 2016 22:16:48 -0400 Subject: [PATCH 0740/2059] Fix validateUnique emitting errors when non-scalar data is received. While normally the IsUnique() rule can expect sane data as it will have already been validated, that is not true when the rule is used inside a validator function. This prevents non-scalar data from getting to the rule by failing early. Refs #9088 --- Table.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 8ab125cd..9cdd30c0 100644 --- a/Table.php +++ b/Table.php @@ -2267,8 +2267,13 @@ public function validateUnique($value, array $options, array $context = null) [$context['field']], isset($options['scope']) ? (array)$options['scope'] : [] ); + $values = $entity->extract($fields); + foreach ($values as $field) { + if ($field !== null && !is_scalar($field)) { + return false; + } + } $rule = new IsUnique($fields); - return $rule($entity, ['repository' => $this]); } From 8a2fdb11fa353888821ba86f3f792752c6717913 Mon Sep 17 00:00:00 2001 From: Daren Sipes Date: Fri, 15 Jul 2016 09:31:51 -0400 Subject: [PATCH 0741/2059] Adding `$nestingKey` to Collection nest method, with tests. This allows for an override of the key used for nesting. Still defaults to `children`. All of the classes using CollectionInterface were checked to make sure the method signature was update. --- Query.php | 4 ++-- Table.php | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Query.php b/Query.php index 1993b023..b3f84822 100644 --- a/Query.php +++ b/Query.php @@ -56,8 +56,8 @@ * @method \Cake\Collection\CollectionInterface append(array|Traversable $items) Appends more rows to the result of the query. * @method \Cake\Collection\CollectionInterface combine($k, $v, $g = null) Returns the values of the column $v index by column $k, * and grouped by $g. - * @method \Cake\Collection\CollectionInterface nest($k, $p) Creates a tree structure by nesting the values of column $p into that - * with the same value for $k. + * @method \Cake\Collection\CollectionInterface nest($k, $p, $n = 'children') Creates a tree structure by nesting the values of column $p into that + * with the same value for $k using $n as the nesting key. * @method array toArray() Returns a key-value array with the results of this query. * @method array toList() Returns a numerically indexed array with the results of this query. * @method \Cake\Collection\CollectionInterface stopWhen(callable $c) Returns each row until the callable returns true. diff --git a/Table.php b/Table.php index 728ce4e0..d38563bc 100644 --- a/Table.php +++ b/Table.php @@ -1085,13 +1085,14 @@ public function findList(Query $query, array $options) * * You can customize what fields are used for nesting results, by default the * primary key and the `parent_id` fields are used. If you wish to change - * these defaults you need to provide the keys `keyField` or `parentField` in + * these defaults you need to provide the keys `keyField`, `parentField` or `childrenKey` in * `$options`: * * ``` * $table->find('threaded', [ * 'keyField' => 'id', * 'parentField' => 'ancestor_id' + * 'childrenKey' => 'children' * ]); * ``` * @@ -1104,6 +1105,7 @@ public function findThreaded(Query $query, array $options) $options += [ 'keyField' => $this->primaryKey(), 'parentField' => 'parent_id', + 'nestingKey' => 'children' ]; if (isset($options['idField'])) { @@ -1115,7 +1117,7 @@ public function findThreaded(Query $query, array $options) $options = $this->_setFieldMatchers($options, ['keyField', 'parentField']); return $query->formatResults(function ($results) use ($options) { - return $results->nest($options['keyField'], $options['parentField']); + return $results->nest($options['keyField'], $options['parentField'], $options['nestingKey']); }); } From 163391c79d571ebbd2b7b2f3f7876cc26ea41683 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 15 Jul 2016 10:39:05 -0400 Subject: [PATCH 0742/2059] Fix PHPCS error. --- Table.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Table.php b/Table.php index 9cdd30c0..5034e940 100644 --- a/Table.php +++ b/Table.php @@ -2274,6 +2274,7 @@ public function validateUnique($value, array $options, array $context = null) } } $rule = new IsUnique($fields); + return $rule($entity, ['repository' => $this]); } From d8744a4bcffe0d05bab7ffe9459735a645687350 Mon Sep 17 00:00:00 2001 From: Daren Sipes Date: Sat, 16 Jul 2016 09:33:34 -0400 Subject: [PATCH 0743/2059] Fixing Comment Renamed from childrenKey to nestingKey and forgot to fix comment --- Table.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index d38563bc..0528261a 100644 --- a/Table.php +++ b/Table.php @@ -1085,14 +1085,14 @@ public function findList(Query $query, array $options) * * You can customize what fields are used for nesting results, by default the * primary key and the `parent_id` fields are used. If you wish to change - * these defaults you need to provide the keys `keyField`, `parentField` or `childrenKey` in + * these defaults you need to provide the keys `keyField`, `parentField` or `nestingKey` in * `$options`: * * ``` * $table->find('threaded', [ * 'keyField' => 'id', * 'parentField' => 'ancestor_id' - * 'childrenKey' => 'children' + * 'nestingKey' => 'children' * ]); * ``` * From 4e1532ee7bd7bf8d1ae5eaf5043d3a3bd3a6e188 Mon Sep 17 00:00:00 2001 From: antograssiot Date: Sat, 16 Jul 2016 15:50:19 +0200 Subject: [PATCH 0744/2059] fix api typos --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 5034e940..a97de666 100644 --- a/Table.php +++ b/Table.php @@ -1702,7 +1702,7 @@ protected function _update($entity, $data) * * @param array|\Cake\ORM\ResultSet $entities Entities to save. * @param array|\ArrayAccess $options Options used when calling Table::save() for each entity. - * @return bool|array|\Cake\ORM\ResultSet False on failure, entities list on succcess. + * @return bool|array|\Cake\ORM\ResultSet False on failure, entities list on success. */ public function saveMany($entities, $options = []) { From c89c6368464149de9bd7333ed1193827f54649b7 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 17 Jul 2016 10:21:21 -0400 Subject: [PATCH 0745/2059] Don't treat replaceLinks() as working when new entities fail to save. When persisting new linked entities, we should not treat the operation as successful if persisting a new linked entity fails. Refs #9109 --- Association/BelongsToMany.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index d5f191d5..89a2ae75 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -606,15 +606,19 @@ protected function _saveTarget(EntityInterface $parentEntity, $entities, $option $entity = clone $entity; } - if ($table->save($entity, $options)) { + $saved = $table->save($entity, $options); + if ($saved) { $entities[$k] = $entity; $persisted[] = $entity; continue; } + // Saving the new linked entity failed, copy errors back into the + // original entity if applicable and abort. if (!empty($options['atomic'])) { $original[$k]->errors($entity->errors()); - + } + if (!$saved) { return false; } } From 6355f3e4f171d6f33bf6583ea79e0c8ad922d719 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sat, 9 Jul 2016 13:06:46 +0200 Subject: [PATCH 0746/2059] Throwing an exception when the afterSave aborts the transaction Fixes #9079 --- Exception/RolledbackTransactionException.php | 26 +++++++++ Table.php | 59 ++++++++++++++------ 2 files changed, 67 insertions(+), 18 deletions(-) create mode 100644 Exception/RolledbackTransactionException.php diff --git a/Exception/RolledbackTransactionException.php b/Exception/RolledbackTransactionException.php new file mode 100644 index 00000000..10d08ace --- /dev/null +++ b/Exception/RolledbackTransactionException.php @@ -0,0 +1,26 @@ +save($entity, ['associated' => false]); * ``` * + * @throws RolledbackTransactionException if the transaction is aborted in the afterSave event */ public function save(EntityInterface $entity, $options = []) { @@ -1487,6 +1489,7 @@ public function save(EntityInterface $entity, $options = []) * @param \ArrayObject $options the options to use for the save operation * @return \Cake\Datasource\EntityInterface|bool * @throws \RuntimeException When an entity is missing some of the primary keys. + * @throws RolledbackTransactionException if the transaction is aborted in the afterSave event */ protected function _processSave($entity, $options) { @@ -1534,32 +1537,52 @@ protected function _processSave($entity, $options) } if ($success) { - $success = $this->_associations->saveChildren( - $this, - $entity, - $options['associated'], - ['_primary' => false] + $options->getArrayCopy() - ); - if ($success || !$options['atomic']) { - $this->dispatchEvent('Model.afterSave', compact('entity', 'options')); - $entity->clean(); - if (!$options['atomic'] && !$options['_primary']) { - $entity->isNew(false); - $entity->source($this->registryAlias()); - } - $success = true; - } + $success = $this->_onSaveSuccess($entity, $options); } if (!$success && $isNew) { $entity->unsetProperty($this->primaryKey()); $entity->isNew(true); } - if ($success) { - return $entity; + + return $success ? $entity : false; + } + + /** + * Handles the saving of children associations and executing the afterSave logic + * once the entity for this table has been saved successfully. + * + * @param \Cake\Datasource\EntityInterface $entity the entity to be saved + * @param \ArrayObject $options the options to use for the save operation + * @return bool True on success + * @throws RolledbackTransactionException if the transaction is aborted in the afterSave event + */ + protected function _onSaveSuccess($entity, $options) + { + $success = $this->_associations->saveChildren( + $this, + $entity, + $options['associated'], + ['_primary' => false] + $options->getArrayCopy() + ); + + if (!$success && $options['atomic']) { + return false; + } + + $this->dispatchEvent('Model.afterSave', compact('entity', 'options')); + + if ($options['atomic'] && !$this->connection()->inTransaction()) { + throw new RolledbackTransactionException(['table' => get_class($this)]); + } + + $entity->clean(); + if (!$options['atomic'] && !$options['_primary']) { + $entity->isNew(false); + $entity->source($this->registryAlias()); } - return false; + return true; } /** From a36107dcb034df111249afe90df6d88fa5421de9 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 9 Jul 2016 17:19:16 +0530 Subject: [PATCH 0747/2059] Fix CS errors. --- Exception/RolledbackTransactionException.php | 1 - Table.php | 9 ++++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Exception/RolledbackTransactionException.php b/Exception/RolledbackTransactionException.php index 10d08ace..a1ae5bdb 100644 --- a/Exception/RolledbackTransactionException.php +++ b/Exception/RolledbackTransactionException.php @@ -23,4 +23,3 @@ class RolledbackTransactionException extends Exception protected $_messageTemplate = 'The afterSave event in "%s" is aborting the transaction before the save process is done.'; } - diff --git a/Table.php b/Table.php index 734af421..6ae4d7c1 100644 --- a/Table.php +++ b/Table.php @@ -1438,7 +1438,8 @@ public function exists($conditions) * $articles->save($entity, ['associated' => false]); * ``` * - * @throws RolledbackTransactionException if the transaction is aborted in the afterSave event + * @throws \Cake\ORM\Exception\RolledbackTransactionException If the transaction + * is aborted in the afterSave event. */ public function save(EntityInterface $entity, $options = []) { @@ -1489,7 +1490,8 @@ public function save(EntityInterface $entity, $options = []) * @param \ArrayObject $options the options to use for the save operation * @return \Cake\Datasource\EntityInterface|bool * @throws \RuntimeException When an entity is missing some of the primary keys. - * @throws RolledbackTransactionException if the transaction is aborted in the afterSave event + * @throws \Cake\ORM\Exception\RolledbackTransactionException If the transaction + * is aborted in the afterSave event. */ protected function _processSave($entity, $options) { @@ -1555,7 +1557,8 @@ protected function _processSave($entity, $options) * @param \Cake\Datasource\EntityInterface $entity the entity to be saved * @param \ArrayObject $options the options to use for the save operation * @return bool True on success - * @throws RolledbackTransactionException if the transaction is aborted in the afterSave event + * @throws \Cake\ORM\Exception\RolledbackTransactionException If the transaction + * is aborted in the afterSave event. */ protected function _onSaveSuccess($entity, $options) { From 0c3d694b63de21e35a770420dfacc15fc9397ec5 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 17 Jul 2016 18:55:21 +0200 Subject: [PATCH 0748/2059] Rebasing and fixing docblock --- Exception/RolledbackTransactionException.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Exception/RolledbackTransactionException.php b/Exception/RolledbackTransactionException.php index a1ae5bdb..6b762a22 100644 --- a/Exception/RolledbackTransactionException.php +++ b/Exception/RolledbackTransactionException.php @@ -15,7 +15,7 @@ use Cake\Core\Exception\Exception; /** - * Used when a behavior cannot be found. + * Used when a transaction was rolled back from a callback event. * */ class RolledbackTransactionException extends Exception From cde8ba36d69e0303f723537771bb99b8c23e38fc Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 17 Jul 2016 23:11:16 +0530 Subject: [PATCH 0749/2059] Update docblock of Table::save() --- Table.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Table.php b/Table.php index a97de666..ce813697 100644 --- a/Table.php +++ b/Table.php @@ -1366,13 +1366,12 @@ public function exists($conditions) * transaction (default: true) * - checkRules: Whether or not to check the rules on entity before saving, if the checking * fails, it will abort the save operation. (default:true) - * - associated: If true it will save all associated entities as they are found + * - associated: If `true` it will save 1st level associated entities as they are found * in the passed `$entity` whenever the property defined for the association - * is marked as dirty. Associated records are saved recursively unless told - * otherwise. If an array, it will be interpreted as the list of associations + * is marked as dirty. If an array, it will be interpreted as the list of associations * to be saved. It is possible to provide different options for saving on associated * table objects using this key by making the custom options the array value. - * If false no associated records will be saved. (default: true) + * If `false` no associated records will be saved. (default: `true`) * - checkExisting: Whether or not to check if the entity already exists, assuming that the * entity is marked as not new, and the primary key has been set. * From 71684efcd3e5163a1411b592d98561aec9328b8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Tue, 19 Jul 2016 16:08:01 +0200 Subject: [PATCH 0750/2059] Implementing an option builder for save options. This will check associations and validation and throw exceptions if something is not set up right. This helps to prevent typos in the options and gives clear error messages for the developer. --- SaveOptionsBuilder.php | 247 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100644 SaveOptionsBuilder.php diff --git a/SaveOptionsBuilder.php b/SaveOptionsBuilder.php new file mode 100644 index 00000000..091b075b --- /dev/null +++ b/SaveOptionsBuilder.php @@ -0,0 +1,247 @@ +_table = $table; + $this->parseArray($options); + } + + /** + * Takes an options array and populates the option object with the data. + * + * @param array $array Options array + * @return \Cake\ORM\SaveOptionsBuilder + */ + public function parseArray($array) + { + foreach ($array as $key => $value) { + if (method_exists($this, $key)) { + $this->{$key}($value); + } + } + return $this; + } + + /** + * Set associated options. + * + * @param string|array $associated String or array of associations. + * @return \Cake\ORM\SaveOptionsBuilder + */ + public function associated($associated) + { + $associated = $this->_normalizeAssociations($associated); + $this->_associated($this->_table, $associated); + $this->_options['associated'] = $associated; + + return $this; + } + + protected function _associated(Table $table, $associations) + { + foreach ($associations as $key => $associated) { + if (is_int($key)) { + $this->_checkAssociation($table, $associated); + continue; + } + $this->_checkAssociation($table, $key); + if (isset($associated['associated'])) { + $this->_associated($table->association($key)->target(), $associated['associated']); + continue; + } + } + } + + protected function _checkAssociation(Table $table, $association) + { + if (!$table->associations()->has($association)) { + throw new RuntimeException(sprintf('Table `%s` is not associated with `%s`', get_class($table), $association)); + } + } + + /** + * Set the guard option. + * + * @param bool $guard Guard the properties or not. + * @return \Cake\ORM\SaveOptionsBuilder + */ + public function guard($guard) + { + $this->_options['guard'] = (bool)$guard; + return $this; + } + + /** + * Set the validation rule set to use. + * + * @param string $validate Name of the validation rule set to use. + * @return \Cake\ORM\SaveOptionsBuilder + */ + public function validate($validate) + { + $this->_table->validator($validate); + $this->_options['validate'] = $validate; + return $this; + } + + /** + * Set check existing option. + * + * @param bool $checkExisting Guard the properties or not. + * @return \Cake\ORM\SaveOptionsBuilder + */ + public function checkExisting($checkExisting) + { + $this->_options['checkExisting'] = (bool)$checkExisting; + return $this; + } + + /** + * Option to check the rules. + * + * @param bool $checkRules Check the rules or not. + * @return \Cake\ORM\SaveOptionsBuilder + */ + public function checkRules($checkRules) + { + $this->_options['checkRules'] = (bool)$checkRules; + return $this; + } + + /** + * Sets the atomic option. + * + * @param bool $atomic Atomic or not. + * @return \Cake\ORM\SaveOptionsBuilder + */ + public function atomic($atomic) + { + $this->_options['atomic'] = (bool)$atomic; + return $this; + } + + /** + * @return array + */ + public function toArray() + { + return $this->_options; + } + + /** + * Whether a offset exists + * + * @link http://php.net/manual/en/arrayaccess.offsetexists.php + * @param mixed $offset

+ * An offset to check for. + *

+ * @return boolean true on success or false on failure. + *

+ *

+ * The return value will be casted to boolean if non-boolean was returned. + * @since 5.0.0 + */ + public function offsetExists($offset) + { + return isset($this->_options[$offset]); + } + + /** + * Offset to retrieve + * + * @link http://php.net/manual/en/arrayaccess.offsetget.php + * @param mixed $offset

+ * The offset to retrieve. + *

+ * @return mixed Can return all value types. + * @since 5.0.0 + */ + public function offsetGet($offset) + { + return $this->_options[$offset]; + } + + /** + * Offset to set + * + * @link http://php.net/manual/en/arrayaccess.offsetset.php + * @param mixed $offset

+ * The offset to assign the value to. + *

+ * @param mixed $value

+ * The value to set. + *

+ * @return void + * @since 5.0.0 + */ + public function offsetSet($offset, $value) + { + $this->{$offset}($value); + } + + /** + * Offset to unset + * + * @link http://php.net/manual/en/arrayaccess.offsetunset.php + * @param mixed $offset

+ * The offset to unset. + *

+ * @return void + * @since 5.0.0 + */ + public function offsetUnset($offset) + { + unset($this->_options[$offset]); + } +} From 80889a66c8f8c35d724ea2d4223a623170e66f7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Wed, 20 Jul 2016 13:06:08 +0200 Subject: [PATCH 0751/2059] Removed the ArrayAccess of the SaveOptionsBuilder Also added more doc blocks and tests. --- SaveOptionsBuilder.php | 96 ++++++++++-------------------------------- 1 file changed, 23 insertions(+), 73 deletions(-) diff --git a/SaveOptionsBuilder.php b/SaveOptionsBuilder.php index 091b075b..6d530df5 100644 --- a/SaveOptionsBuilder.php +++ b/SaveOptionsBuilder.php @@ -14,10 +14,8 @@ */ namespace Cake\ORM; -use ArrayAccess; use ArrayObject; use RuntimeException; -use Cake\ORM\AssociationsNormalizerTrait; /** * OOP style Save Option Builder. @@ -27,7 +25,7 @@ * * @see \Cake\Datasource\RulesChecker */ -class SaveOptionsBuilder extends ArrayObject implements ArrayAccess +class SaveOptionsBuilder extends ArrayObject { use AssociationsNormalizerTrait; @@ -54,21 +52,26 @@ class SaveOptionsBuilder extends ArrayObject implements ArrayAccess public function __construct(Table $table, array $options = []) { $this->_table = $table; - $this->parseArray($options); + $this->parseArrayOptions($options); } /** * Takes an options array and populates the option object with the data. * - * @param array $array Options array + * This can be used to turn an options array into the object. + * + * @throws \InvalidArgumentException If a given option key does not exist. + * @param array $array Options array. * @return \Cake\ORM\SaveOptionsBuilder */ - public function parseArray($array) + public function parseArrayOptions($array) { foreach ($array as $key => $value) { if (method_exists($this, $key)) { $this->{$key}($value); + continue; } + throw new \InvalidArgumentException(sprintf('Key `%s` is not a valid option!', $key)); } return $this; } @@ -88,7 +91,13 @@ public function associated($associated) return $this; } - protected function _associated(Table $table, $associations) + /** + * Checks that the associations exists recursively. + * + * @param \Cake\ORM\Table $table Table object. + * @param array $associations An associations array. + */ + protected function _associated(Table $table, array $associations) { foreach ($associations as $key => $associated) { if (is_int($key)) { @@ -103,6 +112,13 @@ protected function _associated(Table $table, $associations) } } + /** + * Checks if an association exists. + * + * @throws \RuntimeException If no such association exists for the given table. + * @param \Cake\ORM\Table $table Table object. + * @param string $association Association name. + */ protected function _checkAssociation(Table $table, $association) { if (!$table->associations()->has($association)) { @@ -178,70 +194,4 @@ public function toArray() { return $this->_options; } - - /** - * Whether a offset exists - * - * @link http://php.net/manual/en/arrayaccess.offsetexists.php - * @param mixed $offset

- * An offset to check for. - *

- * @return boolean true on success or false on failure. - *

- *

- * The return value will be casted to boolean if non-boolean was returned. - * @since 5.0.0 - */ - public function offsetExists($offset) - { - return isset($this->_options[$offset]); - } - - /** - * Offset to retrieve - * - * @link http://php.net/manual/en/arrayaccess.offsetget.php - * @param mixed $offset

- * The offset to retrieve. - *

- * @return mixed Can return all value types. - * @since 5.0.0 - */ - public function offsetGet($offset) - { - return $this->_options[$offset]; - } - - /** - * Offset to set - * - * @link http://php.net/manual/en/arrayaccess.offsetset.php - * @param mixed $offset

- * The offset to assign the value to. - *

- * @param mixed $value

- * The value to set. - *

- * @return void - * @since 5.0.0 - */ - public function offsetSet($offset, $value) - { - $this->{$offset}($value); - } - - /** - * Offset to unset - * - * @link http://php.net/manual/en/arrayaccess.offsetunset.php - * @param mixed $offset

- * The offset to unset. - *

- * @return void - * @since 5.0.0 - */ - public function offsetUnset($offset) - { - unset($this->_options[$offset]); - } } From cfb9c05dfba190c02c0716701ed46f27d6ec774f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Wed, 20 Jul 2016 13:36:34 +0200 Subject: [PATCH 0752/2059] Adding the SaveOptionsBuilder to the table object. --- Table.php | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/Table.php b/Table.php index f47ad053..315fe9a6 100644 --- a/Table.php +++ b/Table.php @@ -1442,6 +1442,10 @@ public function exists($conditions) */ public function save(EntityInterface $entity, $options = []) { + if ($options instanceof SaveOptionsBuilder) { + $options = $options->toArray(); + } + $options = new ArrayObject($options + [ 'atomic' => true, 'associated' => true, @@ -2113,6 +2117,10 @@ public function marshaller() */ public function newEntity($data = null, array $options = []) { + if ($options instanceof SaveOptionsBuilder) { + $options = $options->toArray(); + } + if ($data === null) { $class = $this->entityClass(); $entity = new $class([], ['source' => $this->registryAlias()]); @@ -2157,6 +2165,10 @@ public function newEntity($data = null, array $options = []) */ public function newEntities(array $data, array $options = []) { + if ($options instanceof SaveOptionsBuilder) { + $options = $options->toArray(); + } + if (!isset($options['associated'])) { $options['associated'] = $this->_associations->keys(); } @@ -2198,6 +2210,10 @@ public function newEntities(array $data, array $options = []) */ public function patchEntity(EntityInterface $entity, array $data, array $options = []) { + if ($options instanceof SaveOptionsBuilder) { + $options = $options->toArray(); + } + if (!isset($options['associated'])) { $options['associated'] = $this->_associations->keys(); } @@ -2233,6 +2249,10 @@ public function patchEntity(EntityInterface $entity, array $data, array $options */ public function patchEntities($entities, array $data, array $options = []) { + if ($options instanceof SaveOptionsBuilder) { + $options = $options->toArray(); + } + if (!isset($options['associated'])) { $options['associated'] = $this->_associations->keys(); } @@ -2364,6 +2384,16 @@ public function buildRules(RulesChecker $rules) return $rules; } + /** + * Gets a SaveOptionsBuilder instance. + * + * @return \Cake\ORM\SaveOptionsBuilder + */ + public function getSaveOptionsBuilder() + { + return new SaveOptionsBuilder(); + } + /** * Loads the specified associations in the passed entity or list of entities * by executing extra queries in the database and merging the results in the From 76e0fb08c98baee0fbb8b74122bc4cc25fbc36b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Wed, 20 Jul 2016 13:36:57 +0200 Subject: [PATCH 0753/2059] Allow user defined options in the SaveOptionsBuilder --- SaveOptionsBuilder.php | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/SaveOptionsBuilder.php b/SaveOptionsBuilder.php index 6d530df5..28c72833 100644 --- a/SaveOptionsBuilder.php +++ b/SaveOptionsBuilder.php @@ -67,11 +67,7 @@ public function __construct(Table $table, array $options = []) public function parseArrayOptions($array) { foreach ($array as $key => $value) { - if (method_exists($this, $key)) { - $this->{$key}($value); - continue; - } - throw new \InvalidArgumentException(sprintf('Key `%s` is not a valid option!', $key)); + $this->{$key}($value); } return $this; } @@ -194,4 +190,17 @@ public function toArray() { return $this->_options; } + + /** + * Setting custom options. + * + * @return \Cake\ORM\SaveOptionsBuilder + */ + public function __call($name, $args) + { + if (isset($args[0])) { + $this->_options[$name] = $args[0]; + return $this; + } + } } From 13323d837929a0ab39acc72f03e8954c1276fd04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Wed, 20 Jul 2016 23:28:18 +0200 Subject: [PATCH 0754/2059] Option builder custom option setting. --- SaveOptionsBuilder.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/SaveOptionsBuilder.php b/SaveOptionsBuilder.php index 28c72833..d7a92639 100644 --- a/SaveOptionsBuilder.php +++ b/SaveOptionsBuilder.php @@ -92,6 +92,7 @@ public function associated($associated) * * @param \Cake\ORM\Table $table Table object. * @param array $associations An associations array. + * @return void */ protected function _associated(Table $table, array $associations) { @@ -114,6 +115,7 @@ protected function _associated(Table $table, array $associations) * @throws \RuntimeException If no such association exists for the given table. * @param \Cake\ORM\Table $table Table object. * @param string $association Association name. + * @return void */ protected function _checkAssociation(Table $table, $association) { @@ -194,13 +196,16 @@ public function toArray() /** * Setting custom options. * + * @param string $option Option key. + * @param mixed $value Option value. * @return \Cake\ORM\SaveOptionsBuilder */ - public function __call($name, $args) + public function set($option, $value) { - if (isset($args[0])) { - $this->_options[$name] = $args[0]; - return $this; + if (method_exists($this, $option)) { + return $this->{$option}($value); } + $this->_options[$option] = $value; + return $this; } } From 03f79ad22645846039de46335ffb7f8c25abdb07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Wed, 20 Jul 2016 23:29:07 +0200 Subject: [PATCH 0755/2059] Reverting some changes to the Table object --- Table.php | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/Table.php b/Table.php index 315fe9a6..bbe51cec 100644 --- a/Table.php +++ b/Table.php @@ -2117,10 +2117,6 @@ public function marshaller() */ public function newEntity($data = null, array $options = []) { - if ($options instanceof SaveOptionsBuilder) { - $options = $options->toArray(); - } - if ($data === null) { $class = $this->entityClass(); $entity = new $class([], ['source' => $this->registryAlias()]); @@ -2165,10 +2161,6 @@ public function newEntity($data = null, array $options = []) */ public function newEntities(array $data, array $options = []) { - if ($options instanceof SaveOptionsBuilder) { - $options = $options->toArray(); - } - if (!isset($options['associated'])) { $options['associated'] = $this->_associations->keys(); } @@ -2210,10 +2202,6 @@ public function newEntities(array $data, array $options = []) */ public function patchEntity(EntityInterface $entity, array $data, array $options = []) { - if ($options instanceof SaveOptionsBuilder) { - $options = $options->toArray(); - } - if (!isset($options['associated'])) { $options['associated'] = $this->_associations->keys(); } @@ -2249,10 +2237,6 @@ public function patchEntity(EntityInterface $entity, array $data, array $options */ public function patchEntities($entities, array $data, array $options = []) { - if ($options instanceof SaveOptionsBuilder) { - $options = $options->toArray(); - } - if (!isset($options['associated'])) { $options['associated'] = $this->_associations->keys(); } From 4e3b4c61633b69e49a073a934b17cf6f595cfec0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Wed, 20 Jul 2016 23:33:46 +0200 Subject: [PATCH 0756/2059] Adding a test for Table::getSaveOptionsBuilder() --- Table.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index bbe51cec..02066cfe 100644 --- a/Table.php +++ b/Table.php @@ -2373,9 +2373,9 @@ public function buildRules(RulesChecker $rules) * * @return \Cake\ORM\SaveOptionsBuilder */ - public function getSaveOptionsBuilder() + public function getSaveOptionsBuilder(array $options = []) { - return new SaveOptionsBuilder(); + return new SaveOptionsBuilder($this, $options); } /** From ded42f9cc4ab4da47483bd1b5c1afbdf060e54ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Wed, 20 Jul 2016 23:36:10 +0200 Subject: [PATCH 0757/2059] phpcs fixes --- SaveOptionsBuilder.php | 1 + Table.php | 1 + 2 files changed, 2 insertions(+) diff --git a/SaveOptionsBuilder.php b/SaveOptionsBuilder.php index d7a92639..b34e1f63 100644 --- a/SaveOptionsBuilder.php +++ b/SaveOptionsBuilder.php @@ -48,6 +48,7 @@ class SaveOptionsBuilder extends ArrayObject * Constructor. * * @param \Cake\ORM\Table $table A table instance. + * @param array $options Options to parse when instantiating. */ public function __construct(Table $table, array $options = []) { diff --git a/Table.php b/Table.php index 02066cfe..efdefa13 100644 --- a/Table.php +++ b/Table.php @@ -2371,6 +2371,7 @@ public function buildRules(RulesChecker $rules) /** * Gets a SaveOptionsBuilder instance. * + * @param array $options Options to load into the builder. * @return \Cake\ORM\SaveOptionsBuilder */ public function getSaveOptionsBuilder(array $options = []) From 1bc1114bc656276e3bcaea877b2ef660378c219b Mon Sep 17 00:00:00 2001 From: David Yell Date: Thu, 21 Jul 2016 12:20:55 +0100 Subject: [PATCH 0758/2059] Correct docblock As the properties default value is set to a boolean. --- Association.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association.php b/Association.php index 93d638a3..b863ed86 100644 --- a/Association.php +++ b/Association.php @@ -134,7 +134,7 @@ abstract class Association /** * Whether or not cascaded deletes should also fire callbacks. * - * @var string + * @var bool */ protected $_cascadeCallbacks = false; From ee5b1984590dc2799653cbbb709c944efb05cc53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Tue, 26 Jul 2016 00:21:24 +0200 Subject: [PATCH 0759/2059] Adding an integration test for the SaveOptionBuilder --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index efdefa13..ed0b2593 100644 --- a/Table.php +++ b/Table.php @@ -2371,7 +2371,7 @@ public function buildRules(RulesChecker $rules) /** * Gets a SaveOptionsBuilder instance. * - * @param array $options Options to load into the builder. + * @param array $options Options to parse by the builder. * @return \Cake\ORM\SaveOptionsBuilder */ public function getSaveOptionsBuilder(array $options = []) From 0c0131d06690573b93c9c68c4e94640be62a0f78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Tue, 26 Jul 2016 00:39:05 +0200 Subject: [PATCH 0760/2059] Improving the ExistsIn exception message. See #9157 --- Rule/ExistsIn.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 47fcdcaf..bf1987c1 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -67,9 +67,10 @@ public function __invoke(EntityInterface $entity, array $options) $repository = $options['repository']->association($this->_repository); if (!$repository) { throw new RuntimeException(sprintf( - "ExistsIn rule for '%s' is invalid. The '%s' association is not defined.", + "ExistsIn rule for '%s' is invalid. '%s' is not associated with '%s'.", implode(', ', $this->_fields), - $this->_repository + $this->_repository, + get_class($options['repository']) )); } $this->_repository = $repository; From 9aff856236554d7872111e6afe7007a539da3432 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 26 Jul 2016 23:13:13 -0400 Subject: [PATCH 0761/2059] Make missing associations fail more loudly. When a developer asks for an association that doesn't exist, we should let them know. Silent errors make the ORM harder to debug and understand. Refs #9166 --- Marshaller.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 7e46a98a..6c6cecbc 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -58,6 +58,7 @@ public function __construct(Table $table) * Build the map of property => association names. * * @param array $options List of options containing the 'associated' key. + * @throws \RuntimeException When associations do not exist. * @return array */ protected function _buildPropertyMap($options) @@ -74,10 +75,18 @@ protected function _buildPropertyMap($options) $key = $nested; $nested = []; } + if ($key === '_joinData') { + continue; + } $assoc = $this->_table->association($key); - if ($assoc) { - $map[$assoc->property()] = ['association' => $assoc] + $nested + ['associated' => []]; + if (!$assoc) { + throw new RuntimeException(sprintf( + 'Cannot marshal data for "%s" association. It is not associated with "%s".', + $key, + $this->_table->alias() + )); } + $map[$assoc->property()] = ['association' => $assoc] + $nested + ['associated' => []]; } return $map; From a86738c5fd28a975084b5f92c6fed22aed1fb03b Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 30 Jul 2016 19:46:21 -0400 Subject: [PATCH 0762/2059] Fix belongsToMany with deeper associations not loading. When deep associations are loaded from junction tables, they are loaded as a hasMany. This means we need to accomodate for this structure as well. This solution doesn't make me happy but it was the smallest change I could make. Other approaches I took caused other things to break. --- Association/BelongsToMany.php | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 267f8094..aa9680c2 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -468,7 +468,14 @@ protected function _buildResultMap($fetchQuery, $options) $property )); } - $result[$this->_junctionProperty] = $result[$property]; + $junctionData = $result[$property]; + + // Account for incorrectly nested data from hasMany + // bindings when junction tables have additional associations loaded + if (isset($result[$property][0])) { + $junctionData = $result[$property][0]; + } + $result[$this->_junctionProperty] = $junctionData; unset($result[$property]); if ($hydrated) { @@ -1282,7 +1289,12 @@ protected function _buildQuery($options) ->where($this->junctionConditions()) ->select($query->aliasFields((array)$assoc->foreignKey(), $name)); - $assoc->attachTo($query); + // If this hasMany attaches any other containments, then the junction + // table is queried twice. Ideally the EagerLoader could resolve the duplication, + // and merge the resulting eagerloadable instances. + // + // We have to define aliasPath, so any nested contains will work. + $assoc->attachTo($query, ['aliasPath' => $assoc->alias()]); return $query; } From c668c0b135d0de3b83840665acead8521365c092 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sat, 6 Aug 2016 16:18:49 +0200 Subject: [PATCH 0763/2059] Temporarily removing the original fix for issue #8485 --- Association/BelongsToMany.php | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index aa9680c2..9423853f 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -468,15 +468,8 @@ protected function _buildResultMap($fetchQuery, $options) $property )); } - $junctionData = $result[$property]; + $result[$this->_junctionProperty] = $result[$property]; - // Account for incorrectly nested data from hasMany - // bindings when junction tables have additional associations loaded - if (isset($result[$property][0])) { - $junctionData = $result[$property][0]; - } - $result[$this->_junctionProperty] = $junctionData; - unset($result[$property]); if ($hydrated) { $result->dirty($this->_junctionProperty, false); @@ -1289,13 +1282,7 @@ protected function _buildQuery($options) ->where($this->junctionConditions()) ->select($query->aliasFields((array)$assoc->foreignKey(), $name)); - // If this hasMany attaches any other containments, then the junction - // table is queried twice. Ideally the EagerLoader could resolve the duplication, - // and merge the resulting eagerloadable instances. - // - // We have to define aliasPath, so any nested contains will work. - $assoc->attachTo($query, ['aliasPath' => $assoc->alias()]); - + $assoc->attachTo($query); return $query; } From 4bff183783fb3b7b90e602aadd04f33f1fd2a591 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sat, 6 Aug 2016 19:30:06 +0200 Subject: [PATCH 0764/2059] Minimal changes to make the tests pass again --- Association.php | 5 ++-- Association/BelongsToMany.php | 44 ++++++++++++++--------------------- EagerLoadable.php | 22 +++++++++++++++++- EagerLoader.php | 9 ++++--- ResultSet.php | 12 ++++++---- 5 files changed, 54 insertions(+), 38 deletions(-) diff --git a/Association.php b/Association.php index 81314bdb..4770de2c 100644 --- a/Association.php +++ b/Association.php @@ -643,12 +643,13 @@ protected function _appendNotMatching($query, $options) * with this association * @return array */ - public function transformRow($row, $nestKey, $joined) + public function transformRow($row, $nestKey, $joined, $targetProperty = null) { $sourceAlias = $this->source()->alias(); $nestKey = $nestKey ?: $this->_name; + $targetProperty = $targetProperty ?: $this->property(); if (isset($row[$sourceAlias])) { - $row[$sourceAlias][$this->property()] = $row[$nestKey]; + $row[$sourceAlias][$targetProperty] = $row[$nestKey]; unset($row[$nestKey]); } diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 9423853f..c897e3f4 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -410,20 +410,6 @@ protected function _appendNotMatching($query, $options) }); } - /** - * {@inheritDoc} - */ - public function transformRow($row, $nestKey, $joined) - { - $alias = $this->junction()->alias(); - if ($joined) { - $row[$this->target()->alias()][$this->_junctionProperty] = $row[$alias]; - unset($row[$alias]); - } - - return parent::transformRow($row, $nestKey, $joined); - } - /** * Get the relationship type. * @@ -458,22 +444,15 @@ protected function _buildResultMap($fetchQuery, $options) { $resultMap = []; $key = (array)$options['foreignKey']; - $property = $this->target()->association($this->junction()->alias())->property(); $hydrated = $fetchQuery->hydrate(); foreach ($fetchQuery->all() as $result) { - if (!isset($result[$property])) { + if (!isset($result[$this->_junctionProperty])) { throw new RuntimeException(sprintf( '"%s" is missing from the belongsToMany results. Results cannot be created.', $property )); } - $result[$this->_junctionProperty] = $result[$property]; - - - if ($hydrated) { - $result->dirty($this->_junctionProperty, false); - } $values = []; foreach ($key as $k) { @@ -966,7 +945,7 @@ public function find($type = null, array $options = []) * @param string|array $conditions The query conditions to use. * @return \Cake\ORM\Query The modified query. */ - protected function _appendJunctionJoin($query, $conditions) + protected function _appendJunctionJoin($query, $conditions, $options = []) { $name = $this->_junctionAssociationName(); $joins = $query->join(); @@ -982,7 +961,9 @@ protected function _appendJunctionJoin($query, $conditions) $query ->addDefaultTypes($assoc->target()) ->join($matching + $joins, [], true); - $query->eagerLoader()->addToJoinsMap($name, $assoc); + + $targetProperty = empty($options['targetProperty']) ? null : $options['targetProperty']; + $query->eagerLoader()->addToJoinsMap($this->_name . '_Cjoin', $assoc, false, $targetProperty); return $query; } @@ -1270,7 +1251,7 @@ protected function _buildQuery($options) $query = $queryBuilder($query); } - $query = $this->_appendJunctionJoin($query, []); + $query = $this->_appendJunctionJoin($query, [], ['targetProperty' => $this->_junctionProperty]); if ($query->autoFields() === null) { $query->autoFields($query->clause('select') === []); @@ -1278,11 +1259,20 @@ protected function _buildQuery($options) // Ensure that association conditions are applied // and that the required keys are in the selected columns. + + $tempName = $this->name(); + $fields = $query->autoFields() ? $assoc->schema()->columns() : (array)$assoc->foreignKey(); + $joinFields = []; + foreach ($fields as $f ) { + $joinFields[$tempName . '_Cjoin__' . $f] = "$name.$f"; + } + + $fields = array_combine(array_keys($joinFields), $fields); $query ->where($this->junctionConditions()) - ->select($query->aliasFields((array)$assoc->foreignKey(), $name)); + ->select($joinFields); - $assoc->attachTo($query); + $assoc->attachTo($query, ['aliasPath' => $assoc->alias(), 'includeFields' => false]); return $query; } diff --git a/EagerLoadable.php b/EagerLoadable.php index 26725934..a86f3a7b 100644 --- a/EagerLoadable.php +++ b/EagerLoadable.php @@ -85,6 +85,14 @@ class EagerLoadable */ protected $_forMatching; + /** + * The property name where the association result should be nested + * in the result. + * + * @var string + */ + protected $_targetProperty; + /** * Constructor. The $config parameter accepts the following array * keys: @@ -96,6 +104,7 @@ class EagerLoadable * - aliasPath * - propertyPath * - forMatching + * - targetProperty * * The keys maps to the settable properties in this class. * @@ -107,7 +116,7 @@ public function __construct($name, array $config = []) $this->_name = $name; $allowed = [ 'associations', 'instance', 'config', 'canBeJoined', - 'aliasPath', 'propertyPath', 'forMatching' + 'aliasPath', 'propertyPath', 'forMatching', 'targetProperty' ]; foreach ($allowed as $property) { if (isset($config[$property])) { @@ -215,6 +224,17 @@ public function forMatching() return $this->_forMatching; } + /** + * The property name where the result of this association + * should be nested at the end. + * + * @return string + */ + public function targetProperty() + { + return $this->_targetProperty; + } + /** * Returns a representation of this object that can be passed to * Cake\ORM\EagerLoader::contain() diff --git a/EagerLoader.php b/EagerLoader.php index d6cfbd9c..39cd713d 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -447,7 +447,8 @@ protected function _normalizeContain(Table $parent, $alias, $options, $paths) 'instance' => $instance, 'config' => array_diff_key($options, $extra), 'aliasPath' => trim($paths['aliasPath'], '.'), - 'propertyPath' => trim($paths['propertyPath'], '.') + 'propertyPath' => trim($paths['propertyPath'], '.'), + 'targetProperty' => $instance->property() ]; $config['canBeJoined'] = $instance->canBeJoined($config['config']); $eagerLoadable = new EagerLoadable($alias, $config); @@ -631,7 +632,8 @@ public function associationsMap($table) 'canBeJoined' => $canBeJoined, 'entityClass' => $instance->target()->entityClass(), 'nestKey' => $canBeJoined ? $assoc : $meta->aliasPath(), - 'matching' => $forMatching !== null ? $forMatching : $matching + 'matching' => $forMatching !== null ? $forMatching : $matching, + 'targetProperty' => $meta->targetProperty() ]; if ($canBeJoined && $associations) { $visitor($associations, $matching); @@ -657,13 +659,14 @@ public function associationsMap($table) * 'matching' association. * @return void */ - public function addToJoinsMap($alias, Association $assoc, $asMatching = false) + public function addToJoinsMap($alias, Association $assoc, $asMatching = false, $targetProperty = null) { $this->_joinsMap[$alias] = new EagerLoadable($alias, [ 'aliasPath' => $alias, 'instance' => $assoc, 'canBeJoined' => true, 'forMatching' => $asMatching, + 'targetProperty' => $targetProperty ?: $assoc->property() ]); } diff --git a/ResultSet.php b/ResultSet.php index 06f6d515..6ad99600 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -380,12 +380,14 @@ protected function _calculateColumnMap($query) $map = []; foreach ($query->clause('select') as $key => $field) { $key = trim($key, '"`[]'); - if (strpos($key, '__') > 0) { - $parts = explode('__', $key, 2); - $map[$parts[0]][$key] = $parts[1]; - } else { + + if (strpos($key, '__') <= 0) { $map[$this->_defaultAlias][$key] = $key; + continue; } + + $parts = explode('__', $key, 2); + $map[$parts[0]][$key] = $parts[1]; } foreach ($this->_matchingMap as $alias => $assoc) { @@ -544,7 +546,7 @@ protected function _groupResult($row) $results[$alias] = $entity; } - $results = $instance->transformRow($results, $alias, $assoc['canBeJoined']); + $results = $instance->transformRow($results, $alias, $assoc['canBeJoined'], $assoc['targetProperty']); } foreach ($presentAliases as $alias => $present) { From 0ca8d9f798b9ee1e9b868540119e81896629c233 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sat, 6 Aug 2016 19:53:48 +0200 Subject: [PATCH 0765/2059] Cleaning up and fixing tests --- Association/BelongsToMany.php | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index c897e3f4..96e1eb93 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -450,7 +450,7 @@ protected function _buildResultMap($fetchQuery, $options) if (!isset($result[$this->_junctionProperty])) { throw new RuntimeException(sprintf( '"%s" is missing from the belongsToMany results. Results cannot be created.', - $property + $this->_junctionProperty )); } @@ -945,7 +945,7 @@ public function find($type = null, array $options = []) * @param string|array $conditions The query conditions to use. * @return \Cake\ORM\Query The modified query. */ - protected function _appendJunctionJoin($query, $conditions, $options = []) + protected function _appendJunctionJoin($query, $conditions) { $name = $this->_junctionAssociationName(); $joins = $query->join(); @@ -962,9 +962,6 @@ protected function _appendJunctionJoin($query, $conditions, $options = []) ->addDefaultTypes($assoc->target()) ->join($matching + $joins, [], true); - $targetProperty = empty($options['targetProperty']) ? null : $options['targetProperty']; - $query->eagerLoader()->addToJoinsMap($this->_name . '_Cjoin', $assoc, false, $targetProperty); - return $query; } @@ -1251,7 +1248,7 @@ protected function _buildQuery($options) $query = $queryBuilder($query); } - $query = $this->_appendJunctionJoin($query, [], ['targetProperty' => $this->_junctionProperty]); + $query = $this->_appendJunctionJoin($query, []); if ($query->autoFields() === null) { $query->autoFields($query->clause('select') === []); @@ -1260,18 +1257,24 @@ protected function _buildQuery($options) // Ensure that association conditions are applied // and that the required keys are in the selected columns. - $tempName = $this->name(); - $fields = $query->autoFields() ? $assoc->schema()->columns() : (array)$assoc->foreignKey(); - $joinFields = []; - foreach ($fields as $f ) { - $joinFields[$tempName . '_Cjoin__' . $f] = "$name.$f"; + $tempName = $this->_name . '_CJoin'; + $schema = $assoc->schema(); + $joinFields = $types = []; + + foreach ($schema->typeMap() as $f => $type ) { + $key = $tempName . '__' . $f; + $joinFields[$key] = "$name.$f"; + $types[$key] = $type; } - $fields = array_combine(array_keys($joinFields), $fields); $query ->where($this->junctionConditions()) - ->select($joinFields); + ->select($joinFields) + ->defaultTypes($types); + $query + ->eagerLoader() + ->addToJoinsMap($tempName, $assoc, false, $this->_junctionProperty); $assoc->attachTo($query, ['aliasPath' => $assoc->alias(), 'includeFields' => false]); return $query; } From ae88f94d25a5714f86749824eb7b25a5c3c37885 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sat, 6 Aug 2016 19:57:04 +0200 Subject: [PATCH 0766/2059] Aadding missing docblock --- Association.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Association.php b/Association.php index 4770de2c..19d7a22b 100644 --- a/Association.php +++ b/Association.php @@ -641,6 +641,8 @@ protected function _appendNotMatching($query, $options) * should be found * @param bool $joined Whether or not the row is a result of a direct join * with this association + * @param string $targetProperty The property name in the source results where the association + * data shuld be nested in. Will use the default one if not provided. * @return array */ public function transformRow($row, $nestKey, $joined, $targetProperty = null) From 0448d5862a59f6e2d1223788268627fdeda009b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Lorenzo=20Rodr=C3=ADguez?= Date: Sat, 6 Aug 2016 23:14:07 +0200 Subject: [PATCH 0767/2059] Fixed CS issue --- Association/BelongsToMany.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 96e1eb93..c6419208 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1261,7 +1261,7 @@ protected function _buildQuery($options) $schema = $assoc->schema(); $joinFields = $types = []; - foreach ($schema->typeMap() as $f => $type ) { + foreach ($schema->typeMap() as $f => $type) { $key = $tempName . '__' . $f; $joinFields[$key] = "$name.$f"; $types[$key] = $type; From 42fe2e55e15ae99b50303fb5294fffb07d3c85f4 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sat, 6 Aug 2016 23:22:26 +0200 Subject: [PATCH 0768/2059] Improving docs --- EagerLoadable.php | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/EagerLoadable.php b/EagerLoadable.php index a86f3a7b..2e62f3a2 100644 --- a/EagerLoadable.php +++ b/EagerLoadable.php @@ -66,6 +66,14 @@ class EagerLoadable * A dotted separated string representing the path of entity properties * in which results for this level should be placed. * + * For example, in the following nested property: + * + * ``` + * $article->author->company->country + * ``` + * + * The property path of `country` will be `author.company` + * * @var string */ protected $_propertyPath; @@ -89,6 +97,14 @@ class EagerLoadable * The property name where the association result should be nested * in the result. * + * For example, in the following nested property: + * + * ``` + * $article->author->company->country + * ``` + * + * The target property of `country` will be just `country` + * * @var string */ protected $_targetProperty; @@ -172,6 +188,14 @@ public function aliasPath() * Gets a dot separated string representing the path of entity properties * in which results for this level should be placed. * + * For example, in the following nested property: + * + * ``` + * $article->author->company->country + * ``` + * + * The property path of `country` will be `author.company` + * * @return string|null */ public function propertyPath() @@ -228,7 +252,15 @@ public function forMatching() * The property name where the result of this association * should be nested at the end. * - * @return string + * For example, in the following nested property: + * + * ``` + * $article->author->company->country + * ``` + * + * The target property of `country` will be just `country` + * + * @return string|null */ public function targetProperty() { From d92853e22aa80e6a820d22b20173d1990bcdcb8d Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 6 Aug 2016 17:53:54 -0400 Subject: [PATCH 0769/2059] Make unlink() return true. Having this method return true allows it to be used in conditionals. While it never returns false naturally, returning true makes this method more consistent with other methods like link() and replaceLinks(). Refs #9208 --- Association/BelongsToMany.php | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 89a2ae75..3b38f8fd 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -709,12 +709,12 @@ protected function _saveLinks(EntityInterface $sourceEntity, $targetEntities, $o * `$article->get('tags')` will contain all tags in `$newTags` after liking * * @param \Cake\Datasource\EntityInterface $sourceEntity the row belonging to the `source` side - * of this association + * of this association * @param array $targetEntities list of entities belonging to the `target` side - * of this association + * of this association * @param array $options list of options to be passed to the internal `save` call * @throws \InvalidArgumentException when any of the values in $targetEntities is - * detected to not be already persisted + * detected to not be already persisted * @return bool true on success, false otherwise */ public function link(EntityInterface $sourceEntity, array $targetEntities, array $options = []) @@ -759,14 +759,14 @@ function () use ($sourceEntity, $targetEntities, $options) { * `$article->get('tags')` will contain only `[$tag4]` after deleting in the database * * @param \Cake\Datasource\EntityInterface $sourceEntity an entity persisted in the source table for - * this association + * this association * @param array $targetEntities list of entities persisted in the target table for - * this association + * this association * @param array|bool $options list of options to be passed to the internal `delete` call, - * or a `boolean` + * or a `boolean` * @throws \InvalidArgumentException if non persisted entities are passed or if - * any of them is lacking a primary key value - * @return void + * any of them is lacking a primary key value + * @return bool Success */ public function unlink(EntityInterface $sourceEntity, array $targetEntities, $options = []) { @@ -792,7 +792,7 @@ function () use ($sourceEntity, $targetEntities, $options) { $existing = $sourceEntity->get($property) ?: []; if (!$options['cleanProperty'] || empty($existing)) { - return; + return true; } $storage = new SplObjectStorage; @@ -808,6 +808,7 @@ function () use ($sourceEntity, $targetEntities, $options) { $sourceEntity->set($property, array_values($existing)); $sourceEntity->dirty($property, false); + return true; } /** @@ -991,12 +992,12 @@ protected function _appendJunctionJoin($query, $conditions) * `$article->get('tags')` will contain only `[$tag1, $tag3]` at the end * * @param \Cake\Datasource\EntityInterface $sourceEntity an entity persisted in the source table for - * this association + * this association * @param array $targetEntities list of entities from the target table to be linked * @param array $options list of options to be passed to the internal `save`/`delete` calls - * when persisting/updating new links, or deleting existing ones + * when persisting/updating new links, or deleting existing ones * @throws \InvalidArgumentException if non persisted entities are passed or if - * any of them is lacking a primary key value + * any of them is lacking a primary key value * @return bool success */ public function replaceLinks(EntityInterface $sourceEntity, array $targetEntities, array $options = []) From 7259c73cb8d4d4df49fe19d2bf58da3529a5afcb Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 6 Aug 2016 18:37:14 -0400 Subject: [PATCH 0770/2059] Fix phpcs error. --- Association/BelongsToMany.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 3b38f8fd..47f7dbe8 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -808,6 +808,7 @@ function () use ($sourceEntity, $targetEntities, $options) { $sourceEntity->set($property, array_values($existing)); $sourceEntity->dirty($property, false); + return true; } From 895da0322f5ddacf64606507bd8064085c806930 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 7 Aug 2016 12:47:48 +0200 Subject: [PATCH 0771/2059] Fixed more CS issues --- Association/BelongsToMany.php | 1 + EagerLoader.php | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index c6419208..49faf9ff 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1276,6 +1276,7 @@ protected function _buildQuery($options) ->eagerLoader() ->addToJoinsMap($tempName, $assoc, false, $this->_junctionProperty); $assoc->attachTo($query, ['aliasPath' => $assoc->alias(), 'includeFields' => false]); + return $query; } diff --git a/EagerLoader.php b/EagerLoader.php index 39cd713d..5d12b226 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -657,6 +657,8 @@ public function associationsMap($table) * will be normalized * @param bool $asMatching Whether or not this join results should be treated as a * 'matching' association. + * @param string $targetProperty The property name where the results of the join should be nested at. + * If not passed, the default property for the association will be used. * @return void */ public function addToJoinsMap($alias, Association $assoc, $asMatching = false, $targetProperty = null) From d69a9021146be92fb21df9b1a159b99d5387a809 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Cansado=20Sol=C3=A0?= Date: Sun, 17 Jul 2016 17:21:29 +0200 Subject: [PATCH 0772/2059] extend Marshaller and TranslateBehavior to patch translations fields --- Behavior/TranslateBehavior.php | 47 +++++++++++++++++++++++++++-- Marshaller.php | 55 +++++++++++++++++++++++++++++++++- 2 files changed, 99 insertions(+), 3 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 5d32ee88..98e2b581 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -74,7 +74,7 @@ class TranslateBehavior extends Behavior */ protected $_defaultConfig = [ 'implementedFinders' => ['translations' => 'findTranslations'], - 'implementedMethods' => ['locale' => 'locale'], + 'implementedMethods' => ['locale' => 'locale', 'mergeTranslations' => 'mergeTranslations'], 'fields' => [], 'translationTable' => 'I18n', 'defaultLocale' => '', @@ -82,7 +82,8 @@ class TranslateBehavior extends Behavior 'allowEmptyTranslations' => true, 'onlyTranslated' => false, 'strategy' => 'subquery', - 'tableLocator' => null + 'tableLocator' => null, + 'validator' => false ]; /** @@ -326,6 +327,48 @@ public function afterSave(Event $event, EntityInterface $entity) $entity->unsetProperty('_i18n'); } + /** + * Merges `$data` into `$original` entity recursively using Marshaller merge method, if + * original entity is null, new one will be created. + * The translated entity may only contain the fields defined in the + * behavior configuration (`fields`), you can use `fieldList` option as a + * whitelist of fields to be assigned. + * + * The result will be and array [entities, errors]: + * - entities indexed by locale name + * - errors indexed by locale name + * or null if there are no fields to merge. + * + * ## Note: Translated entity data not will be validated during merge. + * + * @param \Cake\Datasource\EntityInterface $original The original entity + * @param array $data key value list of languages with fields to be merged into the translate entity + * @param \Cake\ORM\Marshaller $marshaller Marshaller + * @param array $options list of options for Marshaller + * @return array|null + */ + public function mergeTranslations($original, array $data, \Cake\ORM\Marshaller $marshaller, array $options = []) + { + $options['fieldList'] = (isset($options['fieldList'])) ? array_intersect($this->_config['fields'], $options['fieldList']) : $this->_config['fields']; + if (empty($options['fieldList'])) { + return null; + } + + $options['validate'] = $this->_config['validator']; + $errors = []; + foreach ($data as $language => $fields) { + if (!isset($original[$language])) { + $original[$language] = $this->_table->newEntity(); + } + $marshaller->merge($original[$language], $fields, $options); + if ((bool)$original[$language]->errors()) { + $errors[$language] = $original[$language]->errors(); + } + } + + return [$original, $errors]; + } + /** * Sets all future finds for the bound table to also fetch translated fields for * the passed locale. If no value is passed, it returns the currently configured diff --git a/Marshaller.php b/Marshaller.php index 6c132066..7105bca1 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -147,6 +147,8 @@ public function one(array $data, array $options = []) $marshallOptions['forceNew'] = $options['forceNew']; } + $hasTranslations = $this->_hasTranslations($options); + $errors = $this->_validate($data, $options, true); $properties = []; foreach ($data as $key => $value) { @@ -166,6 +168,8 @@ public function one(array $data, array $options = []) } elseif ($columnType) { $converter = Type::build($columnType); $value = $converter->marshal($value); + } elseif ($key === '_translations' && $hasTranslations) { + $value = $this->_mergeTranslations(null, $value, $errors, $options); } $properties[$key] = $value; } @@ -226,7 +230,7 @@ protected function _validate($data, $options, $isNew) */ protected function _prepareDataAndOptions($data, $options) { - $options += ['validate' => true]; + $options += ['validate' => true, 'translations' => true]; $tableName = $this->_table->alias(); if (isset($data[$tableName])) { @@ -449,6 +453,51 @@ protected function _loadBelongsToMany($assoc, $ids) return $this->_loadAssociatedByIds($assoc, $ids); } + /** + * Call translation merge. Validations errors during merge will be added to `$errors` param + * + * @param \Cake\Datasource\EntityInterface $original The original entity + * @param array $data key value list of languages with fields to be merged into the translate entity + * @param array $errors array with entity errors + * @param array $options list of options + * @return array|null + */ + protected function _mergeTranslations($original, array $data, array &$errors, array $options = []) + { + $result = $this->_table->mergeTranslations($original, $data, $this, $options); + + if (is_array($result)) { + if ((bool)$result[1]) { + $errors['_translations'] = $result[1]; + } + $result = $result[0]; + } + + return $result; + } + + /** + * Return if table contains translate behavior or we specificate to use via `translations` options. + * + * In case that $options has `fieldList` option and `_translations` field is not present inside it, it will include + * + * ### Options: + * + * - translations: Set to false to disable translations + * + * @param array $options List of options + * @return bool + */ + protected function _hasTranslations(array &$options = []) + { + $hasTranslations = ($this->_table->behaviors()->hasMethod('mergeTranslations') && (bool)$options['translations']); + if ($hasTranslations && !empty($options['fieldList']) && !in_array('_translations', $options['fieldList'])) { + array_push($options['fieldList'], '_translations'); + } + + return $hasTranslations; + } + /** * Merges `$data` into `$entity` and recursively does the same for each one of * the association names passed in `$options`. When merging associations, if an @@ -503,6 +552,8 @@ public function merge(EntityInterface $entity, array $data, array $options = []) } } + $hasTranslations = $this->_hasTranslations($options); + $errors = $this->_validate($data + $keys, $options, $isNew); $schema = $this->_table->schema(); $properties = $marshalledAssocs = []; @@ -530,6 +581,8 @@ public function merge(EntityInterface $entity, array $data, array $options = []) ) { continue; } + } elseif ($key === '_translations' && $hasTranslations) { + $value = $this->_mergeTranslations($original, $value, $errors, $options); } $properties[$key] = $value; From 8ee3f9760485e25021646b1c0caab062254d4886 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Cansado=20Sol=C3=A0?= Date: Sun, 24 Jul 2016 11:45:06 +0200 Subject: [PATCH 0773/2059] update translateBehavior --- Behavior/TranslateBehavior.php | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 98e2b581..98f5c82d 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -329,19 +329,17 @@ public function afterSave(Event $event, EntityInterface $entity) /** * Merges `$data` into `$original` entity recursively using Marshaller merge method, if - * original entity is null, new one will be created. + * original entity not has language entity, new one will be created. * The translated entity may only contain the fields defined in the * behavior configuration (`fields`), you can use `fieldList` option as a * whitelist of fields to be assigned. * * The result will be and array [entities, errors]: - * - entities indexed by locale name - * - errors indexed by locale name + * - entities: translated entity indexed by locale name + * - errors: array indexed by locale name * or null if there are no fields to merge. * - * ## Note: Translated entity data not will be validated during merge. - * - * @param \Cake\Datasource\EntityInterface $original The original entity + * @param array $original The original entity data * @param array $data key value list of languages with fields to be merged into the translate entity * @param \Cake\ORM\Marshaller $marshaller Marshaller * @param array $options list of options for Marshaller From 981ab0c87d79ee777f648c3c4f723db23766695b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Cansado=20Sol=C3=A0?= Date: Sun, 24 Jul 2016 11:53:32 +0200 Subject: [PATCH 0774/2059] update MarshallerTest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - separate method (_hasTranslations) - delete all “pass by reference” - simplify tests and remove all mocks --- Marshaller.php | 46 +++++++++++++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 7105bca1..0c9d6238 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -148,6 +148,9 @@ public function one(array $data, array $options = []) } $hasTranslations = $this->_hasTranslations($options); + if ($hasTranslations) { + $options = $this->_addTranslationsToFieldList($options); + } $errors = $this->_validate($data, $options, true); $properties = []; @@ -169,7 +172,10 @@ public function one(array $data, array $options = []) $converter = Type::build($columnType); $value = $converter->marshal($value); } elseif ($key === '_translations' && $hasTranslations) { - $value = $this->_mergeTranslations(null, $value, $errors, $options); + list($value, $translationsErrors) = $this->_mergeTranslations(null, $value, $options); + if (!empty($translationsErrors)) { + $errors += $translationsErrors; + } } $properties[$key] = $value; } @@ -454,18 +460,18 @@ protected function _loadBelongsToMany($assoc, $ids) } /** - * Call translation merge. Validations errors during merge will be added to `$errors` param + * Call translation merge * * @param \Cake\Datasource\EntityInterface $original The original entity * @param array $data key value list of languages with fields to be merged into the translate entity - * @param array $errors array with entity errors * @param array $options list of options - * @return array|null + * @return array */ - protected function _mergeTranslations($original, array $data, array &$errors, array $options = []) + protected function _mergeTranslations($original, array $data, array $options = []) { $result = $this->_table->mergeTranslations($original, $data, $this, $options); + $errors = []; if (is_array($result)) { if ((bool)$result[1]) { $errors['_translations'] = $result[1]; @@ -473,14 +479,12 @@ protected function _mergeTranslations($original, array $data, array &$errors, ar $result = $result[0]; } - return $result; + return [$result, $errors]; } /** * Return if table contains translate behavior or we specificate to use via `translations` options. * - * In case that $options has `fieldList` option and `_translations` field is not present inside it, it will include - * * ### Options: * * - translations: Set to false to disable translations @@ -488,14 +492,24 @@ protected function _mergeTranslations($original, array $data, array &$errors, ar * @param array $options List of options * @return bool */ - protected function _hasTranslations(array &$options = []) + protected function _hasTranslations(array $options = []) + { + return ($this->_table->behaviors()->hasMethod('mergeTranslations') && (bool)$options['translations']); + } + + /** + * Add `_translations` field to `fieldList` $options if it's not present inside + * + * @param array $options List of options + * @return array + */ + protected function _addTranslationsToFieldList(array $options = []) { - $hasTranslations = ($this->_table->behaviors()->hasMethod('mergeTranslations') && (bool)$options['translations']); - if ($hasTranslations && !empty($options['fieldList']) && !in_array('_translations', $options['fieldList'])) { + if (!empty($options['fieldList']) && !in_array('_translations', $options['fieldList'])) { array_push($options['fieldList'], '_translations'); } - return $hasTranslations; + return $options; } /** @@ -553,6 +567,9 @@ public function merge(EntityInterface $entity, array $data, array $options = []) } $hasTranslations = $this->_hasTranslations($options); + if ($hasTranslations) { + $options = $this->_addTranslationsToFieldList($options); + } $errors = $this->_validate($data + $keys, $options, $isNew); $schema = $this->_table->schema(); @@ -582,7 +599,10 @@ public function merge(EntityInterface $entity, array $data, array $options = []) continue; } } elseif ($key === '_translations' && $hasTranslations) { - $value = $this->_mergeTranslations($original, $value, $errors, $options); + list($value, $translationsErrors) = $this->_mergeTranslations($original, $value, $options); + if (!empty($translationsErrors)) { + $errors += $translationsErrors; + } } $properties[$key] = $value; From 7dc534a1ab1caf17a04ce81759022197734e8bd0 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 5 Aug 2016 00:22:48 -0400 Subject: [PATCH 0775/2059] Prototype of more extensible marshalling. This is a rough sketch of what it could look like for behaviors to be able to participate in marshalling by providing a map of the fields they can marshal. Each behavior could implement an interface opt-ing it into the marshalling process. The behavior would then return an updated map of fields -> marshalling functions. Each marshalling function is expected to convert the request value into a hydrated object. The unified interface lets database types, associations, and behaviors work the same way. The tests & documentation are not complete, but I wanted to get some feedback before completing all the tests/documentation. --- Behavior/TranslateBehavior.php | 63 +++++++--------- Marshaller.php | 134 ++++++++++++++++----------------- 2 files changed, 93 insertions(+), 104 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 98f5c82d..8fdb981f 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -74,7 +74,7 @@ class TranslateBehavior extends Behavior */ protected $_defaultConfig = [ 'implementedFinders' => ['translations' => 'findTranslations'], - 'implementedMethods' => ['locale' => 'locale', 'mergeTranslations' => 'mergeTranslations'], + 'implementedMethods' => ['locale' => 'locale'], 'fields' => [], 'translationTable' => 'I18n', 'defaultLocale' => '', @@ -327,44 +327,37 @@ public function afterSave(Event $event, EntityInterface $entity) $entity->unsetProperty('_i18n'); } - /** - * Merges `$data` into `$original` entity recursively using Marshaller merge method, if - * original entity not has language entity, new one will be created. - * The translated entity may only contain the fields defined in the - * behavior configuration (`fields`), you can use `fieldList` option as a - * whitelist of fields to be assigned. - * - * The result will be and array [entities, errors]: - * - entities: translated entity indexed by locale name - * - errors: array indexed by locale name - * or null if there are no fields to merge. - * - * @param array $original The original entity data - * @param array $data key value list of languages with fields to be merged into the translate entity - * @param \Cake\ORM\Marshaller $marshaller Marshaller - * @param array $options list of options for Marshaller - * @return array|null - */ - public function mergeTranslations($original, array $data, \Cake\ORM\Marshaller $marshaller, array $options = []) + public function marshalPropertyMap($marshaller, $map, $options) { - $options['fieldList'] = (isset($options['fieldList'])) ? array_intersect($this->_config['fields'], $options['fieldList']) : $this->_config['fields']; - if (empty($options['fieldList'])) { - return null; + if (isset($options['translations']) && !$options['translations']) { + return []; } - $options['validate'] = $this->_config['validator']; - $errors = []; - foreach ($data as $language => $fields) { - if (!isset($original[$language])) { - $original[$language] = $this->_table->newEntity(); - } - $marshaller->merge($original[$language], $fields, $options); - if ((bool)$original[$language]->errors()) { - $errors[$language] = $original[$language]->errors(); + return [ + '_translations' => function ($value, $entity) use ($marshaller, $options) { + $translations = $entity->get('_translations'); + foreach ($this->_config['fields'] as $field) { + $options['validate'] = $this->_config['validator']; + $errors = []; + if (!is_array($value)) { + return; + } + foreach ($value as $language => $fields) { + if (!isset($translations[$language])) { + $translations[$language] = $this->_table->newEntity(); + } + $marshaller->merge($translations[$language], $fields, $options); + if ((bool)$translations[$language]->errors()) { + $errors[$language] = $translations[$language]->errors(); + } + } + // Set errors into the root entity, so validation errors + // match the original form data position. + $entity->errors($errors); + } + return $translations; } - } - - return [$original, $errors]; + ]; } /** diff --git a/Marshaller.php b/Marshaller.php index 0c9d6238..ddfe1833 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -55,39 +55,69 @@ public function __construct(Table $table) } /** - * Build the map of property => association names. + * Build the map of property => marshalling callable. * + * @param array $data The data being marshalled. * @param array $options List of options containing the 'associated' key. * @throws \InvalidArgumentException When associations do not exist. * @return array */ - protected function _buildPropertyMap($options) + protected function _buildPropertyMap($data, $options) { - if (empty($options['associated'])) { - return []; + $map = []; + $schema = $this->_table->schema(); + + // Is a concrete column? + foreach (array_keys($data) as $prop) { + $columnType = $schema->columnType($prop); + if ($columnType) { + $map[$prop] = function ($value, $entity) use ($columnType) { + return Type::build($columnType)->marshal($value); + }; + } } - $include = $options['associated']; - $map = []; - $include = $this->_normalizeAssociations($include); + // Map associations + $options += ['associated' => []]; + $include = $this->_normalizeAssociations($options['associated']); foreach ($include as $key => $nested) { if (is_int($key) && is_scalar($nested)) { $key = $nested; $nested = []; } $assoc = $this->_table->association($key); - if ($assoc) { - $map[$assoc->property()] = ['association' => $assoc] + $nested + ['associated' => []]; - continue; - } // If the key is not a special field like _ids or _joinData // it is a missing association that we should error on. - if (substr($key, 0, 1) !== "_") { - throw new \InvalidArgumentException(sprintf( - 'Cannot marshal data for "%s" association. It is not associated with "%s".', - $key, - $this->_table->alias() - )); + if (!$assoc) + if (substr($key, 0, 1) !== '_') { + throw new \InvalidArgumentException(sprintf( + 'Cannot marshal data for "%s" association. It is not associated with "%s".', + $key, + $this->_table->alias() + )); + } + continue; + } + if (isset($options['forceNew'])) { + $nested['forceNew'] = $options['forceNew']; + } + $new = function ($value, $entity) use ($assoc, $nested) { + $options = $nested + ['associated' => []]; + return $this->_marshalAssociation($assoc, $value, $options); + }; + $merge = function ($value, $entity) use ($assoc, $nested) { + $options = $nested + ['associated' => []]; + return $this->_mergeAssociation($entity->get($assoc->property()), $assoc, $value, $options); + }; + $map[$assoc->property()] = isset($options['isMerge']) ? $merge : $new; + } + + $behaviors = $this->_table->behaviors(); + foreach ($behaviors->loaded() as $name) { + $behavior = $behaviors->get($name); + // TODO use an interface here. + if (method_exists($behavior, 'marshalPropertyMap')) { + $map = $behavior->marshalPropertyMap($this, $map, $options); } } @@ -128,9 +158,6 @@ public function one(array $data, array $options = []) { list($data, $options) = $this->_prepareDataAndOptions($data, $options); - $propertyMap = $this->_buildPropertyMap($options); - - $schema = $this->_table->schema(); $primaryKey = (array)$this->_table->primaryKey(); $entityClass = $this->_table->entityClass(); $entity = new $entityClass(); @@ -141,18 +168,10 @@ public function one(array $data, array $options = []) $entity->accessible($key, $value); } } - - $marshallOptions = []; - if (isset($options['forceNew'])) { - $marshallOptions['forceNew'] = $options['forceNew']; - } - - $hasTranslations = $this->_hasTranslations($options); - if ($hasTranslations) { - $options = $this->_addTranslationsToFieldList($options); - } - $errors = $this->_validate($data, $options, true); + + $options['isMerge'] = false; + $propertyMap = $this->_buildPropertyMap($data, $options); $properties = []; foreach ($data as $key => $value) { if (!empty($errors[$key])) { @@ -161,23 +180,15 @@ public function one(array $data, array $options = []) } continue; } - $columnType = $schema->columnType($key); - if (isset($propertyMap[$key])) { - $assoc = $propertyMap[$key]['association']; - $value = $this->_marshalAssociation($assoc, $value, $propertyMap[$key] + $marshallOptions); - } elseif ($value === '' && in_array($key, $primaryKey, true)) { + + if ($value === '' && in_array($key, $primaryKey, true)) { // Skip marshalling '' for pk fields. continue; - } elseif ($columnType) { - $converter = Type::build($columnType); - $value = $converter->marshal($value); - } elseif ($key === '_translations' && $hasTranslations) { - list($value, $translationsErrors) = $this->_mergeTranslations(null, $value, $options); - if (!empty($translationsErrors)) { - $errors += $translationsErrors; - } + } elseif (isset($propertyMap[$key])) { + $properties[$key] = $propertyMap[$key]($value, $entity); + } else { + $properties[$key] = $value; } - $properties[$key] = $value; } if (!isset($options['fieldList'])) { @@ -552,7 +563,6 @@ public function merge(EntityInterface $entity, array $data, array $options = []) { list($data, $options) = $this->_prepareDataAndOptions($data, $options); - $propertyMap = $this->_buildPropertyMap($options); $isNew = $entity->isNew(); $keys = []; @@ -566,13 +576,10 @@ public function merge(EntityInterface $entity, array $data, array $options = []) } } - $hasTranslations = $this->_hasTranslations($options); - if ($hasTranslations) { - $options = $this->_addTranslationsToFieldList($options); - } - $errors = $this->_validate($data + $keys, $options, $isNew); $schema = $this->_table->schema(); + $options['isMerge'] = true; + $propertyMap = $this->_buildPropertyMap($data, $options); $properties = $marshalledAssocs = []; foreach ($data as $key => $value) { if (!empty($errors[$key])) { @@ -581,30 +588,19 @@ public function merge(EntityInterface $entity, array $data, array $options = []) } continue; } - - $columnType = $schema->columnType($key); $original = $entity->get($key); if (isset($propertyMap[$key])) { - $assoc = $propertyMap[$key]['association']; - $value = $this->_mergeAssociation($original, $assoc, $value, $propertyMap[$key]); - $marshalledAssocs[$key] = true; - } elseif ($columnType) { - $converter = Type::build($columnType); - $value = $converter->marshal($value); + $value = $propertyMap[$key]($value, $entity); + + // Don't dirty complex objects that were objects before. $isObject = is_object($value); if ((!$isObject && $original === $value) || ($isObject && $original == $value) ) { continue; } - } elseif ($key === '_translations' && $hasTranslations) { - list($value, $translationsErrors) = $this->_mergeTranslations($original, $value, $options); - if (!empty($translationsErrors)) { - $errors += $translationsErrors; - } } - $properties[$key] = $value; } @@ -612,9 +608,9 @@ public function merge(EntityInterface $entity, array $data, array $options = []) $entity->set($properties); $entity->errors($errors); - foreach (array_keys($marshalledAssocs) as $field) { - if ($properties[$field] instanceof EntityInterface) { - $entity->dirty($field, $properties[$field]->dirty()); + foreach ($properties as $field => $value) { + if ($value instanceof EntityInterface) { + $entity->dirty($field, $value->dirty()); } } @@ -624,7 +620,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) foreach ((array)$options['fieldList'] as $field) { if (array_key_exists($field, $properties)) { $entity->set($field, $properties[$field]); - if ($properties[$field] instanceof EntityInterface && isset($marshalledAssocs[$field])) { + if ($properties[$field] instanceof EntityInterface) { $entity->dirty($field, $properties[$field]->dirty()); } } From 5c3f4213b7eeabb8995b4a4c4e1b1bfcf115d7f7 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 6 Aug 2016 12:09:39 -0400 Subject: [PATCH 0776/2059] Clean up code. - Fix lint - Remove unused code. --- Behavior/TranslateBehavior.php | 1 + Marshaller.php | 57 ++-------------------------------- 2 files changed, 4 insertions(+), 54 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 8fdb981f..7122687e 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -355,6 +355,7 @@ public function marshalPropertyMap($marshaller, $map, $options) // match the original form data position. $entity->errors($errors); } + return $translations; } ]; diff --git a/Marshaller.php b/Marshaller.php index ddfe1833..142f0749 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -103,10 +103,12 @@ protected function _buildPropertyMap($data, $options) } $new = function ($value, $entity) use ($assoc, $nested) { $options = $nested + ['associated' => []]; + return $this->_marshalAssociation($assoc, $value, $options); }; $merge = function ($value, $entity) use ($assoc, $nested) { $options = $nested + ['associated' => []]; + return $this->_mergeAssociation($entity->get($assoc->property()), $assoc, $value, $options); }; $map[$assoc->property()] = isset($options['isMerge']) ? $merge : $new; @@ -247,7 +249,7 @@ protected function _validate($data, $options, $isNew) */ protected function _prepareDataAndOptions($data, $options) { - $options += ['validate' => true, 'translations' => true]; + $options += ['validate' => true]; $tableName = $this->_table->alias(); if (isset($data[$tableName])) { @@ -470,59 +472,6 @@ protected function _loadBelongsToMany($assoc, $ids) return $this->_loadAssociatedByIds($assoc, $ids); } - /** - * Call translation merge - * - * @param \Cake\Datasource\EntityInterface $original The original entity - * @param array $data key value list of languages with fields to be merged into the translate entity - * @param array $options list of options - * @return array - */ - protected function _mergeTranslations($original, array $data, array $options = []) - { - $result = $this->_table->mergeTranslations($original, $data, $this, $options); - - $errors = []; - if (is_array($result)) { - if ((bool)$result[1]) { - $errors['_translations'] = $result[1]; - } - $result = $result[0]; - } - - return [$result, $errors]; - } - - /** - * Return if table contains translate behavior or we specificate to use via `translations` options. - * - * ### Options: - * - * - translations: Set to false to disable translations - * - * @param array $options List of options - * @return bool - */ - protected function _hasTranslations(array $options = []) - { - return ($this->_table->behaviors()->hasMethod('mergeTranslations') && (bool)$options['translations']); - } - - /** - * Add `_translations` field to `fieldList` $options if it's not present inside - * - * @param array $options List of options - * @return array - */ - protected function _addTranslationsToFieldList(array $options = []) - { - if (!empty($options['fieldList']) && !in_array('_translations', $options['fieldList'])) { - array_push($options['fieldList'], '_translations'); - } - - return $options; - } - /** * Merges `$data` into `$entity` and recursively does the same for each one of * the association names passed in `$options`. When merging associations, if an From 09d6d047163e123bb0aa2ac022bcc67420d7421c Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 7 Aug 2016 10:20:54 -0400 Subject: [PATCH 0777/2059] Add documentation and interface for marshalling behaviors. - Fix rebase mistake. - Add missing docs - Add interface. --- Behavior/TranslateBehavior.php | 12 ++++++++++-- MarshalParticipantInterface.php | 34 +++++++++++++++++++++++++++++++++ Marshaller.php | 8 ++++---- 3 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 MarshalParticipantInterface.php diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 7122687e..807aa126 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -22,6 +22,7 @@ use Cake\ORM\Behavior; use Cake\ORM\Entity; use Cake\ORM\Locator\LocatorAwareTrait; +use Cake\ORM\MarshalParticipantInterface; use Cake\ORM\Query; use Cake\ORM\Table; use Cake\Utility\Inflector; @@ -38,7 +39,7 @@ * If you want to bring all or certain languages for each of the fetched records, * you can use the custom `translations` finders that is exposed to the table. */ -class TranslateBehavior extends Behavior +class TranslateBehavior extends Behavior implements MarshalParticipantInterface { use LocatorAwareTrait; @@ -327,7 +328,14 @@ public function afterSave(Event $event, EntityInterface $entity) $entity->unsetProperty('_i18n'); } - public function marshalPropertyMap($marshaller, $map, $options) + /** + * Add in _translations marshalling handlers if translation marshalling is + * enabled. You need to specifically enable translation marshalling by adding + * `'translations' => true` to the options provided to `Table::newEntity()` or `Table::patchEntity()`. + * + * {@inheritDoc} + */ + public function buildMarhshalMap($marshaller, $map, $options) { if (isset($options['translations']) && !$options['translations']) { return []; diff --git a/MarshalParticipantInterface.php b/MarshalParticipantInterface.php new file mode 100644 index 00000000..1b42a4ba --- /dev/null +++ b/MarshalParticipantInterface.php @@ -0,0 +1,34 @@ + callable]` of additional properties to marshal. + */ + public function buildMarhshalMap($marshaller, $map, $options); +} diff --git a/Marshaller.php b/Marshaller.php index 142f0749..d9193f98 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -20,6 +20,7 @@ use Cake\Database\Type; use Cake\Datasource\EntityInterface; use Cake\Datasource\InvalidPropertyInterface; +use Cake\ORM\MarshalParticipantInterface; use RuntimeException; /** @@ -88,7 +89,7 @@ protected function _buildPropertyMap($data, $options) $assoc = $this->_table->association($key); // If the key is not a special field like _ids or _joinData // it is a missing association that we should error on. - if (!$assoc) + if (!$assoc) { if (substr($key, 0, 1) !== '_') { throw new \InvalidArgumentException(sprintf( 'Cannot marshal data for "%s" association. It is not associated with "%s".', @@ -117,9 +118,8 @@ protected function _buildPropertyMap($data, $options) $behaviors = $this->_table->behaviors(); foreach ($behaviors->loaded() as $name) { $behavior = $behaviors->get($name); - // TODO use an interface here. - if (method_exists($behavior, 'marshalPropertyMap')) { - $map = $behavior->marshalPropertyMap($this, $map, $options); + if ($behavior instanceof MarshalParticipantInterface) { + $map = $behavior->buildMarhshalMap($this, $map, $options); } } From d6b9c4730ef1b7f8bcf779188ffa42cfc1b38c7f Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 7 Aug 2016 22:38:17 -0400 Subject: [PATCH 0778/2059] Rename interface. Use a less dopey name. --- Behavior/TranslateBehavior.php | 4 ++-- Marshaller.php | 4 ++-- ...alParticipantInterface.php => PropertyMarshalInterface.php | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) rename MarshalParticipantInterface.php => PropertyMarshalInterface.php (97%) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 807aa126..13b947c5 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -22,7 +22,7 @@ use Cake\ORM\Behavior; use Cake\ORM\Entity; use Cake\ORM\Locator\LocatorAwareTrait; -use Cake\ORM\MarshalParticipantInterface; +use Cake\ORM\PropertyMarshalInterface; use Cake\ORM\Query; use Cake\ORM\Table; use Cake\Utility\Inflector; @@ -39,7 +39,7 @@ * If you want to bring all or certain languages for each of the fetched records, * you can use the custom `translations` finders that is exposed to the table. */ -class TranslateBehavior extends Behavior implements MarshalParticipantInterface +class TranslateBehavior extends Behavior implements PropertyMarshalInterface { use LocatorAwareTrait; diff --git a/Marshaller.php b/Marshaller.php index d9193f98..84de478e 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -20,7 +20,7 @@ use Cake\Database\Type; use Cake\Datasource\EntityInterface; use Cake\Datasource\InvalidPropertyInterface; -use Cake\ORM\MarshalParticipantInterface; +use Cake\ORM\PropertyMarshalInterface; use RuntimeException; /** @@ -118,7 +118,7 @@ protected function _buildPropertyMap($data, $options) $behaviors = $this->_table->behaviors(); foreach ($behaviors->loaded() as $name) { $behavior = $behaviors->get($name); - if ($behavior instanceof MarshalParticipantInterface) { + if ($behavior instanceof PropertyMarshalInterface) { $map = $behavior->buildMarhshalMap($this, $map, $options); } } diff --git a/MarshalParticipantInterface.php b/PropertyMarshalInterface.php similarity index 97% rename from MarshalParticipantInterface.php rename to PropertyMarshalInterface.php index 1b42a4ba..34a4190d 100644 --- a/MarshalParticipantInterface.php +++ b/PropertyMarshalInterface.php @@ -20,7 +20,7 @@ * This enables behaviors to define behavior for how the properties they provide/manage * should be marshalled. */ -interface MarshalParticipantInterface +interface PropertyMarshalInterface { /** * Build a set of properties that should be included in the marshalling process. From fda7bbb7fa4651552b97caf293a3794fd193ad56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Mon, 8 Aug 2016 15:45:06 +0200 Subject: [PATCH 0779/2059] phpcs fixes --- SaveOptionsBuilder.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/SaveOptionsBuilder.php b/SaveOptionsBuilder.php index b34e1f63..f6a689ac 100644 --- a/SaveOptionsBuilder.php +++ b/SaveOptionsBuilder.php @@ -70,6 +70,7 @@ public function parseArrayOptions($array) foreach ($array as $key => $value) { $this->{$key}($value); } + return $this; } @@ -134,6 +135,7 @@ protected function _checkAssociation(Table $table, $association) public function guard($guard) { $this->_options['guard'] = (bool)$guard; + return $this; } @@ -147,6 +149,7 @@ public function validate($validate) { $this->_table->validator($validate); $this->_options['validate'] = $validate; + return $this; } @@ -159,6 +162,7 @@ public function validate($validate) public function checkExisting($checkExisting) { $this->_options['checkExisting'] = (bool)$checkExisting; + return $this; } @@ -171,6 +175,7 @@ public function checkExisting($checkExisting) public function checkRules($checkRules) { $this->_options['checkRules'] = (bool)$checkRules; + return $this; } @@ -183,6 +188,7 @@ public function checkRules($checkRules) public function atomic($atomic) { $this->_options['atomic'] = (bool)$atomic; + return $this; } @@ -207,6 +213,7 @@ public function set($option, $value) return $this->{$option}($value); } $this->_options[$option] = $value; + return $this; } } From 5d4e059dbabf537b60b786f4a016d2ec5f6c3012 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 9 Aug 2016 21:45:30 -0400 Subject: [PATCH 0780/2059] Remove extra closure allocation. Use an if/else to save allocating a closure. --- Marshaller.php | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 84de478e..0362658e 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -102,17 +102,20 @@ protected function _buildPropertyMap($data, $options) if (isset($options['forceNew'])) { $nested['forceNew'] = $options['forceNew']; } - $new = function ($value, $entity) use ($assoc, $nested) { - $options = $nested + ['associated' => []]; + if (isset($options['isMerge'])) { + $callback = function ($value, $entity) use ($assoc, $nested) { + $options = $nested + ['associated' => []]; - return $this->_marshalAssociation($assoc, $value, $options); - }; - $merge = function ($value, $entity) use ($assoc, $nested) { - $options = $nested + ['associated' => []]; + return $this->_mergeAssociation($entity->get($assoc->property()), $assoc, $value, $options); + }; + } else { + $callback = function ($value, $entity) use ($assoc, $nested) { + $options = $nested + ['associated' => []]; - return $this->_mergeAssociation($entity->get($assoc->property()), $assoc, $value, $options); - }; - $map[$assoc->property()] = isset($options['isMerge']) ? $merge : $new; + return $this->_marshalAssociation($assoc, $value, $options); + }; + } + $map[$assoc->property()] = $callback; } $behaviors = $this->_table->behaviors(); From 39324fce218feb81a00a33f0fb93f1cdf44e68c9 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 10 Aug 2016 14:32:07 +0530 Subject: [PATCH 0781/2059] Make Table::aliasField() idempotent. Closes #9244 --- Table.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Table.php b/Table.php index bbada9f5..af974c8f 100644 --- a/Table.php +++ b/Table.php @@ -371,11 +371,17 @@ public function alias($alias = null) /** * Alias a field with the table's current alias. * + * If field is already aliased it will result in no-op. + * * @param string $field The field to alias. * @return string The field prefixed with the table alias. */ public function aliasField($field) { + if (strpos($field, '.') !== false) { + return $field; + } + return $this->alias() . '.' . $field; } From 1eb6b65b7e3ec4d55351aac8b6a2128df6e06937 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20W=C3=BCrth?= Date: Wed, 10 Aug 2016 12:07:07 +0200 Subject: [PATCH 0782/2059] Remove superfluous whitespace in doc blocks --- Behavior/CounterCacheBehavior.php | 1 - Exception/MissingBehaviorException.php | 1 - Exception/MissingEntityException.php | 1 - Exception/MissingTableClassException.php | 1 - Exception/RolledbackTransactionException.php | 1 - ResultSet.php | 1 - TableRegistry.php | 1 - 7 files changed, 7 deletions(-) diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index ee167f02..12011319 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -74,7 +74,6 @@ * ] * ] * ``` - * */ class CounterCacheBehavior extends Behavior { diff --git a/Exception/MissingBehaviorException.php b/Exception/MissingBehaviorException.php index b71968ce..fda3e6f4 100644 --- a/Exception/MissingBehaviorException.php +++ b/Exception/MissingBehaviorException.php @@ -16,7 +16,6 @@ /** * Used when a behavior cannot be found. - * */ class MissingBehaviorException extends Exception { diff --git a/Exception/MissingEntityException.php b/Exception/MissingEntityException.php index 30ea06a7..9191f8ed 100644 --- a/Exception/MissingEntityException.php +++ b/Exception/MissingEntityException.php @@ -20,7 +20,6 @@ /** * Exception raised when an Entity could not be found. - * */ class MissingEntityException extends Exception { diff --git a/Exception/MissingTableClassException.php b/Exception/MissingTableClassException.php index cfd3c32b..51b91b75 100644 --- a/Exception/MissingTableClassException.php +++ b/Exception/MissingTableClassException.php @@ -18,7 +18,6 @@ /** * Exception raised when a Table could not be found. - * */ class MissingTableClassException extends Exception { diff --git a/Exception/RolledbackTransactionException.php b/Exception/RolledbackTransactionException.php index 6b762a22..6c6c8096 100644 --- a/Exception/RolledbackTransactionException.php +++ b/Exception/RolledbackTransactionException.php @@ -16,7 +16,6 @@ /** * Used when a transaction was rolled back from a callback event. - * */ class RolledbackTransactionException extends Exception { diff --git a/ResultSet.php b/ResultSet.php index 06f6d515..4d74371b 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -27,7 +27,6 @@ * This object is responsible for correctly nesting result keys reported from * the query, casting each field to the correct type and executing the extra * queries required for eager loading external associations. - * */ class ResultSet implements ResultSetInterface { diff --git a/TableRegistry.php b/TableRegistry.php index 2b12a6da..87916501 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -46,7 +46,6 @@ * ``` * $table = TableRegistry::get('Users', $config); * ``` - * */ class TableRegistry { From 426a63eb138a325aa9ef5617f9d4b3db4b3d035e Mon Sep 17 00:00:00 2001 From: Walther Lalk Date: Mon, 15 Aug 2016 10:00:20 +0200 Subject: [PATCH 0783/2059] Do not forget the target table column types --- Association/BelongsToMany.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 201e4821..41b61609 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1272,7 +1272,8 @@ protected function _buildQuery($options) $query ->where($this->junctionConditions()) ->select($joinFields) - ->defaultTypes($types); + ->defaultTypes($types) + ->addDefaultTypes($this->target()); $query ->eagerLoader() From 85ec0681370ea56e57ad8229d67b81a4f5a25185 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 15 Aug 2016 21:45:13 -0400 Subject: [PATCH 0784/2059] Fix dirty tracking for array associations. 1:N association properties should be marked as dirty when they are marshalled, and the resulting list has the same number of elements that it did before. Because we can't easily look inside lists, we'll always treat them as dirty. This is the same behavior that existed prior to Improve test coverage around dirty tracking and associations to help catch future regressions around this behavior. Refs #9267 --- Marshaller.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 0362658e..73d30b8c 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -545,10 +545,12 @@ public function merge(EntityInterface $entity, array $data, array $options = []) if (isset($propertyMap[$key])) { $value = $propertyMap[$key]($value, $entity); - // Don't dirty complex objects that were objects before. - $isObject = is_object($value); - if ((!$isObject && $original === $value) || - ($isObject && $original == $value) + // Arrays will be marked as dirty always because + // the original/updated could contain references to the + // same objects, even those those objects may have changed. + if ( + (is_scalar($value) && $original === $value) || + (is_object($value) && $original == $value) ) { continue; } @@ -556,9 +558,9 @@ public function merge(EntityInterface $entity, array $data, array $options = []) $properties[$key] = $value; } + $entity->errors($errors); if (!isset($options['fieldList'])) { $entity->set($properties); - $entity->errors($errors); foreach ($properties as $field => $value) { if ($value instanceof EntityInterface) { @@ -578,8 +580,6 @@ public function merge(EntityInterface $entity, array $data, array $options = []) } } - $entity->errors($errors); - return $entity; } From b1d07f38832a6a34bcf56f268fad63f01d858261 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 15 Aug 2016 22:34:07 -0400 Subject: [PATCH 0785/2059] Fix PHPCS --- Marshaller.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 73d30b8c..3bf2b8f6 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -548,8 +548,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) // Arrays will be marked as dirty always because // the original/updated could contain references to the // same objects, even those those objects may have changed. - if ( - (is_scalar($value) && $original === $value) || + if ((is_scalar($value) && $original === $value) || (is_object($value) && $original == $value) ) { continue; From 377266e60d5ad3d0aeee2c6735216c8db8f0c007 Mon Sep 17 00:00:00 2001 From: mark_story Date: Tue, 16 Aug 2016 14:19:27 -0400 Subject: [PATCH 0786/2059] Fix dirty tracking for 1:1 associations. Update tests to clean entities before calling merge(). This allows dirty tracking to be tested correctly. Refs #9277 --- Marshaller.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 3bf2b8f6..2cd5fa6c 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -545,11 +545,12 @@ public function merge(EntityInterface $entity, array $data, array $options = []) if (isset($propertyMap[$key])) { $value = $propertyMap[$key]($value, $entity); - // Arrays will be marked as dirty always because - // the original/updated could contain references to the - // same objects, even those those objects may have changed. + // Don't dirty scalar values and objects that didn't + // change. Arrays will always be marked as dirty because + // the original/updated list could contain references to the + // same objects, even though those objects may have changed internally. if ((is_scalar($value) && $original === $value) || - (is_object($value) && $original == $value) + (is_object($value) && !($value instanceof EntityInterface) && $original == $value) ) { continue; } From 30729a17aa46e0de33d433f5f15a42ef11f9e09c Mon Sep 17 00:00:00 2001 From: mark_story Date: Wed, 17 Aug 2016 14:35:21 -0400 Subject: [PATCH 0787/2059] Fix marshalling associations with translated records. When marshalling a record, with translations and associated records, the associated records would not be marshalled correctly as the $map was overwritten and not updated. Refs #9289 --- Marshaller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index 2cd5fa6c..ade2dfdf 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -122,7 +122,7 @@ protected function _buildPropertyMap($data, $options) foreach ($behaviors->loaded() as $name) { $behavior = $behaviors->get($name); if ($behavior instanceof PropertyMarshalInterface) { - $map = $behavior->buildMarhshalMap($this, $map, $options); + $map += $behavior->buildMarhshalMap($this, $map, $options); } } From 918311cc5c8d52378867d725a2d64db359b560bf Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 18 Aug 2016 19:55:31 -0400 Subject: [PATCH 0788/2059] Remove the CrossSchemaTableExpression This change has caused regressions for a few folks. It also doesn't work in all the scenarios that people need it to. Not having a problematic feature is better than having a broken one. There was a reasonable solution in the past that involved using `$this->table('db.table_name')` this is a decent solution that makes the cross db joins explicit. Refs #9293 Refs #8679 --- Association.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Association.php b/Association.php index 19d7a22b..f6c1b9cf 100644 --- a/Association.php +++ b/Association.php @@ -16,7 +16,6 @@ use Cake\Collection\Collection; use Cake\Core\ConventionsTrait; -use Cake\Database\Expression\CrossSchemaTableExpression; use Cake\Database\Expression\IdentifierExpression; use Cake\Datasource\EntityInterface; use Cake\Datasource\ResultSetDecorator; @@ -557,14 +556,7 @@ public function attachTo(Query $query, array $options = []) { $target = $this->target(); $joinType = empty($options['joinType']) ? $this->joinType() : $options['joinType']; - $table = $target->table(); - if ($this->source()->connection()->supportsCrossWith($target->connection())) { - $table = new CrossSchemaTableExpression( - $target->connection()->driver()->schema(), - $table - ); - } $options += [ 'includeFields' => true, From cf9bd099021745ee7b5a60a0ba3ae6ae5347a208 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 22 Aug 2016 09:11:08 -0400 Subject: [PATCH 0789/2059] Fix typo in method name and interface. This is a clear mistake that was replicated into multiple places as the code worked before. --- Behavior/TranslateBehavior.php | 2 +- Marshaller.php | 2 +- PropertyMarshalInterface.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 13b947c5..2b89d4ef 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -335,7 +335,7 @@ public function afterSave(Event $event, EntityInterface $entity) * * {@inheritDoc} */ - public function buildMarhshalMap($marshaller, $map, $options) + public function buildMarshalMap($marshaller, $map, $options) { if (isset($options['translations']) && !$options['translations']) { return []; diff --git a/Marshaller.php b/Marshaller.php index ade2dfdf..01982ccb 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -122,7 +122,7 @@ protected function _buildPropertyMap($data, $options) foreach ($behaviors->loaded() as $name) { $behavior = $behaviors->get($name); if ($behavior instanceof PropertyMarshalInterface) { - $map += $behavior->buildMarhshalMap($this, $map, $options); + $map += $behavior->buildMarshalMap($this, $map, $options); } } diff --git a/PropertyMarshalInterface.php b/PropertyMarshalInterface.php index 34a4190d..a5567c71 100644 --- a/PropertyMarshalInterface.php +++ b/PropertyMarshalInterface.php @@ -30,5 +30,5 @@ interface PropertyMarshalInterface * @param array $options The options array used in the marshalling call. * @return array A map of `[property => callable]` of additional properties to marshal. */ - public function buildMarhshalMap($marshaller, $map, $options); + public function buildMarshalMap($marshaller, $map, $options); } From 9693e564090917341e57fd69dede39108ae9072a Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 22 Aug 2016 21:18:53 -0400 Subject: [PATCH 0790/2059] Fix HasMany::unlink() with empty arrays. Return early as there is no work to be done when the target entities is an empty list. I've removed some of the previously added tests as they violated the typehints for the unlink() method. I've also fixed up some of the logic issues in the previous test. Refs #9323 --- Association/HasMany.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Association/HasMany.php b/Association/HasMany.php index 5ea3fbae..398c20ae 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -293,6 +293,9 @@ public function unlink(EntityInterface $sourceEntity, array $targetEntities, $op } else { $options += ['cleanProperty' => true]; } + if (count($targetEntities) === 0) { + return; + } $foreignKey = (array)$this->foreignKey(); $target = $this->target(); From d5e9e186d452bc5cf4d9fea6915f62849f36a8bf Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 23 Aug 2016 17:04:43 -0400 Subject: [PATCH 0791/2059] Fix computed fields in contained associations When selecting computed fields in contained associations, an object to string error would be emitted. This caused queries to work incorrectly in both PHP5.x and PHP7 but in different ways. By doing a slightly slower check we can avoid string conversion errors. Refs #9326 --- Association/SelectableAssociationTrait.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Association/SelectableAssociationTrait.php b/Association/SelectableAssociationTrait.php index d8ef703a..ecf804a7 100644 --- a/Association/SelectableAssociationTrait.php +++ b/Association/SelectableAssociationTrait.php @@ -134,12 +134,20 @@ protected function _assertFieldsPresent($fetchQuery, $key) if (empty($select)) { return; } - $missingFields = array_diff($key, $select) !== []; + $missingKey = function ($fieldList, $key) { + foreach ($key as $keyField) { + if (!in_array($keyField, $fieldList, true)) { + return true; + } + } + return false; + }; + $missingFields = $missingKey($select, $key); if ($missingFields) { $driver = $fetchQuery->connection()->driver(); $quoted = array_map([$driver, 'quoteIdentifier'], $key); - $missingFields = array_diff($quoted, $select) !== []; + $missingFields = $missingKey($select, $quoted); } if ($missingFields) { From c5df3d6ecd417101bae4479e74e1891950789200 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Lorenzo=20Rodr=C3=ADguez?= Date: Tue, 23 Aug 2016 23:46:39 +0200 Subject: [PATCH 0792/2059] Fixed CS error --- Association/SelectableAssociationTrait.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Association/SelectableAssociationTrait.php b/Association/SelectableAssociationTrait.php index ecf804a7..5e975348 100644 --- a/Association/SelectableAssociationTrait.php +++ b/Association/SelectableAssociationTrait.php @@ -140,6 +140,7 @@ protected function _assertFieldsPresent($fetchQuery, $key) return true; } } + return false; }; From 809c50db05fdeaec5eb31fd83fefe7a25d671f12 Mon Sep 17 00:00:00 2001 From: Thinking Media Date: Wed, 24 Aug 2016 23:46:56 -0400 Subject: [PATCH 0793/2059] Missing @return FYI: Return type is not inherited with @inheritDoc is used. --- Query.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Query.php b/Query.php index b3f84822..17f0f9db 100644 --- a/Query.php +++ b/Query.php @@ -180,6 +180,7 @@ public function __construct($connection, $table) * @param array|\Cake\Database\ExpressionInterface|string|\Cake\ORM\Table|\Cake\ORM\Association $fields fields * to be added to the list. * @param bool $overwrite whether to reset fields with passed list or not + * @return $this */ public function select($fields = [], $overwrite = false) { From fd0df2feecf2353ae4f71b550d160f54f94c5a06 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 28 Aug 2016 22:35:20 -0400 Subject: [PATCH 0794/2059] Do fewer allocations for simple default values. Don't allocate arrays when we are only assigning a single array key value. --- Association/BelongsToMany.php | 4 +++- Association/SelectableAssociationTrait.php | 4 +++- EagerLoader.php | 4 +++- Marshaller.php | 4 +++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 41b61609..4d875a52 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -375,7 +375,9 @@ protected function _appendNotMatching($query, $options) if (empty($options['negateMatch'])) { return; } - $options += ['conditions' => []]; + if (!isset($options['conditions'])) { + $options['conditions'] = []; + } $junction = $this->junction(); $belongsTo = $junction->association($this->source()->alias()); $conds = $belongsTo->_joinCondition(['foreignKey' => $belongsTo->foreignKey()]); diff --git a/Association/SelectableAssociationTrait.php b/Association/SelectableAssociationTrait.php index 5e975348..a5e95790 100644 --- a/Association/SelectableAssociationTrait.php +++ b/Association/SelectableAssociationTrait.php @@ -85,7 +85,9 @@ protected function _buildQuery($options) $finder = isset($options['finder']) ? $options['finder'] : $this->finder(); list($finder, $opts) = $this->_extractFinder($finder); - $options += ['fields' => []]; + if (!isset($options['fields'])) { + $options['fields'] = []; + } $fetchQuery = $this ->find($finder, $opts) diff --git a/EagerLoader.php b/EagerLoader.php index 5d12b226..46b19243 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -200,12 +200,14 @@ public function matching($assoc = null, callable $builder = null, $options = []) if ($assoc === null) { return $this->_matching->contain(); } + if (!isset($options['joinType'])) { + $options['joinType'] = 'INNER'; + } $assocs = explode('.', $assoc); $last = array_pop($assocs); $containments = []; $pointer =& $containments; - $options += ['joinType' => 'INNER']; $opts = ['matching' => true] + $options; unset($opts['negateMatch']); diff --git a/Marshaller.php b/Marshaller.php index 01982ccb..c6d46baf 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -79,7 +79,9 @@ protected function _buildPropertyMap($data, $options) } // Map associations - $options += ['associated' => []]; + if (!isset($options['associated'])) { + $options['associated'] = []; + } $include = $this->_normalizeAssociations($options['associated']); foreach ($include as $key => $nested) { if (is_int($key) && is_scalar($nested)) { From 9d4e67bf5c19ec04aebec6a6f8a1f41b3daaf959 Mon Sep 17 00:00:00 2001 From: thinkingmedia Date: Tue, 30 Aug 2016 09:52:59 -0400 Subject: [PATCH 0795/2059] adds methods for Events with deprecate warnings for data property --- Table.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index c7e61d6f..6b754d0b 100644 --- a/Table.php +++ b/Table.php @@ -1542,7 +1542,7 @@ protected function _processSave($entity, $options) $event = $this->dispatchEvent('Model.beforeSave', compact('entity', 'options')); if ($event->isStopped()) { - return $event->result; + return $event->result(); } $saved = $this->_associations->saveParents( @@ -1879,7 +1879,7 @@ protected function _processDelete($entity, $options) ]); if ($event->isStopped()) { - return $event->result; + return $event->result(); } $this->_associations->cascadeDelete( From fd250f827c45d47ea68e3d9c19c30b1e55adfb6e Mon Sep 17 00:00:00 2001 From: Mark Story Date: Wed, 31 Aug 2016 22:33:08 -0400 Subject: [PATCH 0796/2059] Don't mark null values as dirty when they are still null. `is_scalar()` does not include `null` so treat is specially. Refs #9381 --- Marshaller.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Marshaller.php b/Marshaller.php index c6d46baf..48241607 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -552,6 +552,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) // the original/updated list could contain references to the // same objects, even though those objects may have changed internally. if ((is_scalar($value) && $original === $value) || + ($value === null && $original === $value) || (is_object($value) && !($value instanceof EntityInterface) && $original == $value) ) { continue; From bcae4f0329458b383949900e1cc33b69dc341efc Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 2 Sep 2016 11:49:27 +0530 Subject: [PATCH 0797/2059] Fix option name in ORM package's README Refs cakephp/orm#11 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 326e3a16..bb2f6cc3 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ ConnectionManager::config('default', [ 'database' => 'test', 'username' => 'root', 'password' => 'secret', - 'cacheMetaData' => false // If set to `true` you need to install the optional "cakephp/cache" package. + 'cacheMetadata' => false // If set to `true` you need to install the optional "cakephp/cache" package. ]); ``` From 23cd92f6f93fe2dbc89aa47d14724c18a8537276 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 5 Sep 2016 17:24:30 -0400 Subject: [PATCH 0798/2059] Add result type casting for simple column aliases. When columns in the schema are aliased to other fields we can easily and correctly infer the result set types. Doing this makes the result sets consistent when columns are aliased and when they are not. Refs #9402 --- Query.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Query.php b/Query.php index 17f0f9db..be052ebc 100644 --- a/Query.php +++ b/Query.php @@ -1020,6 +1020,9 @@ protected function _addDefaultSelectTypes() $types[$alias] = $typeMap[$alias]; continue; } + if (is_string($value) && isset($typeMap[$value])) { + $types[$alias] = $typeMap[$value]; + } if ($value instanceof TypedResultInterface) { $types[$alias] = $value->returnType(); } From b03ebaa4233c411a9f050ad43b86e635a323bcef Mon Sep 17 00:00:00 2001 From: mscherer Date: Wed, 7 Sep 2016 15:30:33 +0200 Subject: [PATCH 0799/2059] Fix up yellow warnings in IDE by proper doc block typehinting. --- Association.php | 4 ++-- AssociationCollection.php | 22 ++++++++++++++++++---- Behavior.php | 4 +++- Behavior/TreeBehavior.php | 19 ++++++++++--------- EagerLoadable.php | 2 +- Locator/TableLocator.php | 4 +++- Query.php | 5 ++--- 7 files changed, 39 insertions(+), 21 deletions(-) diff --git a/Association.php b/Association.php index f6c1b9cf..e24210e3 100644 --- a/Association.php +++ b/Association.php @@ -607,7 +607,7 @@ public function attachTo(Query $query, array $options = []) * Conditionally adds a condition to the passed Query that will make it find * records where there is no match with this association. * - * @param \Cake\Database\Query $query The query to modify + * @param \Cake\Datasource\QueryInterface $query The query to modify * @param array $options Options array containing the `negateMatch` key. * @return void */ @@ -695,7 +695,7 @@ public function find($type = null, array $options = []) * Proxies the operation to the target table's exists method after * appending the default conditions for this association * - * @param array|callable|ExpressionInterface $conditions The conditions to use + * @param array|callable|\Cake\Database\ExpressionInterface $conditions The conditions to use * for checking if any record matches. * @see \Cake\ORM\Table::exists() * @return bool diff --git a/AssociationCollection.php b/AssociationCollection.php index 3a6d521f..cae9a360 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -33,7 +33,7 @@ class AssociationCollection implements IteratorAggregate /** * Stored associations * - * @var array + * @var \Cake\ORM\Association[] */ protected $_items = []; @@ -269,6 +269,21 @@ protected function _save($association, $entity, $nested, $options) * @return void */ public function cascadeDelete(EntityInterface $entity, array $options) + { + $noCascade = $this->_getNoCascadeItems($entity, $options); + foreach ($noCascade as $assoc) { + $assoc->cascadeDelete($entity, $options); + } + } + + /** + * Returns items that have no cascade callback. + * + * @param \Cake\Datasource\EntityInterface $entity The entity to delete associations for. + * @param array $options The options used in the delete operation. + * @return \Cake\ORM\Association[] + */ + protected function _getNoCascadeItems($entity, $options) { $noCascade = []; foreach ($this->_items as $assoc) { @@ -278,9 +293,8 @@ public function cascadeDelete(EntityInterface $entity, array $options) } $assoc->cascadeDelete($entity, $options); } - foreach ($noCascade as $assoc) { - $assoc->cascadeDelete($entity, $options); - } + + return $noCascade; } /** diff --git a/Behavior.php b/Behavior.php index 5a835da5..3aebe1bf 100644 --- a/Behavior.php +++ b/Behavior.php @@ -366,7 +366,9 @@ protected function _reflectionCache() $eventMethods = []; foreach ($events as $e => $binding) { if (is_array($binding) && isset($binding['callable'])) { - $binding = $binding['callable']; + /* @var string $callable */ + $callable = $binding['callable']; + $binding = $callable; } $eventMethods[$binding] = true; } diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 4726a558..ec15d9fe 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -56,7 +56,7 @@ class TreeBehavior extends Behavior 'implementedFinders' => [ 'path' => 'findPath', 'children' => 'findChildren', - 'treeList' => 'findTreeList' + 'treeList' => 'findTreeList', ], 'implementedMethods' => [ 'childCount' => 'childCount', @@ -65,14 +65,14 @@ class TreeBehavior extends Behavior 'recover' => 'recover', 'removeFromTree' => 'removeFromTree', 'getLevel' => 'getLevel', - 'formatTreeList' => 'formatTreeList' + 'formatTreeList' => 'formatTreeList', ], 'parent' => 'parent_id', 'left' => 'lft', 'right' => 'rght', 'scope' => null, 'level' => null, - 'recoverOrder' => null + 'recoverOrder' => null, ]; /** @@ -192,9 +192,10 @@ protected function _setChildrenLevel($entity) $children = $this->_table->find('children', [ 'for' => $primaryKeyValue, 'fields' => [$this->_getPrimaryKey(), $config['parent'], $config['level']], - 'order' => $config['left'] + 'order' => $config['left'], ]); + /* @var \Cake\ORM\Entity $node */ foreach ($children as $node) { $parentIdValue = $node->get($config['parent']); $depth = $depths[$parentIdValue] + 1; @@ -384,7 +385,7 @@ function ($field) { return $this->_scope($query) ->where([ "$left <=" => $node->get($config['left']), - "$right >=" => $node->get($config['right']) + "$right >=" => $node->get($config['right']), ]) ->order([$left => 'ASC']); } @@ -459,7 +460,7 @@ function ($field) { return $this->_scope($query) ->where([ "{$right} <" => $node->get($config['right']), - "{$left} >" => $node->get($config['left']) + "{$left} >" => $node->get($config['left']), ]); } @@ -485,7 +486,7 @@ public function findTreeList(Query $query, array $options) $results = $this->_scope($query) ->find('threaded', [ 'parentField' => $this->config('parent'), - 'order' => [$this->config('left') => 'ASC'] + 'order' => [$this->config('left') => 'ASC'], ]); return $this->formatTreeList($results, $options); @@ -514,7 +515,7 @@ public function formatTreeList(Query $query, array $options = []) $options += [ 'keyPath' => $this->_getPrimaryKey(), 'valuePath' => $this->_table->displayField(), - 'spacer' => '_' + 'spacer' => '_', ]; return $results @@ -993,7 +994,7 @@ public function getLevel($entity) $query = $this->_table->find('all')->where([ $config['left'] . ' <' => $entity[$config['left']], - $config['right'] . ' >' => $entity[$config['right']] + $config['right'] . ' >' => $entity[$config['right']], ]); return $this->_scope($query)->count(); diff --git a/EagerLoadable.php b/EagerLoadable.php index 2e62f3a2..c009d7ef 100644 --- a/EagerLoadable.php +++ b/EagerLoadable.php @@ -35,7 +35,7 @@ class EagerLoadable /** * A list of other associations to load from this level. * - * @var array + * @var \Cake\Orm\EagerLoadable[] */ protected $_associations = []; diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 08c90a56..8d511f29 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -168,7 +168,9 @@ public function get($alias, array $options = []) if (!empty($options['connectionName'])) { $connectionName = $options['connectionName']; } else { - $connectionName = $options['className']::defaultConnectionName(); + /* @var \Cake\ORM\Table $className */ + $className = $options['className']; + $connectionName = $className::defaultConnectionName(); } $options['connection'] = ConnectionManager::get($connectionName); } diff --git a/Query.php b/Query.php index 17f0f9db..2ce69ce5 100644 --- a/Query.php +++ b/Query.php @@ -15,7 +15,6 @@ namespace Cake\ORM; use ArrayObject; -use Cake\Collection\CollectionInterface; use Cake\Database\ExpressionInterface; use Cake\Database\Query as DatabaseQuery; use Cake\Database\TypedResultInterface; @@ -157,7 +156,7 @@ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface /** * Constructor * - * @param \Cake\Database\Connection $connection The connection object + * @param \Cake\Datasource\ConnectionInterface $connection The connection object * @param \Cake\ORM\Table $table The table this query is starting on */ public function __construct($connection, $table) @@ -203,7 +202,7 @@ public function select($fields = [], $overwrite = false) * * This method returns the same query object for chaining. * - * @param \Cake\ORM\Table $table The table to pull types from + * @param \Cake\Datasource\RepositoryInterface $table The table to pull types from * @return $this */ public function addDefaultTypes(Table $table) From fd7a42d0a6aee6a282355f38bba474b784f15852 Mon Sep 17 00:00:00 2001 From: Mark Sch Date: Thu, 8 Sep 2016 10:37:00 +0200 Subject: [PATCH 0800/2059] Revert typehint for now. --- Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Query.php b/Query.php index 2ce69ce5..8380daa5 100644 --- a/Query.php +++ b/Query.php @@ -202,7 +202,7 @@ public function select($fields = [], $overwrite = false) * * This method returns the same query object for chaining. * - * @param \Cake\Datasource\RepositoryInterface $table The table to pull types from + * @param \Cake\ORM\Table $table The table to pull types from * @return $this */ public function addDefaultTypes(Table $table) From e9519b16af0f4176437e94565301f7d47b8ac05c Mon Sep 17 00:00:00 2001 From: ndm2 Date: Tue, 13 Sep 2016 17:34:47 +0200 Subject: [PATCH 0801/2059] Fix `notMatching()` without query builder callback triggers an error. --- Association/BelongsToMany.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 4d875a52..212abbff 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -387,7 +387,9 @@ protected function _appendNotMatching($query, $options) ->where($options['conditions']) ->andWhere($this->junctionConditions()); - $subquery = $options['queryBuilder']($subquery); + if (!empty($options['queryBuilder'])) { + $subquery = $options['queryBuilder']($subquery); + } $assoc = $junction->association($this->target()->alias()); $conditions = $assoc->_joinCondition([ From f993ccbc9cd38113c2edefe1d781754a99734dcf Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 14 Sep 2016 21:46:02 +0530 Subject: [PATCH 0802/2059] Clean primary entity after "Model.afterSaveCommit" is triggered. Previously primary entity was being cleaned too early and callbacks for Model.afterSaveCommit become useless if one wanted to acccess original state. --- Table.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Table.php b/Table.php index c7e61d6f..8a7ccf77 100644 --- a/Table.php +++ b/Table.php @@ -1502,6 +1502,7 @@ public function save(EntityInterface $entity, $options = []) $this->dispatchEvent('Model.afterSaveCommit', compact('entity', 'options')); } if ($options['atomic'] || $options['_primary']) { + $entity->clean(); $entity->isNew(false); $entity->source($this->registryAlias()); } @@ -1606,8 +1607,8 @@ protected function _onSaveSuccess($entity, $options) throw new RolledbackTransactionException(['table' => get_class($this)]); } - $entity->clean(); if (!$options['atomic'] && !$options['_primary']) { + $entity->clean(); $entity->isNew(false); $entity->source($this->registryAlias()); } From c65b512567291da7988b672b2bdd6d8e44b7b697 Mon Sep 17 00:00:00 2001 From: Pedro Tanaka Date: Wed, 21 Sep 2016 21:40:14 -0300 Subject: [PATCH 0803/2059] Fixing documentation of Query#isEmpty This method comes from CollectionTrait and should not be declared with and argument. --- Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Query.php b/Query.php index 17f0f9db..7f03f40d 100644 --- a/Query.php +++ b/Query.php @@ -66,7 +66,7 @@ * @method \Cake\Collection\CollectionInterface zipWith(...$collections, callable $c) Returns each of the results out of calling $c * with the first rows of the query and each of the items, then the second rows and so on. * @method \Cake\Collection\CollectionInterface chunk($size) Groups the results in arrays of $size rows each. - * @method bool isEmpty($size) Returns true if this query found no results. + * @method bool isEmpty() Returns true if this query found no results. */ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface { From 2c5480fdcf02291a456004feac20fb27be39aff0 Mon Sep 17 00:00:00 2001 From: Pedro Tanaka Date: Thu, 22 Sep 2016 01:18:09 -0300 Subject: [PATCH 0804/2059] Fixing documentation of method Query#zipWith Fixing documentation of method Query#zipWith to match the declaration in CollectionInterface. --- Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Query.php b/Query.php index 7f03f40d..65ea22c3 100644 --- a/Query.php +++ b/Query.php @@ -63,7 +63,7 @@ * @method \Cake\Collection\CollectionInterface stopWhen(callable $c) Returns each row until the callable returns true. * @method \Cake\Collection\CollectionInterface zip(array|\Traversable $c) Returns the first result of both the query and $c in an array, * then the second results and so on. - * @method \Cake\Collection\CollectionInterface zipWith(...$collections, callable $c) Returns each of the results out of calling $c + * @method \Cake\Collection\CollectionInterface zipWith($collections, callable $callable) Returns each of the results out of calling $c * with the first rows of the query and each of the items, then the second rows and so on. * @method \Cake\Collection\CollectionInterface chunk($size) Groups the results in arrays of $size rows each. * @method bool isEmpty() Returns true if this query found no results. From b7f54274994ae1c1295cd623f4819a0925fc98eb Mon Sep 17 00:00:00 2001 From: mscherer Date: Fri, 23 Sep 2016 11:33:39 +0200 Subject: [PATCH 0805/2059] Fix up FQCN in doc blocks. --- Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Query.php b/Query.php index 7f756a3b..a6a6202e 100644 --- a/Query.php +++ b/Query.php @@ -52,7 +52,7 @@ * @method \Cake\Collection\CollectionInterface take($size = 1, $from = 0) In-memory limit and offset for the query results. * @method \Cake\Collection\CollectionInterface skip(int $howMany) Skips some rows from the start of the query result. * @method mixed last() Return the last row of the query result - * @method \Cake\Collection\CollectionInterface append(array|Traversable $items) Appends more rows to the result of the query. + * @method \Cake\Collection\CollectionInterface append(array|\Traversable $items) Appends more rows to the result of the query. * @method \Cake\Collection\CollectionInterface combine($k, $v, $g = null) Returns the values of the column $v index by column $k, * and grouped by $g. * @method \Cake\Collection\CollectionInterface nest($k, $p, $n = 'children') Creates a tree structure by nesting the values of column $p into that From 242733f58f3172379cc6bc68cdfe8a352fdaee54 Mon Sep 17 00:00:00 2001 From: saeid Date: Sun, 25 Sep 2016 22:13:15 +0330 Subject: [PATCH 0806/2059] apply TypeMap into matching/notMatching/innerJoinWith... apply TypeMap into matching/notMatching/innerJoinWith/leftJoinWith --- Query.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Query.php b/Query.php index a6a6202e..58be767a 100644 --- a/Query.php +++ b/Query.php @@ -450,7 +450,8 @@ protected function _addAssociationsToTypeMap($table, $typeMap, $associations) */ public function matching($assoc, callable $builder = null) { - $this->eagerLoader()->matching($assoc, $builder); + $result = $this->eagerLoader()->matching($assoc, $builder); + $this->_addAssociationsToTypeMap($this->repository(), $this->typeMap(), $result); $this->_dirty(); return $this; @@ -521,10 +522,11 @@ public function matching($assoc, callable $builder = null) */ public function leftJoinWith($assoc, callable $builder = null) { - $this->eagerLoader()->matching($assoc, $builder, [ + $result = $this->eagerLoader()->matching($assoc, $builder, [ 'joinType' => 'LEFT', 'fields' => false ]); + $this->_addAssociationsToTypeMap($this->repository(), $this->typeMap(), $result); $this->_dirty(); return $this; @@ -567,10 +569,11 @@ public function leftJoinWith($assoc, callable $builder = null) */ public function innerJoinWith($assoc, callable $builder = null) { - $this->eagerLoader()->matching($assoc, $builder, [ + $result = $this->eagerLoader()->matching($assoc, $builder, [ 'joinType' => 'INNER', 'fields' => false ]); + $this->_addAssociationsToTypeMap($this->repository(), $this->typeMap(), $result); $this->_dirty(); return $this; @@ -628,11 +631,12 @@ public function innerJoinWith($assoc, callable $builder = null) */ public function notMatching($assoc, callable $builder = null) { - $this->eagerLoader()->matching($assoc, $builder, [ + $result = $this->eagerLoader()->matching($assoc, $builder, [ 'joinType' => 'LEFT', 'fields' => false, 'negateMatch' => true ]); + $this->_addAssociationsToTypeMap($this->repository(), $this->typeMap(), $result); $this->_dirty(); return $this; From d2e9a77588ea8e543f005bc7d12ce8bc77a2c85e Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 29 Sep 2016 23:43:16 +0530 Subject: [PATCH 0807/2059] Don't set associated record to null when autofields is disabled. --- ResultSet.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ResultSet.php b/ResultSet.php index 9450ad21..1b034f31 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -121,6 +121,13 @@ class ResultSet implements ResultSetInterface */ protected $_hydrate = true; + /** + * Tracks value of $_autoFields property of $query passed to constructor. + * + * @var bool + */ + protected $_autoFields; + /** * The fully namespaced name of the class to use for hydrating results * @@ -179,6 +186,7 @@ public function __construct($query, $statement) $this->_defaultAlias = $this->_defaultTable->alias(); $this->_calculateColumnMap($query); $this->_calculateTypeMap(); + $this->_autoFields = $query->autoFields(); if ($this->_useBuffering) { $count = $this->count(); @@ -525,7 +533,7 @@ protected function _groupResult($row) $options['source'] = $target->registryAlias(); unset($presentAliases[$alias]); - if ($assoc['canBeJoined']) { + if ($assoc['canBeJoined'] && $this->_autoFields !== false) { $hasData = false; foreach ($results[$alias] as $v) { if ($v !== null && $v !== []) { From 1e2ad2e72a9ac5e66e7765371911ee9f3f4d33d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Fri, 30 Sep 2016 09:54:38 +0200 Subject: [PATCH 0808/2059] Preserve strategy option in associations generated in BelongsToMany --- Association/BelongsToMany.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 212abbff..d4c2fcc6 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -241,6 +241,7 @@ protected function _generateTargetAssociations($junction, $source, $target) $target->hasMany($junctionAlias, [ 'targetTable' => $junction, 'foreignKey' => $this->targetForeignKey(), + 'strategy' => $this->_strategy, ]); } if (!$target->association($sAlias)) { @@ -251,6 +252,7 @@ protected function _generateTargetAssociations($junction, $source, $target) 'targetForeignKey' => $this->foreignKey(), 'through' => $junction, 'conditions' => $this->conditions(), + 'strategy' => $this->_strategy, ]); } } @@ -276,6 +278,7 @@ protected function _generateSourceAssociations($junction, $source) $source->hasMany($junctionAlias, [ 'targetTable' => $junction, 'foreignKey' => $this->foreignKey(), + 'strategy' => $this->_strategy, ]); } } From 9d2ef478d605ec983398c74ce22af55393ea15e1 Mon Sep 17 00:00:00 2001 From: Hari K T Date: Fri, 7 Oct 2016 11:07:46 +0530 Subject: [PATCH 0809/2059] update suggest with better explanation for i18n If you are using Translate / Timestamp Behavior i18n is needed. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 66983e76..75c43b64 100644 --- a/composer.json +++ b/composer.json @@ -23,6 +23,6 @@ "cakephp/validation": "~3.0" }, "suggest": { - "cakephp/i18n": "~3.0" + "cakephp/i18n": "If you are using Translate / Timestamp Behavior." } } From 77d6f3ba8dd00c687eed7ac4b502c54061ade9e8 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Mon, 10 Oct 2016 13:06:04 +0200 Subject: [PATCH 0810/2059] Strating to refactor SelectableAssociationTrait into a separate class --- Association.php | 9 + Association/BelongsToMany.php | 5 +- Association/ExternalAssociationTrait.php | 13 - Association/HasMany.php | 8 +- Association/HasOne.php | 52 ++- Association/Loader/SelectLoader.php | 431 +++++++++++++++++++++++ 6 files changed, 468 insertions(+), 50 deletions(-) create mode 100644 Association/Loader/SelectLoader.php diff --git a/Association.php b/Association.php index e24210e3..fe3ee7b0 100644 --- a/Association.php +++ b/Association.php @@ -1026,6 +1026,15 @@ abstract public function type(); */ abstract public function eagerLoader(array $options); + /** + * Returns true if the eager loading process will require a set of the owning table's + * binding keys in order to use them as a filter in the finder query. + * + * @param array $options The options containing the strategy to be used. + * @return bool true if a list of keys will be required + */ + abstract function requiresKeys(array $options = []); + /** * Handles cascading a delete from an associated model. * diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index d4c2fcc6..05f8d4dc 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -37,7 +37,6 @@ class BelongsToMany extends Association { use ExternalAssociationTrait { - _options as _externalOptions; _buildQuery as _buildBaseQuery; } @@ -1365,7 +1364,6 @@ protected function _junctionTableName($name = null) */ protected function _options(array $opts) { - $this->_externalOptions($opts); if (!empty($opts['targetForeignKey'])) { $this->targetForeignKey($opts['targetForeignKey']); } @@ -1378,5 +1376,8 @@ protected function _options(array $opts) if (!empty($opts['saveStrategy'])) { $this->saveStrategy($opts['saveStrategy']); } + if (isset($opts['sort'])) { + $this->sort($opts['sort']); + } } } diff --git a/Association/ExternalAssociationTrait.php b/Association/ExternalAssociationTrait.php index 385f9f88..4cc17115 100644 --- a/Association/ExternalAssociationTrait.php +++ b/Association/ExternalAssociationTrait.php @@ -123,17 +123,4 @@ protected function _buildResultMap($fetchQuery, $options) return $resultMap; } - - /** - * Parse extra options passed in the constructor. - * - * @param array $opts original list of options passed in constructor - * @return void - */ - protected function _options(array $opts) - { - if (isset($opts['sort'])) { - $this->sort($opts['sort']); - } - } } diff --git a/Association/HasMany.php b/Association/HasMany.php index 398c20ae..2f482e9c 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -35,9 +35,7 @@ class HasMany extends Association { use DependentDeleteTrait; - use ExternalAssociationTrait { - _options as _externalOptions; - } + use ExternalAssociationTrait; /** * The type of join to be used when adding the association to a query @@ -534,9 +532,11 @@ public function type() */ protected function _options(array $opts) { - $this->_externalOptions($opts); if (!empty($opts['saveStrategy'])) { $this->saveStrategy($opts['saveStrategy']); } + if (isset($opts['sort'])) { + $this->sort($opts['sort']); + } } } diff --git a/Association/HasOne.php b/Association/HasOne.php index 4eea06db..f1d5abcb 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -16,6 +16,7 @@ use Cake\Datasource\EntityInterface; use Cake\ORM\Association; +use Cake\ORM\Association\Loader\SelectLoader; use Cake\ORM\Table; use Cake\Utility\Inflector; @@ -29,7 +30,6 @@ class HasOne extends Association { use DependentDeleteTrait; - use SelectableAssociationTrait; /** * Valid strategies for this type of association @@ -128,41 +128,31 @@ public function saveAssociated(EntityInterface $entity, array $options = []) return $entity; } - /** - * {@inheritDoc} - */ - protected function _linkField($options) - { - $links = []; - $name = $this->alias(); - - foreach ((array)$options['foreignKey'] as $key) { - $links[] = sprintf('%s.%s', $name, $key); - } - - if (count($links) === 1) { - return $links[0]; - } - - return $links; + public function eagerLoader(array $options) { + $loader = new SelectLoader([ + 'alias' => $this->alias(), + 'sourceAlias' => $this->source()->alias(), + 'targetAlias' => $this->target()->alias(), + 'foreignKey' => $this->foreignKey(), + 'bindingKey' => $this->bindingKey(), + 'strategy' => $this->strategy(), + 'associationType' => $this->type(), + 'finder' => [$this, 'find'] + ]); + return $loader->buildLoadingQuery($options); } /** - * {@inheritDoc} + * Returns true if the eager loading process will require a set of the owning table's + * binding keys in order to use them as a filter in the finder query. + * + * @param array $options The options containing the strategy to be used. + * @return bool true if a list of keys will be required */ - protected function _buildResultMap($fetchQuery, $options) + public function requiresKeys(array $options = []) { - $resultMap = []; - $key = (array)$options['foreignKey']; - - foreach ($fetchQuery->all() as $result) { - $values = []; - foreach ($key as $k) { - $values[] = $result[$k]; - } - $resultMap[implode(';', $values)] = $result; - } + $strategy = isset($options['strategy']) ? $options['strategy'] : $this->strategy(); - return $resultMap; + return $strategy === $this::STRATEGY_SELECT; } } diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php new file mode 100644 index 00000000..1c28dc01 --- /dev/null +++ b/Association/Loader/SelectLoader.php @@ -0,0 +1,431 @@ +alias = $options['alias']; + $this->sourceAlias = $options['sourceAlias']; + $this->targetAlias = $options['targetAlias']; + $this->foreignKey = $options['foreignKey']; + $this->strategy = $options['strategy']; + $this->bindingKey = $options['bindingKey']; + $this->finder = $options['finder']; + $this->associationType = $options['associationType']; + } + + + public function buildLoadingQuery(array $options) + { + $options += $this->_defaultOptions(); + $fetchQuery = $this->_buildQuery($options); + $resultMap = $this->_buildResultMap($fetchQuery, $options); + + return $this->_resultInjector($fetchQuery, $resultMap, $options); + } + + /** + * Returns the default options to use for the eagerLoader + * + * @return array + */ + protected function _defaultOptions() + { + return [ + 'foreignKey' => $this->foreignKey, + 'conditions' => [], + 'strategy' => $this->strategy, + 'nestKey' => $this->alias + ]; + } + + /** + * Auxiliary function to construct a new Query object to return all the records + * in the target table that are associated to those specified in $options from + * the source table + * + * @param array $options options accepted by eagerLoader() + * @return \Cake\ORM\Query + * @throws \InvalidArgumentException When a key is required for associations but not selected. + */ + protected function _buildQuery($options) + { + $alias = $this->targetAlias; + $key = $this->_linkField($options); + $filter = $options['keys']; + $useSubquery = $options['strategy'] === Association::STRATEGY_SUBQUERY; + $finder = $this->finder; + + if (!isset($options['fields'])) { + $options['fields'] = []; + } + + $fetchQuery = $finder() + ->select($options['fields']) + ->where($options['conditions']) + ->eagerLoaded(true) + ->hydrate($options['query']->hydrate()); + + if ($useSubquery) { + $filter = $this->_buildSubquery($options['query']); + $fetchQuery = $this->_addFilteringJoin($fetchQuery, $key, $filter); + } else { + $fetchQuery = $this->_addFilteringCondition($fetchQuery, $key, $filter); + } + + if (!empty($options['sort'])) { + $fetchQuery->order($options['sort']); + } + + if (!empty($options['contain'])) { + $fetchQuery->contain($options['contain']); + } + + if (!empty($options['queryBuilder'])) { + $fetchQuery = $options['queryBuilder']($fetchQuery); + } + + $this->_assertFieldsPresent($fetchQuery, (array)$key); + + return $fetchQuery; + } + + /** + * Checks that the fetching query either has auto fields on or + * has the foreignKey fields selected. + * If the required fields are missing, throws an exception. + * + * @param \Cake\ORM\Query $fetchQuery The association fetching query + * @param array $key The foreign key fields to check + * @return void + * @throws InvalidArgumentException + */ + protected function _assertFieldsPresent($fetchQuery, $key) + { + $select = $fetchQuery->aliasFields($fetchQuery->clause('select')); + if (empty($select)) { + return; + } + $missingKey = function ($fieldList, $key) { + foreach ($key as $keyField) { + if (!in_array($keyField, $fieldList, true)) { + return true; + } + } + + return false; + }; + + $missingFields = $missingKey($select, $key); + if ($missingFields) { + $driver = $fetchQuery->connection()->driver(); + $quoted = array_map([$driver, 'quoteIdentifier'], $key); + $missingFields = $missingKey($select, $quoted); + } + + if ($missingFields) { + throw new InvalidArgumentException( + sprintf( + 'You are required to select the "%s" field(s)', + implode(', ', (array)$key) + ) + ); + } + } + + /** + * Appends any conditions required to load the relevant set of records in the + * target table query given a filter key and some filtering values when the + * filtering needs to be done using a subquery. + * + * @param \Cake\ORM\Query $query Target table's query + * @param string $key the fields that should be used for filtering + * @param \Cake\ORM\Query $subquery The Subquery to use for filtering + * @return \Cake\ORM\Query + */ + public function _addFilteringJoin($query, $key, $subquery) + { + $filter = []; + $aliasedTable = $this->sourceAlias; + + foreach ($subquery->clause('select') as $aliasedField => $field) { + if (is_int($aliasedField)) { + $filter[] = new IdentifierExpression($field); + } else { + $filter[$aliasedField] = $field; + } + } + $subquery->select($filter, true); + + if (is_array($key)) { + $conditions = $this->_createTupleCondition($query, $key, $filter, '='); + } else { + $filter = current($filter); + } + + $conditions = isset($conditions) ? $conditions : $query->newExpr([$key => $filter]); + + return $query->innerJoin( + [$aliasedTable => $subquery], + $conditions + ); + } + + /** + * Appends any conditions required to load the relevant set of records in the + * target table query given a filter key and some filtering values. + * + * @param \Cake\ORM\Query $query Target table's query + * @param string|array $key the fields that should be used for filtering + * @param mixed $filter the value that should be used to match for $key + * @return \Cake\ORM\Query + */ + protected function _addFilteringCondition($query, $key, $filter) + { + if (is_array($key)) { + $conditions = $this->_createTupleCondition($query, $key, $filter, 'IN'); + } + + $conditions = isset($conditions) ? $conditions : [$key . ' IN' => $filter]; + + return $query->andWhere($conditions); + } + + /** + * Returns a TupleComparison object that can be used for matching all the fields + * from $keys with the tuple values in $filter using the provided operator. + * + * @param \Cake\ORM\Query $query Target table's query + * @param array $keys the fields that should be used for filtering + * @param mixed $filter the value that should be used to match for $key + * @param string $operator The operator for comparing the tuples + * @return \Cake\Database\Expression\TupleComparison + */ + protected function _createTupleCondition($query, $keys, $filter, $operator) + { + $types = []; + $defaults = $query->defaultTypes(); + foreach ($keys as $k) { + if (isset($defaults[$k])) { + $types[] = $defaults[$k]; + } + } + + return new TupleComparison($keys, $filter, $types, $operator); + } + + /** + * Generates a string used as a table field that contains the values upon + * which the filter should be applied + * + * @param array $options The options for getting the link field. + * @return string|array + */ + protected function _linkField($options) + { + $links = []; + $name = $this->alias; + + foreach ((array)$options['foreignKey'] as $key) { + $links[] = sprintf('%s.%s', $name, $key); + } + + if (count($links) === 1) { + return $links[0]; + } + + return $links; + } + + /** + * Builds a query to be used as a condition for filtering records in the + * target table, it is constructed by cloning the original query that was used + * to load records in the source table. + * + * @param \Cake\ORM\Query $query the original query used to load source records + * @return \Cake\ORM\Query + */ + protected function _buildSubquery($query) + { + $filterQuery = clone $query; + $filterQuery->autoFields(false); + $filterQuery->mapReduce(null, null, true); + $filterQuery->formatResults(null, true); + $filterQuery->contain([], true); + $filterQuery->valueBinder(new ValueBinder()); + + if (!$filterQuery->clause('limit')) { + $filterQuery->limit(null); + $filterQuery->order([], true); + $filterQuery->offset(null); + } + + $fields = $this->_subqueryFields($query); + $filterQuery->select($fields['select'], true)->group($fields['group']); + + return $filterQuery; + } + + /** + * Calculate the fields that need to participate in a subquery. + * + * Normally this includes the binding key columns. If there is a an ORDER BY, + * those columns are also included as the fields may be calculated or constant values, + * that need to be present to ensure the correct association data is loaded. + * + * @param \Cake\ORM\Query $query The query to get fields from. + * @return array The list of fields for the subquery. + */ + protected function _subqueryFields($query) + { + $keys = (array)$this->bindingKey; + + if ($this->associationType === Association::MANY_TO_ONE) { + $keys = (array)$this->foreignKey; + } + + $fields = $query->aliasFields($keys, $this->sourceAlias); + $group = $fields = array_values($fields); + + $order = $query->clause('order'); + if ($order) { + $columns = $query->clause('select'); + $order->iterateParts(function ($direction, $field) use (&$fields, $columns) { + if (isset($columns[$field])) { + $fields[$field] = $columns[$field]; + } + }); + } + + return ['select' => $fields, 'group' => $group]; + } + + /** + * Builds an array containing the results from fetchQuery indexed by + * the foreignKey value corresponding to this association. + * + * @param \Cake\ORM\Query $fetchQuery The query to get results from + * @param array $options The options passed to the eager loader + * @return array + */ + protected function _buildResultMap($fetchQuery, $options) + { + $resultMap = []; + $key = (array)$options['foreignKey']; + + foreach ($fetchQuery->all() as $result) { + $values = []; + foreach ($key as $k) { + $values[] = $result[$k]; + } + $resultMap[implode(';', $values)] = $result; + } + + return $resultMap; + } + + /** + * Returns a callable to be used for each row in a query result set + * for injecting the eager loaded rows + * + * @param \Cake\ORM\Query $fetchQuery the Query used to fetch results + * @param array $resultMap an array with the foreignKey as keys and + * the corresponding target table results as value. + * @param array $options The options passed to the eagerLoader method + * @return \Closure + */ + protected function _resultInjector($fetchQuery, $resultMap, $options) + { + $keys = $this->associationType === Association::MANY_TO_ONE ? + $this->foreignKey : + $this->bindingKey; + + $sourceKeys = []; + foreach ((array)$keys as $key) { + $f = $fetchQuery->aliasField($key, $this->sourceAlias); + $sourceKeys[] = key($f); + } + + $nestKey = $options['nestKey']; + if (count($sourceKeys) > 1) { + return $this->_multiKeysInjector($resultMap, $sourceKeys, $nestKey); + } + + $sourceKey = $sourceKeys[0]; + + return function ($row) use ($resultMap, $sourceKey, $nestKey) { + if (isset($row[$sourceKey], $resultMap[$row[$sourceKey]])) { + $row[$nestKey] = $resultMap[$row[$sourceKey]]; + } + + return $row; + }; + } + + /** + * Returns a callable to be used for each row in a query result set + * for injecting the eager loaded rows when the matching needs to + * be done with multiple foreign keys + * + * @param array $resultMap A keyed arrays containing the target table + * @param array $sourceKeys An array with aliased keys to match + * @param string $nestKey The key under which results should be nested + * @return \Closure + */ + protected function _multiKeysInjector($resultMap, $sourceKeys, $nestKey) + { + return function ($row) use ($resultMap, $sourceKeys, $nestKey) { + $values = []; + foreach ($sourceKeys as $key) { + $values[] = $row[$key]; + } + + $key = implode(';', $values); + if (isset($resultMap[$key])) { + $row[$nestKey] = $resultMap[$key]; + } + + return $row; + }; + } +} From 820f1b5a8e678af7119dff9c1f433e7cc9e22fac Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Mon, 10 Oct 2016 13:51:47 +0200 Subject: [PATCH 0811/2059] Migrating BelongsTo to use the new SelectLoader class --- Association/BelongsTo.php | 35 +++++++++++++++++++++++++++-- Association/HasOne.php | 12 ++++++---- Association/Loader/SelectLoader.php | 10 +++++++-- 3 files changed, 49 insertions(+), 8 deletions(-) diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index ddf5f67c..f9459e77 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -17,6 +17,7 @@ use Cake\Database\Expression\IdentifierExpression; use Cake\Datasource\EntityInterface; use Cake\ORM\Association; +use Cake\ORM\Association\Loader\SelectLoader; use Cake\ORM\Table; use Cake\Utility\Inflector; use RuntimeException; @@ -30,8 +31,6 @@ class BelongsTo extends Association { - use SelectableAssociationTrait; - /** * Valid strategies for this type of association * @@ -184,6 +183,38 @@ protected function _joinCondition($options) return $conditions; } + /** + * {@inheritDoc} + * + * @return callable + */ + public function eagerLoader(array $options) { + $loader = new SelectLoader([ + 'alias' => $this->alias(), + 'sourceAlias' => $this->source()->alias(), + 'targetAlias' => $this->target()->alias(), + 'foreignKey' => $this->foreignKey(), + 'bindingKey' => $this->bindingKey(), + 'strategy' => $this->strategy(), + 'associationType' => $this->type(), + 'finder' => [$this, 'find'] + ]); + + return $loader->buildLoadingQuery($options); + } + + /** + * {@inheritDoc} + * + * @return bool + */ + public function requiresKeys(array $options = []) + { + $strategy = isset($options['strategy']) ? $options['strategy'] : $this->strategy(); + + return $strategy === $this::STRATEGY_SELECT; + } + /** * {@inheritDoc} */ diff --git a/Association/HasOne.php b/Association/HasOne.php index f1d5abcb..4096f45b 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -128,6 +128,11 @@ public function saveAssociated(EntityInterface $entity, array $options = []) return $entity; } + /** + * {@inheritDoc} + * + * @return callable + */ public function eagerLoader(array $options) { $loader = new SelectLoader([ 'alias' => $this->alias(), @@ -139,15 +144,14 @@ public function eagerLoader(array $options) { 'associationType' => $this->type(), 'finder' => [$this, 'find'] ]); + return $loader->buildLoadingQuery($options); } /** - * Returns true if the eager loading process will require a set of the owning table's - * binding keys in order to use them as a filter in the finder query. + * {@inheritDoc} * - * @param array $options The options containing the strategy to be used. - * @return bool true if a list of keys will be required + * @return bool */ public function requiresKeys(array $options = []) { diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 1c28dc01..99b281ac 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -265,8 +265,11 @@ protected function _linkField($options) { $links = []; $name = $this->alias; + $keys = $this->associationType === Association::ONE_TO_ONE ? + $this->foreignKey : + $this->bindingKey; - foreach ((array)$options['foreignKey'] as $key) { + foreach ((array)$keys as $key) { $links[] = sprintf('%s.%s', $name, $key); } @@ -351,7 +354,10 @@ protected function _subqueryFields($query) protected function _buildResultMap($fetchQuery, $options) { $resultMap = []; - $key = (array)$options['foreignKey']; + $keys = $this->associationType === Association::ONE_TO_ONE ? + $this->foreignKey : + $this->bindingKey; + $key = (array)$keys; foreach ($fetchQuery->all() as $result) { $values = []; From 744fd117c0016efea4dcf855e7987db1f6c64495 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Mon, 10 Oct 2016 14:50:57 +0200 Subject: [PATCH 0812/2059] Migrated HasMany to use the new SelectLoader --- Association.php | 23 +++--- Association/BelongsTo.php | 50 ------------- Association/HasMany.php | 106 ++++++++++++++++++++++------ Association/HasOne.php | 12 ---- Association/Loader/SelectLoader.php | 25 +++++-- 5 files changed, 120 insertions(+), 96 deletions(-) diff --git a/Association.php b/Association.php index fe3ee7b0..0236514f 100644 --- a/Association.php +++ b/Association.php @@ -750,6 +750,20 @@ public function deleteAll($conditions) return $target->deleteAll($expression); } + /** + * Returns true if the eager loading process will require a set of the owning table's + * binding keys in order to use them as a filter in the finder query. + * + * @param array $options The options containing the strategy to be used. + * @return bool true if a list of keys will be required + */ + public function requiresKeys(array $options = []) + { + $strategy = isset($options['strategy']) ? $options['strategy'] : $this->strategy(); + + return $strategy === $this::STRATEGY_SELECT; + } + /** * Triggers beforeFind on the target table for the query this association is * attaching to @@ -1026,15 +1040,6 @@ abstract public function type(); */ abstract public function eagerLoader(array $options); - /** - * Returns true if the eager loading process will require a set of the owning table's - * binding keys in order to use them as a filter in the finder query. - * - * @param array $options The options containing the strategy to be used. - * @return bool true if a list of keys will be required - */ - abstract function requiresKeys(array $options = []); - /** * Handles cascading a delete from an associated model. * diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index f9459e77..136680d5 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -202,54 +202,4 @@ public function eagerLoader(array $options) { return $loader->buildLoadingQuery($options); } - - /** - * {@inheritDoc} - * - * @return bool - */ - public function requiresKeys(array $options = []) - { - $strategy = isset($options['strategy']) ? $options['strategy'] : $this->strategy(); - - return $strategy === $this::STRATEGY_SELECT; - } - - /** - * {@inheritDoc} - */ - protected function _linkField($options) - { - $links = []; - $name = $this->alias(); - - foreach ((array)$this->bindingKey() as $key) { - $links[] = sprintf('%s.%s', $name, $key); - } - - if (count($links) === 1) { - return $links[0]; - } - - return $links; - } - - /** - * {@inheritDoc} - */ - protected function _buildResultMap($fetchQuery, $options) - { - $resultMap = []; - $key = (array)$this->bindingKey(); - - foreach ($fetchQuery->all() as $result) { - $values = []; - foreach ($key as $k) { - $values[] = $result[$k]; - } - $resultMap[implode(';', $values)] = $result; - } - - return $resultMap; - } } diff --git a/Association/HasMany.php b/Association/HasMany.php index 2f482e9c..49f31607 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -20,9 +20,9 @@ use Cake\Database\Expression\QueryExpression; use Cake\Datasource\EntityInterface; use Cake\ORM\Association; +use Cake\ORM\Association\Loader\SelectLoader; use Cake\ORM\Table; use InvalidArgumentException; -use RuntimeException; use Traversable; /** @@ -35,7 +35,13 @@ class HasMany extends Association { use DependentDeleteTrait; - use ExternalAssociationTrait; + + /** + * Order in which target records should be returned + * + * @var mixed + */ + protected $_sort; /** * The type of join to be used when adding the association to a query @@ -491,37 +497,74 @@ function ($prop) use ($table) { } /** - * {@inheritDoc} + * Get the relationship type. + * + * @return string */ - protected function _linkField($options) + public function type() { - $links = []; - $name = $this->alias(); - if ($options['foreignKey'] === false) { - $msg = 'Cannot have foreignKey = false for hasMany associations. ' . - 'You must provide a foreignKey column.'; - throw new RuntimeException($msg); - } + return self::ONE_TO_MANY; + } - foreach ((array)$options['foreignKey'] as $key) { - $links[] = sprintf('%s.%s', $name, $key); - } + /** + * Whether this association can be expressed directly in a query join + * + * @param array $options custom options key that could alter the return value + * @return bool if the 'matching' key in $option is true then this function + * will return true, false otherwise + */ + public function canBeJoined(array $options = []) + { + return !empty($options['matching']); + } - if (count($links) === 1) { - return $links[0]; + /** + * Sets the name of the field representing the foreign key to the source table. + * If no parameters are passed current field is returned + * + * @param string|null $key the key to be used to link both tables together + * @return string + */ + public function foreignKey($key = null) + { + if ($key === null) { + if ($this->_foreignKey === null) { + $this->_foreignKey = $this->_modelKey($this->source()->table()); + } + + return $this->_foreignKey; } - return $links; + return parent::foreignKey($key); } /** - * Get the relationship type. + * Sets the sort order in which target records should be returned. + * If no arguments are passed the currently configured value is returned * - * @return string + * @param mixed $sort A find() compatible order clause + * @return mixed */ - public function type() + public function sort($sort = null) { - return self::ONE_TO_MANY; + if ($sort !== null) { + $this->_sort = $sort; + } + + return $this->_sort; + } + + /** + * {@inheritDoc} + */ + public function defaultRowValue($row, $joined) + { + $sourceAlias = $this->source()->alias(); + if (isset($row[$sourceAlias])) { + $row[$sourceAlias][$this->property()] = $joined ? null : []; + } + + return $row; } /** @@ -539,4 +582,25 @@ protected function _options(array $opts) $this->sort($opts['sort']); } } + + /** + * {@inheritDoc} + * + * @return callable + */ + public function eagerLoader(array $options) { + $loader = new SelectLoader([ + 'alias' => $this->alias(), + 'sourceAlias' => $this->source()->alias(), + 'targetAlias' => $this->target()->alias(), + 'foreignKey' => $this->foreignKey(), + 'bindingKey' => $this->bindingKey(), + 'strategy' => $this->strategy(), + 'associationType' => $this->type(), + 'sort' => $this->sort(), + 'finder' => [$this, 'find'] + ]); + + return $loader->buildLoadingQuery($options); + } } diff --git a/Association/HasOne.php b/Association/HasOne.php index 4096f45b..b7e6de80 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -147,16 +147,4 @@ public function eagerLoader(array $options) { return $loader->buildLoadingQuery($options); } - - /** - * {@inheritDoc} - * - * @return bool - */ - public function requiresKeys(array $options = []) - { - $strategy = isset($options['strategy']) ? $options['strategy'] : $this->strategy(); - - return $strategy === $this::STRATEGY_SELECT; - } } diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 99b281ac..8200479e 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -19,6 +19,7 @@ use Cake\Database\ValueBinder; use InvalidArgumentException; use Cake\ORM\Association; +use RuntimeException; /** * Implements the logic for loading an association using a SELECT query @@ -42,6 +43,8 @@ class SelectLoader protected $associationType; + protected $sort; + public function __construct(array $options) { $this->alias = $options['alias']; @@ -52,6 +55,7 @@ public function __construct(array $options) $this->bindingKey = $options['bindingKey']; $this->finder = $options['finder']; $this->associationType = $options['associationType']; + $this->sort = isset($options['sort']) ? $options['sort'] : null; } @@ -75,7 +79,8 @@ protected function _defaultOptions() 'foreignKey' => $this->foreignKey, 'conditions' => [], 'strategy' => $this->strategy, - 'nestKey' => $this->alias + 'nestKey' => $this->alias, + 'sort' => $this->sort ]; } @@ -265,7 +270,14 @@ protected function _linkField($options) { $links = []; $name = $this->alias; - $keys = $this->associationType === Association::ONE_TO_ONE ? + + if ($options['foreignKey'] === false && $this->associationType === Association::ONE_TO_MANY) { + $msg = 'Cannot have foreignKey = false for hasMany associations. ' . + 'You must provide a foreignKey column.'; + throw new RuntimeException($msg); + } + + $keys = in_array($this->associationType, [Association::ONE_TO_ONE, Association::ONE_TO_MANY]) ? $this->foreignKey : $this->bindingKey; @@ -354,7 +366,8 @@ protected function _subqueryFields($query) protected function _buildResultMap($fetchQuery, $options) { $resultMap = []; - $keys = $this->associationType === Association::ONE_TO_ONE ? + $singleResult = in_array($this->associationType, [Association::MANY_TO_ONE, Association::ONE_TO_ONE]); + $keys = in_array($this->associationType, [Association::ONE_TO_ONE, Association::ONE_TO_MANY]) ? $this->foreignKey : $this->bindingKey; $key = (array)$keys; @@ -364,7 +377,11 @@ protected function _buildResultMap($fetchQuery, $options) foreach ($key as $k) { $values[] = $result[$k]; } - $resultMap[implode(';', $values)] = $result; + if ($singleResult) { + $resultMap[implode(';', $values)] = $result; + } else { + $resultMap[implode(';', $values)][] = $result; + } } return $resultMap; From b377d51a91ad5fbcaf65d1a743325f397510fd0c Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Mon, 10 Oct 2016 16:02:25 +0200 Subject: [PATCH 0813/2059] Migrated BelongsToMany to use the new SelectLoader --- Association/BelongsToMany.php | 210 ++++++++++------------- Association/ExternalAssociationTrait.php | 87 ---------- 2 files changed, 93 insertions(+), 204 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 05f8d4dc..12eb27a5 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -19,11 +19,11 @@ use Cake\Database\Expression\IdentifierExpression; use Cake\Datasource\EntityInterface; use Cake\ORM\Association; +use Cake\ORM\Association\Loader\SelectWithPivotLoader; use Cake\ORM\Query; use Cake\ORM\Table; use Cake\Utility\Inflector; use InvalidArgumentException; -use RuntimeException; use SplObjectStorage; use Traversable; @@ -36,10 +36,6 @@ class BelongsToMany extends Association { - use ExternalAssociationTrait { - _buildQuery as _buildBaseQuery; - } - /** * Saving strategy that will only append to the links set * @@ -150,6 +146,13 @@ class BelongsToMany extends Association */ protected $_junctionConditions; + /** + * Order in which target records should be returned + * + * @var mixed + */ + protected $_sort; + /** * Sets the name of the field representing the foreign key to the target table. * If no parameters are passed current field is returned @@ -170,6 +173,67 @@ public function targetForeignKey($key = null) return $this->_targetForeignKey = $key; } + /** + * Whether this association can be expressed directly in a query join + * + * @param array $options custom options key that could alter the return value + * @return bool if the 'matching' key in $option is true then this function + * will return true, false otherwise + */ + public function canBeJoined(array $options = []) + { + return !empty($options['matching']); + } + + /** + * Sets the name of the field representing the foreign key to the source table. + * If no parameters are passed current field is returned + * + * @param string|null $key the key to be used to link both tables together + * @return string + */ + public function foreignKey($key = null) + { + if ($key === null) { + if ($this->_foreignKey === null) { + $this->_foreignKey = $this->_modelKey($this->source()->table()); + } + + return $this->_foreignKey; + } + + return parent::foreignKey($key); + } + + /** + * Sets the sort order in which target records should be returned. + * If no arguments are passed the currently configured value is returned + * + * @param mixed $sort A find() compatible order clause + * @return mixed + */ + public function sort($sort = null) + { + if ($sort !== null) { + $this->_sort = $sort; + } + + return $this->_sort; + } + + /** + * {@inheritDoc} + */ + public function defaultRowValue($row, $joined) + { + $sourceAlias = $this->source()->alias(); + if (isset($row[$sourceAlias])) { + $row[$sourceAlias][$this->property()] = $joined ? null : []; + } + + return $row; + } + /** * Sets the table instance for the junction relation. If no arguments * are passed, the current configured table instance is returned @@ -438,36 +502,32 @@ protected function _joinCondition($options) } /** - * Builds an array containing the results from fetchQuery indexed by - * the foreignKey value corresponding to this association. + * {@inheritDoc} * - * @param \Cake\ORM\Query $fetchQuery The query to get results from - * @param array $options The options passed to the eager loader - * @return array - * @throws \RuntimeException when the association property is not part of the results set. + * @return callable */ - protected function _buildResultMap($fetchQuery, $options) - { - $resultMap = []; - $key = (array)$options['foreignKey']; - $hydrated = $fetchQuery->hydrate(); - - foreach ($fetchQuery->all() as $result) { - if (!isset($result[$this->_junctionProperty])) { - throw new RuntimeException(sprintf( - '"%s" is missing from the belongsToMany results. Results cannot be created.', - $this->_junctionProperty - )); - } - - $values = []; - foreach ($key as $k) { - $values[] = $result[$this->_junctionProperty][$k]; + public function eagerLoader(array $options) { + $name = $this->_junctionAssociationName(); + $loader = new SelectWithPivotLoader([ + 'alias' => $this->alias(), + 'sourceAlias' => $this->source()->alias(), + 'targetAlias' => $this->target()->alias(), + 'foreignKey' => $this->foreignKey(), + 'bindingKey' => $this->bindingKey(), + 'strategy' => $this->strategy(), + 'associationType' => $this->type(), + 'sort' => $this->sort(), + 'junctionAssociationName' => $name, + 'junctionProperty' => $this->_junctionProperty, + 'junctionAssoc' => $this->target()->association($name), + 'junctionConditions' => $this->junctionConditions(), + 'finder' => function () { + $query = $this->find(); + return $this->_appendJunctionJoin($query, []); } - $resultMap[implode(';', $values)][] = $result; - } + ]); - return $resultMap; + return $loader->buildLoadingQuery($options); } /** @@ -931,7 +991,8 @@ public function find($type = null, array $options = []) list($type, $opts) = $this->_extractFinder($type); $query = $this->target() ->find($type, $options + $opts) - ->where($this->targetConditions()); + ->where($this->targetConditions()) + ->addDefaultTypes($this->target()); if (!$this->junctionConditions()) { return $query; @@ -1227,91 +1288,6 @@ protected function _collectJointEntities($sourceEntity, $targetEntities) return array_merge($result, $query->toArray()); } - /** - * Auxiliary function to construct a new Query object to return all the records - * in the target table that are associated to those specified in $options from - * the source table. - * - * This is used for eager loading records on the target table based on conditions. - * - * @param array $options options accepted by eagerLoader() - * @return \Cake\ORM\Query - * @throws \InvalidArgumentException When a key is required for associations but not selected. - */ - protected function _buildQuery($options) - { - $name = $this->_junctionAssociationName(); - $assoc = $this->target()->association($name); - $queryBuilder = false; - - if (!empty($options['queryBuilder'])) { - $queryBuilder = $options['queryBuilder']; - unset($options['queryBuilder']); - } - - $query = $this->_buildBaseQuery($options); - $query->addDefaultTypes($assoc->target()); - - if ($queryBuilder) { - $query = $queryBuilder($query); - } - - $query = $this->_appendJunctionJoin($query, []); - - if ($query->autoFields() === null) { - $query->autoFields($query->clause('select') === []); - } - - // Ensure that association conditions are applied - // and that the required keys are in the selected columns. - - $tempName = $this->_name . '_CJoin'; - $schema = $assoc->schema(); - $joinFields = $types = []; - - foreach ($schema->typeMap() as $f => $type) { - $key = $tempName . '__' . $f; - $joinFields[$key] = "$name.$f"; - $types[$key] = $type; - } - - $query - ->where($this->junctionConditions()) - ->select($joinFields) - ->defaultTypes($types) - ->addDefaultTypes($this->target()); - - $query - ->eagerLoader() - ->addToJoinsMap($tempName, $assoc, false, $this->_junctionProperty); - $assoc->attachTo($query, ['aliasPath' => $assoc->alias(), 'includeFields' => false]); - - return $query; - } - - /** - * Generates a string used as a table field that contains the values upon - * which the filter should be applied - * - * @param array $options the options to use for getting the link field. - * @return string - */ - protected function _linkField($options) - { - $links = []; - $name = $this->_junctionAssociationName(); - - foreach ((array)$options['foreignKey'] as $key) { - $links[] = sprintf('%s.%s', $name, $key); - } - - if (count($links) === 1) { - return $links[0]; - } - - return $links; - } - /** * Returns the name of the association from the target table to the junction table, * this name is used to generate alias in the query and to later on retrieve the diff --git a/Association/ExternalAssociationTrait.php b/Association/ExternalAssociationTrait.php index 4cc17115..be8051cb 100644 --- a/Association/ExternalAssociationTrait.php +++ b/Association/ExternalAssociationTrait.php @@ -25,74 +25,6 @@ trait ExternalAssociationTrait _defaultOptions as private _selectableOptions; } - /** - * Order in which target records should be returned - * - * @var mixed - */ - protected $_sort; - - /** - * Whether this association can be expressed directly in a query join - * - * @param array $options custom options key that could alter the return value - * @return bool if the 'matching' key in $option is true then this function - * will return true, false otherwise - */ - public function canBeJoined(array $options = []) - { - return !empty($options['matching']); - } - - /** - * Sets the name of the field representing the foreign key to the source table. - * If no parameters are passed current field is returned - * - * @param string|null $key the key to be used to link both tables together - * @return string - */ - public function foreignKey($key = null) - { - if ($key === null) { - if ($this->_foreignKey === null) { - $this->_foreignKey = $this->_modelKey($this->source()->table()); - } - - return $this->_foreignKey; - } - - return parent::foreignKey($key); - } - - /** - * Sets the sort order in which target records should be returned. - * If no arguments are passed the currently configured value is returned - * - * @param mixed $sort A find() compatible order clause - * @return mixed - */ - public function sort($sort = null) - { - if ($sort !== null) { - $this->_sort = $sort; - } - - return $this->_sort; - } - - /** - * {@inheritDoc} - */ - public function defaultRowValue($row, $joined) - { - $sourceAlias = $this->source()->alias(); - if (isset($row[$sourceAlias])) { - $row[$sourceAlias][$this->property()] = $joined ? null : []; - } - - return $row; - } - /** * Returns the default options to use for the eagerLoader * @@ -104,23 +36,4 @@ protected function _defaultOptions() 'sort' => $this->sort() ]; } - - /** - * {@inheritDoc} - */ - protected function _buildResultMap($fetchQuery, $options) - { - $resultMap = []; - $key = (array)$options['foreignKey']; - - foreach ($fetchQuery->all() as $result) { - $values = []; - foreach ($key as $k) { - $values[] = $result[$k]; - } - $resultMap[implode(';', $values)][] = $result; - } - - return $resultMap; - } } From 499826ebb1b0686845f47c450d373151c3982d1c Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Mon, 10 Oct 2016 16:03:08 +0200 Subject: [PATCH 0814/2059] Deleting unused traits and adding a file missed in previous commit --- Association/ExternalAssociationTrait.php | 39 -- Association/Loader/SelectWithPivotLoader.php | 160 ++++++++ Association/SelectableAssociationTrait.php | 393 ------------------- 3 files changed, 160 insertions(+), 432 deletions(-) delete mode 100644 Association/ExternalAssociationTrait.php create mode 100644 Association/Loader/SelectWithPivotLoader.php delete mode 100644 Association/SelectableAssociationTrait.php diff --git a/Association/ExternalAssociationTrait.php b/Association/ExternalAssociationTrait.php deleted file mode 100644 index be8051cb..00000000 --- a/Association/ExternalAssociationTrait.php +++ /dev/null @@ -1,39 +0,0 @@ -_selectableOptions() + [ - 'sort' => $this->sort() - ]; - } -} diff --git a/Association/Loader/SelectWithPivotLoader.php b/Association/Loader/SelectWithPivotLoader.php new file mode 100644 index 00000000..9f06f0b9 --- /dev/null +++ b/Association/Loader/SelectWithPivotLoader.php @@ -0,0 +1,160 @@ +junctionAssociationName = $options['junctionAssociationName']; + $this->junctionProperty = $options['junctionProperty']; + $this->junctionAssoc = $options['junctionAssoc']; + $this->junctionConditions = $options['junctionConditions']; + } + + /** + * Auxiliary function to construct a new Query object to return all the records + * in the target table that are associated to those specified in $options from + * the source table. + * + * This is used for eager loading records on the target table based on conditions. + * + * @param array $options options accepted by eagerLoader() + * @return \Cake\ORM\Query + * @throws \InvalidArgumentException When a key is required for associations but not selected. + */ + protected function _buildQuery($options) + { + $name = $this->junctionAssociationName; + $assoc = $this->junctionAssoc; + $queryBuilder = false; + + if (!empty($options['queryBuilder'])) { + $queryBuilder = $options['queryBuilder']; + unset($options['queryBuilder']); + } + + $query = parent::_buildQuery($options); + + if ($queryBuilder) { + $query = $queryBuilder($query); + } + + if ($query->autoFields() === null) { + $query->autoFields($query->clause('select') === []); + } + + // Ensure that association conditions are applied + // and that the required keys are in the selected columns. + + $tempName = $this->alias . '_CJoin'; + $schema = $assoc->schema(); + $joinFields = $types = []; + + foreach ($schema->typeMap() as $f => $type) { + $key = $tempName . '__' . $f; + $joinFields[$key] = "$name.$f"; + $types[$key] = $type; + } + + $query + ->where($this->junctionConditions) + ->select($joinFields); + + $query + ->eagerLoader() + ->addToJoinsMap($tempName, $assoc, false, $this->junctionProperty); + + $assoc->attachTo($query, ['aliasPath' => $assoc->alias(), 'includeFields' => false]); + $query->typeMap()->addDefaults($types); + + return $query; + } + + /** + * Generates a string used as a table field that contains the values upon + * which the filter should be applied + * + * @param array $options the options to use for getting the link field. + * @return string + */ + protected function _linkField($options) + { + $links = []; + $name = $this->junctionAssociationName; + + foreach ((array)$options['foreignKey'] as $key) { + $links[] = sprintf('%s.%s', $name, $key); + } + + if (count($links) === 1) { + return $links[0]; + } + + return $links; + } + + /** + * Builds an array containing the results from fetchQuery indexed by + * the foreignKey value corresponding to this association. + * + * @param \Cake\ORM\Query $fetchQuery The query to get results from + * @param array $options The options passed to the eager loader + * @return array + * @throws \RuntimeException when the association property is not part of the results set. + */ + protected function _buildResultMap($fetchQuery, $options) + { + $resultMap = []; + $key = (array)$options['foreignKey']; + $hydrated = $fetchQuery->hydrate(); + + foreach ($fetchQuery->all() as $result) { + if (!isset($result[$this->junctionProperty])) { + throw new RuntimeException(sprintf( + '"%s" is missing from the belongsToMany results. Results cannot be created.', + $this->_junctionProperty + )); + } + + $values = []; + foreach ($key as $k) { + $values[] = $result[$this->junctionProperty][$k]; + } + $resultMap[implode(';', $values)][] = $result; + } + + return $resultMap; + } +} diff --git a/Association/SelectableAssociationTrait.php b/Association/SelectableAssociationTrait.php deleted file mode 100644 index a5e95790..00000000 --- a/Association/SelectableAssociationTrait.php +++ /dev/null @@ -1,393 +0,0 @@ -strategy(); - - return $strategy === $this::STRATEGY_SELECT; - } - - /** - * {@inheritDoc} - */ - public function eagerLoader(array $options) - { - $options += $this->_defaultOptions(); - $fetchQuery = $this->_buildQuery($options); - $resultMap = $this->_buildResultMap($fetchQuery, $options); - - return $this->_resultInjector($fetchQuery, $resultMap, $options); - } - - /** - * Returns the default options to use for the eagerLoader - * - * @return array - */ - protected function _defaultOptions() - { - return [ - 'foreignKey' => $this->foreignKey(), - 'conditions' => [], - 'strategy' => $this->strategy(), - 'nestKey' => $this->_name - ]; - } - - /** - * Auxiliary function to construct a new Query object to return all the records - * in the target table that are associated to those specified in $options from - * the source table - * - * @param array $options options accepted by eagerLoader() - * @return \Cake\ORM\Query - * @throws \InvalidArgumentException When a key is required for associations but not selected. - */ - protected function _buildQuery($options) - { - $target = $this->target(); - $alias = $target->alias(); - $key = $this->_linkField($options); - $filter = $options['keys']; - $useSubquery = $options['strategy'] === $this::STRATEGY_SUBQUERY; - - $finder = isset($options['finder']) ? $options['finder'] : $this->finder(); - list($finder, $opts) = $this->_extractFinder($finder); - if (!isset($options['fields'])) { - $options['fields'] = []; - } - - $fetchQuery = $this - ->find($finder, $opts) - ->select($options['fields']) - ->where($options['conditions']) - ->eagerLoaded(true) - ->hydrate($options['query']->hydrate()); - - if ($useSubquery) { - $filter = $this->_buildSubquery($options['query']); - $fetchQuery = $this->_addFilteringJoin($fetchQuery, $key, $filter); - } else { - $fetchQuery = $this->_addFilteringCondition($fetchQuery, $key, $filter); - } - - if (!empty($options['sort'])) { - $fetchQuery->order($options['sort']); - } - - if (!empty($options['contain'])) { - $fetchQuery->contain($options['contain']); - } - - if (!empty($options['queryBuilder'])) { - $fetchQuery = $options['queryBuilder']($fetchQuery); - } - - $this->_assertFieldsPresent($fetchQuery, (array)$key); - - return $fetchQuery; - } - - /** - * Checks that the fetching query either has auto fields on or - * has the foreignKey fields selected. - * If the required fields are missing, throws an exception. - * - * @param \Cake\ORM\Query $fetchQuery The association fetching query - * @param array $key The foreign key fields to check - * @return void - * @throws InvalidArgumentException - */ - protected function _assertFieldsPresent($fetchQuery, $key) - { - $select = $fetchQuery->aliasFields($fetchQuery->clause('select')); - if (empty($select)) { - return; - } - $missingKey = function ($fieldList, $key) { - foreach ($key as $keyField) { - if (!in_array($keyField, $fieldList, true)) { - return true; - } - } - - return false; - }; - - $missingFields = $missingKey($select, $key); - if ($missingFields) { - $driver = $fetchQuery->connection()->driver(); - $quoted = array_map([$driver, 'quoteIdentifier'], $key); - $missingFields = $missingKey($select, $quoted); - } - - if ($missingFields) { - throw new InvalidArgumentException( - sprintf( - 'You are required to select the "%s" field(s)', - implode(', ', (array)$key) - ) - ); - } - } - - /** - * Appends any conditions required to load the relevant set of records in the - * target table query given a filter key and some filtering values when the - * filtering needs to be done using a subquery. - * - * @param \Cake\ORM\Query $query Target table's query - * @param string $key the fields that should be used for filtering - * @param \Cake\ORM\Query $subquery The Subquery to use for filtering - * @return \Cake\ORM\Query - */ - public function _addFilteringJoin($query, $key, $subquery) - { - $filter = []; - $aliasedTable = $this->source()->alias(); - - foreach ($subquery->clause('select') as $aliasedField => $field) { - if (is_int($aliasedField)) { - $filter[] = new IdentifierExpression($field); - } else { - $filter[$aliasedField] = $field; - } - } - $subquery->select($filter, true); - - if (is_array($key)) { - $conditions = $this->_createTupleCondition($query, $key, $filter, '='); - } else { - $filter = current($filter); - } - - $conditions = isset($conditions) ? $conditions : $query->newExpr([$key => $filter]); - - return $query->innerJoin( - [$aliasedTable => $subquery], - $conditions - ); - } - - /** - * Appends any conditions required to load the relevant set of records in the - * target table query given a filter key and some filtering values. - * - * @param \Cake\ORM\Query $query Target table's query - * @param string|array $key the fields that should be used for filtering - * @param mixed $filter the value that should be used to match for $key - * @return \Cake\ORM\Query - */ - protected function _addFilteringCondition($query, $key, $filter) - { - if (is_array($key)) { - $conditions = $this->_createTupleCondition($query, $key, $filter, 'IN'); - } - - $conditions = isset($conditions) ? $conditions : [$key . ' IN' => $filter]; - - return $query->andWhere($conditions); - } - - /** - * Returns a TupleComparison object that can be used for matching all the fields - * from $keys with the tuple values in $filter using the provided operator. - * - * @param \Cake\ORM\Query $query Target table's query - * @param array $keys the fields that should be used for filtering - * @param mixed $filter the value that should be used to match for $key - * @param string $operator The operator for comparing the tuples - * @return \Cake\Database\Expression\TupleComparison - */ - protected function _createTupleCondition($query, $keys, $filter, $operator) - { - $types = []; - $defaults = $query->defaultTypes(); - foreach ($keys as $k) { - if (isset($defaults[$k])) { - $types[] = $defaults[$k]; - } - } - - return new TupleComparison($keys, $filter, $types, $operator); - } - - /** - * Generates a string used as a table field that contains the values upon - * which the filter should be applied - * - * @param array $options The options for getting the link field. - * @return string|array - */ - protected abstract function _linkField($options); - - /** - * Builds a query to be used as a condition for filtering records in the - * target table, it is constructed by cloning the original query that was used - * to load records in the source table. - * - * @param \Cake\ORM\Query $query the original query used to load source records - * @return \Cake\ORM\Query - */ - protected function _buildSubquery($query) - { - $filterQuery = clone $query; - $filterQuery->autoFields(false); - $filterQuery->mapReduce(null, null, true); - $filterQuery->formatResults(null, true); - $filterQuery->contain([], true); - $filterQuery->valueBinder(new ValueBinder()); - - if (!$filterQuery->clause('limit')) { - $filterQuery->limit(null); - $filterQuery->order([], true); - $filterQuery->offset(null); - } - - $fields = $this->_subqueryFields($query); - $filterQuery->select($fields['select'], true)->group($fields['group']); - - return $filterQuery; - } - - /** - * Calculate the fields that need to participate in a subquery. - * - * Normally this includes the binding key columns. If there is a an ORDER BY, - * those columns are also included as the fields may be calculated or constant values, - * that need to be present to ensure the correct association data is loaded. - * - * @param \Cake\ORM\Query $query The query to get fields from. - * @return array The list of fields for the subquery. - */ - protected function _subqueryFields($query) - { - $keys = (array)$this->bindingKey(); - if ($this->type() === $this::MANY_TO_ONE) { - $keys = (array)$this->foreignKey(); - } - $fields = $query->aliasFields($keys, $this->source()->alias()); - $group = $fields = array_values($fields); - - $order = $query->clause('order'); - if ($order) { - $columns = $query->clause('select'); - $order->iterateParts(function ($direction, $field) use (&$fields, $columns) { - if (isset($columns[$field])) { - $fields[$field] = $columns[$field]; - } - }); - } - - return ['select' => $fields, 'group' => $group]; - } - - /** - * Builds an array containing the results from fetchQuery indexed by - * the foreignKey value corresponding to this association. - * - * @param \Cake\ORM\Query $fetchQuery The query to get results from - * @param array $options The options passed to the eager loader - * @return array - */ - protected abstract function _buildResultMap($fetchQuery, $options); - - /** - * Returns a callable to be used for each row in a query result set - * for injecting the eager loaded rows - * - * @param \Cake\ORM\Query $fetchQuery the Query used to fetch results - * @param array $resultMap an array with the foreignKey as keys and - * the corresponding target table results as value. - * @param array $options The options passed to the eagerLoader method - * @return \Closure - */ - protected function _resultInjector($fetchQuery, $resultMap, $options) - { - $source = $this->source(); - $sAlias = $source->alias(); - $keys = $this->type() === $this::MANY_TO_ONE ? - $this->foreignKey() : - $this->bindingKey(); - - $sourceKeys = []; - foreach ((array)$keys as $key) { - $f = $fetchQuery->aliasField($key, $sAlias); - $sourceKeys[] = key($f); - } - - $nestKey = $options['nestKey']; - if (count($sourceKeys) > 1) { - return $this->_multiKeysInjector($resultMap, $sourceKeys, $nestKey); - } - - $sourceKey = $sourceKeys[0]; - - return function ($row) use ($resultMap, $sourceKey, $nestKey) { - if (isset($row[$sourceKey], $resultMap[$row[$sourceKey]])) { - $row[$nestKey] = $resultMap[$row[$sourceKey]]; - } - - return $row; - }; - } - - /** - * Returns a callable to be used for each row in a query result set - * for injecting the eager loaded rows when the matching needs to - * be done with multiple foreign keys - * - * @param array $resultMap A keyed arrays containing the target table - * @param array $sourceKeys An array with aliased keys to match - * @param string $nestKey The key under which results should be nested - * @return \Closure - */ - protected function _multiKeysInjector($resultMap, $sourceKeys, $nestKey) - { - return function ($row) use ($resultMap, $sourceKeys, $nestKey) { - $values = []; - foreach ($sourceKeys as $key) { - $values[] = $row[$key]; - } - - $key = implode(';', $values); - if (isset($resultMap[$key])) { - $row[$nestKey] = $resultMap[$key]; - } - - return $row; - }; - } -} From 6a51f7fcf90c5fd84458d8b689d6e3e14f088adf Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Mon, 10 Oct 2016 16:13:40 +0200 Subject: [PATCH 0815/2059] Adding doc blocks --- Association/Loader/SelectLoader.php | 57 ++++++++++++++++++++ Association/Loader/SelectWithPivotLoader.php | 20 +++++++ 2 files changed, 77 insertions(+) diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 8200479e..bd6e9014 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -27,24 +27,74 @@ class SelectLoader { + /** + * The alias of the association loading the results + * + * @var string + */ protected $alias; + /** + * The alias of the source association + * + * @var string + */ protected $sourceAlias; + /** + * The alias of the target association + * + * @var string + */ protected $targetAlias; + /** + * The foreignKey to the target association + * + * @var string|array + */ protected $foreignKey; + /** + * The strategy to use for loading, either select or subquery + * + * @var string + */ protected $strategy = 'select'; + /** + * The binding key for the source association. + * + * @var string + */ protected $bindingKey; + /** + * A callable that will return a query object used for loading the association results + * + * @var callable + */ protected $finder; + /** + * The type of the association triggering the load + * + * @var string + */ protected $associationType; + /** + * The sorting options for loading the association + * + * @var string + */ protected $sort; + /** + * Copies the options array to properties in this class. The keys in the array correspond + * to properties in this class. + * + */ public function __construct(array $options) { $this->alias = $options['alias']; @@ -59,6 +109,13 @@ public function __construct(array $options) } + /** + * Returns a callable that can be used for injecting association results into a given + * iterator. The options accepted by this method are the same as `Association::eagerLoader()` + * + * @param array $options Same options as `Association::eagerLoader()` + * @return callable + */ public function buildLoadingQuery(array $options) { $options += $this->_defaultOptions(); diff --git a/Association/Loader/SelectWithPivotLoader.php b/Association/Loader/SelectWithPivotLoader.php index 9f06f0b9..6ff7215d 100644 --- a/Association/Loader/SelectWithPivotLoader.php +++ b/Association/Loader/SelectWithPivotLoader.php @@ -23,12 +23,32 @@ class SelectWithPivotLoader extends SelectLoader { + /** + * The name of the junction association + * + * @var string + */ protected $junctionAssociationName; + /** + * The property name for the junction association, where its results should be nested at. + * + * @var string + */ protected $junctionProperty; + /** + * The junction association instance + * + * @var \Cake\ORM\Association\HasMany + */ protected $junctionAssoc; + /** + * Custom conditions for the junction association + * + * @var mixed + */ protected $junctionConditions; /** From d338177f9849802b0b0b4fd020039455ada189fb Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Mon, 10 Oct 2016 19:45:58 +0200 Subject: [PATCH 0816/2059] CS fixes --- Association/BelongsTo.php | 3 ++- Association/BelongsToMany.php | 8 ++++---- Association/HasMany.php | 3 ++- Association/HasOne.php | 3 ++- Association/Loader/SelectLoader.php | 5 +++-- Association/Loader/SelectWithPivotLoader.php | 5 +++-- 6 files changed, 16 insertions(+), 11 deletions(-) diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 136680d5..f78784cf 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -188,7 +188,8 @@ protected function _joinCondition($options) * * @return callable */ - public function eagerLoader(array $options) { + public function eagerLoader(array $options) + { $loader = new SelectLoader([ 'alias' => $this->alias(), 'sourceAlias' => $this->source()->alias(), diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 12eb27a5..5f1c6a26 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -506,7 +506,8 @@ protected function _joinCondition($options) * * @return callable */ - public function eagerLoader(array $options) { + public function eagerLoader(array $options) + { $name = $this->_junctionAssociationName(); $loader = new SelectWithPivotLoader([ 'alias' => $this->alias(), @@ -519,11 +520,10 @@ public function eagerLoader(array $options) { 'sort' => $this->sort(), 'junctionAssociationName' => $name, 'junctionProperty' => $this->_junctionProperty, - 'junctionAssoc' => $this->target()->association($name), + 'junctionAssoc' => $this->target()->association($name), 'junctionConditions' => $this->junctionConditions(), 'finder' => function () { - $query = $this->find(); - return $this->_appendJunctionJoin($query, []); + return $this->_appendJunctionJoin($this->find(), []); } ]); diff --git a/Association/HasMany.php b/Association/HasMany.php index 49f31607..b5566940 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -588,7 +588,8 @@ protected function _options(array $opts) * * @return callable */ - public function eagerLoader(array $options) { + public function eagerLoader(array $options) + { $loader = new SelectLoader([ 'alias' => $this->alias(), 'sourceAlias' => $this->source()->alias(), diff --git a/Association/HasOne.php b/Association/HasOne.php index b7e6de80..e53c8904 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -133,7 +133,8 @@ public function saveAssociated(EntityInterface $entity, array $options = []) * * @return callable */ - public function eagerLoader(array $options) { + public function eagerLoader(array $options) + { $loader = new SelectLoader([ 'alias' => $this->alias(), 'sourceAlias' => $this->source()->alias(), diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index bd6e9014..9a43720d 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -17,8 +17,8 @@ use Cake\Database\Expression\IdentifierExpression; use Cake\Database\Expression\TupleComparison; use Cake\Database\ValueBinder; -use InvalidArgumentException; use Cake\ORM\Association; +use InvalidArgumentException; use RuntimeException; /** @@ -94,6 +94,7 @@ class SelectLoader * Copies the options array to properties in this class. The keys in the array correspond * to properties in this class. * + * @param array $options Properties to be copied to this class */ public function __construct(array $options) { @@ -245,7 +246,7 @@ protected function _assertFieldsPresent($fetchQuery, $key) * @param \Cake\ORM\Query $subquery The Subquery to use for filtering * @return \Cake\ORM\Query */ - public function _addFilteringJoin($query, $key, $subquery) + protected function _addFilteringJoin($query, $key, $subquery) { $filter = []; $aliasedTable = $this->sourceAlias; diff --git a/Association/Loader/SelectWithPivotLoader.php b/Association/Loader/SelectWithPivotLoader.php index 6ff7215d..9126bb61 100644 --- a/Association/Loader/SelectWithPivotLoader.php +++ b/Association/Loader/SelectWithPivotLoader.php @@ -55,9 +55,10 @@ class SelectWithPivotLoader extends SelectLoader * {@inheritDoc} * */ - public function __construct(array $options) { + public function __construct(array $options) + { parent::__construct($options); - $this->junctionAssociationName = $options['junctionAssociationName']; + $this->junctionAssociationName = $options['junctionAssociationName']; $this->junctionProperty = $options['junctionProperty']; $this->junctionAssoc = $options['junctionAssoc']; $this->junctionConditions = $options['junctionConditions']; From 99ca7fb97485fcb89d989136647b94bc17d2a27f Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Tue, 11 Oct 2016 10:07:37 +0200 Subject: [PATCH 0817/2059] Small changes to selectloader --- Association.php | 2 +- Association/BelongsTo.php | 2 +- Association/BelongsToMany.php | 2 +- Association/HasMany.php | 2 +- Association/HasOne.php | 2 +- Association/Loader/SelectLoader.php | 6 ++++-- Association/Loader/SelectWithPivotLoader.php | 2 ++ 7 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Association.php b/Association.php index 0236514f..974bbb7c 100644 --- a/Association.php +++ b/Association.php @@ -761,7 +761,7 @@ public function requiresKeys(array $options = []) { $strategy = isset($options['strategy']) ? $options['strategy'] : $this->strategy(); - return $strategy === $this::STRATEGY_SELECT; + return $strategy === static::STRATEGY_SELECT; } /** diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index f78784cf..97314d34 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -201,6 +201,6 @@ public function eagerLoader(array $options) 'finder' => [$this, 'find'] ]); - return $loader->buildLoadingQuery($options); + return $loader->buildEagerLoader($options); } } diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 5f1c6a26..7c3bfa1d 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -527,7 +527,7 @@ public function eagerLoader(array $options) } ]); - return $loader->buildLoadingQuery($options); + return $loader->buildEagerLoader($options); } /** diff --git a/Association/HasMany.php b/Association/HasMany.php index b5566940..c09af003 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -602,6 +602,6 @@ public function eagerLoader(array $options) 'finder' => [$this, 'find'] ]); - return $loader->buildLoadingQuery($options); + return $loader->buildEagerLoader($options); } } diff --git a/Association/HasOne.php b/Association/HasOne.php index e53c8904..5e69063e 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -146,6 +146,6 @@ public function eagerLoader(array $options) 'finder' => [$this, 'find'] ]); - return $loader->buildLoadingQuery($options); + return $loader->buildEagerLoader($options); } } diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 9a43720d..71244ae9 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -23,6 +23,8 @@ /** * Implements the logic for loading an association using a SELECT query + * + * @internal */ class SelectLoader { @@ -60,7 +62,7 @@ class SelectLoader * * @var string */ - protected $strategy = 'select'; + protected $strategy; /** * The binding key for the source association. @@ -117,7 +119,7 @@ public function __construct(array $options) * @param array $options Same options as `Association::eagerLoader()` * @return callable */ - public function buildLoadingQuery(array $options) + public function buildEagerLoader(array $options) { $options += $this->_defaultOptions(); $fetchQuery = $this->_buildQuery($options); diff --git a/Association/Loader/SelectWithPivotLoader.php b/Association/Loader/SelectWithPivotLoader.php index 9126bb61..9f4689ed 100644 --- a/Association/Loader/SelectWithPivotLoader.php +++ b/Association/Loader/SelectWithPivotLoader.php @@ -19,6 +19,8 @@ /** * Implements the logic for loading an association using a SELECT query and a pivot table + * + * @internal */ class SelectWithPivotLoader extends SelectLoader { From ec8caf6c7ec8095f992e05c64465de79bc2490e2 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 22 Oct 2016 12:08:38 -0400 Subject: [PATCH 0818/2059] Fix new entities with no fields, and bundled translations not saving. When we have bundled translations but no changed fields, we need to mark the original fields dirty so we can save the root entity and the attached translations. Refs #9610 --- Behavior/TranslateBehavior.php | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 2b89d4ef..dea4acb2 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -271,22 +271,35 @@ public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $o $this->_bundleTranslatedFields($entity); $bundled = $entity->get('_i18n') ?: []; + $noBundled = count($bundled) === 0; - if ($locale === $this->config('defaultLocale')) { + // No additional translation records need to be saved, + // as the entity is in the default locale. + if ($noBundled && $locale === $this->config('defaultLocale')) { return; } $values = $entity->extract($this->_config['fields'], true); $fields = array_keys($values); + $noFields = empty($fields); + + $primaryKey = (array)$this->_table->primaryKey(); + $key = $entity->get(current($primaryKey)); - if (empty($fields)) { + if ($noFields && $noBundled) { return; } - $primaryKey = (array)$this->_table->primaryKey(); - $key = $entity->get(current($primaryKey)); - $model = $this->_config['referenceName']; + // If there no fields, and bundled translations, + // we need to mark fields as dirty so the entity persists. + if ($noFields && $bundled) { + foreach ($this->_config['fields'] as $field) { + $entity->dirty($field, true); + } + return; + } + $model = $this->_config['referenceName']; $preexistent = $this->_translationTable->find() ->select(['id', 'field']) ->where(['field IN' => $fields, 'locale' => $locale, 'foreign_key' => $key, 'model' => $model]) From eae9e7e5b910375aaccd419207701b886c99bc2b Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 22 Oct 2016 23:23:38 -0400 Subject: [PATCH 0819/2059] Fix PHPCS. --- Behavior/TranslateBehavior.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index dea4acb2..d0fe5acb 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -296,6 +296,7 @@ public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $o foreach ($this->_config['fields'] as $field) { $entity->dirty($field, true); } + return; } From 84dcbaa00e36e034461661a07a5d3ed5d0ee6c48 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 23 Oct 2016 10:10:48 -0400 Subject: [PATCH 0820/2059] Move variables closer to where they are used. --- Behavior/TranslateBehavior.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index d0fe5acb..5c87aab4 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -283,9 +283,6 @@ public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $o $fields = array_keys($values); $noFields = empty($fields); - $primaryKey = (array)$this->_table->primaryKey(); - $key = $entity->get(current($primaryKey)); - if ($noFields && $noBundled) { return; } @@ -299,6 +296,8 @@ public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $o return; } + $primaryKey = (array)$this->_table->primaryKey(); + $key = $entity->get(current($primaryKey)); $model = $this->_config['referenceName']; $preexistent = $this->_translationTable->find() From fdb6b307fe3eafe134ffbaf2df3a3e3a43c3a500 Mon Sep 17 00:00:00 2001 From: dereuromark Date: Mon, 24 Oct 2016 00:05:57 +0200 Subject: [PATCH 0821/2059] Fix up doblocks to be of more concrete type arrays --- Association.php | 10 +++++++--- Association/BelongsTo.php | 5 ++++- Association/BelongsToMany.php | 5 ++++- Association/HasMany.php | 5 ++++- Association/HasOne.php | 5 ++++- Locator/TableLocator.php | 6 +++--- 6 files changed, 26 insertions(+), 10 deletions(-) diff --git a/Association.php b/Association.php index 974bbb7c..9d712269 100644 --- a/Association.php +++ b/Association.php @@ -187,7 +187,11 @@ abstract class Association * * @var array */ - protected $_validStrategies = [self::STRATEGY_JOIN, self::STRATEGY_SELECT, self::STRATEGY_SUBQUERY]; + protected $_validStrategies = [ + self::STRATEGY_JOIN, + self::STRATEGY_SELECT, + self::STRATEGY_SUBQUERY + ]; /** * Constructor. Subclasses can override _options function to get the original @@ -702,7 +706,7 @@ public function find($type = null, array $options = []) */ public function exists($conditions) { - if (!empty($this->_conditions)) { + if ($this->_conditions) { $conditions = $this ->find('all', ['conditions' => $conditions]) ->clause('where'); @@ -805,7 +809,7 @@ protected function _appendFields($query, $surrogate, $options) $fields = array_merge((array)$fields, $target->schema()->columns()); } - if (!empty($fields)) { + if ($fields) { $query->select($query->aliasFields($fields, $target->alias())); } $query->addDefaultTypes($target); diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 97314d34..ef3ee68c 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -36,7 +36,10 @@ class BelongsTo extends Association * * @var array */ - protected $_validStrategies = [self::STRATEGY_JOIN, self::STRATEGY_SELECT]; + protected $_validStrategies = [ + self::STRATEGY_JOIN, + self::STRATEGY_SELECT + ]; /** * Sets the name of the field representing the foreign key to the target table. diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 7c3bfa1d..25393457 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -120,7 +120,10 @@ class BelongsToMany extends Association * * @var array */ - protected $_validStrategies = [self::STRATEGY_SELECT, self::STRATEGY_SUBQUERY]; + protected $_validStrategies = [ + self::STRATEGY_SELECT, + self::STRATEGY_SUBQUERY + ]; /** * Whether the records on the joint table should be removed when a record diff --git a/Association/HasMany.php b/Association/HasMany.php index c09af003..7f4a77e2 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -62,7 +62,10 @@ class HasMany extends Association * * @var array */ - protected $_validStrategies = [self::STRATEGY_SELECT, self::STRATEGY_SUBQUERY]; + protected $_validStrategies = [ + self::STRATEGY_SELECT, + self::STRATEGY_SUBQUERY + ]; /** * Saving strategy that will only append to the links set diff --git a/Association/HasOne.php b/Association/HasOne.php index 5e69063e..8f654170 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -36,7 +36,10 @@ class HasOne extends Association * * @var array */ - protected $_validStrategies = [self::STRATEGY_JOIN, self::STRATEGY_SELECT]; + protected $_validStrategies = [ + self::STRATEGY_JOIN, + self::STRATEGY_SELECT + ]; /** * Sets the name of the field representing the foreign key to the target table. diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 8d511f29..6de93902 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -36,7 +36,7 @@ class TableLocator implements LocatorInterface /** * Instances that belong to the registry. * - * @var array + * @var \Cake\ORM\Table[] */ protected $_instances = []; @@ -44,7 +44,7 @@ class TableLocator implements LocatorInterface * Contains a list of Table objects that were created out of the * built-in Table class. The list is indexed by table alias * - * @var array + * @var \Cake\ORM\Table[] */ protected $_fallbacked = []; @@ -244,7 +244,7 @@ public function clear() * debugging common mistakes when setting up associations or created new table * classes. * - * @return array + * @return \Cake\ORM\Table[] */ public function genericInstances() { From 06822bb4f2100d5ab83540782743e5401edce208 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 23 Oct 2016 21:59:38 -0400 Subject: [PATCH 0822/2059] Only dirty fields when the entity is new. Postgres did not like the previous changes as they caused additional update queries and ids to mismatch. --- Behavior/TranslateBehavior.php | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 5c87aab4..573ffa4d 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -287,22 +287,33 @@ public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $o return; } - // If there no fields, and bundled translations, - // we need to mark fields as dirty so the entity persists. - if ($noFields && $bundled) { + $primaryKey = (array)$this->_table->primaryKey(); + $key = $entity->get(current($primaryKey)); + + // When we have no key and bundled translations, we + // need to mark the entity dirty so the root + // entity persists. + if ($noFields && $bundled && !$key) { foreach ($this->_config['fields'] as $field) { $entity->dirty($field, true); } return; } - $primaryKey = (array)$this->_table->primaryKey(); - $key = $entity->get(current($primaryKey)); + + if ($noFields) { + return; + } $model = $this->_config['referenceName']; $preexistent = $this->_translationTable->find() ->select(['id', 'field']) - ->where(['field IN' => $fields, 'locale' => $locale, 'foreign_key' => $key, 'model' => $model]) + ->where([ + 'field IN' => $fields, + 'locale' => $locale, + 'foreign_key' => $key, + 'model' => $model + ]) ->bufferResults(false) ->indexBy('field'); From 2cdc919cf1a10a5a9299a818884e56516d24a98b Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 29 Oct 2016 09:44:52 -0400 Subject: [PATCH 0823/2059] Update patchEntity docs to be correct. association properties need to be in the fieldList when it is used. Refs #9671 --- Table.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Table.php b/Table.php index 8a7ccf77..877be55b 100644 --- a/Table.php +++ b/Table.php @@ -2105,7 +2105,7 @@ public function marshaller() * * ``` * $article = $this->Articles->newEntity($this->request->data(), [ - * 'fieldList' => ['title', 'body'], + * 'fieldList' => ['title', 'body', 'tags', 'comments'], * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']] * ] * ); @@ -2175,7 +2175,7 @@ public function newEntity($data = null, array $options = []) * * ``` * $articles = $this->Articles->newEntities($this->request->data(), [ - * 'fieldList' => ['title', 'body'], + * 'fieldList' => ['title', 'body', 'tags', 'comments'], * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']] * ] * ); @@ -2206,7 +2206,7 @@ public function newEntities(array $data, array $options = []) * * ``` * $article = $this->Articles->patchEntity($article, $this->request->data(), [ - * 'fieldList' => ['title', 'body'], + * 'fieldList' => ['title', 'body', 'tags', 'comments], * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']] * ] * ); @@ -2251,7 +2251,7 @@ public function patchEntity(EntityInterface $entity, array $data, array $options * * ``` * $articles = $this->Articles->patchEntities($articles, $this->request->data(), [ - * 'fieldList' => ['title', 'body'], + * 'fieldList' => ['title', 'body', 'tags', 'comments'], * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']] * ] * ); From af6e54ce026daa7a7ec923c95cceaa4b4afbb880 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Sat, 29 Oct 2016 10:12:55 -0500 Subject: [PATCH 0824/2059] Allow formatting join tables --- Association/BelongsToMany.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index d4c2fcc6..cd4d12b8 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1285,7 +1285,11 @@ protected function _buildQuery($options) $query ->eagerLoader() ->addToJoinsMap($tempName, $assoc, false, $this->_junctionProperty); - $assoc->attachTo($query, ['aliasPath' => $assoc->alias(), 'includeFields' => false]); + $assoc->attachTo($query, [ + 'aliasPath' => $assoc->alias(), + 'includeFields' => false, + 'propertyPath' => $this->_junctionProperty + ]); return $query; } From 7507c1ce3721b533bd3640cfba69535d4ee19214 Mon Sep 17 00:00:00 2001 From: dereuromark Date: Sun, 30 Oct 2016 00:26:08 +0200 Subject: [PATCH 0825/2059] Define relations as fluent interface via explicit getter/setter, resolves #8757 --- Association.php | 421 +++++++++++++++++++++++++++++----- Association/BelongsTo.php | 25 +- Association/BelongsToMany.php | 102 ++++++-- Association/HasMany.php | 93 ++++++-- Association/HasOne.php | 25 +- 5 files changed, 558 insertions(+), 108 deletions(-) diff --git a/Association.php b/Association.php index 9d712269..43011e0a 100644 --- a/Association.php +++ b/Association.php @@ -237,35 +237,82 @@ public function __construct($alias, array $options = []) } /** - * Sets the name for this association. If no argument is passed then the current - * configured name will be returned + * Sets the name for this association. * + * @param string $name Name to be assigned + * @return $this + */ + public function setName($name) + { + $this->_name = $name; + + return $this; + } + + /** + * Gets the name for this association. + * + * @return string + */ + public function getName() + { + return $this->_name; + } + + /** + * Sets the name for this association. + * + * @deprecated Use setName()/getName() instead. * @param string|null $name Name to be assigned * @return string */ public function name($name = null) { if ($name !== null) { - $this->_name = $name; + $this->setName($name); } - return $this->_name; + return $this->getName(); + } + + /** + * Sets whether or not cascaded deletes should also fire callbacks. + * + * @param bool $cascadeCallbacks cascade callbacks switch value + * @return $this + */ + public function setCascadeCallbacks($cascadeCallbacks) + { + $this->_cascadeCallbacks = $cascadeCallbacks; + + return $this; + } + + /** + * Gets whether or not cascaded deletes should also fire callbacks. + * + * @return bool + */ + public function getCascadeCallbacks() + { + return $this->_cascadeCallbacks; } /** * Sets whether or not cascaded deletes should also fire callbacks. If no * arguments are passed, the current configured value is returned * + * @deprecated Use setCascadeCallbacks()/getCascadeCallbacks() instead. * @param bool|null $cascadeCallbacks cascade callbacks switch value * @return bool */ public function cascadeCallbacks($cascadeCallbacks = null) { if ($cascadeCallbacks !== null) { - $this->_cascadeCallbacks = $cascadeCallbacks; + $this->setCascadeCallbacks($cascadeCallbacks); } - return $this->_cascadeCallbacks; + return $this->getCascadeCallbacks(); } /** @@ -278,10 +325,34 @@ public function className() return $this->_className; } + /** + * Sets the table instance for the source side of the association. + * + * @param \Cake\ORM\Table $table the instance to be assigned as source side + * @return $this + */ + public function setSource(Table $table) + { + $this->_sourceTable = $table; + + return $this; + } + + /** + * Gets the table instance for the source side of the association. + * + * @return \Cake\ORM\Table + */ + public function getSource() + { + return $this->_sourceTable; + } + /** * Sets the table instance for the source side of the association. If no arguments * are passed, the current configured table instance is returned * + * @deprecated Use setSource()/getSource() instead. * @param \Cake\ORM\Table|null $table the instance to be assigned as source side * @return \Cake\ORM\Table */ @@ -294,45 +365,95 @@ public function source(Table $table = null) return $this->_sourceTable = $table; } + /** + * Sets the table instance for the target side of the association. + * + * @param \Cake\ORM\Table $table the instance to be assigned as target side + * @return $this + */ + public function setTarget(Table $table) + { + $this->_targetTable = $table; + + return $this; + } + + /** + * Gets the table instance for the target side of the association. + * + * @return \Cake\ORM\Table + */ + public function getTarget() + { + if (!$this->_targetTable) { + if (strpos($this->_className, '.')) { + list($plugin) = pluginSplit($this->_className, true); + $registryAlias = $plugin . $this->_name; + } else { + $registryAlias = $this->_name; + } + + $tableLocator = $this->tableLocator(); + + $config = []; + if (!$tableLocator->exists($registryAlias)) { + $config = ['className' => $this->_className]; + } + $this->_targetTable = $tableLocator->get($registryAlias, $config); + } + + return $this->_targetTable; + } + /** * Sets the table instance for the target side of the association. If no arguments * are passed, the current configured table instance is returned * + * @deprecated Use setTable()/getTable() instead. * @param \Cake\ORM\Table|null $table the instance to be assigned as target side * @return \Cake\ORM\Table */ public function target(Table $table = null) { - if ($table === null && $this->_targetTable) { - return $this->_targetTable; - } - if ($table !== null) { - return $this->_targetTable = $table; + $this->setTarget($table); } - if (strpos($this->_className, '.')) { - list($plugin) = pluginSplit($this->_className, true); - $registryAlias = $plugin . $this->_name; - } else { - $registryAlias = $this->_name; - } + return $this->getTarget(); + } - $tableLocator = $this->tableLocator(); + /** + * Sets a list of conditions to be always included when fetching records from + * the target association. + * + * @param array $conditions list of conditions to be used + * @see \Cake\Database\Query::where() for examples on the format of the array + * @return $this + */ + public function setConditions($conditions) + { + $this->_conditions = $conditions; - $config = []; - if (!$tableLocator->exists($registryAlias)) { - $config = ['className' => $this->_className]; - } - $this->_targetTable = $tableLocator->get($registryAlias, $config); + return $this; + } - return $this->_targetTable; + /** + * Gets a list of conditions to be always included when fetching records from + * the target association. + * + * @see \Cake\Database\Query::where() for examples on the format of the array + * @return array + */ + public function getConditions() + { + return $this->_conditions; } /** * Sets a list of conditions to be always included when fetching records from * the target association. If no parameters are passed the current list is returned * + * @deprecated Use setConditions()/getConditions() instead. * @param array|null $conditions list of conditions to be used * @see \Cake\Database\Query::where() for examples on the format of the array * @return array @@ -340,27 +461,34 @@ public function target(Table $table = null) public function conditions($conditions = null) { if ($conditions !== null) { - $this->_conditions = $conditions; + $this->setConditions($conditions); } - return $this->_conditions; + return $this->getConditions(); } /** * Sets the name of the field representing the binding field with the target table. * When not manually specified the primary key of the owning side table is used. * - * If no parameters are passed the current field is returned + * @param string $key the table field to be used to link both tables together + * @return $this + */ + public function setBindingKey($key) + { + $this->_bindingKey = $key; + + return $this; + } + + /** + * Gets the name of the field representing the binding field with the target table. + * When not manually specified the primary key of the owning side table is used. * - * @param string|null $key the table field to be used to link both tables together * @return string|array */ - public function bindingKey($key = null) + public function getBindingKey() { - if ($key !== null) { - $this->_bindingKey = $key; - } - if ($this->_bindingKey === null) { $this->_bindingKey = $this->isOwningSide($this->source()) ? $this->source()->primaryKey() : @@ -370,20 +498,94 @@ public function bindingKey($key = null) return $this->_bindingKey; } + /** + * Sets the name of the field representing the binding field with the target table. + * When not manually specified the primary key of the owning side table is used. + * + * If no parameters are passed the current field is returned + * + * @deprecated Use setBindingKey()/getBindingKey() instead. + * @param string|null $key the table field to be used to link both tables together + * @return string|array + */ + public function bindingKey($key = null) + { + if ($key !== null) { + $this->setBindingKey($key); + } + + return $this->getBindingKey(); + } + + /** + * Gets the name of the field representing the foreign key to the target table. + * + * @return string|array + */ + public function getForeignKey() + { + return $this->_foreignKey; + } + + /** + * Sets the name of the field representing the foreign key to the target table. + * + * @param string $key the key to be used to link both tables together + * @return $this + */ + public function setForeignKey($key) + { + $this->_foreignKey = $key; + + return $this; + } + /** * Sets the name of the field representing the foreign key to the target table. * If no parameters are passed the current field is returned * + * @deprecated Use setForeignKey()/getForeignKey() instead. * @param string|null $key the key to be used to link both tables together * @return string|array */ public function foreignKey($key = null) { if ($key !== null) { - $this->_foreignKey = $key; + $this->setForeignKey($key); } - return $this->_foreignKey; + return $this->getForeignKey(); + } + + /** + * Sets whether the records on the target table are dependent on the source table. + * + * This is primarily used to indicate that records should be removed if the owning record in + * the source table is deleted. + * + * If no parameters are passed the current setting is returned. + * + * @param bool $dependent Set the dependent mode. Use null to read the current state. + * @return $this + */ + public function setDependent($dependent) + { + $this->_dependent = $dependent; + + return $this; + } + + /** + * Sets whether the records on the target table are dependent on the source table. + * + * This is primarily used to indicate that records should be removed if the owning record in + * the source table is deleted. + * + * @return bool + */ + public function getDependent() + { + return $this->_dependent; } /** @@ -394,16 +596,17 @@ public function foreignKey($key = null) * * If no parameters are passed the current setting is returned. * + * @deprecated Use setDependent()/getDependent() instead. * @param bool|null $dependent Set the dependent mode. Use null to read the current state. * @return bool */ public function dependent($dependent = null) { if ($dependent !== null) { - $this->_dependent = $dependent; + $this->setDependent($dependent); } - return $this->_dependent; + return $this->getDependent(); } /** @@ -419,36 +622,69 @@ public function canBeJoined(array $options = []) return $strategy == $this::STRATEGY_JOIN; } + /** + * Sets the type of join to be used when adding the association to a query. + * + * @param string $type the join type to be used (e.g. INNER) + * @return $this + */ + public function setJoinType($type) + { + $this->_joinType = $type; + + return $this; + } + + /** + * Gets the type of join to be used when adding the association to a query. + * + * @return string + */ + public function getJoinType() + { + return $this->_joinType; + } + /** * Sets the type of join to be used when adding the association to a query. * If no arguments are passed, the currently configured type is returned. * + * @deprecated Use setJoinType()/getJoinType() instead. * @param string|null $type the join type to be used (e.g. INNER) * @return string */ public function joinType($type = null) { - if ($type === null) { - return $this->_joinType; + if ($type !== null) { + $this->setJoinType($type); } - return $this->_joinType = $type; + return $this->getJoinType(); } /** * Sets the property name that should be filled with data from the target table * in the source table record. - * If no arguments are passed, the currently configured type is returned. * - * @param string|null $name The name of the association property. Use null to read the current value. + * @param string $name The name of the association property. Use null to read the current value. + * @return $this + */ + public function setProperty($name) + { + $this->_propertyName = $name; + + return $this; + } + + /** + * Gets the property name that should be filled with data from the target table + * in the source table record. + * * @return string */ - public function property($name = null) + public function getProperty() { - if ($name !== null) { - $this->_propertyName = $name; - } - if ($name === null && !$this->_propertyName) { + if (!$this->_propertyName) { $this->_propertyName = $this->_propertyName(); if (in_array($this->_propertyName, $this->_sourceTable->schema()->columns())) { $msg = 'Association property name "%s" clashes with field of same name of table "%s".' . @@ -463,6 +699,24 @@ public function property($name = null) return $this->_propertyName; } + /** + * Sets the property name that should be filled with data from the target table + * in the source table record. + * If no arguments are passed, the currently configured type is returned. + * + * @deprecated Use setProperty()/getProperty() instead. + * @param string|null $name The name of the association property. Use null to read the current value. + * @return string + */ + public function property($name = null) + { + if ($name !== null) { + $this->setProperty($name); + } + + return $this->getProperty(); + } + /** * Returns default property name based on association name. * @@ -475,12 +729,46 @@ protected function _propertyName() return Inflector::underscore($name); } + /** + * Sets the strategy name to be used to fetch associated records. Keep in mind + * that some association types might not implement but a default strategy, + * rendering any changes to this setting void. + * + * @param string $name The strategy type. Use null to read the current value. + * @return $this + * @throws \InvalidArgumentException When an invalid strategy is provided. + */ + public function setStrategy($name) + { + if (!in_array($name, $this->_validStrategies)) { + throw new InvalidArgumentException( + sprintf('Invalid strategy "%s" was provided', $name) + ); + } + $this->_strategy = $name; + + return $this; + } + + /** + * Gets the strategy name to be used to fetch associated records. Keep in mind + * that some association types might not implement but a default strategy, + * rendering any changes to this setting void. + * + * @return string + */ + public function getStrategy() + { + return $this->_strategy; + } + /** * Sets the strategy name to be used to fetch associated records. Keep in mind * that some association types might not implement but a default strategy, * rendering any changes to this setting void. * If no arguments are passed, the currently configured strategy is returned. * + * @deprecated Use setStrategy()/getStrategy() instead. * @param string|null $name The strategy type. Use null to read the current value. * @return string * @throws \InvalidArgumentException When an invalid strategy is provided. @@ -488,15 +776,33 @@ protected function _propertyName() public function strategy($name = null) { if ($name !== null) { - if (!in_array($name, $this->_validStrategies)) { - throw new InvalidArgumentException( - sprintf('Invalid strategy "%s" was provided', $name) - ); - } - $this->_strategy = $name; + $this->setStrategy($name); } - return $this->_strategy; + return $this->getStrategy(); + } + + /** + * Gets the default finder to use for fetching rows from the target table. + * + * @return string + */ + public function getFinder() + { + return $this->_finder; + } + + /** + * Sets the default finder to use for fetching rows from the target table. + * + * @param string $finder the finder name to use + * @return $this + */ + public function setFinder($finder) + { + $this->_finder = $finder; + + return $this; } /** @@ -504,16 +810,17 @@ public function strategy($name = null) * If no parameters are passed, it will return the currently configured * finder name. * + * @deprecated Use setFinder()/getFinder() instead. * @param string|null $finder the finder name to use * @return string */ public function finder($finder = null) { if ($finder !== null) { - $this->_finder = $finder; + $this->setFinder($finder); } - return $this->_finder; + return $this->getFinder(); } /** @@ -637,7 +944,7 @@ protected function _appendNotMatching($query, $options) * should be found * @param bool $joined Whether or not the row is a result of a direct join * with this association - * @param string $targetProperty The property name in the source results where the association + * @param string|null $targetProperty The property name in the source results where the association * data shuld be nested in. Will use the default one if not provided. * @return array */ diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index ef3ee68c..4b0cca77 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -41,24 +41,35 @@ class BelongsTo extends Association self::STRATEGY_SELECT ]; + /** + * Gets the name of the field representing the foreign key to the target table. + * + * @return string + */ + public function getForeignKey() + { + if ($this->_foreignKey === null) { + $this->_foreignKey = $this->_modelKey($this->target()->alias()); + } + + return $this->_foreignKey; + } + /** * Sets the name of the field representing the foreign key to the target table. * If no parameters are passed current field is returned * + * @deprecated Use setForeignKey()/getForeignKey() instead. * @param string|null $key the key to be used to link both tables together * @return string */ public function foreignKey($key = null) { - if ($key === null) { - if ($this->_foreignKey === null) { - $this->_foreignKey = $this->_modelKey($this->target()->alias()); - } - - return $this->_foreignKey; + if ($key !== null) { + $this->setForeignKey($key); } - return parent::foreignKey($key); + return $this->getForeignKey(); } /** diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 25393457..a26aceff 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -156,24 +156,48 @@ class BelongsToMany extends Association */ protected $_sort; + /** + * Sets the name of the field representing the foreign key to the target table. + * + * @param string $key the key to be used to link both tables together + * @return $this + */ + public function setTargetForeignKey($key) + { + $this->_targetForeignKey = $key; + + return $this; + } + + /** + * Gets the name of the field representing the foreign key to the target table. + * + * @return string + */ + public function getTargetForeignKey() + { + if ($this->_targetForeignKey === null) { + $this->_targetForeignKey = $this->_modelKey($this->target()->alias()); + } + + return $this->_targetForeignKey; + } + /** * Sets the name of the field representing the foreign key to the target table. * If no parameters are passed current field is returned * + * @deprecated Use setTargetForeignKey()/getTargetForeignKey() instead. * @param string|null $key the key to be used to link both tables together * @return string */ public function targetForeignKey($key = null) { - if ($key === null) { - if ($this->_targetForeignKey === null) { - $this->_targetForeignKey = $this->_modelKey($this->target()->alias()); - } - - return $this->_targetForeignKey; + if ($key !== null) { + return $this->setTargetForeignKey($key); } - return $this->_targetForeignKey = $key; + return $this->getTargetForeignKey(); } /** @@ -188,6 +212,20 @@ public function canBeJoined(array $options = []) return !empty($options['matching']); } + /** + * Gets the name of the field representing the foreign key to the source table. + * + * @return string + */ + public function getForeignKey() + { + if ($this->_foreignKey === null) { + $this->_foreignKey = $this->_modelKey($this->source()->table()); + } + + return $this->_foreignKey; + } + /** * Sets the name of the field representing the foreign key to the source table. * If no parameters are passed current field is returned @@ -197,15 +235,11 @@ public function canBeJoined(array $options = []) */ public function foreignKey($key = null) { - if ($key === null) { - if ($this->_foreignKey === null) { - $this->_foreignKey = $this->_modelKey($this->source()->table()); - } - - return $this->_foreignKey; + if ($key !== null) { + $this->setForeignKey($key); } - return parent::foreignKey($key); + return $this->getForeignKey(); } /** @@ -580,25 +614,51 @@ public function isOwningSide(Table $side) return true; } + /** + * Sets the strategy that should be used for saving. + * + * @param string $strategy the strategy name to be used + * @throws \InvalidArgumentException if an invalid strategy name is passed + * @return $this + */ + public function setSaveStrategy($strategy) + { + if (!in_array($strategy, [self::SAVE_APPEND, self::SAVE_REPLACE])) { + $msg = sprintf('Invalid save strategy "%s"', $strategy); + throw new InvalidArgumentException($msg); + } + + $this->_saveStrategy = $strategy; + + return $this; + } + + /** + * Gets the strategy that should be used for saving. + * + * @return string the strategy to be used for saving + */ + public function getSaveStrategy() + { + return $this->_saveStrategy; + } + /** * Sets the strategy that should be used for saving. If called with no * arguments, it will return the currently configured strategy * + * @deprecated Use setSaveStrategy()/getSaveStrategy() instead. * @param string|null $strategy the strategy name to be used * @throws \InvalidArgumentException if an invalid strategy name is passed * @return string the strategy to be used for saving */ public function saveStrategy($strategy = null) { - if ($strategy === null) { - return $this->_saveStrategy; - } - if (!in_array($strategy, [self::SAVE_APPEND, self::SAVE_REPLACE])) { - $msg = sprintf('Invalid save strategy "%s"', $strategy); - throw new InvalidArgumentException($msg); + if ($strategy !== null) { + $this->setSaveStrategy($strategy); } - return $this->_saveStrategy = $strategy; + return $this->getSaveStrategy(); } /** diff --git a/Association/HasMany.php b/Association/HasMany.php index 7f4a77e2..67da21bb 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -101,25 +101,51 @@ public function isOwningSide(Table $side) return $side === $this->source(); } + /** + * Sets the strategy that should be used for saving. + * + * @param string $strategy the strategy name to be used + * @throws \InvalidArgumentException if an invalid strategy name is passed + * @return $this + */ + public function setSaveStrategy($strategy) + { + if (!in_array($strategy, [self::SAVE_APPEND, self::SAVE_REPLACE])) { + $msg = sprintf('Invalid save strategy "%s"', $strategy); + throw new InvalidArgumentException($msg); + } + + $this->_saveStrategy = $strategy; + + return $this; + } + + /** + * Gets the strategy that should be used for saving. + * + * @return string the strategy to be used for saving + */ + public function getSaveStrategy() + { + return $this->_saveStrategy; + } + /** * Sets the strategy that should be used for saving. If called with no * arguments, it will return the currently configured strategy * + * @deprecated Use setSaveStrategy()/getSaveStrategy() instead. * @param string|null $strategy the strategy name to be used * @throws \InvalidArgumentException if an invalid strategy name is passed * @return string the strategy to be used for saving */ public function saveStrategy($strategy = null) { - if ($strategy === null) { - return $this->_saveStrategy; - } - if (!in_array($strategy, [self::SAVE_APPEND, self::SAVE_REPLACE])) { - $msg = sprintf('Invalid save strategy "%s"', $strategy); - throw new InvalidArgumentException($msg); + if ($strategy !== null) { + $this->setSaveStrategy($strategy); } - return $this->_saveStrategy = $strategy; + return $this->getSaveStrategy(); } /** @@ -521,40 +547,75 @@ public function canBeJoined(array $options = []) return !empty($options['matching']); } + /** + * Gets the name of the field representing the foreign key to the source table. + * + * @return string + */ + public function getForeignKey() + { + if ($this->_foreignKey === null) { + $this->_foreignKey = $this->_modelKey($this->source()->table()); + } + + return $this->_foreignKey; + } + /** * Sets the name of the field representing the foreign key to the source table. * If no parameters are passed current field is returned * + * @deprecated Use setForeignKey()/getForeignKey() instead. * @param string|null $key the key to be used to link both tables together * @return string */ public function foreignKey($key = null) { - if ($key === null) { - if ($this->_foreignKey === null) { - $this->_foreignKey = $this->_modelKey($this->source()->table()); - } - - return $this->_foreignKey; + if ($key !== null) { + return $this->setForeignKey($key); } - return parent::foreignKey($key); + return $this->getForeignKey(); + } + + /** + * Sets the sort order in which target records should be returned. + * + * @param mixed $sort A find() compatible order clause + * @return $this + */ + public function setSort($sort) + { + $this->_sort = $sort; + + return $this; + } + + /** + * Gets the sort order in which target records should be returned. + * + * @return mixed + */ + public function getSort() + { + return $this->_sort; } /** * Sets the sort order in which target records should be returned. * If no arguments are passed the currently configured value is returned * + * @deprecated Use setSort()/getSort() instead. * @param mixed $sort A find() compatible order clause * @return mixed */ public function sort($sort = null) { if ($sort !== null) { - $this->_sort = $sort; + $this->setSort($sort); } - return $this->_sort; + return $this->getSort(); } /** diff --git a/Association/HasOne.php b/Association/HasOne.php index 8f654170..f61ace63 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -41,24 +41,35 @@ class HasOne extends Association self::STRATEGY_SELECT ]; + /** + * Gets the name of the field representing the foreign key to the target table. + * + * @return string + */ + public function getForeignKey() + { + if ($this->_foreignKey === null) { + $this->_foreignKey = $this->_modelKey($this->source()->alias()); + } + + return $this->_foreignKey; + } + /** * Sets the name of the field representing the foreign key to the target table. * If no parameters are passed current field is returned * + * @deprecated Use setForeignKey()/getForeignKey() instead. * @param string|null $key the key to be used to link both tables together * @return string */ public function foreignKey($key = null) { - if ($key === null) { - if ($this->_foreignKey === null) { - $this->_foreignKey = $this->_modelKey($this->source()->alias()); - } - - return $this->_foreignKey; + if ($key !== null) { + return $this->setForeignKey($key); } - return parent::foreignKey($key); + return $this->getForeignKey(); } /** From cf4fb44e5156fbbac083fa9af0c2ddf550721fa5 Mon Sep 17 00:00:00 2001 From: dereuromark Date: Sun, 30 Oct 2016 02:38:22 +0200 Subject: [PATCH 0826/2059] Adding getter/setter for through option --- Association/BelongsToMany.php | 38 +++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index a26aceff..09c68e4a 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -960,14 +960,44 @@ function () use ($sourceEntity, $targetEntities, $options) { /** * {@inheritDoc} */ + public function setConditions($conditions) + { + parent::setConditions($conditions); + $this->_targetConditions = $this->_junctionConditions = null; + + return $this; + } + + /** + * {@inheritDoc} + * @deprecated Use setConditions()/getConditions() instead. + */ public function conditions($conditions = null) { if ($conditions !== null) { - $this->_conditions = $conditions; - $this->_targetConditions = $this->_junctionConditions = null; + $this->setConditions($conditions); } - return $this->_conditions; + return $this->getConditions(); + } + + /** + * @param string|\Cake\ORM\Table $through + * @return $this + */ + public function setThrough($through) + { + $this->_through = $through; + + return $this; + } + + /** + * @return string|\Cake\ORM\Table + */ + public function getThrough() + { + return $this->_through; } /** @@ -1410,7 +1440,7 @@ protected function _options(array $opts) $this->_junctionTableName($opts['joinTable']); } if (!empty($opts['through'])) { - $this->_through = $opts['through']; + $this->setThrough($opts['through']); } if (!empty($opts['saveStrategy'])) { $this->saveStrategy($opts['saveStrategy']); From 7a1a993bc50b1c3ecefed019373460b8f8d17a3c Mon Sep 17 00:00:00 2001 From: dereuromark Date: Sun, 30 Oct 2016 02:41:32 +0200 Subject: [PATCH 0827/2059] Update doc block --- Association/BelongsToMany.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 09c68e4a..9de17828 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -982,7 +982,9 @@ public function conditions($conditions = null) } /** - * @param string|\Cake\ORM\Table $through + * Sets the current join table, either the name of the Table instance or the instance itself. + * + * @param string|\Cake\ORM\Table $through Name of the Table instance or the instance itself * @return $this */ public function setThrough($through) @@ -993,6 +995,8 @@ public function setThrough($through) } /** + * Gets the current join table, either the name of the Table instance or the instance itself. + * * @return string|\Cake\ORM\Table */ public function getThrough() From 3faa3da6920f2b9387c3367a594f06df6ef168de Mon Sep 17 00:00:00 2001 From: mscherer Date: Tue, 1 Nov 2016 10:28:08 +0100 Subject: [PATCH 0828/2059] Rename Table to Schema for consistency and less confusion. --- Table.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Table.php b/Table.php index 577257b4..ffe30bdd 100644 --- a/Table.php +++ b/Table.php @@ -17,7 +17,7 @@ use ArrayObject; use BadMethodCallException; use Cake\Core\App; -use Cake\Database\Schema\Table as Schema; +use Cake\Database\Schema\Schema; use Cake\Database\Type; use Cake\Datasource\ConnectionInterface; use Cake\Datasource\EntityInterface; @@ -176,7 +176,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc /** * The schema object containing a description of this table fields * - * @var \Cake\Database\Schema\Table + * @var \Cake\Database\Schema\Schema */ protected $_schema; @@ -427,8 +427,8 @@ public function connection(ConnectionInterface $conn = null) * If an array is passed, a new \Cake\Database\Schema\Table will be constructed * out of it and used as the schema for this table. * - * @param array|\Cake\Database\Schema\Table|null $schema New schema to be used for this table - * @return \Cake\Database\Schema\Table + * @param array|\Cake\Database\Schema\Schema|null $schema New schema to be used for this table + * @return \Cake\Database\Schema\Schema */ public function schema($schema = null) { @@ -472,19 +472,19 @@ public function schema($schema = null) * ### Example: * * ``` - * protected function _initializeSchema(\Cake\Database\Schema\Table $table) { + * protected function _initializeSchema(\Cake\Database\Schema\Schema $schema) { * $table->columnType('preferences', 'json'); * return $table; * } * ``` * - * @param \Cake\Database\Schema\Table $table The table definition fetched from database. - * @return \Cake\Database\Schema\Table the altered schema + * @param \Cake\Database\Schema\Schema $schema The table definition fetched from database. + * @return \Cake\Database\Schema\Schema the altered schema * @api */ - protected function _initializeSchema(Schema $table) + protected function _initializeSchema(Schema $schema) { - return $table; + return $schema; } /** From 87c93c2d505071e5148645b0439057567f4bb7be Mon Sep 17 00:00:00 2001 From: dereuromark Date: Wed, 2 Nov 2016 01:41:38 +0100 Subject: [PATCH 0829/2059] Rename Schema/Table to Schema/TableSchema via class_alias() --- Table.php | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Table.php b/Table.php index ffe30bdd..913c4cf1 100644 --- a/Table.php +++ b/Table.php @@ -17,7 +17,7 @@ use ArrayObject; use BadMethodCallException; use Cake\Core\App; -use Cake\Database\Schema\Schema; +use Cake\Database\Schema\TableSchema; use Cake\Database\Type; use Cake\Datasource\ConnectionInterface; use Cake\Datasource\EntityInterface; @@ -176,7 +176,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc /** * The schema object containing a description of this table fields * - * @var \Cake\Database\Schema\Schema + * @var \Cake\Database\Schema\TableSchema */ protected $_schema; @@ -232,7 +232,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc * - connection: The connection instance to use * - entityClass: The fully namespaced class name of the entity class that will * represent rows in this table. - * - schema: A \Cake\Database\Schema\Table object or an array that can be + * - schema: A \Cake\Database\Schema\TableSchema object or an array that can be * passed to it. * - eventManager: An instance of an event manager to use for internal events * - behaviors: A BehaviorRegistry. Generally not used outside of tests. @@ -421,14 +421,14 @@ public function connection(ConnectionInterface $conn = null) /** * Returns the schema table object describing this table's properties. * - * If an \Cake\Database\Schema\Table is passed, it will be used for this table + * If a TableSchema is passed, it will be used for this table * instead of the default one. * - * If an array is passed, a new \Cake\Database\Schema\Table will be constructed + * If an array is passed, a new TableSchema will be constructed * out of it and used as the schema for this table. * - * @param array|\Cake\Database\Schema\Schema|null $schema New schema to be used for this table - * @return \Cake\Database\Schema\Schema + * @param array|\Cake\Database\Schema\TableSchema|null $schema New schema to be used for this table + * @return \Cake\Database\Schema\TableSchema */ public function schema($schema = null) { @@ -452,7 +452,7 @@ public function schema($schema = null) unset($schema['_constraints']); } - $schema = new Schema($this->table(), $schema); + $schema = new TableSchema($this->table(), $schema); foreach ($constraints as $name => $value) { $schema->addConstraint($name, $value); @@ -478,11 +478,11 @@ public function schema($schema = null) * } * ``` * - * @param \Cake\Database\Schema\Schema $schema The table definition fetched from database. - * @return \Cake\Database\Schema\Schema the altered schema + * @param \Cake\Database\Schema\TableSchema $schema The table definition fetched from database. + * @return \Cake\Database\Schema\TableSchema the altered schema * @api */ - protected function _initializeSchema(Schema $schema) + protected function _initializeSchema(TableSchema $schema) { return $schema; } From d89855599901b38345a3fb3f04a256194d7da20a Mon Sep 17 00:00:00 2001 From: dereuromark Date: Wed, 2 Nov 2016 02:01:46 +0100 Subject: [PATCH 0830/2059] Fix CS for class instantiations. --- Association/BelongsToMany.php | 2 +- Query.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index cd4d12b8..81b8e116 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -818,7 +818,7 @@ function () use ($sourceEntity, $targetEntities, $options) { return true; } - $storage = new SplObjectStorage; + $storage = new SplObjectStorage(); foreach ($targetEntities as $e) { $storage->attach($e); } diff --git a/Query.php b/Query.php index 58be767a..690c2841 100644 --- a/Query.php +++ b/Query.php @@ -231,7 +231,7 @@ public function eagerLoader(EagerLoader $instance = null) { if ($instance === null) { if ($this->_eagerLoader === null) { - $this->_eagerLoader = new EagerLoader; + $this->_eagerLoader = new EagerLoader(); } return $this->_eagerLoader; From 9d3402cd215717fb563d747fa45a69d59445c63c Mon Sep 17 00:00:00 2001 From: Mark Story Date: Wed, 2 Nov 2016 09:05:55 -0400 Subject: [PATCH 0831/2059] Fix additional translation records being created for the default locale. The default locale should be saved onto the host table and not into the translations tables. By returning early when both the default locale and bundled translations are present we can fix the regression introduced in #9640 Refs #9688 --- Behavior/TranslateBehavior.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 573ffa4d..59315d8e 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -283,7 +283,10 @@ public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $o $fields = array_keys($values); $noFields = empty($fields); - if ($noFields && $noBundled) { + // If there are no fields and no bundled translations, or both fields + // in the default locale and bundled translations we can + // skip the remaining logic as its not necessary. + if ($noFields && $noBundled || ($fields && $bundled)) { return; } From 672ab1552b43e5df41954ffae328ef3518c06545 Mon Sep 17 00:00:00 2001 From: ndm2 Date: Wed, 2 Nov 2016 18:17:18 +0100 Subject: [PATCH 0832/2059] Change variable names in the example method body too. refs #9685 --- Table.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index 913c4cf1..d7b54bc8 100644 --- a/Table.php +++ b/Table.php @@ -473,8 +473,8 @@ public function schema($schema = null) * * ``` * protected function _initializeSchema(\Cake\Database\Schema\Schema $schema) { - * $table->columnType('preferences', 'json'); - * return $table; + * $schema->columnType('preferences', 'json'); + * return $schema; * } * ``` * From f917eae5d5815f64c7e6bb8b210ad3ac3d406a59 Mon Sep 17 00:00:00 2001 From: mscherer Date: Wed, 2 Nov 2016 18:26:35 +0100 Subject: [PATCH 0833/2059] Add version to deprecations --- Association.php | 24 ++++++++++++------------ Association/BelongsTo.php | 2 +- Association/BelongsToMany.php | 6 +++--- Association/HasMany.php | 6 +++--- Association/HasOne.php | 2 +- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Association.php b/Association.php index 43011e0a..e6a23ae9 100644 --- a/Association.php +++ b/Association.php @@ -262,7 +262,7 @@ public function getName() /** * Sets the name for this association. * - * @deprecated Use setName()/getName() instead. + * @deprecated 3.4.0 Use setName()/getName() instead. * @param string|null $name Name to be assigned * @return string */ @@ -302,7 +302,7 @@ public function getCascadeCallbacks() * Sets whether or not cascaded deletes should also fire callbacks. If no * arguments are passed, the current configured value is returned * - * @deprecated Use setCascadeCallbacks()/getCascadeCallbacks() instead. + * @deprecated 3.4.0 Use setCascadeCallbacks()/getCascadeCallbacks() instead. * @param bool|null $cascadeCallbacks cascade callbacks switch value * @return bool */ @@ -352,7 +352,7 @@ public function getSource() * Sets the table instance for the source side of the association. If no arguments * are passed, the current configured table instance is returned * - * @deprecated Use setSource()/getSource() instead. + * @deprecated 3.4.0 Use setSource()/getSource() instead. * @param \Cake\ORM\Table|null $table the instance to be assigned as source side * @return \Cake\ORM\Table */ @@ -409,7 +409,7 @@ public function getTarget() * Sets the table instance for the target side of the association. If no arguments * are passed, the current configured table instance is returned * - * @deprecated Use setTable()/getTable() instead. + * @deprecated 3.4.0 Use setTable()/getTable() instead. * @param \Cake\ORM\Table|null $table the instance to be assigned as target side * @return \Cake\ORM\Table */ @@ -453,7 +453,7 @@ public function getConditions() * Sets a list of conditions to be always included when fetching records from * the target association. If no parameters are passed the current list is returned * - * @deprecated Use setConditions()/getConditions() instead. + * @deprecated 3.4.0 Use setConditions()/getConditions() instead. * @param array|null $conditions list of conditions to be used * @see \Cake\Database\Query::where() for examples on the format of the array * @return array @@ -504,7 +504,7 @@ public function getBindingKey() * * If no parameters are passed the current field is returned * - * @deprecated Use setBindingKey()/getBindingKey() instead. + * @deprecated 3.4.0 Use setBindingKey()/getBindingKey() instead. * @param string|null $key the table field to be used to link both tables together * @return string|array */ @@ -544,7 +544,7 @@ public function setForeignKey($key) * Sets the name of the field representing the foreign key to the target table. * If no parameters are passed the current field is returned * - * @deprecated Use setForeignKey()/getForeignKey() instead. + * @deprecated 3.4.0 Use setForeignKey()/getForeignKey() instead. * @param string|null $key the key to be used to link both tables together * @return string|array */ @@ -596,7 +596,7 @@ public function getDependent() * * If no parameters are passed the current setting is returned. * - * @deprecated Use setDependent()/getDependent() instead. + * @deprecated 3.4.0 Use setDependent()/getDependent() instead. * @param bool|null $dependent Set the dependent mode. Use null to read the current state. * @return bool */ @@ -649,7 +649,7 @@ public function getJoinType() * Sets the type of join to be used when adding the association to a query. * If no arguments are passed, the currently configured type is returned. * - * @deprecated Use setJoinType()/getJoinType() instead. + * @deprecated 3.4.0 Use setJoinType()/getJoinType() instead. * @param string|null $type the join type to be used (e.g. INNER) * @return string */ @@ -704,7 +704,7 @@ public function getProperty() * in the source table record. * If no arguments are passed, the currently configured type is returned. * - * @deprecated Use setProperty()/getProperty() instead. + * @deprecated 3.4.0 Use setProperty()/getProperty() instead. * @param string|null $name The name of the association property. Use null to read the current value. * @return string */ @@ -768,7 +768,7 @@ public function getStrategy() * rendering any changes to this setting void. * If no arguments are passed, the currently configured strategy is returned. * - * @deprecated Use setStrategy()/getStrategy() instead. + * @deprecated 3.4.0 Use setStrategy()/getStrategy() instead. * @param string|null $name The strategy type. Use null to read the current value. * @return string * @throws \InvalidArgumentException When an invalid strategy is provided. @@ -810,7 +810,7 @@ public function setFinder($finder) * If no parameters are passed, it will return the currently configured * finder name. * - * @deprecated Use setFinder()/getFinder() instead. + * @deprecated 3.4.0 Use setFinder()/getFinder() instead. * @param string|null $finder the finder name to use * @return string */ diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 4b0cca77..9409ce85 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -59,7 +59,7 @@ public function getForeignKey() * Sets the name of the field representing the foreign key to the target table. * If no parameters are passed current field is returned * - * @deprecated Use setForeignKey()/getForeignKey() instead. + * @deprecated 3.4.0 Use setForeignKey()/getForeignKey() instead. * @param string|null $key the key to be used to link both tables together * @return string */ diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 9de17828..8e6c8c9f 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -187,7 +187,7 @@ public function getTargetForeignKey() * Sets the name of the field representing the foreign key to the target table. * If no parameters are passed current field is returned * - * @deprecated Use setTargetForeignKey()/getTargetForeignKey() instead. + * @deprecated 3.4.0 Use setTargetForeignKey()/getTargetForeignKey() instead. * @param string|null $key the key to be used to link both tables together * @return string */ @@ -647,7 +647,7 @@ public function getSaveStrategy() * Sets the strategy that should be used for saving. If called with no * arguments, it will return the currently configured strategy * - * @deprecated Use setSaveStrategy()/getSaveStrategy() instead. + * @deprecated 3.4.0 Use setSaveStrategy()/getSaveStrategy() instead. * @param string|null $strategy the strategy name to be used * @throws \InvalidArgumentException if an invalid strategy name is passed * @return string the strategy to be used for saving @@ -970,7 +970,7 @@ public function setConditions($conditions) /** * {@inheritDoc} - * @deprecated Use setConditions()/getConditions() instead. + * @deprecated 3.4.0 Use setConditions()/getConditions() instead. */ public function conditions($conditions = null) { diff --git a/Association/HasMany.php b/Association/HasMany.php index 67da21bb..b93c7479 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -134,7 +134,7 @@ public function getSaveStrategy() * Sets the strategy that should be used for saving. If called with no * arguments, it will return the currently configured strategy * - * @deprecated Use setSaveStrategy()/getSaveStrategy() instead. + * @deprecated 3.4.0 Use setSaveStrategy()/getSaveStrategy() instead. * @param string|null $strategy the strategy name to be used * @throws \InvalidArgumentException if an invalid strategy name is passed * @return string the strategy to be used for saving @@ -565,7 +565,7 @@ public function getForeignKey() * Sets the name of the field representing the foreign key to the source table. * If no parameters are passed current field is returned * - * @deprecated Use setForeignKey()/getForeignKey() instead. + * @deprecated 3.4.0 Use setForeignKey()/getForeignKey() instead. * @param string|null $key the key to be used to link both tables together * @return string */ @@ -605,7 +605,7 @@ public function getSort() * Sets the sort order in which target records should be returned. * If no arguments are passed the currently configured value is returned * - * @deprecated Use setSort()/getSort() instead. + * @deprecated 3.4.0 Use setSort()/getSort() instead. * @param mixed $sort A find() compatible order clause * @return mixed */ diff --git a/Association/HasOne.php b/Association/HasOne.php index f61ace63..4ee4fddd 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -59,7 +59,7 @@ public function getForeignKey() * Sets the name of the field representing the foreign key to the target table. * If no parameters are passed current field is returned * - * @deprecated Use setForeignKey()/getForeignKey() instead. + * @deprecated 3.4.0 Use setForeignKey()/getForeignKey() instead. * @param string|null $key the key to be used to link both tables together * @return string */ From 7017f86562b67841794bf5540be405d97abc97f5 Mon Sep 17 00:00:00 2001 From: mscherer Date: Wed, 2 Nov 2016 18:28:52 +0100 Subject: [PATCH 0834/2059] Fix up example in doc block. --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index d7b54bc8..f91a2037 100644 --- a/Table.php +++ b/Table.php @@ -472,7 +472,7 @@ public function schema($schema = null) * ### Example: * * ``` - * protected function _initializeSchema(\Cake\Database\Schema\Schema $schema) { + * protected function _initializeSchema(\Cake\Database\Schema\TableSchema $schema) { * $schema->columnType('preferences', 'json'); * return $schema; * } From 77587d8440809ec5d25a2e065b229fd51a5c8421 Mon Sep 17 00:00:00 2001 From: thinkingmedia Date: Sat, 22 Oct 2016 11:23:10 -0400 Subject: [PATCH 0835/2059] replaces fieldList with fields --- Marshaller.php | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 48241607..b02ccc76 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -20,7 +20,6 @@ use Cake\Database\Type; use Cake\Datasource\EntityInterface; use Cake\Datasource\InvalidPropertyInterface; -use Cake\ORM\PropertyMarshalInterface; use RuntimeException; /** @@ -139,7 +138,8 @@ protected function _buildPropertyMap($data, $options) * - validate: Set to false to disable validation. Can also be a string of the validator ruleset to be applied. * Defaults to true/default. * - associated: Associations listed here will be marshalled as well. Defaults to null. - * - fieldList: A whitelist of fields to be assigned to the entity. If not present, + * - fieldList: (deprecated) use fields instead. + * - fields: A whitelist of fields to be assigned to the entity. If not present, * the accessible fields list in the entity will be used. Defaults to null. * - accessibleFields: A list of fields to allow or deny in entity accessible fields. Defaults to null * - forceNew: When enabled, belongsToMany associations will have 'new' entities created @@ -167,6 +167,7 @@ public function one(array $data, array $options = []) $primaryKey = (array)$this->_table->primaryKey(); $entityClass = $this->_table->entityClass(); + /* @var Entity $entity */ $entity = new $entityClass(); $entity->source($this->_table->registryAlias()); @@ -198,17 +199,18 @@ public function one(array $data, array $options = []) } } - if (!isset($options['fieldList'])) { - $entity->set($properties); - $entity->errors($errors); - - return $entity; + if (isset($options['fieldList'])) { + $options['fields'] = $options['fieldList']; } - foreach ((array)$options['fieldList'] as $field) { - if (array_key_exists($field, $properties)) { - $entity->set($field, $properties[$field]); + if (isset($options['fields'])) { + foreach ((array)$options['fields'] as $field) { + if (array_key_exists($field, $properties)) { + $entity->set($field, $properties[$field]); + } } + } else { + $entity->set($properties); } $entity->errors($errors); @@ -314,7 +316,8 @@ protected function _marshalAssociation($assoc, $value, $options) * - validate: Set to false to disable validation. Can also be a string of the validator ruleset to be applied. * Defaults to true/default. * - associated: Associations listed here will be marshalled as well. Defaults to null. - * - fieldList: A whitelist of fields to be assigned to the entity. If not present, + * - fieldList: (deprecated) use fields instead + * - fields: A whitelist of fields to be assigned to the entity. If not present, * the accessible fields list in the entity will be used. Defaults to null. * - accessibleFields: A list of fields to allow or deny in entity accessible fields. Defaults to null * - forceNew: When enabled, belongsToMany associations will have 'new' entities created @@ -493,7 +496,8 @@ protected function _loadBelongsToMany($assoc, $ids) * - associated: Associations listed here will be marshalled as well. * - validate: Whether or not to validate data before hydrating the entities. Can * also be set to a string to use a specific validator. Defaults to true/default. - * - fieldList: A whitelist of fields to be assigned to the entity. If not present + * - fieldList: (deprecated) use fields instead + * - fields: A whitelist of fields to be assigned to the entity. If not present * the accessible fields list in the entity will be used. * - accessibleFields: A list of fields to allow or deny in entity accessible fields. * @@ -561,8 +565,12 @@ public function merge(EntityInterface $entity, array $data, array $options = []) $properties[$key] = $value; } + if (isset($options['fieldList'])) { + $options['fields'] = $options['fieldList']; + } + $entity->errors($errors); - if (!isset($options['fieldList'])) { + if (!isset($options['fields'])) { $entity->set($properties); foreach ($properties as $field => $value) { @@ -574,7 +582,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) return $entity; } - foreach ((array)$options['fieldList'] as $field) { + foreach ((array)$options['fields'] as $field) { if (array_key_exists($field, $properties)) { $entity->set($field, $properties[$field]); if ($properties[$field] instanceof EntityInterface) { @@ -606,7 +614,8 @@ public function merge(EntityInterface $entity, array $data, array $options = []) * - validate: Whether or not to validate data before hydrating the entities. Can * also be set to a string to use a specific validator. Defaults to true/default. * - associated: Associations listed here will be marshalled as well. - * - fieldList: A whitelist of fields to be assigned to the entity. If not present, + * - fieldList: (deprecated) use fields instead + * - fields: A whitelist of fields to be assigned to the entity. If not present, * the accessible fields list in the entity will be used. * - accessibleFields: A list of fields to allow or deny in entity accessible fields. * From 1cb448d5e3d2d9881bfa31decda4b831a30a540f Mon Sep 17 00:00:00 2001 From: thinkingmedia Date: Thu, 3 Nov 2016 10:40:45 -0400 Subject: [PATCH 0836/2059] fixes changes requested via comments --- Marshaller.php | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index b02ccc76..257bb3ec 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -138,7 +138,7 @@ protected function _buildPropertyMap($data, $options) * - validate: Set to false to disable validation. Can also be a string of the validator ruleset to be applied. * Defaults to true/default. * - associated: Associations listed here will be marshalled as well. Defaults to null. - * - fieldList: (deprecated) use fields instead. + * - fieldList: (deprecated) Since 3.4.0. Use fields instead. * - fields: A whitelist of fields to be assigned to the entity. If not present, * the accessible fields list in the entity will be used. Defaults to null. * - accessibleFields: A list of fields to allow or deny in entity accessible fields. Defaults to null @@ -160,6 +160,7 @@ protected function _buildPropertyMap($data, $options) * @param array $options List of options * @return \Cake\ORM\Entity * @see \Cake\ORM\Table::newEntity() + * @see \Cake\ORM\Entity::$_accessible */ public function one(array $data, array $options = []) { @@ -199,10 +200,6 @@ public function one(array $data, array $options = []) } } - if (isset($options['fieldList'])) { - $options['fields'] = $options['fieldList']; - } - if (isset($options['fields'])) { foreach ((array)$options['fields'] as $field) { if (array_key_exists($field, $properties)) { @@ -258,6 +255,11 @@ protected function _prepareDataAndOptions($data, $options) { $options += ['validate' => true]; + if (!isset($options['fields']) && isset($options['fieldList'])) { + $options['fields'] = $options['fieldList']; + unset($options['fieldList']); + } + $tableName = $this->_table->alias(); if (isset($data[$tableName])) { $data += $data[$tableName]; @@ -316,7 +318,7 @@ protected function _marshalAssociation($assoc, $value, $options) * - validate: Set to false to disable validation. Can also be a string of the validator ruleset to be applied. * Defaults to true/default. * - associated: Associations listed here will be marshalled as well. Defaults to null. - * - fieldList: (deprecated) use fields instead + * - fieldList: (deprecated) Since 3.4.0. Use fields instead * - fields: A whitelist of fields to be assigned to the entity. If not present, * the accessible fields list in the entity will be used. Defaults to null. * - accessibleFields: A list of fields to allow or deny in entity accessible fields. Defaults to null @@ -328,6 +330,7 @@ protected function _marshalAssociation($assoc, $value, $options) * @param array $options List of options * @return array An array of hydrated records. * @see \Cake\ORM\Table::newEntities() + * @see \Cake\ORM\Entity::$_accessible */ public function many(array $data, array $options = []) { @@ -496,7 +499,7 @@ protected function _loadBelongsToMany($assoc, $ids) * - associated: Associations listed here will be marshalled as well. * - validate: Whether or not to validate data before hydrating the entities. Can * also be set to a string to use a specific validator. Defaults to true/default. - * - fieldList: (deprecated) use fields instead + * - fieldList: (deprecated) Since 3.4.0. Use fields instead * - fields: A whitelist of fields to be assigned to the entity. If not present * the accessible fields list in the entity will be used. * - accessibleFields: A list of fields to allow or deny in entity accessible fields. @@ -516,6 +519,7 @@ protected function _loadBelongsToMany($assoc, $ids) * @param array $data key value list of fields to be merged into the entity * @param array $options List of options. * @return \Cake\Datasource\EntityInterface + * @see \Cake\ORM\Entity::$_accessible */ public function merge(EntityInterface $entity, array $data, array $options = []) { @@ -565,10 +569,6 @@ public function merge(EntityInterface $entity, array $data, array $options = []) $properties[$key] = $value; } - if (isset($options['fieldList'])) { - $options['fields'] = $options['fieldList']; - } - $entity->errors($errors); if (!isset($options['fields'])) { $entity->set($properties); @@ -614,7 +614,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) * - validate: Whether or not to validate data before hydrating the entities. Can * also be set to a string to use a specific validator. Defaults to true/default. * - associated: Associations listed here will be marshalled as well. - * - fieldList: (deprecated) use fields instead + * - fieldList: (deprecated) Since 3.4.0. Use fields instead * - fields: A whitelist of fields to be assigned to the entity. If not present, * the accessible fields list in the entity will be used. * - accessibleFields: A list of fields to allow or deny in entity accessible fields. @@ -624,6 +624,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) * @param array $data list of arrays to be merged into the entities * @param array $options List of options. * @return array + * @see \Cake\ORM\Entity::$_accessible */ public function mergeMany($entities, array $data, array $options = []) { From 4abc201d21dc9e3113da95271ae2bdce59c25b54 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 3 Nov 2016 22:25:27 -0400 Subject: [PATCH 0837/2059] Fix options being passed down from validateUnique Table::validateUnique() should pass options down into the IsUnique. Refs #9656 --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 877be55b..896368ca 100644 --- a/Table.php +++ b/Table.php @@ -2327,7 +2327,7 @@ public function validateUnique($value, array $options, array $context = null) return false; } } - $rule = new IsUnique($fields); + $rule = new IsUnique($fields, $options); return $rule($entity, ['repository' => $this]); } From ed2786f6c2ae053e6acf2e753f7944c4aa1ae9f9 Mon Sep 17 00:00:00 2001 From: Hugh Downer Date: Fri, 4 Nov 2016 11:28:28 +0000 Subject: [PATCH 0838/2059] Add closing ' mark This stops the code colouration going weird on http://api.cakephp.org/3.3/class-Cake.ORM.Table.html#_patchEntity --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 877be55b..388f26f9 100644 --- a/Table.php +++ b/Table.php @@ -2206,7 +2206,7 @@ public function newEntities(array $data, array $options = []) * * ``` * $article = $this->Articles->patchEntity($article, $this->request->data(), [ - * 'fieldList' => ['title', 'body', 'tags', 'comments], + * 'fieldList' => ['title', 'body', 'tags', 'comments'], * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']] * ] * ); From 82531bb75b8abce1d581279fbc41d5d9b9a673dc Mon Sep 17 00:00:00 2001 From: David Yell Date: Wed, 9 Nov 2016 17:13:14 +0000 Subject: [PATCH 0839/2059] Improve descriptiveness of exception message I found this exception a little obscure, especially as the things which it needs are detailed in the function. It could tell me what's actually missing. --- Table.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 43a7bc41..22c9ffe4 100644 --- a/Table.php +++ b/Table.php @@ -1728,7 +1728,8 @@ protected function _update($entity, $data) } if (!$entity->has($primaryColumns)) { - $message = 'All primary key value(s) are needed for updating'; + $message = 'All primary key value(s) are needed for updating, '; + $message .= get_class($entity) . ' is missing ' . implode(', ', $primaryColumns); throw new InvalidArgumentException($message); } From a18688494ebdccfd55149fc79dd82717cb5d7ed8 Mon Sep 17 00:00:00 2001 From: dereuromark Date: Thu, 3 Nov 2016 21:12:18 +0100 Subject: [PATCH 0840/2059] Split combined into separate getter setter for cleaner API. --- EagerLoadable.php | 64 +++++++- EagerLoader.php | 98 ++++++++++-- Locator/TableLocator.php | 69 ++++++-- Query.php | 40 ++++- Table.php | 338 +++++++++++++++++++++++++++++++-------- 5 files changed, 498 insertions(+), 111 deletions(-) diff --git a/EagerLoadable.php b/EagerLoadable.php index c009d7ef..b084f045 100644 --- a/EagerLoadable.php +++ b/EagerLoadable.php @@ -203,20 +203,70 @@ public function propertyPath() return $this->_propertyPath; } + /** + * Sets whether or not this level can be fetched using a join. + * + * @param bool $possible The value to set. + * @return $this + */ + public function setCanBeJoined($possible) + { + $this->_canBeJoined = $possible; + + return $this; + } + + /** + * Gets whether or not this level can be fetched using a join. + * + * @return bool + */ + public function getCanBeJoined() + { + return $this->_canBeJoined; + } + /** * Sets whether or not this level can be fetched using a join. * * If called with no arguments it returns the current value. * + * @deprecated 3.4.0 Use setCanBeJoined()/getCanBeJoined() instead. * @param bool|null $possible The value to set. * @return bool */ public function canBeJoined($possible = null) { - if ($possible === null) { - return $this->_canBeJoined; + if ($possible !== null) { + $this->setCanBeJoined($possible); } - $this->_canBeJoined = $possible; + + return $this->getCanBeJoined(); + } + + /** + * Sets the list of options to pass to the association object for loading + * the records. + * + * @param array $config The value to set. + * @return $this + */ + public function setConfig(array $config) + { + $this->_config = $config; + + return $this; + } + + /** + * Gets the list of options to pass to the association object for loading + * the records. + * + * @return array + */ + public function getConfig() + { + return $this->_config; } /** @@ -226,15 +276,17 @@ public function canBeJoined($possible = null) * If called with no arguments it returns the current * value. * + * @deprecated 3.4.0 Use setConfig()/getConfig() instead. * @param array|null $config The value to set. * @return array */ public function config(array $config = null) { - if ($config === null) { - return $this->_config; + if ($config !== null) { + $this->setConfig($config); } - $this->_config = $config; + + return $this->getConfig(); } /** diff --git a/EagerLoader.php b/EagerLoader.php index 46b19243..c4303ad7 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -160,18 +160,42 @@ public function clearContain() } /** - * Set whether or not contained associations will load fields automatically. + * Sets whether or not contained associations will load fields automatically. * - * @param bool|null $value The value to set. + * @param bool $enable The value to set. + * @return $this + */ + public function enableAutoFields($enable) + { + $this->_autoFields = (bool)$enable; + + return $this; + } + + /** + * Gets whether or not contained associations will load fields automatically. + * + * @return bool The current value. + */ + public function isEnabledAutoFields() + { + return $this->_autoFields; + } + + /** + * Sets/Gets whether or not contained associations will load fields automatically. + * + * @deprecated 3.4.0 Use enableAutoFields()/isEnabledAutoFields() instead. + * @param bool|null $enable The value to set. * @return bool The current value. */ - public function autoFields($value = null) + public function autoFields($enable = null) { - if ($value !== null) { - $this->_autoFields = (bool)$value; + if ($enable !== null) { + $this->enableAutoFields($enable); } - return $this->_autoFields; + return $this->isEnabledAutoFields(); } /** @@ -181,25 +205,22 @@ public function autoFields($value = null) * parameter, this will translate in setting all those associations with the * `matching` option. * - * If called with no arguments it will return the current tree of associations to - * be matched. + * ### Options + * - 'joinType': INNER, OUTER, ... + * - 'fields': Fields to contain * - * @param string|null $assoc A single association or a dot separated path of associations. + * @param string $assoc A single association or a dot separated path of associations. * @param callable|null $builder the callback function to be used for setting extra * options to the filtering query - * @param array $options Extra options for the association matching, such as 'joinType' - * and 'fields' - * @return array The resulting containments array + * @param array $options Extra options for the association matching. + * @return $this */ - public function matching($assoc = null, callable $builder = null, $options = []) + public function setMatching($assoc, callable $builder = null, $options = []) { if ($this->_matching === null) { $this->_matching = new self(); } - if ($assoc === null) { - return $this->_matching->contain(); - } if (!isset($options['joinType'])) { $options['joinType'] = 'INNER'; } @@ -218,7 +239,50 @@ public function matching($assoc = null, callable $builder = null, $options = []) $pointer[$last] = ['queryBuilder' => $builder, 'matching' => true] + $options; - return $this->_matching->contain($containments); + $this->_matching->contain($containments); + + return $this; + } + + /** + * Returns the current tree of associations to be matched. + * + * @return array The resulting containments array + */ + public function getMatching() + { + if ($this->_matching === null) { + $this->_matching = new self(); + } + + return $this->_matching->contain(); + } + + /** + * Adds a new association to the list that will be used to filter the results of + * any given query based on the results of finding records for that association. + * You can pass a dot separated path of associations to this method as its first + * parameter, this will translate in setting all those associations with the + * `matching` option. + * + * If called with no arguments it will return the current tree of associations to + * be matched. + * + * @deprecated 3.4.0 Use setMatching()/getMatching() instead. + * @param string|null $assoc A single association or a dot separated path of associations. + * @param callable|null $builder the callback function to be used for setting extra + * options to the filtering query + * @param array $options Extra options for the association matching, such as 'joinType' + * and 'fields' + * @return array The resulting containments array + */ + public function matching($assoc = null, callable $builder = null, $options = []) + { + if ($assoc !== null) { + $this->setMatching($assoc, $builder, $options); + } + + return $this->getMatching(); } /** diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 6de93902..7e4c686e 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -55,6 +55,50 @@ class TableLocator implements LocatorInterface */ protected $_options = []; + /** + * Stores a list of options to be used when instantiating an object + * with a matching alias. + * + * @param string|array $alias Name of the alias or array to completely overwrite current config. + * @param array|null $options list of options for the alias + * @return $this + * @throws \RuntimeException When you attempt to configure an existing table instance. + */ + public function setConfig($alias, $options = null) + { + if (!is_string($alias)) { + $this->_config = $alias; + + return $this; + } + + if (isset($this->_instances[$alias])) { + throw new RuntimeException(sprintf( + 'You cannot configure "%s", it has already been constructed.', + $alias + )); + } + + $this->_config[$alias] = $options; + + return $this; + } + + /** + * Returns configuration for an alias or the full configuration array for all aliases. + * + * @param string|null $alias Alias to get config for, null for complete config. + * @return array The config data. + */ + public function getConfig($alias = null) + { + if ($alias === null) { + return $this->_config; + } + + return isset($this->_config[$alias]) ? $this->_config[$alias] : []; + } + /** * Stores a list of options to be used when instantiating an object * with a matching alias. @@ -66,30 +110,23 @@ class TableLocator implements LocatorInterface * If no arguments are passed it will return the full configuration array for * all aliases * - * @param string|null $alias Name of the alias + * @deprecated 3.4.0 Use setConfig()/getConfig() instead. + * @param string|array|null $alias Name of the alias * @param array|null $options list of options for the alias * @return array The config data. * @throws \RuntimeException When you attempt to configure an existing table instance. */ public function config($alias = null, $options = null) { - if ($alias === null) { - return $this->_config; - } - if (!is_string($alias)) { - return $this->_config = $alias; - } - if ($options === null) { - return isset($this->_config[$alias]) ? $this->_config[$alias] : []; - } - if (isset($this->_instances[$alias])) { - throw new RuntimeException(sprintf( - 'You cannot configure "%s", it has already been constructed.', - $alias - )); + if ($alias !== null) { + if (is_string($alias) && $options === null) { + return $this->getConfig($alias); + } + + $this->setConfig($alias, $options); } - return $this->_config[$alias] = $options; + return $this->getConfig($alias); } /** diff --git a/Query.php b/Query.php index baa2b2f3..fb4f9ae3 100644 --- a/Query.php +++ b/Query.php @@ -218,27 +218,51 @@ public function addDefaultTypes(Table $table) return $this; } + /** + * Sets the instance of the eager loader class to use for loading associations + * and storing containments. + * + * @param \Cake\ORM\EagerLoader $instance The eager loader to use. + * @return $this + */ + public function setEagerLoader(EagerLoader $instance) + { + $this->_eagerLoader = $instance; + + return $this; + } + + /** + * Returns the currently configured instance. + * + * @return \Cake\ORM\EagerLoader + */ + public function getEagerLoader() + { + if ($this->_eagerLoader === null) { + $this->_eagerLoader = new EagerLoader(); + } + + return $this->_eagerLoader; + } + /** * Sets the instance of the eager loader class to use for loading associations * and storing containments. If called with no arguments, it will return the * currently configured instance. * + * @deprecated 3.4.0 Use setEagerLoader()/getEagerLoader() instead. * @param \Cake\ORM\EagerLoader|null $instance The eager loader to use. Pass null * to get the current eagerloader. * @return \Cake\ORM\EagerLoader|$this */ public function eagerLoader(EagerLoader $instance = null) { - if ($instance === null) { - if ($this->_eagerLoader === null) { - $this->_eagerLoader = new EagerLoader(); - } - - return $this->_eagerLoader; + if ($instance !== null) { + return $this->setEagerLoader($instance); } - $this->_eagerLoader = $instance; - return $this; + return $this->getEagerLoader(); } /** diff --git a/Table.php b/Table.php index d03fa2f5..54d9a39a 100644 --- a/Table.php +++ b/Table.php @@ -329,20 +329,29 @@ public function initialize(array $config) } /** - * Returns the database table name or sets a new one + * Sets the database table name. + * + * @param string $table Table name. + * @return $this + */ + public function setTable($table) + { + $this->_table = $table; + + return $this; + } + + /** + * Returns the database table name. * - * @param string|null $table the new table name * @return string */ - public function table($table = null) + public function getTable() { - if ($table !== null) { - $this->_table = $table; - } if ($this->_table === null) { $table = namespaceSplit(get_class($this)); $table = substr(end($table), 0, -5); - if (empty($table)) { + if (!$table) { $table = $this->alias(); } $this->_table = Inflector::underscore($table); @@ -352,13 +361,41 @@ public function table($table = null) } /** - * {@inheritDoc} + * Returns the database table name or sets a new one. + * + * @deprecated 3.4.0 Use setTable()/getTable() instead. + * @param string|null $table the new table name + * @return string */ - public function alias($alias = null) + public function table($table = null) { - if ($alias !== null) { - $this->_alias = $alias; + if ($table !== null) { + $this->setTable($table); } + + return $this->getTable(); + } + + /** + * Sets the table alias. + * + * @param string $alias Table alias + * @return $this + */ + public function setAlias($alias) + { + $this->_alias = $alias; + + return $this; + } + + /** + * Returns the table alias. + * + * @return string + */ + public function getAlias() + { if ($this->_alias === null) { $alias = namespaceSplit(get_class($this)); $alias = substr(end($alias), 0, -5) ?: $this->_table; @@ -368,6 +405,20 @@ public function alias($alias = null) return $this->_alias; } + + /** + * {@inheritDoc} + * @deprecated 3.4.0 Use setAlias()/getAlias() instead. + */ + public function alias($alias = null) + { + if ($alias !== null) { + $this->setAlias($alias); + } + + return $this->getAlias(); + } + /** * Alias a field with the table's current alias. * @@ -382,68 +433,121 @@ public function aliasField($field) return $field; } - return $this->alias() . '.' . $field; + return $this->getAlias() . '.' . $field; } /** - * Returns the table registry key used to create this table instance + * Sets the table registry key used to create this table instance. * + * @param string $registryAlias The key used to access this object. + * @return $this + */ + public function setRegistryAlias($registryAlias) + { + $this->_registryAlias = $registryAlias; + + return $this; + } + + + /** + * Returns the table registry key used to create this table instance. + * + * @return string + */ + public function getRegistryAlias() + { + if ($this->_registryAlias === null) { + $this->_registryAlias = $this->getAlias(); + } + + return $this->_registryAlias; + } + + /** + * Returns the table registry key used to create this table instance or sets one. + * + * @deprecated 3.4.0 Use setRegistryAlias()/getRegistryAlias() instead. * @param string|null $registryAlias the key used to access this object * @return string */ public function registryAlias($registryAlias = null) { if ($registryAlias !== null) { - $this->_registryAlias = $registryAlias; - } - if ($this->_registryAlias === null) { - $this->_registryAlias = $this->alias(); + $this->setRegistryAlias($registryAlias); } - return $this->_registryAlias; + return $this->getRegistryAlias(); + } + + /** + * Sets the connection instance. + * + * @param \Cake\Datasource\ConnectionInterface $connection The connection instance + * @return $this + */ + public function setConnection(ConnectionInterface $connection) + { + $this->_connection = $connection; + + return $this; + } + + /** + * Returns the connection instance. + * + * @return \Cake\Datasource\ConnectionInterface + */ + public function getConnection() + { + return $this->_connection; } /** * Returns the connection instance or sets a new one * - * @param \Cake\Datasource\ConnectionInterface|null $conn The new connection instance + * @deprecated 3.4.0 Use setConnection()/getConnection() instead. + * @param \Cake\Datasource\ConnectionInterface|null $connection The new connection instance * @return \Cake\Datasource\ConnectionInterface */ - public function connection(ConnectionInterface $conn = null) + public function connection(ConnectionInterface $connection = null) { - if ($conn === null) { - return $this->_connection; + if ($connection !== null) { + $this->setConnection($connection); } - return $this->_connection = $conn; + return $this->getConnection(); } /** * Returns the schema table object describing this table's properties. * - * If a TableSchema is passed, it will be used for this table - * instead of the default one. + * @return \Cake\Database\Schema\TableSchema + */ + public function getSchema() + { + if ($this->_schema === null) { + $this->_schema = $this->_initializeSchema( + $this->connection() + ->getSchemaCollection() + ->describe($this->getTable()) + ); + } + + return $this->_schema; + } + + /** + * Sets the schema table object describing this table's properties. * * If an array is passed, a new TableSchema will be constructed * out of it and used as the schema for this table. * - * @param array|\Cake\Database\Schema\TableSchema|null $schema New schema to be used for this table - * @return \Cake\Database\Schema\TableSchema + * @param array|\Cake\Database\Schema\TableSchema $schema Schema to be used for this table + * @return $this */ - public function schema($schema = null) + public function setSchema($schema) { - if ($schema === null) { - if ($this->_schema === null) { - $this->_schema = $this->_initializeSchema( - $this->connection() - ->schemaCollection() - ->describe($this->table()) - ); - } - - return $this->_schema; - } - if (is_array($schema)) { $constraints = []; @@ -459,7 +563,31 @@ public function schema($schema = null) } } - return $this->_schema = $schema; + $this->_schema = $schema; + + return $this; + } + + /** + * Returns the schema table object describing this table's properties. + * + * If a TableSchema is passed, it will be used for this table + * instead of the default one. + * + * If an array is passed, a new TableSchema will be constructed + * out of it and used as the schema for this table. + * + * @deprecated 3.4.0 Use setSchema()/getSchema() instead. + * @param array|\Cake\Database\Schema\TableSchema|null $schema New schema to be used for this table + * @return \Cake\Database\Schema\TableSchema + */ + public function schema($schema = null) + { + if ($schema !== null) { + $this->setSchema($schema); + } + + return $this->getSchema(); } /** @@ -498,24 +626,33 @@ protected function _initializeSchema(TableSchema $schema) */ public function hasField($field) { - $schema = $this->schema(); + $schema = $this->getSchema(); return $schema->column($field) !== null; } /** - * Returns the primary key field name or sets a new one + * Sets the primary key field name. + * + * @param string|array $key Sets a new name to be used as primary key + * @return $this + */ + public function setPrimaryKey($key) + { + $this->_primaryKey = $key; + + return $this; + } + + /** + * Returns the primary key field name. * - * @param string|array|null $key sets a new name to be used as primary key * @return string|array */ - public function primaryKey($key = null) + public function getPrimaryKey() { - if ($key !== null) { - $this->_primaryKey = $key; - } if ($this->_primaryKey === null) { - $key = (array)$this->schema()->primaryKey(); + $key = (array)$this->getSchema()->primaryKey(); if (count($key) === 1) { $key = $key[0]; } @@ -526,16 +663,41 @@ public function primaryKey($key = null) } /** - * Returns the display field or sets a new one + * Returns the primary key field name or sets a new one * - * @param string|null $key sets a new name to be used as display field - * @return string + * @deprecated 3.4.0 Use setPrimaryKey()/getPrimaryKey() instead. + * @param string|array|null $key Sets a new name to be used as primary key + * @return string|array */ - public function displayField($key = null) + public function primaryKey($key = null) { if ($key !== null) { - $this->_displayField = $key; + $this->setPrimaryKey($key); } + + return $this->getPrimaryKey(); + } + + /** + * Sets the display field. + * + * @param string $key Name to be used as display field. + * @return $this + */ + public function setDisplayField($key) + { + $this->_displayField = $key; + + return $this; + } + + /** + * Returns the display field. + * + * @return string + */ + public function getDisplayField() + { if ($this->_displayField === null) { $schema = $this->schema(); $primary = (array)$this->primaryKey(); @@ -552,16 +714,29 @@ public function displayField($key = null) } /** - * Returns the class used to hydrate rows for this table or sets - * a new one + * Returns the display field or sets a new one * - * @param string|null $name the name of the class to use - * @throws \Cake\ORM\Exception\MissingEntityException when the entity class cannot be found + * @deprecated 3.4.0 Use setDisplayField()/getDisplayField() instead. + * @param string|null $key sets a new name to be used as display field * @return string */ - public function entityClass($name = null) + public function displayField($key = null) { - if ($name === null && !$this->_entityClass) { + if ($key !== null) { + return $this->setDisplayField($key); + } + + return $this->getDisplayField(); + } + + /** + * Returns the class used to hydrate rows for this table. + * + * @return string + */ + public function getEntityClass() + { + if (!$this->_entityClass) { $default = '\Cake\ORM\Entity'; $self = get_called_class(); $parts = explode('\\', $self); @@ -575,18 +750,53 @@ public function entityClass($name = null) if (!class_exists($name)) { return $this->_entityClass = $default; } - } - if ($name !== null) { $class = App::className($name, 'Model/Entity'); + if (!$class) { + throw new MissingEntityException([$name]); + } + $this->_entityClass = $class; } - if (!$this->_entityClass) { + return $this->_entityClass; + } + + /** + * Sets the class used to hydrate rows for this table. + * + * @param string $name The name of the class to use + * @throws \Cake\ORM\Exception\MissingEntityException when the entity class cannot be found + * @return $this + */ + public function setEntityClass($name) + { + $class = App::className($name, 'Model/Entity'); + if (!$class) { throw new MissingEntityException([$name]); } - return $this->_entityClass; + $this->_entityClass = $class; + + return $this; + } + + /** + * Returns the class used to hydrate rows for this table or sets + * a new one + * + * @deprecated 3.4.0 Use setEntityClass()/getEntityClass() instead. + * @param string|null $name The name of the class to use + * @throws \Cake\ORM\Exception\MissingEntityException when the entity class cannot be found + * @return string + */ + public function entityClass($name = null) + { + if ($name !== null) { + $this->setEntityClass($name); + } + + return $this->getEntityClass(); } /** From f67cb1a9b9713690b72d8082ce9a2b194f0ada35 Mon Sep 17 00:00:00 2001 From: dereuromark Date: Mon, 7 Nov 2016 19:29:03 +0100 Subject: [PATCH 0841/2059] Rename as per review. --- EagerLoader.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index c4303ad7..ac999c17 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -177,7 +177,7 @@ public function enableAutoFields($enable) * * @return bool The current value. */ - public function isEnabledAutoFields() + public function isAutoFieldsEnabled() { return $this->_autoFields; } @@ -185,7 +185,7 @@ public function isEnabledAutoFields() /** * Sets/Gets whether or not contained associations will load fields automatically. * - * @deprecated 3.4.0 Use enableAutoFields()/isEnabledAutoFields() instead. + * @deprecated 3.4.0 Use enableAutoFields()/isAutoFieldsEnabled() instead. * @param bool|null $enable The value to set. * @return bool The current value. */ @@ -195,7 +195,7 @@ public function autoFields($enable = null) $this->enableAutoFields($enable); } - return $this->isEnabledAutoFields(); + return $this->isAutoFieldsEnabled(); } /** From 763d2f9990085da77aa9bdb53c326290382fee05 Mon Sep 17 00:00:00 2001 From: mscherer Date: Mon, 14 Nov 2016 12:32:50 +0100 Subject: [PATCH 0842/2059] Adjustments as per review. --- EagerLoadable.php | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/EagerLoadable.php b/EagerLoadable.php index b084f045..ba0aa03d 100644 --- a/EagerLoadable.php +++ b/EagerLoadable.php @@ -216,22 +216,13 @@ public function setCanBeJoined($possible) return $this; } - /** - * Gets whether or not this level can be fetched using a join. - * - * @return bool - */ - public function getCanBeJoined() - { - return $this->_canBeJoined; - } - /** * Sets whether or not this level can be fetched using a join. * * If called with no arguments it returns the current value. * - * @deprecated 3.4.0 Use setCanBeJoined()/getCanBeJoined() instead. + * As of 3.4.0 the setter part is deprecated, use setCanBeJoined() instead. + * * @param bool|null $possible The value to set. * @return bool */ From ab1b6534ce424c270c0e766e143b79646ff3d78e Mon Sep 17 00:00:00 2001 From: mscherer Date: Mon, 14 Nov 2016 13:51:27 +0100 Subject: [PATCH 0843/2059] Fix tests. --- EagerLoadable.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/EagerLoadable.php b/EagerLoadable.php index ba0aa03d..6303b62e 100644 --- a/EagerLoadable.php +++ b/EagerLoadable.php @@ -211,16 +211,15 @@ public function propertyPath() */ public function setCanBeJoined($possible) { - $this->_canBeJoined = $possible; + $this->_canBeJoined = (bool)$possible; return $this; } /** - * Sets whether or not this level can be fetched using a join. - * - * If called with no arguments it returns the current value. + * Gets whether or not this level can be fetched using a join. * + * If called with arguments it sets the value. * As of 3.4.0 the setter part is deprecated, use setCanBeJoined() instead. * * @param bool|null $possible The value to set. @@ -232,7 +231,7 @@ public function canBeJoined($possible = null) $this->setCanBeJoined($possible); } - return $this->getCanBeJoined(); + return $this->_canBeJoined; } /** From 6bcf14a2516acda3a3c9c845b6dbccd6ff1f7766 Mon Sep 17 00:00:00 2001 From: thinkingmedia Date: Thu, 17 Nov 2016 22:32:16 -0500 Subject: [PATCH 0844/2059] added transaction helper --- Table.php | 72 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 40 insertions(+), 32 deletions(-) diff --git a/Table.php b/Table.php index 54d9a39a..6573459f 100644 --- a/Table.php +++ b/Table.php @@ -36,7 +36,6 @@ use Cake\ORM\Exception\RolledbackTransactionException; use Cake\ORM\Rule\IsUnique; use Cake\Utility\Inflector; -use Cake\Validation\Validation; use Cake\Validation\ValidatorAwareTrait; use InvalidArgumentException; use RuntimeException; @@ -1435,6 +1434,36 @@ public function get($primaryKey, $options = []) return $query->firstOrFail(); } + /** + * Handles the logic executing of a worker inside a transaction. + * + * @param bool $atomic Whether to execute the worker inside a database transaction. + * @param callable $worker The worker that will run inside the transaction. + * @return mixed + */ + protected function _executeTransaction($atomic, callable $worker) + { + if ($atomic) { + return $this->connection()->transactional(function () use ($worker) { + return call_user_func($worker); + }); + } + + return call_user_func($worker); + } + + /** + * Checks if the caller would have executed a commit on a transaction. + * + * @param bool $atomic True if an atomic transaction was used. + * @param bool $primary True if a primary was used. + * @return bool Returns true if a transaction was committed. + */ + protected function _getCommitUsed($atomic, $primary) + { + return !$this->connection()->inTransaction() && ($atomic || (!$atomic && $primary)); + } + /** * Finds an existing record or creates a new one. * @@ -1475,13 +1504,9 @@ public function findOrCreate($search, callable $callback = null, $options = []) 'defaults' => true ]; - if ($options['atomic']) { - return $this->connection()->transactional(function () use ($search, $callback, $options) { - return $this->_processFindOrCreate($search, $callback, $options); - }); - } - - return $this->_processFindOrCreate($search, $callback, $options); + return $this->_executeTransaction($options['atomic'], function () use ($search, $callback, $options) { + return $this->_processFindOrCreate($search, $callback, $options); + }); } /** @@ -1696,19 +1721,12 @@ public function save(EntityInterface $entity, $options = []) return $entity; } - $connection = $this->connection(); - if ($options['atomic']) { - $success = $connection->transactional(function () use ($entity, $options) { - return $this->_processSave($entity, $options); - }); - } else { - $success = $this->_processSave($entity, $options); - } + $success = $this->_executeTransaction($options['atomic'], function () use ($entity, $options) { + return $this->_processSave($entity, $options); + }); if ($success) { - if (!$connection->inTransaction() && - ($options['atomic'] || (!$options['atomic'] && $options['_primary'])) - ) { + if ($this->_getCommitUsed($options['atomic'], $options['_primary'])) { $this->dispatchEvent('Model.afterSaveCommit', compact('entity', 'options')); } if ($options['atomic'] || $options['_primary']) { @@ -2033,21 +2051,11 @@ public function delete(EntityInterface $entity, $options = []) '_primary' => true, ]); - $process = function () use ($entity, $options) { + $success = $this->_executeTransaction($options['atomic'], function () use ($entity, $options) { return $this->_processDelete($entity, $options); - }; - - $connection = $this->connection(); - if ($options['atomic']) { - $success = $connection->transactional($process); - } else { - $success = $process(); - } + }); - if ($success && - !$connection->inTransaction() && - ($options['atomic'] || (!$options['atomic'] && $options['_primary'])) - ) { + if ($success && $this->_getCommitUsed($options['atomic'], $options['_primary'])) { $this->dispatchEvent('Model.afterDeleteCommit', [ 'entity' => $entity, 'options' => $options From 7003f1bf82dddadfdfca4ba29667ee86a004f447 Mon Sep 17 00:00:00 2001 From: thinkingmedia Date: Sat, 19 Nov 2016 09:21:53 -0500 Subject: [PATCH 0845/2059] PR review changes --- Table.php | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Table.php b/Table.php index 6573459f..34301d12 100644 --- a/Table.php +++ b/Table.php @@ -1437,19 +1437,19 @@ public function get($primaryKey, $options = []) /** * Handles the logic executing of a worker inside a transaction. * - * @param bool $atomic Whether to execute the worker inside a database transaction. * @param callable $worker The worker that will run inside the transaction. + * @param bool $atomic Whether to execute the worker inside a database transaction. * @return mixed */ - protected function _executeTransaction($atomic, callable $worker) + protected function _executeTransaction(callable $worker, $atomic = true) { if ($atomic) { return $this->connection()->transactional(function () use ($worker) { - return call_user_func($worker); + return $worker(); }); } - return call_user_func($worker); + return $worker(); } /** @@ -1459,7 +1459,7 @@ protected function _executeTransaction($atomic, callable $worker) * @param bool $primary True if a primary was used. * @return bool Returns true if a transaction was committed. */ - protected function _getCommitUsed($atomic, $primary) + protected function _transactionCommitted($atomic, $primary) { return !$this->connection()->inTransaction() && ($atomic || (!$atomic && $primary)); } @@ -1504,9 +1504,9 @@ public function findOrCreate($search, callable $callback = null, $options = []) 'defaults' => true ]; - return $this->_executeTransaction($options['atomic'], function () use ($search, $callback, $options) { + return $this->_executeTransaction(function () use ($search, $callback, $options) { return $this->_processFindOrCreate($search, $callback, $options); - }); + }, $options['atomic']); } /** @@ -1721,12 +1721,12 @@ public function save(EntityInterface $entity, $options = []) return $entity; } - $success = $this->_executeTransaction($options['atomic'], function () use ($entity, $options) { + $success = $this->_executeTransaction(function () use ($entity, $options) { return $this->_processSave($entity, $options); - }); + }, $options['atomic']); if ($success) { - if ($this->_getCommitUsed($options['atomic'], $options['_primary'])) { + if ($this->_transactionCommitted($options['atomic'], $options['_primary'])) { $this->dispatchEvent('Model.afterSaveCommit', compact('entity', 'options')); } if ($options['atomic'] || $options['_primary']) { @@ -2051,11 +2051,11 @@ public function delete(EntityInterface $entity, $options = []) '_primary' => true, ]); - $success = $this->_executeTransaction($options['atomic'], function () use ($entity, $options) { + $success = $this->_executeTransaction(function () use ($entity, $options) { return $this->_processDelete($entity, $options); - }); + }, $options['atomic']); - if ($success && $this->_getCommitUsed($options['atomic'], $options['_primary'])) { + if ($success && $this->_transactionCommitted($options['atomic'], $options['_primary'])) { $this->dispatchEvent('Model.afterDeleteCommit', [ 'entity' => $entity, 'options' => $options From c4e23d4d92a6c165ccbc822e6117ade61b2516d7 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 3 Dec 2016 13:40:31 +0530 Subject: [PATCH 0846/2059] Alias order field in findTreeList(). --- Behavior/TreeBehavior.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index ec15d9fe..ad829ceb 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -483,10 +483,12 @@ function ($field) { */ public function findTreeList(Query $query, array $options) { + $left = $this->_table->aliasField($this->config('left')); + $results = $this->_scope($query) ->find('threaded', [ 'parentField' => $this->config('parent'), - 'order' => [$this->config('left') => 'ASC'], + 'order' => [$left => 'ASC'], ]); return $this->formatTreeList($results, $options); From 28712ef4b8156ff40303861a3dde3c8187277d4d Mon Sep 17 00:00:00 2001 From: Bryan Crowe Date: Sun, 11 Dec 2016 20:27:07 -0500 Subject: [PATCH 0847/2059] Remove unused use statements --- Marshaller.php | 1 - Query.php | 1 - Rule/ValidCount.php | 1 - 3 files changed, 3 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 48241607..b994a8d8 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -20,7 +20,6 @@ use Cake\Database\Type; use Cake\Datasource\EntityInterface; use Cake\Datasource\InvalidPropertyInterface; -use Cake\ORM\PropertyMarshalInterface; use RuntimeException; /** diff --git a/Query.php b/Query.php index 690c2841..8b2e2873 100644 --- a/Query.php +++ b/Query.php @@ -24,7 +24,6 @@ use Cake\Datasource\QueryTrait; use JsonSerializable; use RuntimeException; -use Traversable; /** * Extends the base Query class to provide new methods related to association diff --git a/Rule/ValidCount.php b/Rule/ValidCount.php index 4a47d7f5..d4f09620 100644 --- a/Rule/ValidCount.php +++ b/Rule/ValidCount.php @@ -15,7 +15,6 @@ namespace Cake\ORM\Rule; use Cake\Datasource\EntityInterface; -use Cake\ORM\Association; use Cake\Validation\Validation; use Countable; From aa96ff2181d8597be750e20229719bcc2ecc4356 Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Wed, 14 Dec 2016 00:36:56 -0500 Subject: [PATCH 0848/2059] Using variadic instead of call_user_func_array --- Association.php | 2 +- TableRegistry.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Association.php b/Association.php index e6a23ae9..809b3860 100644 --- a/Association.php +++ b/Association.php @@ -1309,7 +1309,7 @@ public function __isset($property) */ public function __call($method, $argument) { - return call_user_func_array([$this->target(), $method], $argument); + return $this->target()->$method(...$argument); } /** diff --git a/TableRegistry.php b/TableRegistry.php index 87916501..e5ecb4d5 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -163,6 +163,6 @@ public static function clear() */ public static function __callStatic($name, $arguments) { - return call_user_func_array([static::locator(), $name], $arguments); + return static::locator()->$name(...$arguments); } } From fa20366f2c5791888d913ebf1feee04afc036566 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 15 Dec 2016 01:21:15 +0530 Subject: [PATCH 0849/2059] Remove extra params in constructor call for ValidCount. --- RulesChecker.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/RulesChecker.php b/RulesChecker.php index 9780e689..c68be5f8 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -133,6 +133,10 @@ public function validCount($field, $count = 0, $operator = '>', $message = null) $errorField = $field; - return $this->_addError(new ValidCount($field, $count, $operator), '_validCount', compact('count', 'operator', 'errorField', 'message')); + return $this->_addError( + new ValidCount($field), + '_validCount', + compact('count', 'operator', 'errorField', 'message') + ); } } From 51f24ae6158651e1990a17bec29bf7eed487e5f6 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 15 Dec 2016 01:24:08 +0530 Subject: [PATCH 0850/2059] Fix docblocks --- Behavior/TimestampBehavior.php | 2 +- PropertyMarshalInterface.php | 2 +- SaveOptionsBuilder.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index de101b4d..3b6e5372 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -83,7 +83,7 @@ public function initialize(array $config) * @param \Cake\Event\Event $event Event instance. * @param \Cake\Datasource\EntityInterface $entity Entity instance. * @throws \UnexpectedValueException if a field's when value is misdefined - * @return true (irrespective of the behavior logic, the save will not be prevented) + * @return bool Returns true irrespective of the behavior logic, the save will not be prevented. * @throws \UnexpectedValueException When the value for an event is not 'always', 'new' or 'existing' */ public function handleEvent(Event $event, EntityInterface $entity) diff --git a/PropertyMarshalInterface.php b/PropertyMarshalInterface.php index a5567c71..4d8791ed 100644 --- a/PropertyMarshalInterface.php +++ b/PropertyMarshalInterface.php @@ -25,7 +25,7 @@ interface PropertyMarshalInterface /** * Build a set of properties that should be included in the marshalling process. * - * @param \Cake\ORM\Marhshaller $marshaller The marhshaller of the table the behavior is attached to. + * @param \Cake\ORM\Marshaller $marshaller The marhshaller of the table the behavior is attached to. * @param array $map The property map being built. * @param array $options The options array used in the marshalling call. * @return array A map of `[property => callable]` of additional properties to marshal. diff --git a/SaveOptionsBuilder.php b/SaveOptionsBuilder.php index f6a689ac..d118d596 100644 --- a/SaveOptionsBuilder.php +++ b/SaveOptionsBuilder.php @@ -40,7 +40,7 @@ class SaveOptionsBuilder extends ArrayObject /** * Table object. * - * @var \Cake\ORM\Table; + * @var \Cake\ORM\Table */ protected $_table; From 49644bfdc47ef93a8aee8e127c5e42a3b60ba7b8 Mon Sep 17 00:00:00 2001 From: dereuromark Date: Sun, 18 Dec 2016 15:05:11 +0100 Subject: [PATCH 0851/2059] Fixing a few code smells detected by phpstan. --- SaveOptionsBuilder.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SaveOptionsBuilder.php b/SaveOptionsBuilder.php index d118d596..e19056de 100644 --- a/SaveOptionsBuilder.php +++ b/SaveOptionsBuilder.php @@ -54,6 +54,8 @@ public function __construct(Table $table, array $options = []) { $this->_table = $table; $this->parseArrayOptions($options); + + parent::__construct(); } /** From c355da5eb0509ea5a6d9197a16d55f9cce35982f Mon Sep 17 00:00:00 2001 From: dereuromark Date: Tue, 20 Dec 2016 00:19:27 +0100 Subject: [PATCH 0852/2059] Fix more FQCN docblocks. --- Table.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index 34301d12..3204a3ed 100644 --- a/Table.php +++ b/Table.php @@ -1495,7 +1495,7 @@ protected function _transactionCommitted($atomic, $primary) * created entities. This callback will be called *before* the entity * is persisted. * @param array $options The options to use when saving. - * @return EntityInterface An entity. + * @return \Cake\Datasource\EntityInterface An entity. */ public function findOrCreate($search, callable $callback = null, $options = []) { @@ -1518,7 +1518,7 @@ public function findOrCreate($search, callable $callback = null, $options = []) * created entities. This callback will be called *before* the entity * is persisted. * @param array $options The options to use when saving. - * @return EntityInterface An entity. + * @return \Cake\Datasource\EntityInterface An entity. */ protected function _processFindOrCreate($search, callable $callback = null, $options = []) { From 32ad70548b40199323b490db66dbf4ec79384ec6 Mon Sep 17 00:00:00 2001 From: dereuromark Date: Tue, 20 Dec 2016 00:33:16 +0100 Subject: [PATCH 0853/2059] Enable further clickability in IDE. --- Behavior/TreeBehavior.php | 2 ++ ResultSet.php | 6 +++++- Table.php | 4 ++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index ad829ceb..619a6e92 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -719,6 +719,7 @@ protected function _moveDown($node, $number) ->select([$left, $right]) ->where(["$parent IS" => $nodeParent]) ->where(function ($exp) use ($config, $nodeRight) { + /* @var \Cake\Database\Expression\QueryExpression $exp */ return $exp->gt($config['leftField'], $nodeRight); }) ->orderAsc($config['leftField']) @@ -731,6 +732,7 @@ protected function _moveDown($node, $number) ->select([$left, $right]) ->where(["$parent IS" => $nodeParent]) ->where(function ($exp) use ($config, $nodeRight) { + /* @var \Cake\Database\Expression\QueryExpression $exp */ return $exp->gt($config['leftField'], $nodeRight); }) ->orderDesc($config['leftField']) diff --git a/ResultSet.php b/ResultSet.php index 1b034f31..311e76d6 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -497,7 +497,10 @@ protected function _groupResult($row) array_intersect_key($row, $keys) ); if ($this->_hydrate) { - $options['source'] = $matching['instance']->registryAlias(); + /** @var \Cake\ORM\Table $table */ + $table = $matching['instance']; + $options['source'] = $table->registryAlias(); + /** @var \Cake\Datasource\EntityInterface $entity */ $entity = new $matching['entityClass']($results['_matchingData'][$alias], $options); $entity->clean(); $results['_matchingData'][$alias] = $entity; @@ -518,6 +521,7 @@ protected function _groupResult($row) continue; } + /** @var \Cake\ORM\Association $instance */ $instance = $assoc['instance']; if (!$assoc['canBeJoined'] && !isset($row[$alias])) { diff --git a/Table.php b/Table.php index 3204a3ed..57e0c112 100644 --- a/Table.php +++ b/Table.php @@ -2669,8 +2669,8 @@ public function loadInto($entities, array $contain) public function __debugInfo() { $conn = $this->connection(); - $associations = $this->_associations ?: false; - $behaviors = $this->_behaviors ?: false; + $associations = $this->_associations; + $behaviors = $this->_behaviors; return [ 'registryAlias' => $this->registryAlias(), From 70aa6b344c203637bfc31c9654a32c011802294b Mon Sep 17 00:00:00 2001 From: dereuromark Date: Tue, 20 Dec 2016 00:35:47 +0100 Subject: [PATCH 0854/2059] Fix CS --- ResultSet.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ResultSet.php b/ResultSet.php index 311e76d6..48f90f18 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -497,10 +497,10 @@ protected function _groupResult($row) array_intersect_key($row, $keys) ); if ($this->_hydrate) { - /** @var \Cake\ORM\Table $table */ + /* @var \Cake\ORM\Table $table */ $table = $matching['instance']; $options['source'] = $table->registryAlias(); - /** @var \Cake\Datasource\EntityInterface $entity */ + /* @var \Cake\Datasource\EntityInterface $entity */ $entity = new $matching['entityClass']($results['_matchingData'][$alias], $options); $entity->clean(); $results['_matchingData'][$alias] = $entity; @@ -521,7 +521,7 @@ protected function _groupResult($row) continue; } - /** @var \Cake\ORM\Association $instance */ + /* @var \Cake\ORM\Association $instance */ $instance = $assoc['instance']; if (!$assoc['canBeJoined'] && !isset($row[$alias])) { From d6ec8b4579d3c347a74a05d65db717f001215972 Mon Sep 17 00:00:00 2001 From: dereuromark Date: Tue, 20 Dec 2016 00:44:31 +0100 Subject: [PATCH 0855/2059] Use self instead of $this for IDE compatibility. --- Association.php | 24 ++++++++++---------- Association/BelongsToMany.php | 6 ++--- Association/HasMany.php | 4 ++-- Behavior/Translate/TranslateTrait.php | 2 +- EagerLoadable.php | 4 ++-- EagerLoader.php | 4 ++-- Locator/TableLocator.php | 2 +- Query.php | 32 +++++++++++++-------------- Table.php | 16 +++++++------- 9 files changed, 47 insertions(+), 47 deletions(-) diff --git a/Association.php b/Association.php index 809b3860..ce4175fb 100644 --- a/Association.php +++ b/Association.php @@ -240,7 +240,7 @@ public function __construct($alias, array $options = []) * Sets the name for this association. * * @param string $name Name to be assigned - * @return $this + * @return self */ public function setName($name) { @@ -279,7 +279,7 @@ public function name($name = null) * Sets whether or not cascaded deletes should also fire callbacks. * * @param bool $cascadeCallbacks cascade callbacks switch value - * @return $this + * @return self */ public function setCascadeCallbacks($cascadeCallbacks) { @@ -329,7 +329,7 @@ public function className() * Sets the table instance for the source side of the association. * * @param \Cake\ORM\Table $table the instance to be assigned as source side - * @return $this + * @return self */ public function setSource(Table $table) { @@ -369,7 +369,7 @@ public function source(Table $table = null) * Sets the table instance for the target side of the association. * * @param \Cake\ORM\Table $table the instance to be assigned as target side - * @return $this + * @return self */ public function setTarget(Table $table) { @@ -428,7 +428,7 @@ public function target(Table $table = null) * * @param array $conditions list of conditions to be used * @see \Cake\Database\Query::where() for examples on the format of the array - * @return $this + * @return self */ public function setConditions($conditions) { @@ -472,7 +472,7 @@ public function conditions($conditions = null) * When not manually specified the primary key of the owning side table is used. * * @param string $key the table field to be used to link both tables together - * @return $this + * @return self */ public function setBindingKey($key) { @@ -531,7 +531,7 @@ public function getForeignKey() * Sets the name of the field representing the foreign key to the target table. * * @param string $key the key to be used to link both tables together - * @return $this + * @return self */ public function setForeignKey($key) { @@ -566,7 +566,7 @@ public function foreignKey($key = null) * If no parameters are passed the current setting is returned. * * @param bool $dependent Set the dependent mode. Use null to read the current state. - * @return $this + * @return self */ public function setDependent($dependent) { @@ -626,7 +626,7 @@ public function canBeJoined(array $options = []) * Sets the type of join to be used when adding the association to a query. * * @param string $type the join type to be used (e.g. INNER) - * @return $this + * @return self */ public function setJoinType($type) { @@ -667,7 +667,7 @@ public function joinType($type = null) * in the source table record. * * @param string $name The name of the association property. Use null to read the current value. - * @return $this + * @return self */ public function setProperty($name) { @@ -735,7 +735,7 @@ protected function _propertyName() * rendering any changes to this setting void. * * @param string $name The strategy type. Use null to read the current value. - * @return $this + * @return self * @throws \InvalidArgumentException When an invalid strategy is provided. */ public function setStrategy($name) @@ -796,7 +796,7 @@ public function getFinder() * Sets the default finder to use for fetching rows from the target table. * * @param string $finder the finder name to use - * @return $this + * @return self */ public function setFinder($finder) { diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 10c9b9f0..cf205179 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -160,7 +160,7 @@ class BelongsToMany extends Association * Sets the name of the field representing the foreign key to the target table. * * @param string $key the key to be used to link both tables together - * @return $this + * @return self */ public function setTargetForeignKey($key) { @@ -619,7 +619,7 @@ public function isOwningSide(Table $side) * * @param string $strategy the strategy name to be used * @throws \InvalidArgumentException if an invalid strategy name is passed - * @return $this + * @return self */ public function setSaveStrategy($strategy) { @@ -985,7 +985,7 @@ public function conditions($conditions = null) * Sets the current join table, either the name of the Table instance or the instance itself. * * @param string|\Cake\ORM\Table $through Name of the Table instance or the instance itself - * @return $this + * @return self */ public function setThrough($through) { diff --git a/Association/HasMany.php b/Association/HasMany.php index b93c7479..da92a0e3 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -106,7 +106,7 @@ public function isOwningSide(Table $side) * * @param string $strategy the strategy name to be used * @throws \InvalidArgumentException if an invalid strategy name is passed - * @return $this + * @return self */ public function setSaveStrategy($strategy) { @@ -582,7 +582,7 @@ public function foreignKey($key = null) * Sets the sort order in which target records should be returned. * * @param mixed $sort A find() compatible order clause - * @return $this + * @return self */ public function setSort($sort) { diff --git a/Behavior/Translate/TranslateTrait.php b/Behavior/Translate/TranslateTrait.php index b98738d4..ebbaadd1 100644 --- a/Behavior/Translate/TranslateTrait.php +++ b/Behavior/Translate/TranslateTrait.php @@ -30,7 +30,7 @@ trait TranslateTrait * it. * * @param string $language Language to return entity for. - * @return $this|\Cake\ORM\Entity + * @return self|\Cake\ORM\Entity */ public function translation($language) { diff --git a/EagerLoadable.php b/EagerLoadable.php index 6303b62e..93762ef9 100644 --- a/EagerLoadable.php +++ b/EagerLoadable.php @@ -207,7 +207,7 @@ public function propertyPath() * Sets whether or not this level can be fetched using a join. * * @param bool $possible The value to set. - * @return $this + * @return self */ public function setCanBeJoined($possible) { @@ -239,7 +239,7 @@ public function canBeJoined($possible = null) * the records. * * @param array $config The value to set. - * @return $this + * @return self */ public function setConfig(array $config) { diff --git a/EagerLoader.php b/EagerLoader.php index ac999c17..e2bf54f3 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -163,7 +163,7 @@ public function clearContain() * Sets whether or not contained associations will load fields automatically. * * @param bool $enable The value to set. - * @return $this + * @return self */ public function enableAutoFields($enable) { @@ -213,7 +213,7 @@ public function autoFields($enable = null) * @param callable|null $builder the callback function to be used for setting extra * options to the filtering query * @param array $options Extra options for the association matching. - * @return $this + * @return self */ public function setMatching($assoc, callable $builder = null, $options = []) { diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 7e4c686e..5625e9e8 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -61,7 +61,7 @@ class TableLocator implements LocatorInterface * * @param string|array $alias Name of the alias or array to completely overwrite current config. * @param array|null $options list of options for the alias - * @return $this + * @return self * @throws \RuntimeException When you attempt to configure an existing table instance. */ public function setConfig($alias, $options = null) diff --git a/Query.php b/Query.php index 5f75d1b7..62af6044 100644 --- a/Query.php +++ b/Query.php @@ -178,7 +178,7 @@ public function __construct($connection, $table) * @param array|\Cake\Database\ExpressionInterface|string|\Cake\ORM\Table|\Cake\ORM\Association $fields fields * to be added to the list. * @param bool $overwrite whether to reset fields with passed list or not - * @return $this + * @return self */ public function select($fields = [], $overwrite = false) { @@ -202,7 +202,7 @@ public function select($fields = [], $overwrite = false) * This method returns the same query object for chaining. * * @param \Cake\ORM\Table $table The table to pull types from - * @return $this + * @return self */ public function addDefaultTypes(Table $table) { @@ -222,7 +222,7 @@ public function addDefaultTypes(Table $table) * and storing containments. * * @param \Cake\ORM\EagerLoader $instance The eager loader to use. - * @return $this + * @return self */ public function setEagerLoader(EagerLoader $instance) { @@ -253,7 +253,7 @@ public function getEagerLoader() * @deprecated 3.4.0 Use setEagerLoader()/getEagerLoader() instead. * @param \Cake\ORM\EagerLoader|null $instance The eager loader to use. Pass null * to get the current eagerloader. - * @return \Cake\ORM\EagerLoader|$this + * @return \Cake\ORM\EagerLoader|self */ public function eagerLoader(EagerLoader $instance = null) { @@ -373,7 +373,7 @@ public function eagerLoader(EagerLoader $instance = null) * @param array|string|null $associations List of table aliases to be queried. * @param bool $override Whether override previous list with the one passed * defaults to merging previous list with the new one. - * @return array|$this + * @return array|self */ public function contain($associations = null, $override = false) { @@ -469,7 +469,7 @@ protected function _addAssociationsToTypeMap($table, $typeMap, $associations) * @param string $assoc The association to filter by * @param callable|null $builder a function that will receive a pre-made query object * that can be used to add custom conditions or selecting some fields - * @return $this + * @return self */ public function matching($assoc, callable $builder = null) { @@ -541,7 +541,7 @@ public function matching($assoc, callable $builder = null) * @param string $assoc The association to join with * @param callable|null $builder a function that will receive a pre-made query object * that can be used to add custom conditions or selecting some fields - * @return $this + * @return self */ public function leftJoinWith($assoc, callable $builder = null) { @@ -587,7 +587,7 @@ public function leftJoinWith($assoc, callable $builder = null) * @param string $assoc The association to join with * @param callable|null $builder a function that will receive a pre-made query object * that can be used to add custom conditions or selecting some fields - * @return $this + * @return self * @see \Cake\ORM\Query::matching() */ public function innerJoinWith($assoc, callable $builder = null) @@ -650,7 +650,7 @@ public function innerJoinWith($assoc, callable $builder = null) * @param string $assoc The association to filter by * @param callable|null $builder a function that will receive a pre-made query object * that can be used to add custom conditions or selecting some fields - * @return $this + * @return self */ public function notMatching($assoc, callable $builder = null) { @@ -868,7 +868,7 @@ protected function _performCount() * instead * * @param callable|null $counter The counter value - * @return $this + * @return self */ public function counter($counter) { @@ -884,7 +884,7 @@ public function counter($counter) * * @param bool|null $enable Use a boolean to set the hydration mode. * Null will fetch the current hydration mode. - * @return bool|$this A boolean when reading, and $this when setting the mode. + * @return bool|self A boolean when reading, and $this when setting the mode. */ public function hydrate($enable = null) { @@ -901,7 +901,7 @@ public function hydrate($enable = null) /** * {@inheritDoc} * - * @return $this + * @return self * @throws \RuntimeException When you attempt to cache a non-select query. */ public function cache($key, $config = 'default') @@ -1086,7 +1086,7 @@ protected function _dirty() * Can be combined with set() and where() methods to create update queries. * * @param string|null $table Unused parameter. - * @return $this + * @return self */ public function update($table = null) { @@ -1102,7 +1102,7 @@ public function update($table = null) * Can be combined with the where() method to create delete queries. * * @param string|null $table Unused parameter. - * @return $this + * @return self */ public function delete($table = null) { @@ -1123,7 +1123,7 @@ public function delete($table = null) * * @param array $columns The columns to insert into. * @param array $types A map between columns & their datatypes. - * @return $this + * @return self */ public function insert(array $columns, array $types = []) { @@ -1187,7 +1187,7 @@ public function jsonSerialize() * auto-fields with this method. * * @param bool|null $value The value to set or null to read the current value. - * @return bool|$this Either the current value or the query object. + * @return bool|self Either the current value or the query object. */ public function autoFields($value = null) { diff --git a/Table.php b/Table.php index 34301d12..34929770 100644 --- a/Table.php +++ b/Table.php @@ -331,7 +331,7 @@ public function initialize(array $config) * Sets the database table name. * * @param string $table Table name. - * @return $this + * @return self */ public function setTable($table) { @@ -379,7 +379,7 @@ public function table($table = null) * Sets the table alias. * * @param string $alias Table alias - * @return $this + * @return self */ public function setAlias($alias) { @@ -439,7 +439,7 @@ public function aliasField($field) * Sets the table registry key used to create this table instance. * * @param string $registryAlias The key used to access this object. - * @return $this + * @return self */ public function setRegistryAlias($registryAlias) { @@ -483,7 +483,7 @@ public function registryAlias($registryAlias = null) * Sets the connection instance. * * @param \Cake\Datasource\ConnectionInterface $connection The connection instance - * @return $this + * @return self */ public function setConnection(ConnectionInterface $connection) { @@ -543,7 +543,7 @@ public function getSchema() * out of it and used as the schema for this table. * * @param array|\Cake\Database\Schema\TableSchema $schema Schema to be used for this table - * @return $this + * @return self */ public function setSchema($schema) { @@ -634,7 +634,7 @@ public function hasField($field) * Sets the primary key field name. * * @param string|array $key Sets a new name to be used as primary key - * @return $this + * @return self */ public function setPrimaryKey($key) { @@ -681,7 +681,7 @@ public function primaryKey($key = null) * Sets the display field. * * @param string $key Name to be used as display field. - * @return $this + * @return self */ public function setDisplayField($key) { @@ -766,7 +766,7 @@ public function getEntityClass() * * @param string $name The name of the class to use * @throws \Cake\ORM\Exception\MissingEntityException when the entity class cannot be found - * @return $this + * @return self */ public function setEntityClass($name) { From fbe1efc7b2baa2b72bd81f836e2a6da78f00dca3 Mon Sep 17 00:00:00 2001 From: dereuromark Date: Mon, 19 Dec 2016 23:41:10 +0100 Subject: [PATCH 0856/2059] Fix some more doc blocks and smells. Enable further clickability in IDE. --- Behavior/TimestampBehavior.php | 2 +- Behavior/TranslateBehavior.php | 4 ++-- Behavior/TreeBehavior.php | 4 ++-- EagerLoader.php | 15 +++++++++------ Query.php | 2 +- 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 3b6e5372..063538b6 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -132,7 +132,7 @@ public function implementedEvents() * * @param \DateTime|null $ts Timestamp * @param bool $refreshTimestamp If true timestamp is refreshed. - * @return \Cake\I18n\Time + * @return \DateTime */ public function timestamp(DateTime $ts = null, $refreshTimestamp = false) { diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 59315d8e..51eb078a 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -480,7 +480,7 @@ protected function _referenceName(Table $table) * * @param \Cake\Datasource\ResultSetInterface $results Results to map. * @param string $locale Locale string - * @return \Cake\Collection\Collection + * @return \Cake\Collection\CollectionInterface */ protected function _rowMapper($results, $locale) { @@ -521,7 +521,7 @@ protected function _rowMapper($results, $locale) * into each entity under the `_translations` key * * @param \Cake\Datasource\ResultSetInterface $results Results to modify. - * @return \Cake\Collection\Collection + * @return \Cake\Collection\CollectionInterface */ public function groupTranslations($results) { diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 619a6e92..1fb89664 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -966,8 +966,8 @@ protected function _ensureFields($entity) protected function _getPrimaryKey() { if (!$this->_primaryKey) { - $this->_primaryKey = (array)$this->_table->primaryKey(); - $this->_primaryKey = $this->_primaryKey[0]; + $primaryKey = (array)$this->_table->primaryKey(); + $this->_primaryKey = $primaryKey[0]; } return $this->_primaryKey; diff --git a/EagerLoader.php b/EagerLoader.php index e2bf54f3..055eec82 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -40,9 +40,9 @@ class EagerLoader * Contains a nested array with the compiled containments tree * This is a normalized version of the user provided containments array. * - * @var array + * @var \Cake\ORM\EagerLoadable[]|\Cake\ORM\EagerLoadable|null */ - protected $_normalized; + protected $_normalized = null; /** * List of options accepted by associations in contain() @@ -67,7 +67,7 @@ class EagerLoader /** * A list of associations that should be loaded with a separate query * - * @var array + * @var \Cake\ORM\EagerLoadable[] */ protected $_loadExternal = []; @@ -457,7 +457,7 @@ public function attachableAssociations(Table $repository) * * @param \Cake\ORM\Table $repository The table containing the associations * to be loaded - * @return array + * @return \Cake\ORM\EagerLoadable[] */ public function externalAssociations(Table $repository) { @@ -481,7 +481,7 @@ public function externalAssociations(Table $repository) * separated strings representing associations that lead to this `$alias` in the * chain of associations to be loaded. The second value is the path to follow in * entities' properties to fetch a record of the corresponding association. - * @return array normalized associations + * @return \Cake\ORM\EagerLoadable Object with normalized associations * @throws \InvalidArgumentException When containments refer to associations that do not exist. */ protected function _normalizeContain(Table $parent, $alias, $options, $paths) @@ -551,6 +551,7 @@ protected function _fixStrategies() if (count($configs) < 2) { continue; } + /* @var \Cake\ORM\EagerLoadable $loadable */ foreach ($configs as $loadable) { if (strpos($loadable->aliasPath(), '.')) { $this->_correctStrategy($loadable); @@ -624,7 +625,7 @@ protected function _resolveJoins($associations, $matching = []) * @param \Cake\ORM\Query $query The query for which to eager load external * associations * @param \Cake\Database\StatementInterface $statement The statement created after executing the $query - * @return \Cake\Database\Statement\CallbackStatement statement modified statement with extra loaders + * @return \Cake\Database\StatementInterface statement modified statement with extra loaders */ public function loadExternal($query, $statement) { @@ -687,6 +688,7 @@ public function associationsMap($table) } $visitor = function ($level, $matching = false) use (&$visitor, &$map) { + /* @var \Cake\ORM\EagerLoadable $meta */ foreach ($level as $assoc => $meta) { $canBeJoined = $meta->canBeJoined(); $instance = $meta->instance(); @@ -750,6 +752,7 @@ public function addToJoinsMap($alias, Association $assoc, $asMatching = false, $ protected function _collectKeys($external, $query, $statement) { $collectKeys = []; + /* @var \Cake\ORM\EagerLoadable $meta */ foreach ($external as $meta) { $instance = $meta->instance(); if (!$instance->requiresKeys($meta->config())) { diff --git a/Query.php b/Query.php index 62af6044..0e19ab77 100644 --- a/Query.php +++ b/Query.php @@ -148,7 +148,7 @@ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface * * When set, count query execution will be bypassed. * - * @var int + * @var int|null */ protected $_resultsCount; From e55925de76204f65dc5e162e2468275f2bb4057b Mon Sep 17 00:00:00 2001 From: ndm2 Date: Sun, 4 Sep 2016 17:59:09 +0200 Subject: [PATCH 0857/2059] Fix has many save replace doesn't respect association conditions. When removing associated records using the `SAVE_REPLACE` save strategy, the associations conditions need to be included for non-dependent/cascading configurations, otherwise the only condition used will be the primary key of the parent record, causing all linked records to be deleted, irrespectively of whether they are actually in the scope of the association. --- Association/HasMany.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Association/HasMany.php b/Association/HasMany.php index 398c20ae..fe18ee0b 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -461,12 +461,14 @@ protected function _unlink(array $foreignKey, Table $target, array $conditions = return $ok; } + $conditions = array_merge($conditions, $this->conditions()); $target->deleteAll($conditions); return true; } $updateFields = array_fill_keys($foreignKey, null); + $conditions = array_merge($conditions, $this->conditions()); $target->updateAll($updateFields, $conditions); return true; From 39966d300a748cb89c4d9476a242042f9eb30222 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 30 Dec 2016 21:51:00 +0100 Subject: [PATCH 0858/2059] Added missing properties --- Association/Loader/SelectWithPivotLoader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/Loader/SelectWithPivotLoader.php b/Association/Loader/SelectWithPivotLoader.php index f9ec121f..918d602f 100644 --- a/Association/Loader/SelectWithPivotLoader.php +++ b/Association/Loader/SelectWithPivotLoader.php @@ -171,7 +171,7 @@ protected function _buildResultMap($fetchQuery, $options) if (!isset($result[$this->junctionProperty])) { throw new RuntimeException(sprintf( '"%s" is missing from the belongsToMany results. Results cannot be created.', - $this->_junctionProperty + $this->junctionProperty )); } From 28d2a370b18853cc0778eea3edd6cd56f54c4f21 Mon Sep 17 00:00:00 2001 From: Anthony GRASSIOT Date: Tue, 3 Jan 2017 06:11:41 +0100 Subject: [PATCH 0859/2059] Deprecation removal --- Association.php | 18 +++++------ Association/BelongsToMany.php | 10 +++--- Association/HasMany.php | 6 ++-- Association/HasOne.php | 16 +++++----- Association/Loader/SelectWithPivotLoader.php | 8 ++--- Behavior/TranslateBehavior.php | 4 +-- Behavior/TreeBehavior.php | 2 +- LazyEagerLoader.php | 6 ++-- Marshaller.php | 14 ++++----- Query.php | 26 ++++++++-------- ResultSet.php | 4 +-- Rule/ExistsIn.php | 6 ++-- Rule/IsUnique.php | 2 +- Table.php | 32 ++++++++++---------- 14 files changed, 77 insertions(+), 77 deletions(-) diff --git a/Association.php b/Association.php index ce4175fb..291b953d 100644 --- a/Association.php +++ b/Association.php @@ -491,8 +491,8 @@ public function getBindingKey() { if ($this->_bindingKey === null) { $this->_bindingKey = $this->isOwningSide($this->source()) ? - $this->source()->primaryKey() : - $this->target()->primaryKey(); + $this->source()->getPrimaryKey() : + $this->target()->getPrimaryKey(); } return $this->_bindingKey; @@ -686,7 +686,7 @@ public function getProperty() { if (!$this->_propertyName) { $this->_propertyName = $this->_propertyName(); - if (in_array($this->_propertyName, $this->_sourceTable->schema()->columns())) { + if (in_array($this->_propertyName, $this->_sourceTable->getSchema()->columns())) { $msg = 'Association property name "%s" clashes with field of same name of table "%s".' . ' You should explicitly specify the "propertyName" option.'; trigger_error( @@ -926,7 +926,7 @@ protected function _appendNotMatching($query, $options) { $target = $this->_targetTable; if (!empty($options['negateMatch'])) { - $primaryKey = $query->aliasFields((array)$target->primaryKey(), $this->_name); + $primaryKey = $query->aliasFields((array)$target->getPrimaryKey(), $this->_name); $query->andWhere(function ($exp) use ($primaryKey) { array_map([$exp, 'isNull'], $primaryKey); @@ -1098,7 +1098,7 @@ protected function _dispatchBeforeFind($query) */ protected function _appendFields($query, $surrogate, $options) { - if ($query->eagerLoader()->autoFields() === false) { + if ($query->getEagerLoader()->autoFields() === false) { return; } @@ -1108,12 +1108,12 @@ protected function _appendFields($query, $surrogate, $options) if (empty($fields) && !$autoFields) { if ($options['includeFields'] && ($fields === null || $fields !== false)) { - $fields = $target->schema()->columns(); + $fields = $target->getSchema()->columns(); } } if ($autoFields === true) { - $fields = array_merge((array)$fields, $target->schema()->columns()); + $fields = array_merge((array)$fields, $target->getSchema()->columns()); } if ($fields) { @@ -1181,7 +1181,7 @@ protected function _formatAssociationResults($query, $surrogate, $options) */ protected function _bindNewAssociations($query, $surrogate, $options) { - $loader = $surrogate->eagerLoader(); + $loader = $surrogate->getEagerLoader(); $contain = $loader->contain(); $matching = $loader->matching(); @@ -1194,7 +1194,7 @@ protected function _bindNewAssociations($query, $surrogate, $options) $newContain[$options['aliasPath'] . '.' . $alias] = $value; } - $eagerLoader = $query->eagerLoader(); + $eagerLoader = $query->getEagerLoader(); $eagerLoader->contain($newContain); foreach ($matching as $alias => $value) { diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index cf205179..fc0e8655 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -461,7 +461,7 @@ public function attachTo(Query $query, array $options = []) 'foreignKey' => false, ]; $assoc->attachTo($query, $newOptions); - $query->eagerLoader()->addToJoinsMap($junction->alias(), $assoc, true); + $query->getEagerLoader()->addToJoinsMap($junction->alias(), $assoc, true); parent::attachTo($query, $options); @@ -799,7 +799,7 @@ protected function _saveLinks(EntityInterface $sourceEntity, $targetEntities, $o $belongsTo = $junction->association($target->alias()); $foreignKey = (array)$this->foreignKey(); $assocForeignKey = (array)$belongsTo->foreignKey(); - $targetPrimaryKey = (array)$target->primaryKey(); + $targetPrimaryKey = (array)$target->getPrimaryKey(); $bindingKey = (array)$this->bindingKey(); $jointProperty = $this->_junctionProperty; $junctionAlias = $junction->alias(); @@ -1273,7 +1273,7 @@ protected function _diffLinks($existing, $jointEntities, $targetEntities, $optio } } - $primary = (array)$target->primaryKey(); + $primary = (array)$target->getPrimaryKey(); $jointProperty = $this->_junctionProperty; foreach ($targetEntities as $k => $entity) { if (!($entity instanceof EntityInterface)) { @@ -1342,7 +1342,7 @@ protected function _collectJointEntities($sourceEntity, $targetEntities) $source = $this->source(); $junction = $this->junction(); $jointProperty = $this->_junctionProperty; - $primary = (array)$target->primaryKey(); + $primary = (array)$target->getPrimaryKey(); $result = []; $missing = []; @@ -1369,7 +1369,7 @@ protected function _collectJointEntities($sourceEntity, $targetEntities) $hasMany = $source->association($junction->alias()); $foreignKey = (array)$this->foreignKey(); $assocForeignKey = (array)$belongsTo->foreignKey(); - $sourceKey = $sourceEntity->extract((array)$source->primaryKey()); + $sourceKey = $sourceEntity->extract((array)$source->getPrimaryKey()); foreach ($missing as $key) { $unions[] = $hasMany->find('all') diff --git a/Association/HasMany.php b/Association/HasMany.php index 2146f7ff..534316f2 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -332,7 +332,7 @@ public function unlink(EntityInterface $sourceEntity, array $targetEntities, $op $foreignKey = (array)$this->foreignKey(); $target = $this->target(); - $targetPrimaryKey = array_merge((array)$target->primaryKey(), $foreignKey); + $targetPrimaryKey = array_merge((array)$target->getPrimaryKey(), $foreignKey); $property = $this->property(); $conditions = [ @@ -435,7 +435,7 @@ public function replace(EntityInterface $sourceEntity, array $targetEntities, ar */ protected function _unlinkAssociated(array $properties, EntityInterface $entity, Table $target, array $remainingEntities = [], array $options = []) { - $primaryKey = (array)$target->primaryKey(); + $primaryKey = (array)$target->getPrimaryKey(); $exclusions = new Collection($remainingEntities); $exclusions = $exclusions->map( function ($ent) use ($primaryKey) { @@ -520,7 +520,7 @@ protected function _foreignKeyAcceptsNull(Table $table, array $properties) false, array_map( function ($prop) use ($table) { - return $table->schema()->isNullable($prop); + return $table->getSchema()->isNullable($prop); }, $properties ) diff --git a/Association/HasOne.php b/Association/HasOne.php index 4ee4fddd..36a34b7f 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -49,7 +49,7 @@ class HasOne extends Association public function getForeignKey() { if ($this->_foreignKey === null) { - $this->_foreignKey = $this->_modelKey($this->source()->alias()); + $this->_foreignKey = $this->_modelKey($this->getSource()->getAlias()); } return $this->_foreignKey; @@ -94,7 +94,7 @@ protected function _propertyName() */ public function isOwningSide(Table $side) { - return $side === $this->source(); + return $side === $this->getSource(); } /** @@ -150,12 +150,12 @@ public function saveAssociated(EntityInterface $entity, array $options = []) public function eagerLoader(array $options) { $loader = new SelectLoader([ - 'alias' => $this->alias(), - 'sourceAlias' => $this->source()->alias(), - 'targetAlias' => $this->target()->alias(), - 'foreignKey' => $this->foreignKey(), - 'bindingKey' => $this->bindingKey(), - 'strategy' => $this->strategy(), + 'alias' => $this->getAlias(), + 'sourceAlias' => $this->getSource()->getAlias(), + 'targetAlias' => $this->getTarget()->getAlias(), + 'foreignKey' => $this->getForeignKey(), + 'bindingKey' => $this->getBindingKey(), + 'strategy' => $this->getStrategy(), 'associationType' => $this->type(), 'finder' => [$this, 'find'] ]); diff --git a/Association/Loader/SelectWithPivotLoader.php b/Association/Loader/SelectWithPivotLoader.php index f9ec121f..41b6ec40 100644 --- a/Association/Loader/SelectWithPivotLoader.php +++ b/Association/Loader/SelectWithPivotLoader.php @@ -102,7 +102,7 @@ protected function _buildQuery($options) // and that the required keys are in the selected columns. $tempName = $this->alias . '_CJoin'; - $schema = $assoc->schema(); + $schema = $assoc->getSchema(); $joinFields = $types = []; foreach ($schema->typeMap() as $f => $type) { @@ -116,15 +116,15 @@ protected function _buildQuery($options) ->select($joinFields); $query - ->eagerLoader() + ->getEagerLoader() ->addToJoinsMap($tempName, $assoc, false, $this->junctionProperty); $assoc->attachTo($query, [ - 'aliasPath' => $assoc->alias(), + 'aliasPath' => $assoc->getAlias(), 'includeFields' => false, 'propertyPath' => $this->junctionProperty, ]); - $query->typeMap()->addDefaults($types); + $query->getTypeMap()->addDefaults($types); return $query; } diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 51eb078a..34c88f07 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -290,7 +290,7 @@ public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $o return; } - $primaryKey = (array)$this->_table->primaryKey(); + $primaryKey = (array)$this->_table->getPrimaryKey(); $key = $entity->get(current($primaryKey)); // When we have no key and bundled translations, we @@ -572,7 +572,7 @@ protected function _bundleTranslatedFields($entity) } $fields = $this->_config['fields']; - $primaryKey = (array)$this->_table->primaryKey(); + $primaryKey = (array)$this->_table->getPrimaryKey(); $key = $entity->get(current($primaryKey)); $find = []; diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 1fb89664..87f9fabd 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -966,7 +966,7 @@ protected function _ensureFields($entity) protected function _getPrimaryKey() { if (!$this->_primaryKey) { - $primaryKey = (array)$this->_table->primaryKey(); + $primaryKey = (array)$this->_table->getPrimaryKey(); $this->_primaryKey = $primaryKey[0]; } diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index a7bc4d2c..395e360f 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -69,7 +69,7 @@ public function loadInto($entities, array $contain, Table $source) */ protected function _getQuery($objects, $contain, $source) { - $primaryKey = $source->primaryKey(); + $primaryKey = $source->getPrimaryKey(); $method = is_string($primaryKey) ? 'get' : 'extract'; $keys = $objects->map(function ($entity) use ($primaryKey, $method) { @@ -95,7 +95,7 @@ protected function _getQuery($objects, $contain, $source) }) ->contain($contain); - foreach ($query->eagerLoader()->attachableAssociations($source) as $loadable) { + foreach ($query->getEagerLoader()->attachableAssociations($source) as $loadable) { $config = $loadable->config(); $config['includeFields'] = true; $loadable->config($config); @@ -137,7 +137,7 @@ protected function _injectResults($objects, $results, $associations, $source) { $injected = []; $properties = $this->_getPropertyMap($source, $associations); - $primaryKey = (array)$source->primaryKey(); + $primaryKey = (array)$source->getPrimaryKey(); $results = $results ->indexBy(function ($e) use ($primaryKey) { return implode(';', $e->extract($primaryKey)); diff --git a/Marshaller.php b/Marshaller.php index 257bb3ec..72ff399a 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -65,7 +65,7 @@ public function __construct(Table $table) protected function _buildPropertyMap($data, $options) { $map = []; - $schema = $this->_table->schema(); + $schema = $this->_table->getSchema(); // Is a concrete column? foreach (array_keys($data) as $prop) { @@ -166,7 +166,7 @@ public function one(array $data, array $options = []) { list($data, $options) = $this->_prepareDataAndOptions($data, $options); - $primaryKey = (array)$this->_table->primaryKey(); + $primaryKey = (array)$this->_table->getPrimaryKey(); $entityClass = $this->_table->entityClass(); /* @var Entity $entity */ $entity = new $entityClass(); @@ -364,7 +364,7 @@ protected function _belongsToMany(Association $assoc, array $data, $options = [] $data = array_values($data); $target = $assoc->target(); - $primaryKey = array_flip((array)$target->primaryKey()); + $primaryKey = array_flip((array)$target->getPrimaryKey()); $records = $conditions = []; $primaryCount = count($primaryKey); $conditions = []; @@ -454,7 +454,7 @@ protected function _loadAssociatedByIds($assoc, $ids) } $target = $assoc->target(); - $primaryKey = (array)$target->primaryKey(); + $primaryKey = (array)$target->getPrimaryKey(); $multi = count($primaryKey) > 1; $primaryKey = array_map([$target, 'aliasField'], $primaryKey); @@ -529,7 +529,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) $keys = []; if (!$isNew) { - $keys = $entity->extract((array)$this->_table->primaryKey()); + $keys = $entity->extract((array)$this->_table->getPrimaryKey()); } if (isset($options['accessibleFields'])) { @@ -539,7 +539,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) } $errors = $this->_validate($data + $keys, $options, $isNew); - $schema = $this->_table->schema(); + $schema = $this->_table->getSchema(); $options['isMerge'] = true; $propertyMap = $this->_buildPropertyMap($data, $options); $properties = $marshalledAssocs = []; @@ -628,7 +628,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) */ public function mergeMany($entities, array $data, array $options = []) { - $primary = (array)$this->_table->primaryKey(); + $primary = (array)$this->_table->getPrimaryKey(); $indexed = (new Collection($data)) ->groupBy(function ($el) use ($primary) { diff --git a/Query.php b/Query.php index 0e19ab77..b4626828 100644 --- a/Query.php +++ b/Query.php @@ -187,7 +187,7 @@ public function select($fields = [], $overwrite = false) } if ($fields instanceof Table) { - $fields = $this->aliasFields($fields->schema()->columns(), $fields->alias()); + $fields = $this->aliasFields($fields->getSchema()->columns(), $fields->alias()); } return parent::select($fields, $overwrite); @@ -207,7 +207,7 @@ public function select($fields = [], $overwrite = false) public function addDefaultTypes(Table $table) { $alias = $table->alias(); - $map = $table->schema()->typeMap(); + $map = $table->getSchema()->typeMap(); $fields = []; foreach ($map as $f => $type) { $fields[$f] = $fields[$alias . '.' . $f] = $fields[$alias . '__' . $f] = $type; @@ -377,7 +377,7 @@ public function eagerLoader(EagerLoader $instance = null) */ public function contain($associations = null, $override = false) { - $loader = $this->eagerLoader(); + $loader = $this->getEagerLoader(); if ($override === true) { $loader->clearContain(); $this->_dirty(); @@ -411,7 +411,7 @@ protected function _addAssociationsToTypeMap($table, $typeMap, $associations) continue; } $target = $association->target(); - $primary = (array)$target->primaryKey(); + $primary = (array)$target->getPrimaryKey(); if (empty($primary) || $typeMap->type($target->aliasField($primary[0])) === null) { $this->addDefaultTypes($target); } @@ -473,7 +473,7 @@ protected function _addAssociationsToTypeMap($table, $typeMap, $associations) */ public function matching($assoc, callable $builder = null) { - $result = $this->eagerLoader()->matching($assoc, $builder); + $result = $this->getEagerLoader()->matching($assoc, $builder); $this->_addAssociationsToTypeMap($this->repository(), $this->typeMap(), $result); $this->_dirty(); @@ -545,7 +545,7 @@ public function matching($assoc, callable $builder = null) */ public function leftJoinWith($assoc, callable $builder = null) { - $result = $this->eagerLoader()->matching($assoc, $builder, [ + $result = $this->getEagerLoader()->matching($assoc, $builder, [ 'joinType' => 'LEFT', 'fields' => false ]); @@ -592,7 +592,7 @@ public function leftJoinWith($assoc, callable $builder = null) */ public function innerJoinWith($assoc, callable $builder = null) { - $result = $this->eagerLoader()->matching($assoc, $builder, [ + $result = $this->getEagerLoader()->matching($assoc, $builder, [ 'joinType' => 'INNER', 'fields' => false ]); @@ -654,7 +654,7 @@ public function innerJoinWith($assoc, callable $builder = null) */ public function notMatching($assoc, callable $builder = null) { - $result = $this->eagerLoader()->matching($assoc, $builder, [ + $result = $this->getEagerLoader()->matching($assoc, $builder, [ 'joinType' => 'LEFT', 'fields' => false, 'negateMatch' => true @@ -834,7 +834,7 @@ protected function _performCount() $count = ['count' => $query->func()->count('*')]; if (!$complex) { - $query->eagerLoader()->autoFields(false); + $query->getEagerLoader()->autoFields(false); $statement = $query ->select($count, true) ->autoFields(false) @@ -978,7 +978,7 @@ protected function _execute() return new $decorator($this->_results); } - $statement = $this->eagerLoader()->loadExternal($this, $this->execute()); + $statement = $this->getEagerLoader()->loadExternal($this, $this->execute()); return new ResultSet($this, $statement); } @@ -1005,7 +1005,7 @@ protected function _transformQuery() $this->from([$this->_repository->alias() => $this->_repository->table()]); } $this->_addDefaultFields(); - $this->eagerLoader()->attachAssociations($this, $this->_repository, !$this->_hasFields); + $this->getEagerLoader()->attachAssociations($this, $this->_repository, !$this->_hasFields); $this->_addDefaultSelectTypes(); } @@ -1022,7 +1022,7 @@ protected function _addDefaultFields() if (!count($select) || $this->_autoFields === true) { $this->_hasFields = false; - $this->select($this->repository()->schema()->columns()); + $this->select($this->repository()->getSchema()->columns()); $select = $this->clause('select'); } @@ -1154,7 +1154,7 @@ public function __call($method, $arguments) */ public function __debugInfo() { - $eagerLoader = $this->eagerLoader(); + $eagerLoader = $this->getEagerLoader(); return parent::__debugInfo() + [ 'hydrate' => $this->_hydrate, diff --git a/ResultSet.php b/ResultSet.php index 48f90f18..b0e85cec 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -363,7 +363,7 @@ public function count() */ protected function _calculateAssociationMap($query) { - $map = $query->eagerLoader()->associationsMap($this->_defaultTable); + $map = $query->getEagerLoader()->associationsMap($this->_defaultTable); $this->_matchingMap = (new Collection($map)) ->match(['matching' => true]) ->indexBy('alias') @@ -430,7 +430,7 @@ protected function _calculateTypeMap() protected function _getTypes($table, $fields) { $types = []; - $schema = $table->schema(); + $schema = $table->getSchema(); $map = array_keys(Type::map() + ['string' => 1, 'text' => 1, 'boolean' => 1]); $typeMap = array_combine( $map, diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index b6a42573..7c854a5d 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -92,7 +92,7 @@ public function __invoke(EntityInterface $entity, array $options) $source = $target = $this->_repository; $isAssociation = $target instanceof Association; - $bindingKey = $isAssociation ? (array)$target->bindingKey() : (array)$target->primaryKey(); + $bindingKey = $isAssociation ? (array)$target->bindingKey() : (array)$target->getPrimaryKey(); $realTarget = $isAssociation ? $target->target() : $target; if (!empty($options['_sourceTable']) && $realTarget === $options['_sourceTable']) { @@ -115,7 +115,7 @@ public function __invoke(EntityInterface $entity, array $options) } if ($this->_options['allowNullableNulls']) { - $schema = $source->schema(); + $schema = $source->getSchema(); foreach ($this->_fields as $i => $field) { if ($schema->column($field) && $schema->isNullable($field) && $entity->get($field) === null) { unset($bindingKey[$i]); @@ -146,7 +146,7 @@ public function __invoke(EntityInterface $entity, array $options) protected function _fieldsAreNull($entity, $source) { $nulls = 0; - $schema = $source->schema(); + $schema = $source->getSchema(); foreach ($this->_fields as $field) { if ($schema->column($field) && $schema->isNullable($field) && $entity->get($field) === null) { $nulls++; diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index 5c64b434..b28ed9d2 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -72,7 +72,7 @@ public function __invoke(EntityInterface $entity, array $options) $alias = $options['repository']->alias(); $conditions = $this->_alias($alias, $entity->extract($this->_fields), $allowMultipleNulls); if ($entity->isNew() === false) { - $keys = (array)$options['repository']->primaryKey(); + $keys = (array)$options['repository']->getPrimaryKey(); $keys = $this->_alias($alias, $entity->extract($keys), $allowMultipleNulls); if (array_filter($keys, 'strlen')) { $conditions['NOT'] = $keys; diff --git a/Table.php b/Table.php index 0a5d2118..885dcf2a 100644 --- a/Table.php +++ b/Table.php @@ -698,8 +698,8 @@ public function setDisplayField($key) public function getDisplayField() { if ($this->_displayField === null) { - $schema = $this->schema(); - $primary = (array)$this->primaryKey(); + $schema = $this->getSchema(); + $primary = (array)$this->getPrimaryKey(); $this->_displayField = array_shift($primary); if ($schema->column('title')) { $this->_displayField = 'title'; @@ -1251,7 +1251,7 @@ public function findAll(Query $query, array $options) public function findList(Query $query, array $options) { $options += [ - 'keyField' => $this->primaryKey(), + 'keyField' => $this->getPrimaryKey(), 'valueField' => $this->displayField(), 'groupField' => null ]; @@ -1272,7 +1272,7 @@ public function findList(Query $query, array $options) (array)$options['valueField'], (array)$options['groupField'] ); - $columns = $this->schema()->columns(); + $columns = $this->getSchema()->columns(); if (count($fields) === count(array_intersect($fields, $columns))) { $query->select($fields); } @@ -1319,7 +1319,7 @@ public function findList(Query $query, array $options) public function findThreaded(Query $query, array $options) { $options += [ - 'keyField' => $this->primaryKey(), + 'keyField' => $this->getPrimaryKey(), 'parentField' => 'parent_id', 'nestingKey' => 'children' ]; @@ -1392,7 +1392,7 @@ protected function _setFieldMatchers($options, $keys) */ public function get($primaryKey, $options = []) { - $key = (array)$this->primaryKey(); + $key = (array)$this->getPrimaryKey(); $alias = $this->alias(); foreach ($key as $index => $keyname) { $key[$index] = $alias . '.' . $keyname; @@ -1751,7 +1751,7 @@ public function save(EntityInterface $entity, $options = []) */ protected function _processSave($entity, $options) { - $primaryColumns = (array)$this->primaryKey(); + $primaryColumns = (array)$this->getPrimaryKey(); if ($options['checkExisting'] && $primaryColumns && $entity->isNew() && $entity->has($primaryColumns)) { $alias = $this->alias(); @@ -1785,7 +1785,7 @@ protected function _processSave($entity, $options) return false; } - $data = $entity->extract($this->schema()->columns(), true); + $data = $entity->extract($this->getSchema()->columns(), true); $isNew = $entity->isNew(); if ($isNew) { @@ -1799,7 +1799,7 @@ protected function _processSave($entity, $options) } if (!$success && $isNew) { - $entity->unsetProperty($this->primaryKey()); + $entity->unsetProperty($this->getPrimaryKey()); $entity->isNew(true); } @@ -1855,7 +1855,7 @@ protected function _onSaveSuccess($entity, $options) */ protected function _insert($entity, $data) { - $primary = (array)$this->primaryKey(); + $primary = (array)$this->getPrimaryKey(); if (empty($primary)) { $msg = sprintf( 'Cannot insert row in "%s" table, it has no primary key.', @@ -1874,7 +1874,7 @@ protected function _insert($entity, $data) $data = $data + $filteredKeys; if (count($primary) > 1) { - $schema = $this->schema(); + $schema = $this->getSchema(); foreach ($primary as $k => $v) { if (!isset($data[$k]) && empty($schema->column($k)['autoIncrement'])) { $msg = 'Cannot insert row, some of the primary key values are missing. '; @@ -1900,7 +1900,7 @@ protected function _insert($entity, $data) if ($statement->rowCount() !== 0) { $success = $entity; $entity->set($filteredKeys, ['guard' => false]); - $schema = $this->schema(); + $schema = $this->getSchema(); $driver = $this->connection()->driver(); foreach ($primary as $key => $v) { if (!isset($data[$key])) { @@ -1931,7 +1931,7 @@ protected function _newId($primary) if (!$primary || count((array)$primary) > 1) { return null; } - $typeName = $this->schema()->columnType($primary[0]); + $typeName = $this->getSchema()->columnType($primary[0]); $type = Type::build($typeName); return $type->newId(); @@ -1947,7 +1947,7 @@ protected function _newId($primary) */ protected function _update($entity, $data) { - $primaryColumns = (array)$this->primaryKey(); + $primaryColumns = (array)$this->getPrimaryKey(); $primaryKey = $entity->extract($primaryColumns); $data = array_diff_key($data, $primaryKey); @@ -2005,7 +2005,7 @@ function () use ($entities, $options, &$isNew) { if ($return === false) { foreach ($entities as $key => $entity) { if (isset($isNew[$key]) && $isNew[$key]) { - $entity->unsetProperty($this->primaryKey()); + $entity->unsetProperty($this->getPrimaryKey()); $entity->isNew(true); } } @@ -2083,7 +2083,7 @@ protected function _processDelete($entity, $options) return false; } - $primaryKey = (array)$this->primaryKey(); + $primaryKey = (array)$this->getPrimaryKey(); if (!$entity->has($primaryKey)) { $msg = 'Deleting requires all primary key values.'; throw new InvalidArgumentException($msg); From a522edd95999ccb24b728237b301aa1a6f20f2ce Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Tue, 3 Jan 2017 01:51:07 +0100 Subject: [PATCH 0860/2059] Add an option which enables ignoring counter cache fields from being updated --- Behavior/CounterCacheBehavior.php | 59 +++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 12011319..064764a6 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -18,6 +18,8 @@ use Cake\Event\Event; use Cake\ORM\Association; use Cake\ORM\Behavior; +use Cake\Utility\Hash; +use Cake\Utility\Inflector; /** * CounterCache behavior @@ -74,10 +76,60 @@ * ] * ] * ``` + * + * Ignore updating the field if it is dirty + * ``` + * [ + * 'Users' => [ + * 'posts_published' => [ + * 'ignoreDirty' => true + * ] + * ] + * ] + * ``` */ class CounterCacheBehavior extends Behavior { + /** + * Store the fields which should be ignored + * + * @var array + */ + protected $_ignoreDirty = []; + + /** + * beforeSave callback. + * + * Check if a field, which should be ignored, is dirty + * + * @param \Cake\Event\Event $event The beforeSave event that was fired + * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved + * @return void + */ + public function beforeSave(Event $event, EntityInterface $entity) + { + foreach ($this->_config as $assoc => $settings) { + $assoc = $this->_table->association($assoc); + foreach ($settings as $field => $config) { + if (is_int($field)) { + continue; + } + + $registryAlias = $assoc->target()->registryAlias(); + $entityAlias = $assoc->property(); + + if (!is_callable($config) && + isset($config['ignoreDirty']) && + $config['ignoreDirty'] === true && + $entity->$entityAlias->dirty($field) + ) { + $this->_ignoreDirty[$registryAlias][$field] = true; + } + } + } + } + /** * afterSave callback. * @@ -90,6 +142,7 @@ class CounterCacheBehavior extends Behavior public function afterSave(Event $event, EntityInterface $entity) { $this->_processAssociations($event, $entity); + $this->_ignoreDirty = []; } /** @@ -148,6 +201,12 @@ protected function _processAssociation(Event $event, EntityInterface $entity, As $config = []; } + if (isset($this->_ignoreDirty[$assoc->target()->registryAlias()][$field]) && + $this->_ignoreDirty[$assoc->target()->registryAlias()][$field] === true + ) { + continue; + } + if (!is_string($config) && is_callable($config)) { $count = $config($event, $entity, $this->_table, false); } else { From 0d0de2f1234667e291e812c08c2361e5ccba9357 Mon Sep 17 00:00:00 2001 From: dereuromark Date: Thu, 5 Jan 2017 23:49:37 +0100 Subject: [PATCH 0861/2059] Clean up ORM Query API. --- Query.php | 70 +++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 60 insertions(+), 10 deletions(-) diff --git a/Query.php b/Query.php index b4626828..b4dcf27a 100644 --- a/Query.php +++ b/Query.php @@ -880,8 +880,32 @@ public function counter($counter) /** * Toggle hydrating entities. * - * If set to false array results will be returned + * If set to false array results will be returned. * + * @param bool $enable Use a boolean to set the hydration mode. + * @return self + */ + public function enableHydration($enable) + { + $this->_dirty(); + $this->_hydrate = (bool)$enable; + + return $this; + } + + /** + * @return bool + */ + public function isHydrationEnabled() { + return $this->_hydrate; + } + + /** + * Toggle hydrating entities. + * + * If set to false array results will be returned. + * + * @deprecated 3.4.0 Use enableHydration()/isHydrationEnabled() instead. * @param bool|null $enable Use a boolean to set the hydration mode. * Null will fetch the current hydration mode. * @return bool|self A boolean when reading, and $this when setting the mode. @@ -889,13 +913,10 @@ public function counter($counter) public function hydrate($enable = null) { if ($enable === null) { - return $this->_hydrate; + return $this->isHydrationEnabled(); } - $this->_dirty(); - $this->_hydrate = (bool)$enable; - - return $this; + return $this->enableHydration($enable); } /** @@ -1180,23 +1201,52 @@ public function jsonSerialize() return $this->all(); } + /** + * Sets whether or not the ORM should automatically append fields. + * + * By default calling select() will disable auto-fields. You can re-enable + * auto-fields with this method. + * + * @param bool $value Set true to enable, false to disable. + * @return self + */ + public function enableAutoFields($value) + { + $this->_autoFields = (bool)$value; + + return $this; + } + + /** + * Gets whether or not the ORM should automatically append fields. + * + * By default calling select() will disable auto-fields. You can re-enable + * auto-fields with enableAutoFields(). + * + * @return bool The current value. + */ + public function isAutoFieldsEnabled() + { + return $this->_autoFields; + } + /** * Get/Set whether or not the ORM should automatically append fields. * * By default calling select() will disable auto-fields. You can re-enable * auto-fields with this method. * + * @deprecated 3.4.0 Use enableAutoFields()/isAutoFieldsEnabled() instead. * @param bool|null $value The value to set or null to read the current value. * @return bool|self Either the current value or the query object. */ public function autoFields($value = null) { if ($value === null) { - return $this->_autoFields; + return $this->isAutoFieldsEnabled(); } - $this->_autoFields = (bool)$value; - return $this; + return $this->enableAutoFields($value); } /** @@ -1209,7 +1259,7 @@ protected function _decorateResults($result) { $result = $this->_applyDecorators($result); - if (!($result instanceof ResultSet) && $this->bufferResults()) { + if (!($result instanceof ResultSet) && $this->isBufferedResultsEnabled()) { $class = $this->_decoratorClass(); $result = new $class($result->buffered()); } From 7d3b5e52205267c426e9ba108ec4d743dd84e5eb Mon Sep 17 00:00:00 2001 From: dereuromark Date: Thu, 5 Jan 2017 23:52:48 +0100 Subject: [PATCH 0862/2059] Fix CS. --- Query.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Query.php b/Query.php index b4dcf27a..5517fefd 100644 --- a/Query.php +++ b/Query.php @@ -896,7 +896,8 @@ public function enableHydration($enable) /** * @return bool */ - public function isHydrationEnabled() { + public function isHydrationEnabled() + { return $this->_hydrate; } From fd9fd2700bdde010256b67ef53da777e14ea4cb1 Mon Sep 17 00:00:00 2001 From: Koen Brouwer Date: Fri, 6 Jan 2017 10:06:35 +0100 Subject: [PATCH 0863/2059] Update TreeBehavior.php Changed incorrect beforeSave to correct afterSave in comments at line 161. --- Behavior/TreeBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index ad829ceb..1620d7b2 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -158,7 +158,7 @@ public function beforeSave(Event $event, EntityInterface $entity) * * Manages updating level of descendents of currently saved entity. * - * @param \Cake\Event\Event $event The beforeSave event that was fired + * @param \Cake\Event\Event $event The afterSave event that was fired * @param \Cake\Datasource\EntityInterface $entity the entity that is going to be saved * @return void */ From 38895a6a843d3da37c667e0a1d6f23e8559e78da Mon Sep 17 00:00:00 2001 From: Mark Sch Date: Fri, 6 Jan 2017 15:02:51 +0100 Subject: [PATCH 0864/2059] Fix description. --- Query.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Query.php b/Query.php index 5517fefd..5113abab 100644 --- a/Query.php +++ b/Query.php @@ -880,7 +880,7 @@ public function counter($counter) /** * Toggle hydrating entities. * - * If set to false array results will be returned. + * If set to false array results will be returned for the query. * * @param bool $enable Use a boolean to set the hydration mode. * @return self @@ -894,6 +894,8 @@ public function enableHydration($enable) } /** + * Returns the current hydration mode. + * * @return bool */ public function isHydrationEnabled() From 70378536b47de29df89172f41530989bb7afb3cb Mon Sep 17 00:00:00 2001 From: antograssiot Date: Mon, 9 Jan 2017 13:26:18 +0100 Subject: [PATCH 0865/2059] Fix setMatching return type --- EagerLoader.php | 6 ++---- Query.php | 8 ++++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index 055eec82..72591c01 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -213,7 +213,7 @@ public function autoFields($enable = null) * @param callable|null $builder the callback function to be used for setting extra * options to the filtering query * @param array $options Extra options for the association matching. - * @return self + * @return array Containments. */ public function setMatching($assoc, callable $builder = null, $options = []) { @@ -239,9 +239,7 @@ public function setMatching($assoc, callable $builder = null, $options = []) $pointer[$last] = ['queryBuilder' => $builder, 'matching' => true] + $options; - $this->_matching->contain($containments); - - return $this; + return $this->_matching->contain($containments); } /** diff --git a/Query.php b/Query.php index 5113abab..322ddb97 100644 --- a/Query.php +++ b/Query.php @@ -473,7 +473,7 @@ protected function _addAssociationsToTypeMap($table, $typeMap, $associations) */ public function matching($assoc, callable $builder = null) { - $result = $this->getEagerLoader()->matching($assoc, $builder); + $result = $this->getEagerLoader()->setMatching($assoc, $builder); $this->_addAssociationsToTypeMap($this->repository(), $this->typeMap(), $result); $this->_dirty(); @@ -545,7 +545,7 @@ public function matching($assoc, callable $builder = null) */ public function leftJoinWith($assoc, callable $builder = null) { - $result = $this->getEagerLoader()->matching($assoc, $builder, [ + $result = $this->getEagerLoader()->setMatching($assoc, $builder, [ 'joinType' => 'LEFT', 'fields' => false ]); @@ -592,7 +592,7 @@ public function leftJoinWith($assoc, callable $builder = null) */ public function innerJoinWith($assoc, callable $builder = null) { - $result = $this->getEagerLoader()->matching($assoc, $builder, [ + $result = $this->getEagerLoader()->setMatching($assoc, $builder, [ 'joinType' => 'INNER', 'fields' => false ]); @@ -654,7 +654,7 @@ public function innerJoinWith($assoc, callable $builder = null) */ public function notMatching($assoc, callable $builder = null) { - $result = $this->getEagerLoader()->matching($assoc, $builder, [ + $result = $this->getEagerLoader()->setMatching($assoc, $builder, [ 'joinType' => 'LEFT', 'fields' => false, 'negateMatch' => true From c2724d689c47def252b7968d196354135a77792e Mon Sep 17 00:00:00 2001 From: antograssiot Date: Tue, 10 Jan 2017 06:34:34 +0100 Subject: [PATCH 0866/2059] Use the fluent pattern and update the code related/docs --- EagerLoader.php | 6 ++++-- Query.php | 34 ++++++++++++++++++++-------------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index 72591c01..ddf0041d 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -213,7 +213,7 @@ public function autoFields($enable = null) * @param callable|null $builder the callback function to be used for setting extra * options to the filtering query * @param array $options Extra options for the association matching. - * @return array Containments. + * @return $this */ public function setMatching($assoc, callable $builder = null, $options = []) { @@ -239,7 +239,9 @@ public function setMatching($assoc, callable $builder = null, $options = []) $pointer[$last] = ['queryBuilder' => $builder, 'matching' => true] + $options; - return $this->_matching->contain($containments); + $this->_matching->contain($containments); + + return $this; } /** diff --git a/Query.php b/Query.php index 322ddb97..f03f15cb 100644 --- a/Query.php +++ b/Query.php @@ -473,7 +473,7 @@ protected function _addAssociationsToTypeMap($table, $typeMap, $associations) */ public function matching($assoc, callable $builder = null) { - $result = $this->getEagerLoader()->setMatching($assoc, $builder); + $result = $this->getEagerLoader()->setMatching($assoc, $builder)->getMatching(); $this->_addAssociationsToTypeMap($this->repository(), $this->typeMap(), $result); $this->_dirty(); @@ -545,10 +545,12 @@ public function matching($assoc, callable $builder = null) */ public function leftJoinWith($assoc, callable $builder = null) { - $result = $this->getEagerLoader()->setMatching($assoc, $builder, [ - 'joinType' => 'LEFT', - 'fields' => false - ]); + $result = $this->getEagerLoader() + ->setMatching($assoc, $builder, [ + 'joinType' => 'LEFT', + 'fields' => false + ]) + ->getMatching(); $this->_addAssociationsToTypeMap($this->repository(), $this->typeMap(), $result); $this->_dirty(); @@ -592,10 +594,12 @@ public function leftJoinWith($assoc, callable $builder = null) */ public function innerJoinWith($assoc, callable $builder = null) { - $result = $this->getEagerLoader()->setMatching($assoc, $builder, [ - 'joinType' => 'INNER', - 'fields' => false - ]); + $result = $this->getEagerLoader() + ->setMatching($assoc, $builder, [ + 'joinType' => 'INNER', + 'fields' => false + ]) + ->getMatching(); $this->_addAssociationsToTypeMap($this->repository(), $this->typeMap(), $result); $this->_dirty(); @@ -654,11 +658,13 @@ public function innerJoinWith($assoc, callable $builder = null) */ public function notMatching($assoc, callable $builder = null) { - $result = $this->getEagerLoader()->setMatching($assoc, $builder, [ - 'joinType' => 'LEFT', - 'fields' => false, - 'negateMatch' => true - ]); + $result = $this->getEagerLoader() + ->setMatching($assoc, $builder, [ + 'joinType' => 'LEFT', + 'fields' => false, + 'negateMatch' => true + ]) + ->getMatching(); $this->_addAssociationsToTypeMap($this->repository(), $this->typeMap(), $result); $this->_dirty(); From 71b6f44bbecf34dd59ccf576336e74d58e69ad9e Mon Sep 17 00:00:00 2001 From: Anthony GRASSIOT Date: Tue, 10 Jan 2017 14:17:57 +0100 Subject: [PATCH 0867/2059] Remove deprecated calls in Cake\ORM --- Association.php | 74 ++++---- Association/BelongsTo.php | 32 ++-- Association/BelongsToMany.php | 188 +++++++++---------- Association/DependentDeleteTrait.php | 10 +- Association/HasMany.php | 70 +++---- Association/HasOne.php | 8 +- Association/Loader/SelectLoader.php | 6 +- Association/Loader/SelectWithPivotLoader.php | 6 +- AssociationCollection.php | 8 +- Behavior/CounterCacheBehavior.php | 8 +- Behavior/TranslateBehavior.php | 20 +- Behavior/TreeBehavior.php | 16 +- EagerLoader.php | 42 ++--- LazyEagerLoader.php | 2 +- Marshaller.php | 20 +- Query.php | 38 ++-- ResultSet.php | 14 +- Rule/ExistsIn.php | 2 +- SaveOptionsBuilder.php | 2 +- Table.php | 74 ++++---- 20 files changed, 320 insertions(+), 320 deletions(-) diff --git a/Association.php b/Association.php index 291b953d..3a4f54c4 100644 --- a/Association.php +++ b/Association.php @@ -232,7 +232,7 @@ public function __construct($alias, array $options = []) $this->_options($options); if (!empty($options['strategy'])) { - $this->strategy($options['strategy']); + $this->setStrategy($options['strategy']); } } @@ -490,9 +490,9 @@ public function setBindingKey($key) public function getBindingKey() { if ($this->_bindingKey === null) { - $this->_bindingKey = $this->isOwningSide($this->source()) ? - $this->source()->getPrimaryKey() : - $this->target()->getPrimaryKey(); + $this->_bindingKey = $this->isOwningSide($this->getSource()) ? + $this->getSource()->getPrimaryKey() : + $this->getTarget()->getPrimaryKey(); } return $this->_bindingKey; @@ -617,7 +617,7 @@ public function dependent($dependent = null) */ public function canBeJoined(array $options = []) { - $strategy = isset($options['strategy']) ? $options['strategy'] : $this->strategy(); + $strategy = isset($options['strategy']) ? $options['strategy'] : $this->getStrategy(); return $strategy == $this::STRATEGY_JOIN; } @@ -690,7 +690,7 @@ public function getProperty() $msg = 'Association property name "%s" clashes with field of same name of table "%s".' . ' You should explicitly specify the "propertyName" option.'; trigger_error( - sprintf($msg, $this->_propertyName, $this->_sourceTable->table()), + sprintf($msg, $this->_propertyName, $this->_sourceTable->getTable()), E_USER_WARNING ); } @@ -865,18 +865,18 @@ protected function _options(array $options) */ public function attachTo(Query $query, array $options = []) { - $target = $this->target(); - $joinType = empty($options['joinType']) ? $this->joinType() : $options['joinType']; - $table = $target->table(); + $target = $this->getTarget(); + $joinType = empty($options['joinType']) ? $this->getJoinType() : $options['joinType']; + $table = $target->getTable(); $options += [ 'includeFields' => true, - 'foreignKey' => $this->foreignKey(), + 'foreignKey' => $this->getForeignKey(), 'conditions' => [], 'fields' => [], 'type' => $joinType, 'table' => $table, - 'finder' => $this->finder() + 'finder' => $this->getFinder() ]; if (!empty($options['foreignKey'])) { @@ -896,7 +896,7 @@ public function attachTo(Query $query, array $options = []) if (!($dummy instanceof Query)) { throw new RuntimeException(sprintf( 'Query builder for association "%s" did not return a query', - $this->name() + $this->getName() )); } } @@ -950,9 +950,9 @@ protected function _appendNotMatching($query, $options) */ public function transformRow($row, $nestKey, $joined, $targetProperty = null) { - $sourceAlias = $this->source()->alias(); + $sourceAlias = $this->getSource()->getAlias(); $nestKey = $nestKey ?: $this->_name; - $targetProperty = $targetProperty ?: $this->property(); + $targetProperty = $targetProperty ?: $this->getProperty(); if (isset($row[$sourceAlias])) { $row[$sourceAlias][$targetProperty] = $row[$nestKey]; unset($row[$nestKey]); @@ -973,9 +973,9 @@ public function transformRow($row, $nestKey, $joined, $targetProperty = null) */ public function defaultRowValue($row, $joined) { - $sourceAlias = $this->source()->alias(); + $sourceAlias = $this->getSource()->getAlias(); if (isset($row[$sourceAlias])) { - $row[$sourceAlias][$this->property()] = null; + $row[$sourceAlias][$this->getProperty()] = null; } return $row; @@ -994,12 +994,12 @@ public function defaultRowValue($row, $joined) */ public function find($type = null, array $options = []) { - $type = $type ?: $this->finder(); + $type = $type ?: $this->getFinder(); list($type, $opts) = $this->_extractFinder($type); - return $this->target() + return $this->getTarget() ->find($type, $options + $opts) - ->where($this->conditions()); + ->where($this->getConditions()); } /** @@ -1019,7 +1019,7 @@ public function exists($conditions) ->clause('where'); } - return $this->target()->exists($conditions); + return $this->getTarget()->exists($conditions); } /** @@ -1033,9 +1033,9 @@ public function exists($conditions) */ public function updateAll($fields, $conditions) { - $target = $this->target(); + $target = $this->getTarget(); $expression = $target->query() - ->where($this->conditions()) + ->where($this->getConditions()) ->where($conditions) ->clause('where'); @@ -1052,9 +1052,9 @@ public function updateAll($fields, $conditions) */ public function deleteAll($conditions) { - $target = $this->target(); + $target = $this->getTarget(); $expression = $target->query() - ->where($this->conditions()) + ->where($this->getConditions()) ->where($conditions) ->clause('where'); @@ -1070,7 +1070,7 @@ public function deleteAll($conditions) */ public function requiresKeys(array $options = []) { - $strategy = isset($options['strategy']) ? $options['strategy'] : $this->strategy(); + $strategy = isset($options['strategy']) ? $options['strategy'] : $this->getStrategy(); return $strategy === static::STRATEGY_SELECT; } @@ -1098,13 +1098,13 @@ protected function _dispatchBeforeFind($query) */ protected function _appendFields($query, $surrogate, $options) { - if ($query->getEagerLoader()->autoFields() === false) { + if ($query->getEagerLoader()->isAutoFieldsEnabled() === false) { return; } $fields = $surrogate->clause('select') ?: $options['fields']; $target = $this->_targetTable; - $autoFields = $surrogate->autoFields(); + $autoFields = $surrogate->isAutoFieldsEnabled(); if (empty($fields) && !$autoFields) { if ($options['includeFields'] && ($fields === null || $fields !== false)) { @@ -1117,7 +1117,7 @@ protected function _appendFields($query, $surrogate, $options) } if ($fields) { - $query->select($query->aliasFields($fields, $target->alias())); + $query->select($query->aliasFields($fields, $target->getAlias())); } $query->addDefaultTypes($target); } @@ -1183,7 +1183,7 @@ protected function _bindNewAssociations($query, $surrogate, $options) { $loader = $surrogate->getEagerLoader(); $contain = $loader->contain(); - $matching = $loader->matching(); + $matching = $loader->getMatching(); if (!$contain && !$matching) { return; @@ -1198,7 +1198,7 @@ protected function _bindNewAssociations($query, $surrogate, $options) $eagerLoader->contain($newContain); foreach ($matching as $alias => $value) { - $eagerLoader->matching( + $eagerLoader->setMatching( $options['aliasPath'] . '.' . $alias, $value['queryBuilder'], $value @@ -1218,15 +1218,15 @@ protected function _bindNewAssociations($query, $surrogate, $options) protected function _joinCondition($options) { $conditions = []; - $tAlias = $this->target()->alias(); - $sAlias = $this->source()->alias(); + $tAlias = $this->getTarget()->getAlias(); + $sAlias = $this->getSource()->getAlias(); $foreignKey = (array)$options['foreignKey']; - $bindingKey = (array)$this->bindingKey(); + $bindingKey = (array)$this->getBindingKey(); if (count($foreignKey) !== count($bindingKey)) { if (empty($bindingKey)) { $msg = 'The "%s" table does not define a primary key. Please set one.'; - throw new RuntimeException(sprintf($msg, $this->target()->table())); + throw new RuntimeException(sprintf($msg, $this->getTarget()->getTable())); } $msg = 'Cannot match provided foreignKey for "%s", got "(%s)" but expected foreign key for "(%s)"'; @@ -1284,7 +1284,7 @@ protected function _extractFinder($finderData) */ public function __get($property) { - return $this->target()->{$property}; + return $this->getTarget()->{$property}; } /** @@ -1296,7 +1296,7 @@ public function __get($property) */ public function __isset($property) { - return isset($this->target()->{$property}); + return isset($this->getTarget()->{$property}); } /** @@ -1309,7 +1309,7 @@ public function __isset($property) */ public function __call($method, $argument) { - return $this->target()->$method(...$argument); + return $this->getTarget()->$method(...$argument); } /** diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 9409ce85..d89d0e60 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -49,7 +49,7 @@ class BelongsTo extends Association public function getForeignKey() { if ($this->_foreignKey === null) { - $this->_foreignKey = $this->_modelKey($this->target()->alias()); + $this->_foreignKey = $this->_modelKey($this->getTarget()->getAlias()); } return $this->_foreignKey; @@ -108,7 +108,7 @@ protected function _propertyName() */ public function isOwningSide(Table $side) { - return $side === $this->target(); + return $side === $this->getTarget(); } /** @@ -136,20 +136,20 @@ public function type() */ public function saveAssociated(EntityInterface $entity, array $options = []) { - $targetEntity = $entity->get($this->property()); + $targetEntity = $entity->get($this->getProperty()); if (empty($targetEntity) || !($targetEntity instanceof EntityInterface)) { return $entity; } - $table = $this->target(); + $table = $this->getTarget(); $targetEntity = $table->save($targetEntity, $options); if (!$targetEntity) { return false; } $properties = array_combine( - (array)$this->foreignKey(), - $targetEntity->extract((array)$this->bindingKey()) + (array)$this->getForeignKey(), + $targetEntity->extract((array)$this->getBindingKey()) ); $entity->set($properties, ['guard' => false]); @@ -168,15 +168,15 @@ public function saveAssociated(EntityInterface $entity, array $options = []) protected function _joinCondition($options) { $conditions = []; - $tAlias = $this->target()->alias(); - $sAlias = $this->_sourceTable->alias(); + $tAlias = $this->getTarget()->getAlias(); + $sAlias = $this->_sourceTable->getAlias(); $foreignKey = (array)$options['foreignKey']; - $bindingKey = (array)$this->bindingKey(); + $bindingKey = (array)$this->getBindingKey(); if (count($foreignKey) !== count($bindingKey)) { if (empty($bindingKey)) { $msg = 'The "%s" table does not define a primary key. Please set one.'; - throw new RuntimeException(sprintf($msg, $this->target()->table())); + throw new RuntimeException(sprintf($msg, $this->setTarget()->getTable())); } $msg = 'Cannot match provided foreignKey for "%s", got "(%s)" but expected foreign key for "(%s)"'; @@ -205,12 +205,12 @@ protected function _joinCondition($options) public function eagerLoader(array $options) { $loader = new SelectLoader([ - 'alias' => $this->alias(), - 'sourceAlias' => $this->source()->alias(), - 'targetAlias' => $this->target()->alias(), - 'foreignKey' => $this->foreignKey(), - 'bindingKey' => $this->bindingKey(), - 'strategy' => $this->strategy(), + 'alias' => $this->getAlias(), + 'sourceAlias' => $this->getSource()->getAlias(), + 'targetAlias' => $this->getTarget()->getAlias(), + 'foreignKey' => $this->getForeignKey(), + 'bindingKey' => $this->getBindingKey(), + 'strategy' => $this->getStrategy(), 'associationType' => $this->type(), 'finder' => [$this, 'find'] ]); diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index fc0e8655..9135f95c 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -177,7 +177,7 @@ public function setTargetForeignKey($key) public function getTargetForeignKey() { if ($this->_targetForeignKey === null) { - $this->_targetForeignKey = $this->_modelKey($this->target()->alias()); + $this->_targetForeignKey = $this->_modelKey($this->getTarget()->getAlias()); } return $this->_targetForeignKey; @@ -220,7 +220,7 @@ public function canBeJoined(array $options = []) public function getForeignKey() { if ($this->_foreignKey === null) { - $this->_foreignKey = $this->_modelKey($this->source()->table()); + $this->_foreignKey = $this->_modelKey($this->getSource()->getTable()); } return $this->_foreignKey; @@ -263,9 +263,9 @@ public function sort($sort = null) */ public function defaultRowValue($row, $joined) { - $sourceAlias = $this->source()->alias(); + $sourceAlias = $this->getSource()->getAlias(); if (isset($row[$sourceAlias])) { - $row[$sourceAlias][$this->property()] = $joined ? null : []; + $row[$sourceAlias][$this->getProperty()] = $joined ? null : []; } return $row; @@ -297,7 +297,7 @@ public function junction($table = null) // Propagate the connection if we'll get an auto-model if (!App::className($tableAlias, 'Model/Table', 'Table')) { - $config['connection'] = $this->source()->connection(); + $config['connection'] = $this->getSource()->getConnection(); } } $table = $tableLocator->get($tableAlias, $config); @@ -306,8 +306,8 @@ public function junction($table = null) if (is_string($table)) { $table = $tableLocator->get($table); } - $source = $this->source(); - $target = $this->target(); + $source = $this->getSource(); + $target = $this->getTarget(); $this->_generateSourceAssociations($table, $source); $this->_generateTargetAssociations($table, $source, $target); @@ -334,13 +334,13 @@ public function junction($table = null) */ protected function _generateTargetAssociations($junction, $source, $target) { - $junctionAlias = $junction->alias(); - $sAlias = $source->alias(); + $junctionAlias = $junction->getAlias(); + $sAlias = $source->getAlias(); if (!$target->association($junctionAlias)) { $target->hasMany($junctionAlias, [ 'targetTable' => $junction, - 'foreignKey' => $this->targetForeignKey(), + 'foreignKey' => $this->getTargetForeignKey(), 'strategy' => $this->_strategy, ]); } @@ -348,10 +348,10 @@ protected function _generateTargetAssociations($junction, $source, $target) $target->belongsToMany($sAlias, [ 'sourceTable' => $target, 'targetTable' => $source, - 'foreignKey' => $this->targetForeignKey(), - 'targetForeignKey' => $this->foreignKey(), + 'foreignKey' => $this->getTargetForeignKey(), + 'targetForeignKey' => $this->getForeignKey(), 'through' => $junction, - 'conditions' => $this->conditions(), + 'conditions' => $this->getConditions(), 'strategy' => $this->_strategy, ]); } @@ -373,11 +373,11 @@ protected function _generateTargetAssociations($junction, $source, $target) */ protected function _generateSourceAssociations($junction, $source) { - $junctionAlias = $junction->alias(); + $junctionAlias = $junction->getAlias(); if (!$source->association($junctionAlias)) { $source->hasMany($junctionAlias, [ 'targetTable' => $junction, - 'foreignKey' => $this->foreignKey(), + 'foreignKey' => $this->getForeignKey(), 'strategy' => $this->_strategy, ]); } @@ -401,18 +401,18 @@ protected function _generateSourceAssociations($junction, $source) */ protected function _generateJunctionAssociations($junction, $source, $target) { - $tAlias = $target->alias(); - $sAlias = $source->alias(); + $tAlias = $target->getAlias(); + $sAlias = $source->getAlias(); if (!$junction->association($tAlias)) { $junction->belongsTo($tAlias, [ - 'foreignKey' => $this->targetForeignKey(), + 'foreignKey' => $this->getTargetForeignKey(), 'targetTable' => $target ]); } if (!$junction->association($sAlias)) { $junction->belongsTo($sAlias, [ - 'foreignKey' => $this->foreignKey(), + 'foreignKey' => $this->getForeignKey(), 'targetTable' => $source ]); } @@ -444,8 +444,8 @@ public function attachTo(Query $query, array $options = []) } $junction = $this->junction(); - $belongsTo = $junction->association($this->source()->alias()); - $cond = $belongsTo->_joinCondition(['foreignKey' => $belongsTo->foreignKey()]); + $belongsTo = $junction->association($this->getSource()->getAlias()); + $cond = $belongsTo->_joinCondition(['foreignKey' => $belongsTo->getForeignKey()]); $cond += $this->junctionConditions(); if (isset($options['includeFields'])) { @@ -453,7 +453,7 @@ public function attachTo(Query $query, array $options = []) } // Attach the junction table as well we need it to populate _joinData. - $assoc = $this->_targetTable->association($junction->alias()); + $assoc = $this->_targetTable->association($junction->getAlias()); $newOptions = array_intersect_key($options, ['joinType' => 1, 'fields' => 1]); $newOptions += [ 'conditions' => $cond, @@ -461,12 +461,12 @@ public function attachTo(Query $query, array $options = []) 'foreignKey' => false, ]; $assoc->attachTo($query, $newOptions); - $query->getEagerLoader()->addToJoinsMap($junction->alias(), $assoc, true); + $query->getEagerLoader()->addToJoinsMap($junction->getAlias(), $assoc, true); parent::attachTo($query, $options); - $foreignKey = $this->targetForeignKey(); - $thisJoin = $query->clause('join')[$this->name()]; + $foreignKey = $this->getTargetForeignKey(); + $thisJoin = $query->clause('join')[$this->getName()]; $thisJoin['conditions']->add($assoc->_joinCondition(['foreignKey' => $foreignKey])); } @@ -482,8 +482,8 @@ protected function _appendNotMatching($query, $options) $options['conditions'] = []; } $junction = $this->junction(); - $belongsTo = $junction->association($this->source()->alias()); - $conds = $belongsTo->_joinCondition(['foreignKey' => $belongsTo->foreignKey()]); + $belongsTo = $junction->association($this->getSource()->getAlias()); + $conds = $belongsTo->_joinCondition(['foreignKey' => $belongsTo->getForeignKey()]); $subquery = $this->find() ->select(array_values($conds)) @@ -494,9 +494,9 @@ protected function _appendNotMatching($query, $options) $subquery = $options['queryBuilder']($subquery); } - $assoc = $junction->association($this->target()->alias()); + $assoc = $junction->association($this->getTarget()->getAlias()); $conditions = $assoc->_joinCondition([ - 'foreignKey' => $this->targetForeignKey() + 'foreignKey' => $this->getTargetForeignKey() ]); $subquery = $this->_appendJunctionJoin($subquery, $conditions); @@ -506,7 +506,7 @@ protected function _appendNotMatching($query, $options) foreach (array_keys($conds) as $field) { $identifiers = new IdentifierExpression($field); } - $identifiers = $subquery->newExpr()->add($identifiers)->tieWith(','); + $identifiers = $subquery->newExpr()->add($identifiers)->setConjunction(','); $nullExp = clone $exp; return $exp @@ -547,17 +547,17 @@ public function eagerLoader(array $options) { $name = $this->_junctionAssociationName(); $loader = new SelectWithPivotLoader([ - 'alias' => $this->alias(), - 'sourceAlias' => $this->source()->alias(), - 'targetAlias' => $this->target()->alias(), - 'foreignKey' => $this->foreignKey(), - 'bindingKey' => $this->bindingKey(), - 'strategy' => $this->strategy(), + 'alias' => $this->getAlias(), + 'sourceAlias' => $this->getSource()->getAlias(), + 'targetAlias' => $this->getTarget()->getAlias(), + 'foreignKey' => $this->getForeignKey(), + 'bindingKey' => $this->getBindingKey(), + 'strategy' => $this->getStrategy(), 'associationType' => $this->type(), 'sort' => $this->sort(), 'junctionAssociationName' => $name, 'junctionProperty' => $this->_junctionProperty, - 'junctionAssoc' => $this->target()->association($name), + 'junctionAssoc' => $this->getTarget()->association($name), 'junctionConditions' => $this->junctionConditions(), 'finder' => function () { return $this->_appendJunctionJoin($this->find(), []); @@ -576,11 +576,11 @@ public function eagerLoader(array $options) */ public function cascadeDelete(EntityInterface $entity, array $options = []) { - if (!$this->dependent()) { + if (!$this->getDependent()) { return true; } - $foreignKey = (array)$this->foreignKey(); - $bindingKey = (array)$this->bindingKey(); + $foreignKey = (array)$this->getForeignKey(); + $bindingKey = (array)$this->getBindingKey(); $conditions = []; if (!empty($bindingKey)) { @@ -588,7 +588,7 @@ public function cascadeDelete(EntityInterface $entity, array $options = []) } $table = $this->junction(); - $hasMany = $this->source()->association($table->alias()); + $hasMany = $this->getSource()->association($table->getAlias()); if ($this->_cascadeCallbacks) { foreach ($hasMany->find('all')->where($conditions)->toList() as $related) { $table->delete($related, $options); @@ -597,7 +597,7 @@ public function cascadeDelete(EntityInterface $entity, array $options = []) return true; } - $conditions = array_merge($conditions, $hasMany->conditions()); + $conditions = array_merge($conditions, $hasMany->getConditions()); return $table->deleteAll($conditions); } @@ -688,8 +688,8 @@ public function saveStrategy($strategy = null) */ public function saveAssociated(EntityInterface $entity, array $options = []) { - $targetEntity = $entity->get($this->property()); - $strategy = $this->saveStrategy(); + $targetEntity = $entity->get($this->getProperty()); + $strategy = $this->getSaveStrategy(); $isEmpty = in_array($targetEntity, [null, [], '', false], true); if ($isEmpty && $entity->isNew()) { @@ -733,12 +733,12 @@ protected function _saveTarget(EntityInterface $parentEntity, $entities, $option unset($options['associated'][$this->_junctionProperty]); if (!(is_array($entities) || $entities instanceof Traversable)) { - $name = $this->property(); + $name = $this->getProperty(); $message = sprintf('Could not save %s, it cannot be traversed', $name); throw new InvalidArgumentException($message); } - $table = $this->target(); + $table = $this->getTarget(); $original = $entities; $persisted = []; @@ -771,12 +771,12 @@ protected function _saveTarget(EntityInterface $parentEntity, $entities, $option $options['associated'] = $joinAssociations; $success = $this->_saveLinks($parentEntity, $persisted, $options); if (!$success && !empty($options['atomic'])) { - $parentEntity->set($this->property(), $original); + $parentEntity->set($this->getProperty(), $original); return false; } - $parentEntity->set($this->property(), $entities); + $parentEntity->set($this->getProperty(), $entities); return $parentEntity; } @@ -793,16 +793,16 @@ protected function _saveTarget(EntityInterface $parentEntity, $entities, $option */ protected function _saveLinks(EntityInterface $sourceEntity, $targetEntities, $options) { - $target = $this->target(); + $target = $this->getTarget(); $junction = $this->junction(); - $entityClass = $junction->entityClass(); - $belongsTo = $junction->association($target->alias()); - $foreignKey = (array)$this->foreignKey(); - $assocForeignKey = (array)$belongsTo->foreignKey(); + $entityClass = $junction->getEntityClass(); + $belongsTo = $junction->association($target->getAlias()); + $foreignKey = (array)$this->getForeignKey(); + $assocForeignKey = (array)$belongsTo->getForeignKey(); $targetPrimaryKey = (array)$target->getPrimaryKey(); - $bindingKey = (array)$this->bindingKey(); + $bindingKey = (array)$this->getBindingKey(); $jointProperty = $this->_junctionProperty; - $junctionAlias = $junction->alias(); + $junctionAlias = $junction->getAlias(); foreach ($targetEntities as $e) { $joint = $e->get($jointProperty); @@ -865,12 +865,12 @@ protected function _saveLinks(EntityInterface $sourceEntity, $targetEntities, $o public function link(EntityInterface $sourceEntity, array $targetEntities, array $options = []) { $this->_checkPersistenceStatus($sourceEntity, $targetEntities); - $property = $this->property(); + $property = $this->getProperty(); $links = $sourceEntity->get($property) ?: []; $links = array_merge($links, $targetEntities); $sourceEntity->set($property, $links); - return $this->junction()->connection()->transactional( + return $this->junction()->getConnection()->transactional( function () use ($sourceEntity, $targetEntities, $options) { return $this->_saveLinks($sourceEntity, $targetEntities, $options); } @@ -924,9 +924,9 @@ public function unlink(EntityInterface $sourceEntity, array $targetEntities, $op } $this->_checkPersistenceStatus($sourceEntity, $targetEntities); - $property = $this->property(); + $property = $this->getProperty(); - $this->junction()->connection()->transactional( + $this->junction()->getConnection()->transactional( function () use ($sourceEntity, $targetEntities, $options) { $links = $this->_collectJointEntities($sourceEntity, $targetEntities); foreach ($links as $entity) { @@ -1019,12 +1019,12 @@ protected function targetConditions() if ($this->_targetConditions !== null) { return $this->_targetConditions; } - $conditions = $this->conditions(); + $conditions = $this->getConditions(); if (!is_array($conditions)) { return $conditions; } $matching = []; - $alias = $this->alias() . '.'; + $alias = $this->getAlias() . '.'; foreach ($conditions as $field => $value) { if (is_string($field) && strpos($field, $alias) === 0) { $matching[$field] = $value; @@ -1048,7 +1048,7 @@ protected function junctionConditions() return $this->_junctionConditions; } $matching = []; - $conditions = $this->conditions(); + $conditions = $this->getConditions(); if (!is_array($conditions)) { return $matching; } @@ -1084,20 +1084,20 @@ protected function junctionConditions() */ public function find($type = null, array $options = []) { - $type = $type ?: $this->finder(); + $type = $type ?: $this->getFinder() ; list($type, $opts) = $this->_extractFinder($type); - $query = $this->target() + $query = $this->getTarget() ->find($type, $options + $opts) ->where($this->targetConditions()) - ->addDefaultTypes($this->target()); + ->addDefaultTypes($this->getTarget()); if (!$this->junctionConditions()) { return $query; } - $belongsTo = $this->junction()->association($this->target()->alias()); + $belongsTo = $this->junction()->association($this->getTarget()->getAlias()); $conditions = $belongsTo->_joinCondition([ - 'foreignKey' => $this->targetForeignKey() + 'foreignKey' => $this->getTargetForeignKey() ]); $conditions += $this->junctionConditions(); @@ -1117,15 +1117,15 @@ protected function _appendJunctionJoin($query, $conditions) $joins = $query->join(); $matching = [ $name => [ - 'table' => $this->junction()->table(), + 'table' => $this->junction()->getTable(), 'conditions' => $conditions, 'type' => 'INNER' ] ]; - $assoc = $this->target()->association($name); + $assoc = $this->getTarget()->association($name); $query - ->addDefaultTypes($assoc->target()) + ->addDefaultTypes($assoc->getTarget()) ->join($matching + $joins, [], true); return $query; @@ -1182,7 +1182,7 @@ protected function _appendJunctionJoin($query, $conditions) */ public function replaceLinks(EntityInterface $sourceEntity, array $targetEntities, array $options = []) { - $bindingKey = (array)$this->bindingKey(); + $bindingKey = (array)$this->getBindingKey(); $primaryValue = $sourceEntity->extract($bindingKey); if (count(array_filter($primaryValue, 'strlen')) !== count($bindingKey)) { @@ -1190,16 +1190,16 @@ public function replaceLinks(EntityInterface $sourceEntity, array $targetEntitie throw new InvalidArgumentException($message); } - return $this->junction()->connection()->transactional( + return $this->junction()->getConnection()->transactional( function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { - $foreignKey = array_map([$this->_junctionTable, 'aliasField'], (array)$this->foreignKey()); - $hasMany = $this->source()->association($this->_junctionTable->alias()); + $foreignKey = array_map([$this->_junctionTable, 'aliasField'], (array)$this->getForeignKey()); + $hasMany = $this->getSource()->association($this->_junctionTable->getAlias()); $existing = $hasMany->find('all') ->where(array_combine($foreignKey, $primaryValue)); - $associationConditions = $this->conditions(); + $associationConditions = $this->getConditions(); if ($associationConditions) { - $existing->contain($this->target()->alias()); + $existing->contain($this->getTarget()->getAlias()); $existing->andWhere($associationConditions); } @@ -1210,7 +1210,7 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { return false; } - $property = $this->property(); + $property = $this->getProperty(); if (count($inserts)) { $inserted = array_combine( @@ -1244,10 +1244,10 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { protected function _diffLinks($existing, $jointEntities, $targetEntities, $options = []) { $junction = $this->junction(); - $target = $this->target(); - $belongsTo = $junction->association($target->alias()); - $foreignKey = (array)$this->foreignKey(); - $assocForeignKey = (array)$belongsTo->foreignKey(); + $target = $this->getTarget(); + $belongsTo = $junction->association($target->getAlias()); + $foreignKey = (array)$this->getForeignKey(); + $assocForeignKey = (array)$belongsTo->getForeignKey(); $keys = array_merge($foreignKey, $assocForeignKey); $deletes = $indexed = $present = []; @@ -1338,8 +1338,8 @@ protected function _checkPersistenceStatus($sourceEntity, array $targetEntities) */ protected function _collectJointEntities($sourceEntity, $targetEntities) { - $target = $this->target(); - $source = $this->source(); + $target = $this->getTarget(); + $source = $this->getSource(); $junction = $this->junction(); $jointProperty = $this->_junctionProperty; $primary = (array)$target->getPrimaryKey(); @@ -1365,10 +1365,10 @@ protected function _collectJointEntities($sourceEntity, $targetEntities) return $result; } - $belongsTo = $junction->association($target->alias()); - $hasMany = $source->association($junction->alias()); - $foreignKey = (array)$this->foreignKey(); - $assocForeignKey = (array)$belongsTo->foreignKey(); + $belongsTo = $junction->association($target->getAlias()); + $hasMany = $source->association($junction->getAlias()); + $foreignKey = (array)$this->getForeignKey(); + $assocForeignKey = (array)$belongsTo->getForeignKey(); $sourceKey = $sourceEntity->extract((array)$source->getPrimaryKey()); foreach ($missing as $key) { @@ -1395,9 +1395,9 @@ protected function _collectJointEntities($sourceEntity, $targetEntities) protected function _junctionAssociationName() { if (!$this->_junctionAssociationName) { - $this->_junctionAssociationName = $this->target() - ->association($this->junction()->alias()) - ->name(); + $this->_junctionAssociationName = $this->getTarget() + ->association($this->junction()->getAlias()) + ->getName(); } return $this->_junctionAssociationName; @@ -1416,8 +1416,8 @@ protected function _junctionTableName($name = null) if ($name === null) { if (empty($this->_junctionTableName)) { $tablesNames = array_map('\Cake\Utility\Inflector::underscore', [ - $this->source()->table(), - $this->target()->table() + $this->getSource()->getTable(), + $this->getTarget()->getTable() ]); sort($tablesNames); $this->_junctionTableName = implode('_', $tablesNames); @@ -1438,7 +1438,7 @@ protected function _junctionTableName($name = null) protected function _options(array $opts) { if (!empty($opts['targetForeignKey'])) { - $this->targetForeignKey($opts['targetForeignKey']); + $this->setTargetForeignKey($opts['targetForeignKey']); } if (!empty($opts['joinTable'])) { $this->_junctionTableName($opts['joinTable']); @@ -1447,7 +1447,7 @@ protected function _options(array $opts) $this->setThrough($opts['through']); } if (!empty($opts['saveStrategy'])) { - $this->saveStrategy($opts['saveStrategy']); + $this->setSaveStrategy($opts['saveStrategy']); } if (isset($opts['sort'])) { $this->sort($opts['sort']); diff --git a/Association/DependentDeleteTrait.php b/Association/DependentDeleteTrait.php index 1c2424b6..b3614f2e 100644 --- a/Association/DependentDeleteTrait.php +++ b/Association/DependentDeleteTrait.php @@ -35,12 +35,12 @@ trait DependentDeleteTrait */ public function cascadeDelete(EntityInterface $entity, array $options = []) { - if (!$this->dependent()) { + if (!$this->getDependent()) { return true; } - $table = $this->target(); - $foreignKey = (array)$this->foreignKey(); - $bindingKey = (array)$this->bindingKey(); + $table = $this->getTarget(); + $foreignKey = (array)$this->getForeignKey(); + $bindingKey = (array)$this->getBindingKey(); $conditions = array_combine($foreignKey, $entity->extract($bindingKey)); if ($this->_cascadeCallbacks) { @@ -51,7 +51,7 @@ public function cascadeDelete(EntityInterface $entity, array $options = []) return true; } - $conditions = array_merge($conditions, $this->conditions()); + $conditions = array_merge($conditions, $this->getConditions()); return $table->deleteAll($conditions); } diff --git a/Association/HasMany.php b/Association/HasMany.php index 534316f2..ffbb39b4 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -98,7 +98,7 @@ class HasMany extends Association */ public function isOwningSide(Table $side) { - return $side === $this->source(); + return $side === $this->getSource(); } /** @@ -164,25 +164,25 @@ public function saveStrategy($strategy = null) */ public function saveAssociated(EntityInterface $entity, array $options = []) { - $targetEntities = $entity->get($this->property()); + $targetEntities = $entity->get($this->getProperty()); if (empty($targetEntities) && $this->_saveStrategy !== self::SAVE_REPLACE) { return $entity; } if (!is_array($targetEntities) && !($targetEntities instanceof Traversable)) { - $name = $this->property(); + $name = $this->getProperty(); $message = sprintf('Could not save %s, it cannot be traversed', $name); throw new InvalidArgumentException($message); } - $foreignKey = (array)$this->foreignKey(); + $foreignKey = (array)$this->getForeignKey(); $properties = array_combine( $foreignKey, - $entity->extract((array)$this->bindingKey()) + $entity->extract((array)$this->getBindingKey()) ); - $target = $this->target(); + $target = $this->getTarget(); $original = $targetEntities; - $options['_sourceTable'] = $this->source(); + $options['_sourceTable'] = $this->getSource(); $unlinkSuccessful = null; if ($this->_saveStrategy === self::SAVE_REPLACE) { @@ -213,13 +213,13 @@ public function saveAssociated(EntityInterface $entity, array $options = []) if (!empty($options['atomic'])) { $original[$k]->errors($targetEntity->errors()); - $entity->set($this->property(), $original); + $entity->set($this->getProperty(), $original); return false; } } - $entity->set($this->property(), $targetEntities); + $entity->set($this->getProperty(), $targetEntities); return $entity; } @@ -251,9 +251,9 @@ public function saveAssociated(EntityInterface $entity, array $options = []) */ public function link(EntityInterface $sourceEntity, array $targetEntities, array $options = []) { - $saveStrategy = $this->saveStrategy(); - $this->saveStrategy(self::SAVE_APPEND); - $property = $this->property(); + $saveStrategy = $this->getSaveStrategy(); + $this->setSaveStrategy(self::SAVE_APPEND); + $property = $this->getProperty(); $currentEntities = array_unique( array_merge( @@ -268,7 +268,7 @@ public function link(EntityInterface $sourceEntity, array $targetEntities, array $ok = ($savedEntity instanceof EntityInterface); - $this->saveStrategy($saveStrategy); + $this->setSaveStrategy($saveStrategy); if ($ok) { $sourceEntity->set($property, $savedEntity->get($property)); @@ -330,10 +330,10 @@ public function unlink(EntityInterface $sourceEntity, array $targetEntities, $op return; } - $foreignKey = (array)$this->foreignKey(); - $target = $this->target(); + $foreignKey = (array)$this->getForeignKey(); + $target = $this->getTarget(); $targetPrimaryKey = array_merge((array)$target->getPrimaryKey(), $foreignKey); - $property = $this->property(); + $property = $this->getProperty(); $conditions = [ 'OR' => (new Collection($targetEntities)) @@ -407,17 +407,17 @@ function ($assoc) use ($targetEntities) { */ public function replace(EntityInterface $sourceEntity, array $targetEntities, array $options = []) { - $property = $this->property(); + $property = $this->getProperty(); $sourceEntity->set($property, $targetEntities); - $saveStrategy = $this->saveStrategy(); - $this->saveStrategy(self::SAVE_REPLACE); + $saveStrategy = $this->getSaveStrategy(); + $this->setSaveStrategy(self::SAVE_REPLACE); $result = $this->saveAssociated($sourceEntity, $options); $ok = ($result instanceof EntityInterface); if ($ok) { $sourceEntity = $result; } - $this->saveStrategy($saveStrategy); + $this->setSaveStrategy($saveStrategy); return $ok; } @@ -475,7 +475,7 @@ function ($v) { */ protected function _unlink(array $foreignKey, Table $target, array $conditions = [], array $options = []) { - $mustBeDependent = (!$this->_foreignKeyAcceptsNull($target, $foreignKey) || $this->dependent()); + $mustBeDependent = (!$this->_foreignKeyAcceptsNull($target, $foreignKey) || $this->getDependent()); if ($mustBeDependent) { if ($this->_cascadeCallbacks) { @@ -494,14 +494,14 @@ protected function _unlink(array $foreignKey, Table $target, array $conditions = return $ok; } - $conditions = array_merge($conditions, $this->conditions()); + $conditions = array_merge($conditions, $this->getConditions()); $target->deleteAll($conditions); return true; } $updateFields = array_fill_keys($foreignKey, null); - $conditions = array_merge($conditions, $this->conditions()); + $conditions = array_merge($conditions, $this->getConditions()); $target->updateAll($updateFields, $conditions); return true; @@ -557,7 +557,7 @@ public function canBeJoined(array $options = []) public function getForeignKey() { if ($this->_foreignKey === null) { - $this->_foreignKey = $this->_modelKey($this->source()->table()); + $this->_foreignKey = $this->_modelKey($this->getSource()->getTable()); } return $this->_foreignKey; @@ -625,9 +625,9 @@ public function sort($sort = null) */ public function defaultRowValue($row, $joined) { - $sourceAlias = $this->source()->alias(); + $sourceAlias = $this->getSource()->getAlias(); if (isset($row[$sourceAlias])) { - $row[$sourceAlias][$this->property()] = $joined ? null : []; + $row[$sourceAlias][$this->getProperty()] = $joined ? null : []; } return $row; @@ -642,10 +642,10 @@ public function defaultRowValue($row, $joined) protected function _options(array $opts) { if (!empty($opts['saveStrategy'])) { - $this->saveStrategy($opts['saveStrategy']); + $this->setSaveStrategy($opts['saveStrategy']); } if (isset($opts['sort'])) { - $this->sort($opts['sort']); + $this->setSort($opts['sort']); } } @@ -657,14 +657,14 @@ protected function _options(array $opts) public function eagerLoader(array $options) { $loader = new SelectLoader([ - 'alias' => $this->alias(), - 'sourceAlias' => $this->source()->alias(), - 'targetAlias' => $this->target()->alias(), - 'foreignKey' => $this->foreignKey(), - 'bindingKey' => $this->bindingKey(), - 'strategy' => $this->strategy(), + 'alias' => $this->getAlias(), + 'sourceAlias' => $this->getSource()->getAlias(), + 'targetAlias' => $this->getTarget()->getAlias(), + 'foreignKey' => $this->getForeignKey(), + 'bindingKey' => $this->getBindingKey(), + 'strategy' => $this->getStrategy(), 'associationType' => $this->type(), - 'sort' => $this->sort(), + 'sort' => $this->getSort(), 'finder' => [$this, 'find'] ]); diff --git a/Association/HasOne.php b/Association/HasOne.php index 36a34b7f..8b4fb081 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -122,18 +122,18 @@ public function type() */ public function saveAssociated(EntityInterface $entity, array $options = []) { - $targetEntity = $entity->get($this->property()); + $targetEntity = $entity->get($this->getProperty()); if (empty($targetEntity) || !($targetEntity instanceof EntityInterface)) { return $entity; } $properties = array_combine( - (array)$this->foreignKey(), - $entity->extract((array)$this->bindingKey()) + (array)$this->getForeignKey(), + $entity->extract((array)$this->getBindingKey()) ); $targetEntity->set($properties, ['guard' => false]); - if (!$this->target()->save($targetEntity, $options)) { + if (!$this->getTarget()->save($targetEntity, $options)) { $targetEntity->unsetProperty(array_keys($properties)); return false; diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 71244ae9..e4c663f5 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -223,7 +223,7 @@ protected function _assertFieldsPresent($fetchQuery, $key) $missingFields = $missingKey($select, $key); if ($missingFields) { - $driver = $fetchQuery->connection()->driver(); + $driver = $fetchQuery->getConnection()->driver(); $quoted = array_map([$driver, 'quoteIdentifier'], $key); $missingFields = $missingKey($select, $quoted); } @@ -309,7 +309,7 @@ protected function _addFilteringCondition($query, $key, $filter) protected function _createTupleCondition($query, $keys, $filter, $operator) { $types = []; - $defaults = $query->defaultTypes(); + $defaults = $query->getDefaultTypes(); foreach ($keys as $k) { if (isset($defaults[$k])) { $types[] = $defaults[$k]; @@ -363,7 +363,7 @@ protected function _linkField($options) protected function _buildSubquery($query) { $filterQuery = clone $query; - $filterQuery->autoFields(false); + $filterQuery->enableAutoFields(false); $filterQuery->mapReduce(null, null, true); $filterQuery->formatResults(null, true); $filterQuery->contain([], true); diff --git a/Association/Loader/SelectWithPivotLoader.php b/Association/Loader/SelectWithPivotLoader.php index 9589076b..0ab49225 100644 --- a/Association/Loader/SelectWithPivotLoader.php +++ b/Association/Loader/SelectWithPivotLoader.php @@ -94,8 +94,8 @@ protected function _buildQuery($options) $query = $queryBuilder($query); } - if ($query->autoFields() === null) { - $query->autoFields($query->clause('select') === []); + if ($query->isAutoFieldsEnabled() === null) { + $query->enableAutoFields($query->clause('select') === []); } // Ensure that association conditions are applied @@ -165,7 +165,7 @@ protected function _buildResultMap($fetchQuery, $options) { $resultMap = []; $key = (array)$options['foreignKey']; - $hydrated = $fetchQuery->hydrate(); + $hydrated = $fetchQuery->isHydrationEnabled(); foreach ($fetchQuery->all() as $result) { if (!isset($result[$this->junctionProperty])) { diff --git a/AssociationCollection.php b/AssociationCollection.php index cae9a360..c3ac6c66 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -79,7 +79,7 @@ public function get($alias) public function getByProperty($prop) { foreach ($this->_items as $assoc) { - if ($assoc->property() === $prop) { + if ($assoc->getProperty() === $prop) { return $assoc; } } @@ -224,7 +224,7 @@ protected function _saveAssociations($table, $entity, $associations, $options, $ $msg = sprintf( 'Cannot save %s, it is not associated to %s', $alias, - $table->alias() + $table->getAlias() ); throw new InvalidArgumentException($msg); } @@ -250,7 +250,7 @@ protected function _saveAssociations($table, $entity, $associations, $options, $ */ protected function _save($association, $entity, $nested, $options) { - if (!$entity->dirty($association->property())) { + if (!$entity->dirty($association->getProperty())) { return true; } if (!empty($nested)) { @@ -287,7 +287,7 @@ protected function _getNoCascadeItems($entity, $options) { $noCascade = []; foreach ($this->_items as $assoc) { - if (!$assoc->cascadeCallbacks()) { + if (!$assoc->getCascadeCallbacks()) { $noCascade[] = $assoc; continue; } diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 12011319..5603591a 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -132,8 +132,8 @@ protected function _processAssociations(Event $event, EntityInterface $entity) */ protected function _processAssociation(Event $event, EntityInterface $entity, Association $assoc, array $settings) { - $foreignKeys = (array)$assoc->foreignKey(); - $primaryKeys = (array)$assoc->bindingKey(); + $foreignKeys = (array)$assoc->getForeignKey(); + $primaryKeys = (array)$assoc->getBindingKey(); $countConditions = $entity->extract($foreignKeys); $updateConditions = array_combine($primaryKeys, $countConditions); $countOriginalConditions = $entity->extractOriginalChanged($foreignKeys); @@ -154,7 +154,7 @@ protected function _processAssociation(Event $event, EntityInterface $entity, As $count = $this->_getCount($config, $countConditions); } - $assoc->target()->updateAll([$field => $count], $updateConditions); + $assoc->getTarget()->updateAll([$field => $count], $updateConditions); if (isset($updateOriginalConditions)) { if (!is_string($config) && is_callable($config)) { @@ -162,7 +162,7 @@ protected function _processAssociation(Event $event, EntityInterface $entity, As } else { $count = $this->_getCount($config, $countOriginalConditions); } - $assoc->target()->updateAll([$field => $count], $updateOriginalConditions); + $assoc->getTarget()->updateAll([$field => $count], $updateOriginalConditions); } } } diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 34c88f07..9a21f508 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -141,8 +141,8 @@ public function initialize(array $config) */ public function setupFieldAssociations($fields, $table, $model, $strategy) { - $targetAlias = $this->_translationTable->alias(); - $alias = $this->_table->alias(); + $targetAlias = $this->_translationTable->getAlias(); + $alias = $this->_table->getAlias(); $filter = $this->_config['onlyTranslated']; $tableLocator = $this->tableLocator(); @@ -153,7 +153,7 @@ public function setupFieldAssociations($fields, $table, $model, $strategy) $fieldTable = $tableLocator->get($name, [ 'className' => $table, 'alias' => $name, - 'table' => $this->_translationTable->table() + 'table' => $this->_translationTable->getTable() ]); } else { $fieldTable = $tableLocator->get($name); @@ -226,7 +226,7 @@ public function beforeFind(Event $event, Query $query, $options) $contain = []; $fields = $this->_config['fields']; - $alias = $this->_table->alias(); + $alias = $this->_table->getAlias(); $select = $query->clause('select'); $changeFilter = isset($options['filterByCurrentLocale']) && @@ -266,7 +266,7 @@ public function beforeFind(Event $event, Query $query, $options) public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options) { $locale = $entity->get('_locale') ?: $this->locale(); - $newOptions = [$this->_translationTable->alias() => ['validate' => false]]; + $newOptions = [$this->_translationTable->getAlias() => ['validate' => false]]; $options['associated'] = $newOptions + $options['associated']; $this->_bundleTranslatedFields($entity); @@ -317,7 +317,7 @@ public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $o 'foreign_key' => $key, 'model' => $model ]) - ->bufferResults(false) + ->enableBufferedResults(false) ->indexBy('field'); $modified = []; @@ -438,7 +438,7 @@ public function locale($locale = null) public function findTranslations(Query $query, array $options) { $locales = isset($options['locales']) ? $options['locales'] : []; - $targetAlias = $this->_translationTable->alias(); + $targetAlias = $this->_translationTable->getAlias(); return $query ->contain([$targetAlias => function ($q) use ($locales, $targetAlias) { @@ -467,7 +467,7 @@ protected function _referenceName(Table $table) $name = namespaceSplit(get_class($table)); $name = substr(end($name), 0, -5); if (empty($name)) { - $name = $table->table() ?: $table->alias(); + $name = $table->getTable() ?: $table->getAlias(); $name = Inflector::camelize($name); } @@ -537,7 +537,7 @@ public function groupTranslations($results) $result = []; foreach ($grouped->combine('field', 'content', 'locale') as $locale => $keys) { - $entityClass = $this->_table->entityClass(); + $entityClass = $this->_table->getEntityClass(); $translation = new $entityClass($keys + ['locale' => $locale], [ 'markNew' => false, 'useSetters' => false, @@ -617,7 +617,7 @@ protected function _bundleTranslatedFields($entity) */ protected function _findExistingTranslations($ruleSet) { - $association = $this->_table->association($this->_translationTable->alias()); + $association = $this->_table->association($this->_translationTable->getAlias()); $query = $association->find() ->select(['id', 'num' => 0]) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index fe19d072..0d1346e5 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -516,7 +516,7 @@ public function formatTreeList(Query $query, array $options = []) return $query->formatResults(function ($results) use ($options) { $options += [ 'keyPath' => $this->_getPrimaryKey(), - 'valuePath' => $this->_table->displayField(), + 'valuePath' => $this->_table->getDisplayField(), 'spacer' => '_', ]; @@ -539,7 +539,7 @@ public function formatTreeList(Query $query, array $options = []) */ public function removeFromTree(EntityInterface $node) { - return $this->_table->connection()->transactional(function () use ($node) { + return $this->_table->getConnection()->transactional(function () use ($node) { $this->_ensureFields($node); return $this->_removeFromTree($node); @@ -604,7 +604,7 @@ public function moveUp(EntityInterface $node, $number = 1) return false; } - return $this->_table->connection()->transactional(function () use ($node, $number) { + return $this->_table->getConnection()->transactional(function () use ($node, $number) { $this->_ensureFields($node); return $this->_moveUp($node, $number); @@ -692,7 +692,7 @@ public function moveDown(EntityInterface $node, $number = 1) return false; } - return $this->_table->connection()->transactional(function () use ($node, $number) { + return $this->_table->getConnection()->transactional(function () use ($node, $number) { $this->_ensureFields($node); return $this->_moveDown($node, $number); @@ -802,7 +802,7 @@ protected function _getNode($id) */ public function recover() { - $this->_table->connection()->transactional(function () { + $this->_table->getConnection()->transactional(function () { $this->_recoverTree(); }); } @@ -895,15 +895,15 @@ protected function _sync($shift, $dir, $conditions, $mark = false) $exp = $query->newExpr(); $movement = clone $exp; - $movement->add($field)->add("$shift")->tieWith($dir); + $movement->add($field)->add("$shift")->setConjunction($dir); $inverse = clone $exp; $movement = $mark ? - $inverse->add($movement)->tieWith('*')->add('-1') : + $inverse->add($movement)->setConjunction('*')->add('-1') : $movement; $where = clone $exp; - $where->add($field)->add($conditions)->tieWith(''); + $where->add($field)->add($conditions)->setConjunction(''); $query->update() ->set($exp->eq($field, $movement)) diff --git a/EagerLoader.php b/EagerLoader.php index ddf0041d..e62b8d56 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -418,7 +418,7 @@ public function attachAssociations(Query $query, Table $repository, $includeFiel $processed = []; do { foreach ($attachable as $alias => $loadable) { - $config = $loadable->config() + [ + $config = $loadable->getConfig() + [ 'aliasPath' => $loadable->aliasPath(), 'propertyPath' => $loadable->propertyPath(), 'includeFields' => $includeFields, @@ -490,22 +490,22 @@ protected function _normalizeContain(Table $parent, $alias, $options, $paths) $instance = $parent->association($alias); if (!$instance) { throw new InvalidArgumentException( - sprintf('%s is not associated with %s', $parent->alias(), $alias) + sprintf('%s is not associated with %s', $parent->getAlias(), $alias) ); } - if ($instance->alias() !== $alias) { + if ($instance->getAlias() !== $alias) { throw new InvalidArgumentException(sprintf( "You have contained '%s' but that association was bound as '%s'.", $alias, - $instance->alias() + $instance->getAlias() )); } $paths += ['aliasPath' => '', 'propertyPath' => '', 'root' => $alias]; $paths['aliasPath'] .= '.' . $alias; - $paths['propertyPath'] .= '.' . $instance->property(); + $paths['propertyPath'] .= '.' . $instance->getProperty(); - $table = $instance->target(); + $table = $instance->getTarget(); $extra = array_diff_key($options, $defaults); $config = [ @@ -514,7 +514,7 @@ protected function _normalizeContain(Table $parent, $alias, $options, $paths) 'config' => array_diff_key($options, $extra), 'aliasPath' => trim($paths['aliasPath'], '.'), 'propertyPath' => trim($paths['propertyPath'], '.'), - 'targetProperty' => $instance->property() + 'targetProperty' => $instance->getProperty() ]; $config['canBeJoined'] = $instance->canBeJoined($config['config']); $eagerLoadable = new EagerLoadable($alias, $config); @@ -570,7 +570,7 @@ protected function _fixStrategies() */ protected function _correctStrategy($loadable) { - $config = $loadable->config(); + $config = $loadable->getConfig(); $currentStrategy = isset($config['strategy']) ? $config['strategy'] : 'join'; @@ -580,7 +580,7 @@ protected function _correctStrategy($loadable) } $config['strategy'] = Association::STRATEGY_SELECT; - $loadable->config($config); + $loadable->setConfig($config); $loadable->canBeJoined(false); } @@ -634,14 +634,14 @@ public function loadExternal($query, $statement) return $statement; } - $driver = $query->connection()->driver(); + $driver = $query->getConnection()->driver(); list($collected, $statement) = $this->_collectKeys($external, $query, $statement); foreach ($external as $meta) { $contain = $meta->associations(); $instance = $meta->instance(); - $config = $meta->config(); - $alias = $instance->source()->alias(); + $config = $meta->getConfig(); + $alias = $instance->getSource()->getAlias(); $path = $meta->aliasPath(); $requiresKeys = $instance->requiresKeys($config); @@ -683,7 +683,7 @@ public function associationsMap($table) { $map = []; - if (!$this->matching() && !$this->contain() && empty($this->_joinsMap)) { + if (!$this->getMatching() && !$this->contain() && empty($this->_joinsMap)) { return $map; } @@ -698,7 +698,7 @@ public function associationsMap($table) 'alias' => $assoc, 'instance' => $instance, 'canBeJoined' => $canBeJoined, - 'entityClass' => $instance->target()->entityClass(), + 'entityClass' => $instance->getTarget()->getEntityClass(), 'nestKey' => $canBeJoined ? $assoc : $meta->aliasPath(), 'matching' => $forMatching !== null ? $forMatching : $matching, 'targetProperty' => $meta->targetProperty() @@ -736,7 +736,7 @@ public function addToJoinsMap($alias, Association $assoc, $asMatching = false, $ 'instance' => $assoc, 'canBeJoined' => true, 'forMatching' => $asMatching, - 'targetProperty' => $targetProperty ?: $assoc->property() + 'targetProperty' => $targetProperty ?: $assoc->getProperty() ]); } @@ -755,16 +755,16 @@ protected function _collectKeys($external, $query, $statement) /* @var \Cake\ORM\EagerLoadable $meta */ foreach ($external as $meta) { $instance = $meta->instance(); - if (!$instance->requiresKeys($meta->config())) { + if (!$instance->requiresKeys($meta->getConfig())) { continue; } - $source = $instance->source(); + $source = $instance->getSource(); $keys = $instance->type() === Association::MANY_TO_ONE ? - (array)$instance->foreignKey() : - (array)$instance->bindingKey(); + (array)$instance->getForeignKey() : + (array)$instance->getBindingKey(); - $alias = $source->alias(); + $alias = $source->getAlias(); $pkFields = []; foreach ($keys as $key) { $pkFields[] = key($query->aliasField($key, $alias)); @@ -777,7 +777,7 @@ protected function _collectKeys($external, $query, $statement) } if (!($statement instanceof BufferedStatement)) { - $statement = new BufferedStatement($statement, $query->connection()->driver()); + $statement = new BufferedStatement($statement, $query->getConnection()->driver()); } return [$this->_groupKeys($statement, $collectKeys), $statement]; diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 395e360f..f943d763 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -117,7 +117,7 @@ protected function _getPropertyMap($source, $associations) $map = []; $container = $source->associations(); foreach ($associations as $assoc) { - $map[$assoc] = $container->get($assoc)->property(); + $map[$assoc] = $container->get($assoc)->getProperty(); } return $map; diff --git a/Marshaller.php b/Marshaller.php index 72ff399a..d2c7edce 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -95,7 +95,7 @@ protected function _buildPropertyMap($data, $options) throw new \InvalidArgumentException(sprintf( 'Cannot marshal data for "%s" association. It is not associated with "%s".', $key, - $this->_table->alias() + $this->_table->getAlias() )); } continue; @@ -107,7 +107,7 @@ protected function _buildPropertyMap($data, $options) $callback = function ($value, $entity) use ($assoc, $nested) { $options = $nested + ['associated' => []]; - return $this->_mergeAssociation($entity->get($assoc->property()), $assoc, $value, $options); + return $this->_mergeAssociation($entity->get($assoc->getProperty()), $assoc, $value, $options); }; } else { $callback = function ($value, $entity) use ($assoc, $nested) { @@ -116,7 +116,7 @@ protected function _buildPropertyMap($data, $options) return $this->_marshalAssociation($assoc, $value, $options); }; } - $map[$assoc->property()] = $callback; + $map[$assoc->getProperty()] = $callback; } $behaviors = $this->_table->behaviors(); @@ -167,10 +167,10 @@ public function one(array $data, array $options = []) list($data, $options) = $this->_prepareDataAndOptions($data, $options); $primaryKey = (array)$this->_table->getPrimaryKey(); - $entityClass = $this->_table->entityClass(); + $entityClass = $this->_table->getEntityClass(); /* @var Entity $entity */ $entity = new $entityClass(); - $entity->source($this->_table->registryAlias()); + $entity->source($this->_table->getRegistryAlias()); if (isset($options['accessibleFields'])) { foreach ((array)$options['accessibleFields'] as $key => $value) { @@ -260,7 +260,7 @@ protected function _prepareDataAndOptions($data, $options) unset($options['fieldList']); } - $tableName = $this->_table->alias(); + $tableName = $this->_table->getAlias(); if (isset($data[$tableName])) { $data += $data[$tableName]; unset($data[$tableName]); @@ -286,7 +286,7 @@ protected function _marshalAssociation($assoc, $value, $options) if (!is_array($value)) { return null; } - $targetTable = $assoc->target(); + $targetTable = $assoc->getTarget(); $marshaller = $targetTable->marshaller(); $types = [Association::ONE_TO_ONE, Association::MANY_TO_ONE]; if (in_array($assoc->type(), $types)) { @@ -363,7 +363,7 @@ protected function _belongsToMany(Association $assoc, array $data, $options = [] $data = array_values($data); - $target = $assoc->target(); + $target = $assoc->getTarget(); $primaryKey = array_flip((array)$target->getPrimaryKey()); $records = $conditions = []; $primaryCount = count($primaryKey); @@ -453,7 +453,7 @@ protected function _loadAssociatedByIds($assoc, $ids) return []; } - $target = $assoc->target(); + $target = $assoc->getTarget(); $primaryKey = (array)$target->getPrimaryKey(); $multi = count($primaryKey) > 1; $primaryKey = array_map([$target, 'aliasField'], $primaryKey); @@ -710,7 +710,7 @@ protected function _mergeAssociation($original, $assoc, $value, $options) return $this->_marshalAssociation($assoc, $value, $options); } - $targetTable = $assoc->target(); + $targetTable = $assoc->getTarget(); $marshaller = $targetTable->marshaller(); $types = [Association::ONE_TO_ONE, Association::MANY_TO_ONE]; if (in_array($assoc->type(), $types)) { diff --git a/Query.php b/Query.php index f03f15cb..393696d6 100644 --- a/Query.php +++ b/Query.php @@ -183,11 +183,11 @@ public function __construct($connection, $table) public function select($fields = [], $overwrite = false) { if ($fields instanceof Association) { - $fields = $fields->target(); + $fields = $fields->getTarget(); } if ($fields instanceof Table) { - $fields = $this->aliasFields($fields->getSchema()->columns(), $fields->alias()); + $fields = $this->aliasFields($fields->getSchema()->columns(), $fields->getAlias()); } return parent::select($fields, $overwrite); @@ -206,13 +206,13 @@ public function select($fields = [], $overwrite = false) */ public function addDefaultTypes(Table $table) { - $alias = $table->alias(); + $alias = $table->getAlias(); $map = $table->getSchema()->typeMap(); $fields = []; foreach ($map as $f => $type) { $fields[$f] = $fields[$alias . '.' . $f] = $fields[$alias . '__' . $f] = $type; } - $this->typeMap()->addDefaults($fields); + $this->getTypeMap()->addDefaults($fields); return $this; } @@ -388,7 +388,7 @@ public function contain($associations = null, $override = false) } $result = $loader->contain($associations); - $this->_addAssociationsToTypeMap($this->repository(), $this->typeMap(), $result); + $this->_addAssociationsToTypeMap($this->repository(), $this->getTypeMap(), $result); return $this; } @@ -410,7 +410,7 @@ protected function _addAssociationsToTypeMap($table, $typeMap, $associations) if (!$association) { continue; } - $target = $association->target(); + $target = $association->getTarget(); $primary = (array)$target->getPrimaryKey(); if (empty($primary) || $typeMap->type($target->aliasField($primary[0])) === null) { $this->addDefaultTypes($target); @@ -474,7 +474,7 @@ protected function _addAssociationsToTypeMap($table, $typeMap, $associations) public function matching($assoc, callable $builder = null) { $result = $this->getEagerLoader()->setMatching($assoc, $builder)->getMatching(); - $this->_addAssociationsToTypeMap($this->repository(), $this->typeMap(), $result); + $this->_addAssociationsToTypeMap($this->repository(), $this->getTypeMap(), $result); $this->_dirty(); return $this; @@ -551,7 +551,7 @@ public function leftJoinWith($assoc, callable $builder = null) 'fields' => false ]) ->getMatching(); - $this->_addAssociationsToTypeMap($this->repository(), $this->typeMap(), $result); + $this->_addAssociationsToTypeMap($this->repository(), $this->getTypeMap(), $result); $this->_dirty(); return $this; @@ -597,10 +597,10 @@ public function innerJoinWith($assoc, callable $builder = null) $result = $this->getEagerLoader() ->setMatching($assoc, $builder, [ 'joinType' => 'INNER', - 'fields' => false ]) + 'fields' => false ->getMatching(); - $this->_addAssociationsToTypeMap($this->repository(), $this->typeMap(), $result); + $this->_addAssociationsToTypeMap($this->repository(), $this->getTypeMap(), $result); $this->_dirty(); return $this; @@ -665,7 +665,7 @@ public function notMatching($assoc, callable $builder = null) 'negateMatch' => true ]) ->getMatching(); - $this->_addAssociationsToTypeMap($this->repository(), $this->typeMap(), $result); + $this->_addAssociationsToTypeMap($this->repository(), $this->getTypeMap(), $result); $this->_dirty(); return $this; @@ -757,13 +757,13 @@ public function cleanCopy() { $clone = clone $this; $clone->triggerBeforeFind(); - $clone->autoFields(false); + $clone->enableAutoFields(false); $clone->limit(null); $clone->order([], true); $clone->offset(null); $clone->mapReduce(null, null, true); $clone->formatResults(null, true); - $clone->selectTypeMap(new TypeMap()); + $clone->setSelectTypeMap(new TypeMap()); $clone->decorateResults(null, true); return $clone; @@ -840,13 +840,13 @@ protected function _performCount() $count = ['count' => $query->func()->count('*')]; if (!$complex) { - $query->getEagerLoader()->autoFields(false); + $query->getEagerLoader()->enableAutoFields(false); $statement = $query ->select($count, true) - ->autoFields(false) + ->enableAutoFields(false) ->execute(); } else { - $statement = $this->connection()->newQuery() + $statement = $this->getConnection()->newQuery() ->select($count) ->from(['count_source' => $query]) ->execute(); @@ -1067,7 +1067,7 @@ protected function _addDefaultFields() */ protected function _addDefaultSelectTypes() { - $typeMap = $this->typeMap()->defaults(); + $typeMap = $this->getTypeMap()->getDefaults(); $select = $this->clause('select'); $types = []; @@ -1083,7 +1083,7 @@ protected function _addDefaultSelectTypes() $types[$alias] = $value->returnType(); } } - $this->selectTypeMap()->addDefaults($types); + $this->getSelectTypeMap()->addDefaults($types); } /** @@ -1192,7 +1192,7 @@ public function __debugInfo() 'formatters' => count($this->_formatters), 'mapReducers' => count($this->_mapReduce), 'contain' => $eagerLoader ? $eagerLoader->contain() : [], - 'matching' => $eagerLoader ? $eagerLoader->matching() : [], + 'matching' => $eagerLoader ? $eagerLoader->getMatching() : [], 'extraOptions' => $this->_options, 'repository' => $this->_repository ]; diff --git a/ResultSet.php b/ResultSet.php index b0e85cec..c9a4f7a6 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -177,16 +177,16 @@ public function __construct($query, $statement) { $repository = $query->repository(); $this->_statement = $statement; - $this->_driver = $query->connection()->driver(); + $this->_driver = $query->getConnection()->driver(); $this->_defaultTable = $query->repository(); $this->_calculateAssociationMap($query); - $this->_hydrate = $query->hydrate(); + $this->_hydrate = $query->isHydrationEnabled(); $this->_entityClass = $repository->entityClass(); - $this->_useBuffering = $query->bufferResults(); + $this->_useBuffering = $query->isBufferedResultsEnabled(); $this->_defaultAlias = $this->_defaultTable->alias(); $this->_calculateColumnMap($query); $this->_calculateTypeMap(); - $this->_autoFields = $query->autoFields(); + $this->_autoFields = $query->isAutoFieldsEnabled(); if ($this->_useBuffering) { $count = $this->count(); @@ -499,7 +499,7 @@ protected function _groupResult($row) if ($this->_hydrate) { /* @var \Cake\ORM\Table $table */ $table = $matching['instance']; - $options['source'] = $table->registryAlias(); + $options['source'] = $table->getRegistryAlias(); /* @var \Cake\Datasource\EntityInterface $entity */ $entity = new $matching['entityClass']($results['_matchingData'][$alias], $options); $entity->clean(); @@ -533,8 +533,8 @@ protected function _groupResult($row) $results[$alias] = $row[$alias]; } - $target = $instance->target(); - $options['source'] = $target->registryAlias(); + $target = $instance->getTarget(); + $options['source'] = $target->getRegistryAlias(); unset($presentAliases[$alias]); if ($assoc['canBeJoined'] && $this->_autoFields !== false) { diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 7c854a5d..e370a592 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -103,7 +103,7 @@ public function __invoke(EntityInterface $entity, array $options) $source = $options['repository']; } if ($source instanceof Association) { - $source = $source->source(); + $source = $source->getSource(); } if (!$entity->extract($this->_fields, true)) { diff --git a/SaveOptionsBuilder.php b/SaveOptionsBuilder.php index e19056de..30f36599 100644 --- a/SaveOptionsBuilder.php +++ b/SaveOptionsBuilder.php @@ -107,7 +107,7 @@ protected function _associated(Table $table, array $associations) } $this->_checkAssociation($table, $key); if (isset($associated['associated'])) { - $this->_associated($table->association($key)->target(), $associated['associated']); + $this->_associated($table->association($key)->getTarget(), $associated['associated']); continue; } } diff --git a/Table.php b/Table.php index 885dcf2a..4af39bdf 100644 --- a/Table.php +++ b/Table.php @@ -245,22 +245,22 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc public function __construct(array $config = []) { if (!empty($config['registryAlias'])) { - $this->registryAlias($config['registryAlias']); + $this->setRegistryAlias($config['registryAlias']); } if (!empty($config['table'])) { - $this->table($config['table']); + $this->setTable($config['table']); } if (!empty($config['alias'])) { - $this->alias($config['alias']); + $this->setAlias($config['alias']); } if (!empty($config['connection'])) { - $this->connection($config['connection']); + $this->setConnection($config['connection']); } if (!empty($config['schema'])) { - $this->schema($config['schema']); + $this->setSchema($config['schema']); } if (!empty($config['entityClass'])) { - $this->entityClass($config['entityClass']); + $this->setEntityClass($config['entityClass']); } $eventManager = $behaviors = $associations = null; if (!empty($config['eventManager'])) { @@ -351,7 +351,7 @@ public function getTable() $table = namespaceSplit(get_class($this)); $table = substr(end($table), 0, -5); if (!$table) { - $table = $this->alias(); + $table = $this->getAlias(); } $this->_table = Inflector::underscore($table); } @@ -527,7 +527,7 @@ public function getSchema() { if ($this->_schema === null) { $this->_schema = $this->_initializeSchema( - $this->connection() + $this->getConnection() ->getSchemaCollection() ->describe($this->getTable()) ); @@ -555,7 +555,7 @@ public function setSchema($schema) unset($schema['_constraints']); } - $schema = new TableSchema($this->table(), $schema); + $schema = new TableSchema($this->getTable(), $schema); foreach ($constraints as $name => $value) { $schema->addConstraint($name, $value); @@ -964,7 +964,7 @@ public function belongsTo($associated, array $options = []) $options += ['sourceTable' => $this]; $association = new BelongsTo($associated, $options); - return $this->_associations->add($association->name(), $association); + return $this->_associations->add($association->getName(), $association); } /** @@ -1008,7 +1008,7 @@ public function hasOne($associated, array $options = []) $options += ['sourceTable' => $this]; $association = new HasOne($associated, $options); - return $this->_associations->add($association->name(), $association); + return $this->_associations->add($association->getName(), $association); } /** @@ -1058,7 +1058,7 @@ public function hasMany($associated, array $options = []) $options += ['sourceTable' => $this]; $association = new HasMany($associated, $options); - return $this->_associations->add($association->name(), $association); + return $this->_associations->add($association->getName(), $association); } /** @@ -1110,7 +1110,7 @@ public function belongsToMany($associated, array $options = []) $options += ['sourceTable' => $this]; $association = new BelongsToMany($associated, $options); - return $this->_associations->add($association->name(), $association); + return $this->_associations->add($association->getName(), $association); } /** @@ -1252,7 +1252,7 @@ public function findList(Query $query, array $options) { $options += [ 'keyField' => $this->getPrimaryKey(), - 'valueField' => $this->displayField(), + 'valueField' => $this->getDisplayField(), 'groupField' => null ]; @@ -1393,7 +1393,7 @@ protected function _setFieldMatchers($options, $keys) public function get($primaryKey, $options = []) { $key = (array)$this->getPrimaryKey(); - $alias = $this->alias(); + $alias = $this->getAlias(); foreach ($key as $index => $keyname) { $key[$index] = $alias . '.' . $keyname; } @@ -1406,7 +1406,7 @@ public function get($primaryKey, $options = []) throw new InvalidPrimaryKeyException(sprintf( 'Record not found in table "%s" with primary key [%s]', - $this->table(), + $this->getTable(), implode($primaryKey, ', ') )); } @@ -1423,8 +1423,8 @@ public function get($primaryKey, $options = []) if (!$cacheKey) { $cacheKey = sprintf( "get:%s.%s%s", - $this->connection()->configName(), - $this->table(), + $this->getConnection()->configName(), + $this->getTable(), json_encode($primaryKey) ); } @@ -1444,7 +1444,7 @@ public function get($primaryKey, $options = []) protected function _executeTransaction(callable $worker, $atomic = true) { if ($atomic) { - return $this->connection()->transactional(function () use ($worker) { + return $this->getConnection()->transactional(function () use ($worker) { return $worker(); }); } @@ -1461,7 +1461,7 @@ protected function _executeTransaction(callable $worker, $atomic = true) */ protected function _transactionCommitted($atomic, $primary) { - return !$this->connection()->inTransaction() && ($atomic || (!$atomic && $primary)); + return !$this->getConnection()->inTransaction() && ($atomic || (!$atomic && $primary)); } /** @@ -1568,7 +1568,7 @@ protected function _getFindOrCreateQuery($search) */ public function query() { - return new Query($this->connection(), $this); + return new Query($this->getConnection(), $this); } /** @@ -1732,7 +1732,7 @@ public function save(EntityInterface $entity, $options = []) if ($options['atomic'] || $options['_primary']) { $entity->clean(); $entity->isNew(false); - $entity->source($this->registryAlias()); + $entity->source($this->getRegistryAlias()); } } @@ -1754,7 +1754,7 @@ protected function _processSave($entity, $options) $primaryColumns = (array)$this->getPrimaryKey(); if ($options['checkExisting'] && $primaryColumns && $entity->isNew() && $entity->has($primaryColumns)) { - $alias = $this->alias(); + $alias = $this->getAlias(); $conditions = []; foreach ($entity->extract($primaryColumns) as $k => $v) { $conditions["$alias.$k"] = $v; @@ -1831,14 +1831,14 @@ protected function _onSaveSuccess($entity, $options) $this->dispatchEvent('Model.afterSave', compact('entity', 'options')); - if ($options['atomic'] && !$this->connection()->inTransaction()) { + if ($options['atomic'] && !$this->getConnection()->inTransaction()) { throw new RolledbackTransactionException(['table' => get_class($this)]); } if (!$options['atomic'] && !$options['_primary']) { $entity->clean(); $entity->isNew(false); - $entity->source($this->registryAlias()); + $entity->source($this->getRegistryAlias()); } return true; @@ -1859,7 +1859,7 @@ protected function _insert($entity, $data) if (empty($primary)) { $msg = sprintf( 'Cannot insert row in "%s" table, it has no primary key.', - $this->table() + $this->getTable() ); throw new RuntimeException($msg); } @@ -1901,10 +1901,10 @@ protected function _insert($entity, $data) $success = $entity; $entity->set($filteredKeys, ['guard' => false]); $schema = $this->getSchema(); - $driver = $this->connection()->driver(); + $driver = $this->getConnection()->driver(); foreach ($primary as $key => $v) { if (!isset($data[$key])) { - $id = $statement->lastInsertId($this->table(), $key); + $id = $statement->lastInsertId($this->getTable(), $key); $type = $schema->columnType($key); $entity->set($key, Type::build($type)->toPHP($id, $driver)); break; @@ -1991,7 +1991,7 @@ public function saveMany($entities, $options = []) { $isNew = []; - $return = $this->connection()->transactional( + $return = $this->getConnection()->transactional( function () use ($entities, $options, &$isNew) { foreach ($entities as $key => $entity) { $isNew[$key] = $entity->isNew(); @@ -2362,8 +2362,8 @@ public function marshaller() public function newEntity($data = null, array $options = []) { if ($data === null) { - $class = $this->entityClass(); - $entity = new $class([], ['source' => $this->registryAlias()]); + $class = $this->getEntityClass(); + $entity = new $class([], ['source' => $this->getRegistryAlias()]); return $entity; } @@ -2533,7 +2533,7 @@ public function validateUnique($value, array $options, array $context = null) [ 'useSetters' => false, 'markNew' => $context['newRecord'], - 'source' => $this->registryAlias() + 'source' => $this->getRegistryAlias() ] ); $fields = array_merge( @@ -2668,15 +2668,15 @@ public function loadInto($entities, array $contain) */ public function __debugInfo() { - $conn = $this->connection(); + $conn = $this->getConnection(); $associations = $this->_associations; $behaviors = $this->_behaviors; return [ - 'registryAlias' => $this->registryAlias(), - 'table' => $this->table(), - 'alias' => $this->alias(), - 'entityClass' => $this->entityClass(), + 'registryAlias' => $this->getRegistryAlias(), + 'table' => $this->getTable(), + 'alias' => $this->getAlias(), + 'entityClass' => $this->getEntityClass(), 'associations' => $associations ? $associations->keys() : false, 'behaviors' => $behaviors ? $behaviors->loaded() : false, 'defaultConnection' => $this->defaultConnectionName(), From e1cf0018abfefe35bc0de1b2add23e76df954249 Mon Sep 17 00:00:00 2001 From: antograssiot Date: Tue, 10 Jan 2017 15:35:38 +0100 Subject: [PATCH 0868/2059] fIx CS and wrong function calls --- Association/BelongsTo.php | 2 +- Association/BelongsToMany.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index d89d0e60..1fea0a2d 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -176,7 +176,7 @@ protected function _joinCondition($options) if (count($foreignKey) !== count($bindingKey)) { if (empty($bindingKey)) { $msg = 'The "%s" table does not define a primary key. Please set one.'; - throw new RuntimeException(sprintf($msg, $this->setTarget()->getTable())); + throw new RuntimeException(sprintf($msg, $this->getTarget()->getTable())); } $msg = 'Cannot match provided foreignKey for "%s", got "(%s)" but expected foreign key for "(%s)"'; diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 9135f95c..a6d1fa95 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1084,7 +1084,7 @@ protected function junctionConditions() */ public function find($type = null, array $options = []) { - $type = $type ?: $this->getFinder() ; + $type = $type ?: $this->getFinder(); list($type, $opts) = $this->_extractFinder($type); $query = $this->getTarget() ->find($type, $options + $opts) From 9528d9c280a70248e3f41504d23c0955cc53bec8 Mon Sep 17 00:00:00 2001 From: antograssiot Date: Wed, 11 Jan 2017 08:14:05 +0100 Subject: [PATCH 0869/2059] Set default arg to `true` for all `enable...()`setters --- EagerLoader.php | 2 +- Query.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index e62b8d56..2f7c89f7 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -165,7 +165,7 @@ public function clearContain() * @param bool $enable The value to set. * @return self */ - public function enableAutoFields($enable) + public function enableAutoFields($enable = true) { $this->_autoFields = (bool)$enable; diff --git a/Query.php b/Query.php index 393696d6..dce92778 100644 --- a/Query.php +++ b/Query.php @@ -597,8 +597,8 @@ public function innerJoinWith($assoc, callable $builder = null) $result = $this->getEagerLoader() ->setMatching($assoc, $builder, [ 'joinType' => 'INNER', - ]) 'fields' => false + ]) ->getMatching(); $this->_addAssociationsToTypeMap($this->repository(), $this->getTypeMap(), $result); $this->_dirty(); @@ -891,7 +891,7 @@ public function counter($counter) * @param bool $enable Use a boolean to set the hydration mode. * @return self */ - public function enableHydration($enable) + public function enableHydration($enable = true) { $this->_dirty(); $this->_hydrate = (bool)$enable; @@ -1219,7 +1219,7 @@ public function jsonSerialize() * @param bool $value Set true to enable, false to disable. * @return self */ - public function enableAutoFields($value) + public function enableAutoFields($value = true) { $this->_autoFields = (bool)$value; From 0696520e093450ef5963fd9f10c2e365d70dd9aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Mon, 9 Jan 2017 11:42:30 +0100 Subject: [PATCH 0870/2059] Added translationField method. --- Behavior/TranslateBehavior.php | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 9a21f508..d2645f0e 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -75,7 +75,10 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface */ protected $_defaultConfig = [ 'implementedFinders' => ['translations' => 'findTranslations'], - 'implementedMethods' => ['locale' => 'locale'], + 'implementedMethods' => [ + 'locale' => 'locale', + 'translationField' => 'translationField' + ], 'fields' => [], 'translationTable' => 'I18n', 'defaultLocale' => '', @@ -413,6 +416,28 @@ public function locale($locale = null) return $this->_locale = (string)$locale; } + /** + * Returns a fully aliased field name for translated fields. + * + * If the requested field is configured as a translation field, the `content` + * field with an alias of a corresponding association is returned. Table-aliased + * field name is returned for all other fields. + * + * @param string $field Field name to be aliased. + * @return string + */ + public function translationField($field) + { + $table = $this->_table; + $associationName = $table->alias() . '_' . $field . '_translation'; + + if ($table->associations()->has($associationName)) { + return $associationName . '.content'; + } else { + return $table->aliasField($field); + } + } + /** * Custom finder method used to retrieve all translations for the found records. * Fetched translations can be filtered by locale by passing the `locales` key From d8de8c55959af74df88cad3332c7388f6af75d6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Thu, 12 Jan 2017 14:58:59 +0100 Subject: [PATCH 0871/2059] Removed deprecated call and an unnessessary else block. --- Behavior/TranslateBehavior.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index d2645f0e..0e5d09d8 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -429,13 +429,13 @@ public function locale($locale = null) public function translationField($field) { $table = $this->_table; - $associationName = $table->alias() . '_' . $field . '_translation'; + $associationName = $table->getAlias() . '_' . $field . '_translation'; if ($table->associations()->has($associationName)) { return $associationName . '.content'; - } else { - return $table->aliasField($field); } + + return $table->aliasField($field); } /** From 2277d17cdc1354c15410f0837e8ced932013c4ee Mon Sep 17 00:00:00 2001 From: Anthony GRASSIOT Date: Thu, 12 Jan 2017 15:09:42 +0100 Subject: [PATCH 0872/2059] Remove more deprecated function's call --- Behavior/TimestampBehavior.php | 2 +- Table.php | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 063538b6..4bdd926f 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -88,7 +88,7 @@ public function initialize(array $config) */ public function handleEvent(Event $event, EntityInterface $entity) { - $eventName = $event->name(); + $eventName = $event->getName(); $events = $this->_config['events']; $new = $entity->isNew() !== false; diff --git a/Table.php b/Table.php index 4af39bdf..7ac89338 100644 --- a/Table.php +++ b/Table.php @@ -2314,7 +2314,7 @@ public function marshaller() * * ``` * $article = $this->Articles->newEntity( - * $this->request->data(), + * $this->request->getData(), * ['associated' => ['Tags', 'Comments.Users']] * ); * ``` @@ -2323,7 +2323,7 @@ public function marshaller() * passing the `fieldList` option, which is also accepted for associations: * * ``` - * $article = $this->Articles->newEntity($this->request->data(), [ + * $article = $this->Articles->newEntity($this->request->getData(), [ * 'fieldList' => ['title', 'body', 'tags', 'comments'], * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']] * ] @@ -2336,7 +2336,7 @@ public function marshaller() * * ``` * $article = $this->Articles->newEntity( - * $this->request->data(), + * $this->request->getData(), * ['accessibleFields' => ['protected_field' => true]] * ); * ``` @@ -2347,7 +2347,7 @@ public function marshaller() * * ``` * $article = $this->Articles->newEntity( - * $this->request->data(), + * $this->request->getData(), * ['validate' => false] * ); * ``` @@ -2384,7 +2384,7 @@ public function newEntity($data = null, array $options = []) * * ``` * $articles = $this->Articles->newEntities( - * $this->request->data(), + * $this->request->getData(), * ['associated' => ['Tags', 'Comments.Users']] * ); * ``` @@ -2393,7 +2393,7 @@ public function newEntity($data = null, array $options = []) * passing the `fieldList` option, which is also accepted for associations: * * ``` - * $articles = $this->Articles->newEntities($this->request->data(), [ + * $articles = $this->Articles->newEntities($this->request->getData(), [ * 'fieldList' => ['title', 'body', 'tags', 'comments'], * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']] * ] @@ -2424,7 +2424,7 @@ public function newEntities(array $data, array $options = []) * passing the `fieldList` option, which is also accepted for associations: * * ``` - * $article = $this->Articles->patchEntity($article, $this->request->data(), [ + * $article = $this->Articles->patchEntity($article, $this->request->getData(), [ * 'fieldList' => ['title', 'body', 'tags', 'comments'], * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']] * ] @@ -2436,7 +2436,7 @@ public function newEntities(array $data, array $options = []) * The `validate` option can be used to disable validation on the passed data: * * ``` - * $article = $this->patchEntity($article, $this->request->data(),[ + * $article = $this->patchEntity($article, $this->request->getData(),[ * 'validate' => false * ]); * ``` @@ -2469,7 +2469,7 @@ public function patchEntity(EntityInterface $entity, array $data, array $options * passing the `fieldList` option, which is also accepted for associations: * * ``` - * $articles = $this->Articles->patchEntities($articles, $this->request->data(), [ + * $articles = $this->Articles->patchEntities($articles, $this->request->getData(), [ * 'fieldList' => ['title', 'body', 'tags', 'comments'], * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']] * ] From 1704ee2c09a363682f2d86cbb328b23e4a16c40e Mon Sep 17 00:00:00 2001 From: dereuromark Date: Fri, 13 Jan 2017 19:52:41 +0100 Subject: [PATCH 0873/2059] Fix up doc block return types for chaining. --- Association.php | 24 +++++++++++------------ Association/BelongsToMany.php | 6 +++--- Association/HasMany.php | 4 ++-- Behavior/Translate/TranslateTrait.php | 2 +- EagerLoadable.php | 4 ++-- EagerLoader.php | 6 +++--- Locator/TableLocator.php | 2 +- Query.php | 28 +++++++++++++-------------- Table.php | 16 +++++++-------- 9 files changed, 46 insertions(+), 46 deletions(-) diff --git a/Association.php b/Association.php index 3a4f54c4..45c60ec3 100644 --- a/Association.php +++ b/Association.php @@ -240,7 +240,7 @@ public function __construct($alias, array $options = []) * Sets the name for this association. * * @param string $name Name to be assigned - * @return self + * @return $this */ public function setName($name) { @@ -279,7 +279,7 @@ public function name($name = null) * Sets whether or not cascaded deletes should also fire callbacks. * * @param bool $cascadeCallbacks cascade callbacks switch value - * @return self + * @return $this */ public function setCascadeCallbacks($cascadeCallbacks) { @@ -329,7 +329,7 @@ public function className() * Sets the table instance for the source side of the association. * * @param \Cake\ORM\Table $table the instance to be assigned as source side - * @return self + * @return $this */ public function setSource(Table $table) { @@ -369,7 +369,7 @@ public function source(Table $table = null) * Sets the table instance for the target side of the association. * * @param \Cake\ORM\Table $table the instance to be assigned as target side - * @return self + * @return $this */ public function setTarget(Table $table) { @@ -428,7 +428,7 @@ public function target(Table $table = null) * * @param array $conditions list of conditions to be used * @see \Cake\Database\Query::where() for examples on the format of the array - * @return self + * @return $this */ public function setConditions($conditions) { @@ -472,7 +472,7 @@ public function conditions($conditions = null) * When not manually specified the primary key of the owning side table is used. * * @param string $key the table field to be used to link both tables together - * @return self + * @return $this */ public function setBindingKey($key) { @@ -531,7 +531,7 @@ public function getForeignKey() * Sets the name of the field representing the foreign key to the target table. * * @param string $key the key to be used to link both tables together - * @return self + * @return $this */ public function setForeignKey($key) { @@ -566,7 +566,7 @@ public function foreignKey($key = null) * If no parameters are passed the current setting is returned. * * @param bool $dependent Set the dependent mode. Use null to read the current state. - * @return self + * @return $this */ public function setDependent($dependent) { @@ -626,7 +626,7 @@ public function canBeJoined(array $options = []) * Sets the type of join to be used when adding the association to a query. * * @param string $type the join type to be used (e.g. INNER) - * @return self + * @return $this */ public function setJoinType($type) { @@ -667,7 +667,7 @@ public function joinType($type = null) * in the source table record. * * @param string $name The name of the association property. Use null to read the current value. - * @return self + * @return $this */ public function setProperty($name) { @@ -735,7 +735,7 @@ protected function _propertyName() * rendering any changes to this setting void. * * @param string $name The strategy type. Use null to read the current value. - * @return self + * @return $this * @throws \InvalidArgumentException When an invalid strategy is provided. */ public function setStrategy($name) @@ -796,7 +796,7 @@ public function getFinder() * Sets the default finder to use for fetching rows from the target table. * * @param string $finder the finder name to use - * @return self + * @return $this */ public function setFinder($finder) { diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index a6d1fa95..1e80386c 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -160,7 +160,7 @@ class BelongsToMany extends Association * Sets the name of the field representing the foreign key to the target table. * * @param string $key the key to be used to link both tables together - * @return self + * @return $this */ public function setTargetForeignKey($key) { @@ -619,7 +619,7 @@ public function isOwningSide(Table $side) * * @param string $strategy the strategy name to be used * @throws \InvalidArgumentException if an invalid strategy name is passed - * @return self + * @return $this */ public function setSaveStrategy($strategy) { @@ -985,7 +985,7 @@ public function conditions($conditions = null) * Sets the current join table, either the name of the Table instance or the instance itself. * * @param string|\Cake\ORM\Table $through Name of the Table instance or the instance itself - * @return self + * @return $this */ public function setThrough($through) { diff --git a/Association/HasMany.php b/Association/HasMany.php index ffbb39b4..ae831737 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -106,7 +106,7 @@ public function isOwningSide(Table $side) * * @param string $strategy the strategy name to be used * @throws \InvalidArgumentException if an invalid strategy name is passed - * @return self + * @return $this */ public function setSaveStrategy($strategy) { @@ -584,7 +584,7 @@ public function foreignKey($key = null) * Sets the sort order in which target records should be returned. * * @param mixed $sort A find() compatible order clause - * @return self + * @return $this */ public function setSort($sort) { diff --git a/Behavior/Translate/TranslateTrait.php b/Behavior/Translate/TranslateTrait.php index ebbaadd1..b98738d4 100644 --- a/Behavior/Translate/TranslateTrait.php +++ b/Behavior/Translate/TranslateTrait.php @@ -30,7 +30,7 @@ trait TranslateTrait * it. * * @param string $language Language to return entity for. - * @return self|\Cake\ORM\Entity + * @return $this|\Cake\ORM\Entity */ public function translation($language) { diff --git a/EagerLoadable.php b/EagerLoadable.php index 93762ef9..6303b62e 100644 --- a/EagerLoadable.php +++ b/EagerLoadable.php @@ -207,7 +207,7 @@ public function propertyPath() * Sets whether or not this level can be fetched using a join. * * @param bool $possible The value to set. - * @return self + * @return $this */ public function setCanBeJoined($possible) { @@ -239,7 +239,7 @@ public function canBeJoined($possible = null) * the records. * * @param array $config The value to set. - * @return self + * @return $this */ public function setConfig(array $config) { diff --git a/EagerLoader.php b/EagerLoader.php index 2f7c89f7..25a76b14 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -163,7 +163,7 @@ public function clearContain() * Sets whether or not contained associations will load fields automatically. * * @param bool $enable The value to set. - * @return self + * @return $this */ public function enableAutoFields($enable = true) { @@ -218,7 +218,7 @@ public function autoFields($enable = null) public function setMatching($assoc, callable $builder = null, $options = []) { if ($this->_matching === null) { - $this->_matching = new self(); + $this->_matching = new static(); } if (!isset($options['joinType'])) { @@ -252,7 +252,7 @@ public function setMatching($assoc, callable $builder = null, $options = []) public function getMatching() { if ($this->_matching === null) { - $this->_matching = new self(); + $this->_matching = new static(); } return $this->_matching->contain(); diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 5625e9e8..7e4c686e 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -61,7 +61,7 @@ class TableLocator implements LocatorInterface * * @param string|array $alias Name of the alias or array to completely overwrite current config. * @param array|null $options list of options for the alias - * @return self + * @return $this * @throws \RuntimeException When you attempt to configure an existing table instance. */ public function setConfig($alias, $options = null) diff --git a/Query.php b/Query.php index dce92778..fe398493 100644 --- a/Query.php +++ b/Query.php @@ -178,7 +178,7 @@ public function __construct($connection, $table) * @param array|\Cake\Database\ExpressionInterface|string|\Cake\ORM\Table|\Cake\ORM\Association $fields fields * to be added to the list. * @param bool $overwrite whether to reset fields with passed list or not - * @return self + * @return $this */ public function select($fields = [], $overwrite = false) { @@ -202,7 +202,7 @@ public function select($fields = [], $overwrite = false) * This method returns the same query object for chaining. * * @param \Cake\ORM\Table $table The table to pull types from - * @return self + * @return $this */ public function addDefaultTypes(Table $table) { @@ -222,7 +222,7 @@ public function addDefaultTypes(Table $table) * and storing containments. * * @param \Cake\ORM\EagerLoader $instance The eager loader to use. - * @return self + * @return $this */ public function setEagerLoader(EagerLoader $instance) { @@ -469,7 +469,7 @@ protected function _addAssociationsToTypeMap($table, $typeMap, $associations) * @param string $assoc The association to filter by * @param callable|null $builder a function that will receive a pre-made query object * that can be used to add custom conditions or selecting some fields - * @return self + * @return $this */ public function matching($assoc, callable $builder = null) { @@ -541,7 +541,7 @@ public function matching($assoc, callable $builder = null) * @param string $assoc The association to join with * @param callable|null $builder a function that will receive a pre-made query object * that can be used to add custom conditions or selecting some fields - * @return self + * @return $this */ public function leftJoinWith($assoc, callable $builder = null) { @@ -589,7 +589,7 @@ public function leftJoinWith($assoc, callable $builder = null) * @param string $assoc The association to join with * @param callable|null $builder a function that will receive a pre-made query object * that can be used to add custom conditions or selecting some fields - * @return self + * @return $this * @see \Cake\ORM\Query::matching() */ public function innerJoinWith($assoc, callable $builder = null) @@ -654,7 +654,7 @@ public function innerJoinWith($assoc, callable $builder = null) * @param string $assoc The association to filter by * @param callable|null $builder a function that will receive a pre-made query object * that can be used to add custom conditions or selecting some fields - * @return self + * @return $this */ public function notMatching($assoc, callable $builder = null) { @@ -874,7 +874,7 @@ protected function _performCount() * instead * * @param callable|null $counter The counter value - * @return self + * @return $this */ public function counter($counter) { @@ -889,7 +889,7 @@ public function counter($counter) * If set to false array results will be returned for the query. * * @param bool $enable Use a boolean to set the hydration mode. - * @return self + * @return $this */ public function enableHydration($enable = true) { @@ -931,7 +931,7 @@ public function hydrate($enable = null) /** * {@inheritDoc} * - * @return self + * @return $this * @throws \RuntimeException When you attempt to cache a non-select query. */ public function cache($key, $config = 'default') @@ -1116,7 +1116,7 @@ protected function _dirty() * Can be combined with set() and where() methods to create update queries. * * @param string|null $table Unused parameter. - * @return self + * @return $this */ public function update($table = null) { @@ -1132,7 +1132,7 @@ public function update($table = null) * Can be combined with the where() method to create delete queries. * * @param string|null $table Unused parameter. - * @return self + * @return $this */ public function delete($table = null) { @@ -1153,7 +1153,7 @@ public function delete($table = null) * * @param array $columns The columns to insert into. * @param array $types A map between columns & their datatypes. - * @return self + * @return $this */ public function insert(array $columns, array $types = []) { @@ -1217,7 +1217,7 @@ public function jsonSerialize() * auto-fields with this method. * * @param bool $value Set true to enable, false to disable. - * @return self + * @return $this */ public function enableAutoFields($value = true) { diff --git a/Table.php b/Table.php index 7ac89338..8ff6eedc 100644 --- a/Table.php +++ b/Table.php @@ -331,7 +331,7 @@ public function initialize(array $config) * Sets the database table name. * * @param string $table Table name. - * @return self + * @return $this */ public function setTable($table) { @@ -379,7 +379,7 @@ public function table($table = null) * Sets the table alias. * * @param string $alias Table alias - * @return self + * @return $this */ public function setAlias($alias) { @@ -439,7 +439,7 @@ public function aliasField($field) * Sets the table registry key used to create this table instance. * * @param string $registryAlias The key used to access this object. - * @return self + * @return $this */ public function setRegistryAlias($registryAlias) { @@ -483,7 +483,7 @@ public function registryAlias($registryAlias = null) * Sets the connection instance. * * @param \Cake\Datasource\ConnectionInterface $connection The connection instance - * @return self + * @return $this */ public function setConnection(ConnectionInterface $connection) { @@ -543,7 +543,7 @@ public function getSchema() * out of it and used as the schema for this table. * * @param array|\Cake\Database\Schema\TableSchema $schema Schema to be used for this table - * @return self + * @return $this */ public function setSchema($schema) { @@ -634,7 +634,7 @@ public function hasField($field) * Sets the primary key field name. * * @param string|array $key Sets a new name to be used as primary key - * @return self + * @return $this */ public function setPrimaryKey($key) { @@ -681,7 +681,7 @@ public function primaryKey($key = null) * Sets the display field. * * @param string $key Name to be used as display field. - * @return self + * @return $this */ public function setDisplayField($key) { @@ -766,7 +766,7 @@ public function getEntityClass() * * @param string $name The name of the class to use * @throws \Cake\ORM\Exception\MissingEntityException when the entity class cannot be found - * @return self + * @return $this */ public function setEntityClass($name) { From d325a24383dbc47ba02d610bd3d380fbfd1a3afa Mon Sep 17 00:00:00 2001 From: Yves P Date: Tue, 17 Jan 2017 21:27:50 +0100 Subject: [PATCH 0874/2059] Fix docblocks of the ORM `deleteAll()` method --- Association.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association.php b/Association.php index e24210e3..f6c321b1 100644 --- a/Association.php +++ b/Association.php @@ -736,7 +736,7 @@ public function updateAll($fields, $conditions) * * @param mixed $conditions Conditions to be used, accepts anything Query::where() * can take. - * @return bool Success Returns true if one or more rows are affected. + * @return int Returns the number of affected rows. * @see \Cake\ORM\Table::deleteAll() */ public function deleteAll($conditions) From 33cbb20f6f57bb61f7f156cec02b5f69271a2487 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 20 Jan 2017 22:59:22 -0500 Subject: [PATCH 0875/2059] Improve error message on belongsToMany associations Name the correct table when bindingKey() is empty. Refs #10054 --- Association.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Association.php b/Association.php index f6c321b1..8b046b84 100644 --- a/Association.php +++ b/Association.php @@ -900,8 +900,9 @@ protected function _joinCondition($options) if (count($foreignKey) !== count($bindingKey)) { if (empty($bindingKey)) { - $msg = 'The "%s" table does not define a primary key. Please set one.'; - throw new RuntimeException(sprintf($msg, $this->target()->table())); + $table = $this->isOwningSide($this->source()) ? $this->source()->table() : $this->target()->table(); + $msg = 'The "%s" table does not define a primary key, and cannot have join conditions generated.'; + throw new RuntimeException(sprintf($msg, $table)); } $msg = 'Cannot match provided foreignKey for "%s", got "(%s)" but expected foreign key for "(%s)"'; From b547b2d48cfd7640c402b03aa0a05270aac1a41a Mon Sep 17 00:00:00 2001 From: Cory Thompson Date: Sun, 22 Jan 2017 19:17:31 +1100 Subject: [PATCH 0876/2059] Fix numerous typos --- Association.php | 2 +- Association/BelongsToMany.php | 2 +- Behavior/TimestampBehavior.php | 2 +- Behavior/TreeBehavior.php | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Association.php b/Association.php index 8b046b84..5440cb54 100644 --- a/Association.php +++ b/Association.php @@ -634,7 +634,7 @@ protected function _appendNotMatching($query, $options) * @param bool $joined Whether or not the row is a result of a direct join * with this association * @param string $targetProperty The property name in the source results where the association - * data shuld be nested in. Will use the default one if not provided. + * data should be nested in. Will use the default one if not provided. * @return array */ public function transformRow($row, $nestKey, $joined, $targetProperty = null) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 81b8e116..689b7471 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -903,7 +903,7 @@ protected function junctionConditions() $matching[$field] = $value; } // Assume that operators contain junction conditions. - // Trying to munge complex conditions could result in incorrect queries. + // Trying to manage complex conditions could result in incorrect queries. if ($isString && in_array(strtoupper($field), ['OR', 'NOT', 'AND', 'XOR'])) { $matching[$field] = $value; } diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index de101b4d..aa77b07d 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -82,7 +82,7 @@ public function initialize(array $config) * * @param \Cake\Event\Event $event Event instance. * @param \Cake\Datasource\EntityInterface $entity Entity instance. - * @throws \UnexpectedValueException if a field's when value is misdefined + * @throws \UnexpectedValueException if a field's value is incorrectly defined * @return true (irrespective of the behavior logic, the save will not be prevented) * @throws \UnexpectedValueException When the value for an event is not 'always', 'new' or 'existing' */ diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 1620d7b2..c4d5cd48 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -156,7 +156,7 @@ public function beforeSave(Event $event, EntityInterface $entity) /** * After save listener. * - * Manages updating level of descendents of currently saved entity. + * Manages updating level of descendants of currently saved entity. * * @param \Cake\Event\Event $event The afterSave event that was fired * @param \Cake\Datasource\EntityInterface $entity the entity that is going to be saved @@ -172,9 +172,9 @@ public function afterSave(Event $event, EntityInterface $entity) } /** - * Set level for descendents. + * Set level for descendants. * - * @param \Cake\Datasource\EntityInterface $entity The entity whose descendents need to be updated. + * @param \Cake\Datasource\EntityInterface $entity The entity whose descendants need to be updated. * @return void */ protected function _setChildrenLevel($entity) From a69761ee05106a5ce9d278698a67bab3a7c9667e Mon Sep 17 00:00:00 2001 From: dereuromark Date: Sun, 22 Jan 2017 22:58:05 +0100 Subject: [PATCH 0877/2059] Fix up return self to $this for chainable. --- Query.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Query.php b/Query.php index fe398493..60b33f49 100644 --- a/Query.php +++ b/Query.php @@ -253,7 +253,7 @@ public function getEagerLoader() * @deprecated 3.4.0 Use setEagerLoader()/getEagerLoader() instead. * @param \Cake\ORM\EagerLoader|null $instance The eager loader to use. Pass null * to get the current eagerloader. - * @return \Cake\ORM\EagerLoader|self + * @return \Cake\ORM\EagerLoader|$this */ public function eagerLoader(EagerLoader $instance = null) { @@ -373,7 +373,7 @@ public function eagerLoader(EagerLoader $instance = null) * @param array|string|null $associations List of table aliases to be queried. * @param bool $override Whether override previous list with the one passed * defaults to merging previous list with the new one. - * @return array|self + * @return array|$this */ public function contain($associations = null, $override = false) { @@ -917,7 +917,7 @@ public function isHydrationEnabled() * @deprecated 3.4.0 Use enableHydration()/isHydrationEnabled() instead. * @param bool|null $enable Use a boolean to set the hydration mode. * Null will fetch the current hydration mode. - * @return bool|self A boolean when reading, and $this when setting the mode. + * @return bool|$this A boolean when reading, and $this when setting the mode. */ public function hydrate($enable = null) { @@ -1247,7 +1247,7 @@ public function isAutoFieldsEnabled() * * @deprecated 3.4.0 Use enableAutoFields()/isAutoFieldsEnabled() instead. * @param bool|null $value The value to set or null to read the current value. - * @return bool|self Either the current value or the query object. + * @return bool|$this Either the current value or the query object. */ public function autoFields($value = null) { From a2b1b05436b54ba013ee9d0236e48fdecd6ed47e Mon Sep 17 00:00:00 2001 From: Cory Thompson Date: Mon, 23 Jan 2017 18:48:50 +1100 Subject: [PATCH 0878/2059] HasMany.link using a single transaction --- Association/HasMany.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index fe18ee0b..a6cb8646 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -231,7 +231,9 @@ public function link(EntityInterface $sourceEntity, array $targetEntities, array $sourceEntity->set($property, $currentEntities); - $savedEntity = $this->saveAssociated($sourceEntity, $options); + $savedEntity = $this->connection()->transactional(function () use ($sourceEntity, $options) { + return $this->saveAssociated($sourceEntity, $options); + }); $ok = ($savedEntity instanceof EntityInterface); From 856f4ca09ab0a86e14565ec67766cfb217825c9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20W=C3=BCrth?= Date: Thu, 26 Jan 2017 19:50:05 +0100 Subject: [PATCH 0879/2059] Improve return value description of validateUnique() Fixes #10104 --- Table.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Table.php b/Table.php index 22c9ffe4..af080d9c 100644 --- a/Table.php +++ b/Table.php @@ -2299,11 +2299,11 @@ public function patchEntities($entities, array $data, array $options = []) * the same site_id. Scoping will only be used if the scoping field is present in * the data to be validated. * - * @param mixed $value The value of column to be checked for uniqueness + * @param mixed $value The value of column to be checked for uniqueness. * @param array $options The options array, optionally containing the 'scope' key. - * May also be the validation context if there are no options. + * May also be the validation context, if there are no options. * @param array|null $context Either the validation context or null. - * @return bool true if the value is unique + * @return bool True, if the value is unique, or false, if a non-scalar value was given. */ public function validateUnique($value, array $options, array $context = null) { From f5f7aed42857d49b63883de3d3e81eae9779ae80 Mon Sep 17 00:00:00 2001 From: mark_story Date: Thu, 26 Jan 2017 14:43:11 -0500 Subject: [PATCH 0880/2059] Tweak API doc wording. Mention non-unique values too. Refs #10110 --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index af080d9c..cf695f84 100644 --- a/Table.php +++ b/Table.php @@ -2303,7 +2303,7 @@ public function patchEntities($entities, array $data, array $options = []) * @param array $options The options array, optionally containing the 'scope' key. * May also be the validation context, if there are no options. * @param array|null $context Either the validation context or null. - * @return bool True, if the value is unique, or false, if a non-scalar value was given. + * @return bool True if the value is unique, or false if a non-scalar, non-unique value was given. */ public function validateUnique($value, array $options, array $context = null) { From 51f145d9167212ff2877ae06907068d5e56f05e5 Mon Sep 17 00:00:00 2001 From: Marc Ypes Date: Thu, 26 Jan 2017 22:55:54 +0100 Subject: [PATCH 0881/2059] Remove unneeded call of clean() as options already contain markClean --- ResultSet.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/ResultSet.php b/ResultSet.php index 1b034f31..5e871d72 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -499,7 +499,6 @@ protected function _groupResult($row) if ($this->_hydrate) { $options['source'] = $matching['instance']->registryAlias(); $entity = new $matching['entityClass']($results['_matchingData'][$alias], $options); - $entity->clean(); $results['_matchingData'][$alias] = $entity; } } @@ -549,7 +548,6 @@ protected function _groupResult($row) if ($this->_hydrate && $results[$alias] !== null && $assoc['canBeJoined']) { $entity = new $assoc['entityClass']($results[$alias], $options); - $entity->clean(); $results[$alias] = $entity; } From 02c34e99b617b2ad3e68cfb6e752ce341c3cd7dd Mon Sep 17 00:00:00 2001 From: thinkingmedia Date: Sun, 29 Jan 2017 18:19:57 -0500 Subject: [PATCH 0882/2059] fixes issue #101111 by forcing array type --- ResultSet.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ResultSet.php b/ResultSet.php index 5e871d72..19bebb33 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -330,7 +330,8 @@ public function serialize() */ public function unserialize($serialized) { - $this->_results = unserialize($serialized); + // closes #10111 prevent SqlFixedArray instances + $this->_results = (array)unserialize($serialized); $this->_useBuffering = true; $this->_count = count($this->_results); } From f8332413d8d01e2dfc459e03377dca9c327628cb Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 29 Jan 2017 22:38:18 -0500 Subject: [PATCH 0883/2059] Replace calls to config() with get/setConfig() Update internal uses of deprecated methods with new methods. I've not updated config() on the TableLocator as getConfig/setConfig() are not part of that interface. Refs #10128 --- Behavior.php | 12 +++++------ Behavior/TimestampBehavior.php | 2 +- Behavior/TranslateBehavior.php | 4 ++-- Behavior/TreeBehavior.php | 38 +++++++++++++++++----------------- LazyEagerLoader.php | 4 ++-- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/Behavior.php b/Behavior.php index 3aebe1bf..2f08f75b 100644 --- a/Behavior.php +++ b/Behavior.php @@ -162,7 +162,7 @@ public function __construct(Table $table, array $config = []) $config ); $this->_table = $table; - $this->config($config); + $this->setConfig($config); $this->initialize($config); } @@ -193,7 +193,7 @@ protected function _resolveMethodAliases($key, $defaults, $config) return $config; } if (isset($config[$key]) && $config[$key] === []) { - $this->config($key, [], false); + $this->setConfig($key, [], false); unset($config[$key]); return $config; @@ -206,7 +206,7 @@ protected function _resolveMethodAliases($key, $defaults, $config) $indexedCustom[$method] = $alias; } } - $this->config($key, array_flip($indexedCustom), false); + $this->setConfig($key, array_flip($indexedCustom), false); unset($config[$key]); return $config; @@ -263,7 +263,7 @@ public function implementedEvents() 'Model.beforeRules' => 'beforeRules', 'Model.afterRules' => 'afterRules', ]; - $config = $this->config(); + $config = $this->getConfig(); $priority = isset($config['priority']) ? $config['priority'] : null; $events = []; @@ -307,7 +307,7 @@ public function implementedEvents() */ public function implementedFinders() { - $methods = $this->config('implementedFinders'); + $methods = $this->getConfig('implementedFinders'); if (isset($methods)) { return $methods; } @@ -338,7 +338,7 @@ public function implementedFinders() */ public function implementedMethods() { - $methods = $this->config('implementedMethods'); + $methods = $this->getConfig('implementedMethods'); if (isset($methods)) { return $methods; } diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 4bdd926f..06342bb0 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -73,7 +73,7 @@ class TimestampBehavior extends Behavior public function initialize(array $config) { if (isset($config['events'])) { - $this->config('events', $config['events'], false); + $this->setConfig('events', $config['events'], false); } } diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 0e5d09d8..9b0266af 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -208,7 +208,7 @@ public function beforeFind(Event $event, Query $query, $options) { $locale = $this->locale(); - if ($locale === $this->config('defaultLocale')) { + if ($locale === $this->getConfig('defaultLocale')) { return; } @@ -278,7 +278,7 @@ public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $o // No additional translation records need to be saved, // as the entity is in the default locale. - if ($noBundled && $locale === $this->config('defaultLocale')) { + if ($noBundled && $locale === $this->getConfig('defaultLocale')) { return; } diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 81683f56..275bcb9e 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -97,7 +97,7 @@ public function initialize(array $config) public function beforeSave(Event $event, EntityInterface $entity) { $isNew = $entity->isNew(); - $config = $this->config(); + $config = $this->getConfig(); $parent = $entity->get($config['parent']); $primaryKey = $this->_getPrimaryKey(); $dirty = $entity->dirty($config['parent']); @@ -179,7 +179,7 @@ public function afterSave(Event $event, EntityInterface $entity) */ protected function _setChildrenLevel($entity) { - $config = $this->config(); + $config = $this->getConfig(); if ($entity->get($config['left']) + 1 === $entity->get($config['right'])) { return; @@ -217,7 +217,7 @@ protected function _setChildrenLevel($entity) */ public function beforeDelete(Event $event, EntityInterface $entity) { - $config = $this->config(); + $config = $this->getConfig(); $this->_ensureFields($entity); $left = $entity->get($config['left']); $right = $entity->get($config['right']); @@ -246,7 +246,7 @@ public function beforeDelete(Event $event, EntityInterface $entity) */ protected function _setParent($entity, $parent) { - $config = $this->config(); + $config = $this->getConfig(); $parentNode = $this->_getNode($parent); $this->_ensureFields($entity); $parentLeft = $parentNode->get($config['left']); @@ -306,7 +306,7 @@ protected function _setParent($entity, $parent) */ protected function _setAsRoot($entity) { - $config = $this->config(); + $config = $this->getConfig(); $edge = $this->_getMax(); $this->_ensureFields($entity); $right = $entity->get($config['right']); @@ -339,7 +339,7 @@ protected function _setAsRoot($entity) */ protected function _unmarkInternalTree() { - $config = $this->config(); + $config = $this->getConfig(); $this->_table->updateAll( function ($exp) use ($config) { $leftInverse = clone $exp; @@ -372,7 +372,7 @@ public function findPath(Query $query, array $options) throw new InvalidArgumentException("The 'for' key is required for find('path')"); } - $config = $this->config(); + $config = $this->getConfig(); list($left, $right) = array_map( function ($field) { return $this->_table->aliasField($field); @@ -400,7 +400,7 @@ function ($field) { */ public function childCount(EntityInterface $node, $direct = false) { - $config = $this->config(); + $config = $this->getConfig(); $parent = $this->_table->aliasField($config['parent']); if ($direct) { @@ -432,7 +432,7 @@ public function childCount(EntityInterface $node, $direct = false) */ public function findChildren(Query $query, array $options) { - $config = $this->config(); + $config = $this->getConfig(); $options += ['for' => null, 'direct' => false]; list($parent, $left, $right) = array_map( function ($field) { @@ -483,11 +483,11 @@ function ($field) { */ public function findTreeList(Query $query, array $options) { - $left = $this->_table->aliasField($this->config('left')); + $left = $this->_table->aliasField($this->getConfig('left')); $results = $this->_scope($query) ->find('threaded', [ - 'parentField' => $this->config('parent'), + 'parentField' => $this->getConfig('parent'), 'order' => [$left => 'ASC'], ]); @@ -555,7 +555,7 @@ public function removeFromTree(EntityInterface $node) */ protected function _removeFromTree($node) { - $config = $this->config(); + $config = $this->getConfig(); $left = $node->get($config['left']); $right = $node->get($config['right']); $parent = $node->get($config['parent']); @@ -621,7 +621,7 @@ public function moveUp(EntityInterface $node, $number = 1) */ protected function _moveUp($node, $number) { - $config = $this->config(); + $config = $this->getConfig(); list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']]; list($nodeParent, $nodeLeft, $nodeRight) = array_values($node->extract([$parent, $left, $right])); @@ -709,7 +709,7 @@ public function moveDown(EntityInterface $node, $number = 1) */ protected function _moveDown($node, $number) { - $config = $this->config(); + $config = $this->getConfig(); list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']]; list($nodeParent, $nodeLeft, $nodeRight) = array_values($node->extract([$parent, $left, $right])); @@ -774,7 +774,7 @@ protected function _moveDown($node, $number) */ protected function _getNode($id) { - $config = $this->config(); + $config = $this->getConfig(); list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']]; $primaryKey = $this->_getPrimaryKey(); $fields = [$parent, $left, $right]; @@ -817,7 +817,7 @@ public function recover() */ protected function _recoverTree($counter = 0, $parentId = null, $level = -1) { - $config = $this->config(); + $config = $this->getConfig(); list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']]; $primaryKey = $this->_getPrimaryKey(); $aliasedPrimaryKey = $this->_table->aliasField($primaryKey); @@ -922,7 +922,7 @@ protected function _sync($shift, $dir, $conditions, $mark = false) */ protected function _scope($query) { - $config = $this->config(); + $config = $this->getConfig(); if (is_array($config['scope'])) { return $query->where($config['scope']); @@ -943,7 +943,7 @@ protected function _scope($query) */ protected function _ensureFields($entity) { - $config = $this->config(); + $config = $this->getConfig(); $fields = [$config['left'], $config['right']]; $values = array_filter($entity->extract($fields)); if (count($values) === count($fields)) { @@ -986,7 +986,7 @@ public function getLevel($entity) if ($entity instanceof EntityInterface) { $id = $entity->get($primaryKey); } - $config = $this->config(); + $config = $this->getConfig(); $entity = $this->_table->find('all') ->select([$config['left'], $config['right']]) ->where([$primaryKey => $id]) diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index f943d763..de6234ae 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -96,9 +96,9 @@ protected function _getQuery($objects, $contain, $source) ->contain($contain); foreach ($query->getEagerLoader()->attachableAssociations($source) as $loadable) { - $config = $loadable->config(); + $config = $loadable->getConfig(); $config['includeFields'] = true; - $loadable->config($config); + $loadable->setConfig($config); } return $query; From c1ba6d3630c1031dcfb901c151e2416290861265 Mon Sep 17 00:00:00 2001 From: thinkingmedia Date: Mon, 30 Jan 2017 08:11:31 -0500 Subject: [PATCH 0884/2059] improves fix by not serializing SplFixedArray objects --- ResultSet.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ResultSet.php b/ResultSet.php index 19bebb33..a93990db 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -317,6 +317,10 @@ public function serialize() $this->next(); } + if($this->_results instanceof SplFixedArray) { + return serialize($this->_results->toArray()); + } + return serialize($this->_results); } @@ -330,8 +334,7 @@ public function serialize() */ public function unserialize($serialized) { - // closes #10111 prevent SqlFixedArray instances - $this->_results = (array)unserialize($serialized); + $this->_results = (array)(unserialize($serialized) ?: []); $this->_useBuffering = true; $this->_count = count($this->_results); } From 3d7fba3120558dcf0363b25c0d3fdacb297d049f Mon Sep 17 00:00:00 2001 From: thinkingmedia Date: Mon, 30 Jan 2017 08:14:31 -0500 Subject: [PATCH 0885/2059] use SplFixedArray count method --- ResultSet.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ResultSet.php b/ResultSet.php index a93990db..fc566743 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -355,7 +355,13 @@ public function count() return $this->_count = $this->_statement->rowCount(); } - return $this->_count = count($this->_results); + if($this->_results instanceof SplFixedArray) { + $this->_count = $this->_results->count(); + } else { + $this->_count = count($this->_results); + } + + return $this->_count; } /** From 726c29fe7cabe63d8eb76f94f9f3d641f5c5a45a Mon Sep 17 00:00:00 2001 From: thinkingmedia Date: Mon, 30 Jan 2017 08:37:27 -0500 Subject: [PATCH 0886/2059] this restores SplFixedArray performance op after unserialization --- ResultSet.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ResultSet.php b/ResultSet.php index fc566743..a20f15fe 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -317,7 +317,7 @@ public function serialize() $this->next(); } - if($this->_results instanceof SplFixedArray) { + if ($this->_results instanceof SplFixedArray) { return serialize($this->_results->toArray()); } @@ -334,9 +334,10 @@ public function serialize() */ public function unserialize($serialized) { - $this->_results = (array)(unserialize($serialized) ?: []); + $results = (array)(unserialize($serialized) ?: []); + $this->_results = SplFixedArray::fromArray($results); $this->_useBuffering = true; - $this->_count = count($this->_results); + $this->_count = $this->_results->count(); } /** @@ -355,7 +356,7 @@ public function count() return $this->_count = $this->_statement->rowCount(); } - if($this->_results instanceof SplFixedArray) { + if ($this->_results instanceof SplFixedArray) { $this->_count = $this->_results->count(); } else { $this->_count = count($this->_results); From 6efe272e30ea403338b40df850171ec24e33b076 Mon Sep 17 00:00:00 2001 From: thinkingmedia Date: Mon, 30 Jan 2017 09:31:58 -0500 Subject: [PATCH 0887/2059] added exception for serialize an unbuffered result set, and also a test for this edge case --- ResultSet.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ResultSet.php b/ResultSet.php index a20f15fe..dd9e64f3 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -313,6 +313,11 @@ public function first() */ public function serialize() { + if (!$this->_useBuffering) { + $msg = 'You cannot serialize an un-buffered ResultSet. Use Query::bufferResults() to get a buffered ResultSet.'; + throw new Exception($msg); + } + while ($this->valid()) { $this->next(); } From 9ef2e31dc642f5613ef67e939cda5d5c0d2affdb Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 31 Jan 2017 23:20:35 -0500 Subject: [PATCH 0888/2059] Fix a few deprecated method call sites. I found a few more quickly grepping through the code. --- Behavior/TranslateBehavior.php | 2 +- EagerLoader.php | 4 ++-- Query.php | 4 ++-- Table.php | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 9b0266af..44d87aed 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -216,7 +216,7 @@ public function beforeFind(Event $event, Query $query, $options) return function ($q) use ($field, $locale, $query, $select) { $q->where([$q->repository()->aliasField('locale') => $locale]); - if ($query->autoFields() || + if ($query->isAutoFieldsEnabled() || in_array($field, $select, true) || in_array($this->_table->aliasField($field), $select, true) ) { diff --git a/EagerLoader.php b/EagerLoader.php index 25a76b14..11ca37c0 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -581,7 +581,7 @@ protected function _correctStrategy($loadable) $config['strategy'] = Association::STRATEGY_SELECT; $loadable->setConfig($config); - $loadable->canBeJoined(false); + $loadable->setCanBeJoined(false); } /** @@ -611,7 +611,7 @@ protected function _resolveJoins($associations, $matching = []) $this->_correctStrategy($loadable); } - $loadable->canBeJoined(false); + $loadable->setCanBeJoined(false); $this->_loadExternal[] = $loadable; } diff --git a/Query.php b/Query.php index 60b33f49..7d6c6091 100644 --- a/Query.php +++ b/Query.php @@ -495,7 +495,7 @@ public function matching($assoc, callable $builder = null) * ->select(['total_articles' => $query->func()->count('Articles.id')]) * ->leftJoinWith('Articles') * ->group(['Users.id']) - * ->autoFields(true); + * ->setAutoFields(true); * ``` * * You can also customize the conditions passed to the LEFT JOIN: @@ -508,7 +508,7 @@ public function matching($assoc, callable $builder = null) * return $q->where(['Articles.votes >=' => 5]); * }) * ->group(['Users.id']) - * ->autoFields(true); + * ->setAutoFields(true); * ``` * * This will create the following SQL: diff --git a/Table.php b/Table.php index 8a529c17..eb248611 100644 --- a/Table.php +++ b/Table.php @@ -316,7 +316,7 @@ public static function defaultConnectionName() * { * $this->belongsTo('Users'); * $this->belongsToMany('Tagging.Tags'); - * $this->primaryKey('something_else'); + * $this->setPrimaryKey('something_else'); * } * ``` * From 3eeced0eac4589853836f8bcc1b4b0fdc6d6df92 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 31 Jan 2017 23:27:01 -0500 Subject: [PATCH 0889/2059] Fix mistakes in API docs. --- Query.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Query.php b/Query.php index 7d6c6091..236c6f7f 100644 --- a/Query.php +++ b/Query.php @@ -495,7 +495,7 @@ public function matching($assoc, callable $builder = null) * ->select(['total_articles' => $query->func()->count('Articles.id')]) * ->leftJoinWith('Articles') * ->group(['Users.id']) - * ->setAutoFields(true); + * ->enableAutoFields(true); * ``` * * You can also customize the conditions passed to the LEFT JOIN: @@ -508,7 +508,7 @@ public function matching($assoc, callable $builder = null) * return $q->where(['Articles.votes >=' => 5]); * }) * ->group(['Users.id']) - * ->setAutoFields(true); + * ->enableAutoFields(true); * ``` * * This will create the following SQL: From 6fd42f32c3b7e5b58ad05f9b7e2c291701183631 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Thu, 2 Feb 2017 15:09:48 +0100 Subject: [PATCH 0890/2059] Make sure the target table is an instance of a correct class. --- Association.php | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Association.php b/Association.php index ca4c3676..66465c44 100644 --- a/Association.php +++ b/Association.php @@ -15,6 +15,7 @@ namespace Cake\ORM; use Cake\Collection\Collection; +use Cake\Core\App; use Cake\Core\ConventionsTrait; use Cake\Database\Expression\IdentifierExpression; use Cake\Datasource\EntityInterface; @@ -400,6 +401,15 @@ public function getTarget() $config = ['className' => $this->_className]; } $this->_targetTable = $tableLocator->get($registryAlias, $config); + + $className = $this->_getClassName($registryAlias, ['className' => $this->_className]); + if (!is_a($this->_targetTable, $className)) { + throw new RuntimeException(sprintf( + 'Invalid Table retrieved from a registry. Requested: %s, got: %s', + get_class($this->_targetTable), + $className + )); + } } return $this->_targetTable; @@ -1278,6 +1288,22 @@ protected function _extractFinder($finderData) return [key($finderData), current($finderData)]; } + /** + * Gets the table class name. + * + * @param string $alias The alias name you want to get. + * @param array $options Table options array. + * @return string + */ + protected function _getClassName($alias, array $options = []) + { + if (empty($options['className'])) { + $options['className'] = Inflector::camelize($alias); + } + + return App::className($options['className'], 'Model/Table', 'Table') ?: 'Cake\ORM\Table'; + } + /** * Proxies property retrieval to the target table. This is handy for getting this * association's associations From 3acd85b0fd17fa394be47ee0a5559d88e33a9372 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Thu, 2 Feb 2017 15:24:23 +0100 Subject: [PATCH 0891/2059] Fix exception message --- Association.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Association.php b/Association.php index 66465c44..e4c28ece 100644 --- a/Association.php +++ b/Association.php @@ -406,8 +406,8 @@ public function getTarget() if (!is_a($this->_targetTable, $className)) { throw new RuntimeException(sprintf( 'Invalid Table retrieved from a registry. Requested: %s, got: %s', - get_class($this->_targetTable), - $className + $className, + get_class($this->_targetTable) )); } } From 4b6c696b116d32b50934c6e3b30c63e696be582f Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 2 Feb 2017 22:29:17 -0500 Subject: [PATCH 0892/2059] Update more deprecated method usage. Refs #10128 --- Association/Loader/SelectLoader.php | 2 +- Behavior/TranslateBehavior.php | 4 ++-- Behavior/TreeBehavior.php | 2 +- Table.php | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index e4c663f5..d8a62db2 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -169,7 +169,7 @@ protected function _buildQuery($options) ->select($options['fields']) ->where($options['conditions']) ->eagerLoaded(true) - ->hydrate($options['query']->hydrate()); + ->enableHydration($options['query']->isHydrationEnabled()); if ($useSubquery) { $filter = $this->_buildSubquery($options['query']); diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 44d87aed..efbe5ea8 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -647,8 +647,8 @@ protected function _findExistingTranslations($ruleSet) $query = $association->find() ->select(['id', 'num' => 0]) ->where(current($ruleSet)) - ->hydrate(false) - ->bufferResults(false); + ->enableHydration(false) + ->enableBufferedResults(false); unset($ruleSet[0]); foreach ($ruleSet as $i => $conditions) { diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 275bcb9e..412e6645 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -827,7 +827,7 @@ protected function _recoverTree($counter = 0, $parentId = null, $level = -1) ->select([$aliasedPrimaryKey]) ->where([$this->_table->aliasField($parent) . ' IS' => $parentId]) ->order($order) - ->hydrate(false); + ->enableHydration(false); $leftCounter = $counter; $nextLevel = $level + 1; diff --git a/Table.php b/Table.php index eb248611..eabf3e9c 100644 --- a/Table.php +++ b/Table.php @@ -1610,7 +1610,7 @@ public function exists($conditions) ->select(['existing' => 1]) ->where($conditions) ->limit(1) - ->hydrate(false) + ->enableHydration(false) ->toArray() ); } From d5dedd354d69506e327bc62a37b8c56da8bd6f13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Fri, 3 Feb 2017 10:07:34 +0100 Subject: [PATCH 0893/2059] Optimize class name check. --- Association.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Association.php b/Association.php index e4c28ece..979a9161 100644 --- a/Association.php +++ b/Association.php @@ -402,12 +402,14 @@ public function getTarget() } $this->_targetTable = $tableLocator->get($registryAlias, $config); + $targetClassName = get_class($this->_targetTable); $className = $this->_getClassName($registryAlias, ['className' => $this->_className]); - if (!is_a($this->_targetTable, $className)) { + + if ($targetClassName !== $className) { throw new RuntimeException(sprintf( 'Invalid Table retrieved from a registry. Requested: %s, got: %s', $className, - get_class($this->_targetTable) + $targetClassName )); } } @@ -1301,7 +1303,9 @@ protected function _getClassName($alias, array $options = []) $options['className'] = Inflector::camelize($alias); } - return App::className($options['className'], 'Model/Table', 'Table') ?: 'Cake\ORM\Table'; + $className = App::className($options['className'], 'Model/Table', 'Table') ?: 'Cake\ORM\Table'; + + return ltrim($className, '\\'); } /** From ef5ac3c0a42c7a71d3b5a6fb5814b77f2e37865b Mon Sep 17 00:00:00 2001 From: chinpei215 Date: Fri, 3 Feb 2017 19:21:02 +0900 Subject: [PATCH 0894/2059] Fix an incorrect deprecation note --- Association.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association.php b/Association.php index ca4c3676..ceb6aebb 100644 --- a/Association.php +++ b/Association.php @@ -409,7 +409,7 @@ public function getTarget() * Sets the table instance for the target side of the association. If no arguments * are passed, the current configured table instance is returned * - * @deprecated 3.4.0 Use setTable()/getTable() instead. + * @deprecated 3.4.0 Use setTarget()/getTarget() instead. * @param \Cake\ORM\Table|null $table the instance to be assigned as target side * @return \Cake\ORM\Table */ From f4c8191c72b5e156178aa3415b5d2a9ebe5034df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Fri, 3 Feb 2017 13:59:21 +0100 Subject: [PATCH 0895/2059] Optimization. --- Association.php | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/Association.php b/Association.php index 979a9161..069a492e 100644 --- a/Association.php +++ b/Association.php @@ -397,20 +397,23 @@ public function getTarget() $tableLocator = $this->tableLocator(); $config = []; - if (!$tableLocator->exists($registryAlias)) { + $exists = $tableLocator->exists($registryAlias); + if (!$exists) { $config = ['className' => $this->_className]; } $this->_targetTable = $tableLocator->get($registryAlias, $config); - $targetClassName = get_class($this->_targetTable); - $className = $this->_getClassName($registryAlias, ['className' => $this->_className]); + if ($exists) { + $targetClassName = get_class($this->_targetTable); + $className = $this->_getClassName($registryAlias, ['className' => $this->_className]); - if ($targetClassName !== $className) { - throw new RuntimeException(sprintf( - 'Invalid Table retrieved from a registry. Requested: %s, got: %s', - $className, - $targetClassName - )); + if ($targetClassName !== $className) { + throw new RuntimeException(sprintf( + 'Invalid Table retrieved from a registry. Requested: %s, got: %s', + $className, + $targetClassName + )); + } } } From dbf7daa459721d6b9e308cfd99ff4501b5a2d8d1 Mon Sep 17 00:00:00 2001 From: Anthony GRASSIOT Date: Sat, 4 Feb 2017 18:09:56 +0100 Subject: [PATCH 0896/2059] remove more deprecated methods usage --- Association/HasMany.php | 2 +- Behavior/CounterCacheBehavior.php | 8 ++++---- Behavior/Translate/TranslateTrait.php | 2 +- Entity.php | 2 +- Marshaller.php | 6 +++--- Table.php | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index a7aff92a..cdeede64 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -264,7 +264,7 @@ public function link(EntityInterface $sourceEntity, array $targetEntities, array $sourceEntity->set($property, $currentEntities); - $savedEntity = $this->connection()->transactional(function () use ($sourceEntity, $options) { + $savedEntity = $this->getConnection()->transactional(function () use ($sourceEntity, $options) { return $this->saveAssociated($sourceEntity, $options); }); diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 56621c2d..1b8f0f67 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -116,8 +116,8 @@ public function beforeSave(Event $event, EntityInterface $entity) continue; } - $registryAlias = $assoc->target()->registryAlias(); - $entityAlias = $assoc->property(); + $registryAlias = $assoc->getTarget()->getRegistryAlias(); + $entityAlias = $assoc->getProperty(); if (!is_callable($config) && isset($config['ignoreDirty']) && @@ -201,8 +201,8 @@ protected function _processAssociation(Event $event, EntityInterface $entity, As $config = []; } - if (isset($this->_ignoreDirty[$assoc->target()->registryAlias()][$field]) && - $this->_ignoreDirty[$assoc->target()->registryAlias()][$field] === true + if (isset($this->_ignoreDirty[$assoc->getTarget()->getRegistryAlias()][$field]) && + $this->_ignoreDirty[$assoc->getTarget()->getRegistryAlias()][$field] === true ) { continue; } diff --git a/Behavior/Translate/TranslateTrait.php b/Behavior/Translate/TranslateTrait.php index b98738d4..7df90772 100644 --- a/Behavior/Translate/TranslateTrait.php +++ b/Behavior/Translate/TranslateTrait.php @@ -58,7 +58,7 @@ public function translation($language) } // Assume the user will modify any of the internal translations, helps with saving - $this->dirty('_translations', true); + $this->setDirty('_translations', true); return $i18n[$language]; } diff --git a/Entity.php b/Entity.php index ce96056d..6eea8da3 100644 --- a/Entity.php +++ b/Entity.php @@ -56,7 +56,7 @@ public function __construct(array $properties = [], array $options = []) ]; if (!empty($options['source'])) { - $this->source($options['source']); + $this->setSource($options['source']); } if ($options['markNew'] !== null) { diff --git a/Marshaller.php b/Marshaller.php index d2c7edce..4f29cdac 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -170,11 +170,11 @@ public function one(array $data, array $options = []) $entityClass = $this->_table->getEntityClass(); /* @var Entity $entity */ $entity = new $entityClass(); - $entity->source($this->_table->getRegistryAlias()); + $entity->setSource($this->_table->getRegistryAlias()); if (isset($options['accessibleFields'])) { foreach ((array)$options['accessibleFields'] as $key => $value) { - $entity->accessible($key, $value); + $entity->setAccess($key, $value); } } $errors = $this->_validate($data, $options, true); @@ -210,7 +210,7 @@ public function one(array $data, array $options = []) $entity->set($properties); } - $entity->errors($errors); + $entity->setErrors($errors); return $entity; } diff --git a/Table.php b/Table.php index eabf3e9c..8cfa54d3 100644 --- a/Table.php +++ b/Table.php @@ -1771,7 +1771,7 @@ protected function _processSave($entity, $options) $event = $this->dispatchEvent('Model.beforeSave', compact('entity', 'options')); if ($event->isStopped()) { - return $event->result(); + return $event->getResult(); } $saved = $this->_associations->saveParents( @@ -2099,7 +2099,7 @@ protected function _processDelete($entity, $options) ]); if ($event->isStopped()) { - return $event->result(); + return $event->getResult(); } $this->_associations->cascadeDelete( From aa126e087da2292d9545c0c323e0fa6b6bb1f5bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Fri, 3 Feb 2017 10:44:01 +0100 Subject: [PATCH 0897/2059] Added a check to name setter. --- Association.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Association.php b/Association.php index ceb6aebb..332ef884 100644 --- a/Association.php +++ b/Association.php @@ -244,6 +244,13 @@ public function __construct($alias, array $options = []) */ public function setName($name) { + if ($this->_targetTable !== null) { + $alias = $this->_targetTable->getAlias(); + if ($alias !== $name) { + throw new InvalidArgumentException('Association name does not match target table alias.'); + } + } + $this->_name = $name; return $this; From 972eb7fe52d7223f19c8585f21e3364c95c4da73 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 9 Feb 2017 22:09:57 -0500 Subject: [PATCH 0898/2059] Don't emit errors when marhshaller gets a scalar for an association If associations are patchable, and a scalar value is sent no errors should be emitted. Refs #10184 --- Marshaller.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Marshaller.php b/Marshaller.php index b994a8d8..c6146908 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -698,6 +698,9 @@ protected function _mergeAssociation($original, $assoc, $value, $options) if (!$original) { return $this->_marshalAssociation($assoc, $value, $options); } + if (!is_array($value)) { + return null; + } $targetTable = $assoc->target(); $marshaller = $targetTable->marshaller(); From 67a2dac806bca4010b4b9a3d66fc47d6bc828e54 Mon Sep 17 00:00:00 2001 From: Bryan Crowe Date: Fri, 10 Feb 2017 12:19:55 -0500 Subject: [PATCH 0899/2059] Remove bunk annotation, use full stop --- Table.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Table.php b/Table.php index cf695f84..3bfcdb54 100644 --- a/Table.php +++ b/Table.php @@ -479,8 +479,7 @@ public function schema($schema = null) * ``` * * @param \Cake\Database\Schema\Table $table The table definition fetched from database. - * @return \Cake\Database\Schema\Table the altered schema - * @api + * @return \Cake\Database\Schema\Table The altered schema. */ protected function _initializeSchema(Schema $table) { From 83b3384f2922208c1232d9ad523278226583780c Mon Sep 17 00:00:00 2001 From: dereuromark Date: Fri, 10 Feb 2017 19:25:59 +0100 Subject: [PATCH 0900/2059] Fix up contain() to be BC with key value contains. --- EagerLoader.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/EagerLoader.php b/EagerLoader.php index 11ca37c0..0b0c9717 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -389,6 +389,10 @@ protected function _reformatContain($associations, $original) }; } + if (!is_array($options)) { + $options = [$options => []]; + } + $pointer[$table] = $options + $pointer[$table]; } From afa6ac992d7337a10b1a4f1848b54af58b8ad440 Mon Sep 17 00:00:00 2001 From: Walther Lalk Date: Mon, 13 Feb 2017 12:17:51 +0200 Subject: [PATCH 0901/2059] Add option to prevent counter cache from updating the counter cache --- Behavior/CounterCacheBehavior.php | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 1b8f0f67..607171e7 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -105,10 +105,15 @@ class CounterCacheBehavior extends Behavior * * @param \Cake\Event\Event $event The beforeSave event that was fired * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved + * @param \ArrayObject $options The options for the query * @return void */ - public function beforeSave(Event $event, EntityInterface $entity) + public function beforeSave(Event $event, EntityInterface $entity, $options) { + if (isset($options['ignoreCounterCache']) && $options['ignoreCounterCache'] === true) { + return; + } + foreach ($this->_config as $assoc => $settings) { $assoc = $this->_table->association($assoc); foreach ($settings as $field => $config) { @@ -137,10 +142,15 @@ public function beforeSave(Event $event, EntityInterface $entity) * * @param \Cake\Event\Event $event The afterSave event that was fired. * @param \Cake\Datasource\EntityInterface $entity The entity that was saved. + * @param \ArrayObject $options The options for the query * @return void */ - public function afterSave(Event $event, EntityInterface $entity) + public function afterSave(Event $event, EntityInterface $entity, $options) { + if (isset($options['ignoreCounterCache']) && $options['ignoreCounterCache'] === true) { + return; + } + $this->_processAssociations($event, $entity); $this->_ignoreDirty = []; } @@ -152,10 +162,15 @@ public function afterSave(Event $event, EntityInterface $entity) * * @param \Cake\Event\Event $event The afterDelete event that was fired. * @param \Cake\Datasource\EntityInterface $entity The entity that was deleted. + * @param \ArrayObject $options The options for the query * @return void */ - public function afterDelete(Event $event, EntityInterface $entity) + public function afterDelete(Event $event, EntityInterface $entity, $options) { + if (isset($options['ignoreCounterCache']) && $options['ignoreCounterCache'] === true) { + return; + } + $this->_processAssociations($event, $entity); } From 707e3088166c6246f724ab2699b14cbfb3731c3d Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Fri, 3 Feb 2017 14:49:29 +0100 Subject: [PATCH 0902/2059] Add new saveOrFail and deleteOrFail method --- Exception/PersistenceFailedException.php | 24 ++++++++++++++ Table.php | 41 ++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 Exception/PersistenceFailedException.php diff --git a/Exception/PersistenceFailedException.php b/Exception/PersistenceFailedException.php new file mode 100644 index 00000000..59983eae --- /dev/null +++ b/Exception/PersistenceFailedException.php @@ -0,0 +1,24 @@ +save($entity, $options); + if ($saved === false) { + throw new PersistenceFailedException(['save', $entity]); + } + + return $saved; + } + /** * Performs the actual saving of an entity based on the passed options. * @@ -1847,6 +1868,26 @@ public function delete(EntityInterface $entity, $options = []) return $success; } + /** + * Try to delete an entity or throw a PersistenceFailedException if the entity is new, + * has no primary key value, application rules checks failed or the delete was aborted by a callback. + * + * @param \Cake\Datasource\EntityInterface $entity The entity to remove. + * @param array|\ArrayAccess $options The options for the delete. + * @return bool success + * @throws \Cake\ORM\Exception\PersistenceFailedException + * @see \Cake\ORM\Table::delete() + */ + public function deleteOrFail(EntityInterface $entity, $options = []) + { + $deleted = $this->delete($entity, $options); + if ($deleted === false) { + throw new PersistenceFailedException(['delete', $entity]); + } + + return $deleted; + } + /** * Perform the delete operation. * From df961cca9dd62751e8733cd6e0ae7c2d2981febb Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Mon, 13 Feb 2017 21:31:58 +0100 Subject: [PATCH 0903/2059] Add an entity getter --- Exception/PersistenceFailedException.php | 36 ++++++++++++++++++++++++ Table.php | 4 +-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/Exception/PersistenceFailedException.php b/Exception/PersistenceFailedException.php index 59983eae..456b6fed 100644 --- a/Exception/PersistenceFailedException.php +++ b/Exception/PersistenceFailedException.php @@ -13,6 +13,7 @@ namespace Cake\ORM\Exception; use Cake\Core\Exception\Exception; +use Cake\Datasource\EntityInterface; /** * Used when a strict save or delete fails @@ -20,5 +21,40 @@ class PersistenceFailedException extends Exception { + /** + * The entity on which the persistence operation failed + * + * @var \Cake\Datasource\EntityInterface + */ + protected $_entity; + + /** + * {@inheritDoc} + */ protected $_messageTemplate = 'Entity %s failure.'; + + /** + * Constructor. + * + * @param \Cake\Datasource\EntityInterface $entity The entity on which the persistence operation failed + * @param string|array $message Either the string of the error message, or an array of attributes + * that are made available in the view, and sprintf()'d into Exception::$_messageTemplate + * @param int $code The code of the error, is also the HTTP status code for the error. + * @param \Exception|null $previous the previous exception. + */ + public function __construct(EntityInterface $entity, $message, $code = 500, $previous = null) + { + $this->_entity = $entity; + parent::__construct($message, $code, $previous); + } + + /** + * Get the passed in entity + * + * @return \Cake\Datasource\EntityInterface + */ + public function getEntity() + { + return $this->_entity; + } } diff --git a/Table.php b/Table.php index a33a73c7..ef07b320 100644 --- a/Table.php +++ b/Table.php @@ -1526,7 +1526,7 @@ public function saveOrFail(EntityInterface $entity, $options = []) { $saved = $this->save($entity, $options); if ($saved === false) { - throw new PersistenceFailedException(['save', $entity]); + throw new PersistenceFailedException($entity, ['save']); } return $saved; @@ -1882,7 +1882,7 @@ public function deleteOrFail(EntityInterface $entity, $options = []) { $deleted = $this->delete($entity, $options); if ($deleted === false) { - throw new PersistenceFailedException(['delete', $entity]); + throw new PersistenceFailedException($entity, ['delete']); } return $deleted; From f0f1dc812e04ba99b273d0b5a7b841d062dabb07 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 14 Feb 2017 12:31:23 +0530 Subject: [PATCH 0904/2059] Add getter for table instance. --- Behavior.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Behavior.php b/Behavior.php index 2f08f75b..c0916520 100644 --- a/Behavior.php +++ b/Behavior.php @@ -179,6 +179,16 @@ public function initialize(array $config) { } + /** + * Get the table instance this behavior is bound to. + * + * @return \Cake\ORM\Table The bound table instance. + */ + public function getTable() + { + return $this->_table; + } + /** * Removes aliased methods that would otherwise be duplicated by userland configuration. * From e8ed38ff86a83f3fd0091a8aa9b56619ce3b3053 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Wed, 15 Feb 2017 10:00:58 +0100 Subject: [PATCH 0905/2059] Make target table class name check less strict. --- Association.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Association.php b/Association.php index 4b893b6f..2a6a0dbe 100644 --- a/Association.php +++ b/Association.php @@ -411,10 +411,10 @@ public function getTarget() $this->_targetTable = $tableLocator->get($registryAlias, $config); if ($exists) { - $targetClassName = get_class($this->_targetTable); $className = $this->_getClassName($registryAlias, ['className' => $this->_className]); - if ($targetClassName !== $className) { + if (!$this->_targetTable instanceof $className) { + $targetClassName = get_class($this->_targetTable); throw new RuntimeException(sprintf( 'Invalid Table retrieved from a registry. Requested: %s, got: %s', $className, From 3fc9d78e4344afd4ef1b1be2e1973523f8051392 Mon Sep 17 00:00:00 2001 From: chinpei215 Date: Sat, 4 Feb 2017 14:20:26 +0900 Subject: [PATCH 0906/2059] Remove overridden combined setter/getter methods Previously, foreignKey() returns $this incorrectly. Also, fixes BelongsToMany::targetForeignKey() returns $this. --- Association/BelongsTo.php | 17 ----------------- Association/BelongsToMany.php | 31 +------------------------------ Association/HasMany.php | 17 ----------------- Association/HasOne.php | 17 ----------------- 4 files changed, 1 insertion(+), 81 deletions(-) diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 1fea0a2d..1452f31b 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -55,23 +55,6 @@ public function getForeignKey() return $this->_foreignKey; } - /** - * Sets the name of the field representing the foreign key to the target table. - * If no parameters are passed current field is returned - * - * @deprecated 3.4.0 Use setForeignKey()/getForeignKey() instead. - * @param string|null $key the key to be used to link both tables together - * @return string - */ - public function foreignKey($key = null) - { - if ($key !== null) { - $this->setForeignKey($key); - } - - return $this->getForeignKey(); - } - /** * Handle cascading deletes. * diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 32d6e4db..1671dc7f 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -194,7 +194,7 @@ public function getTargetForeignKey() public function targetForeignKey($key = null) { if ($key !== null) { - return $this->setTargetForeignKey($key); + $this->setTargetForeignKey($key); } return $this->getTargetForeignKey(); @@ -226,22 +226,6 @@ public function getForeignKey() return $this->_foreignKey; } - /** - * Sets the name of the field representing the foreign key to the source table. - * If no parameters are passed current field is returned - * - * @param string|null $key the key to be used to link both tables together - * @return string - */ - public function foreignKey($key = null) - { - if ($key !== null) { - $this->setForeignKey($key); - } - - return $this->getForeignKey(); - } - /** * Sets the sort order in which target records should be returned. * If no arguments are passed the currently configured value is returned @@ -968,19 +952,6 @@ public function setConditions($conditions) return $this; } - /** - * {@inheritDoc} - * @deprecated 3.4.0 Use setConditions()/getConditions() instead. - */ - public function conditions($conditions = null) - { - if ($conditions !== null) { - $this->setConditions($conditions); - } - - return $this->getConditions(); - } - /** * Sets the current join table, either the name of the Table instance or the instance itself. * diff --git a/Association/HasMany.php b/Association/HasMany.php index cdeede64..6e99d7cd 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -565,23 +565,6 @@ public function getForeignKey() return $this->_foreignKey; } - /** - * Sets the name of the field representing the foreign key to the source table. - * If no parameters are passed current field is returned - * - * @deprecated 3.4.0 Use setForeignKey()/getForeignKey() instead. - * @param string|null $key the key to be used to link both tables together - * @return string - */ - public function foreignKey($key = null) - { - if ($key !== null) { - return $this->setForeignKey($key); - } - - return $this->getForeignKey(); - } - /** * Sets the sort order in which target records should be returned. * diff --git a/Association/HasOne.php b/Association/HasOne.php index 8b4fb081..0be3521d 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -55,23 +55,6 @@ public function getForeignKey() return $this->_foreignKey; } - /** - * Sets the name of the field representing the foreign key to the target table. - * If no parameters are passed current field is returned - * - * @deprecated 3.4.0 Use setForeignKey()/getForeignKey() instead. - * @param string|null $key the key to be used to link both tables together - * @return string - */ - public function foreignKey($key = null) - { - if ($key !== null) { - return $this->setForeignKey($key); - } - - return $this->getForeignKey(); - } - /** * Returns default property name based on association name. * From ee737e38cb52f6b7599a1b93aa06af270c91fb1c Mon Sep 17 00:00:00 2001 From: inoas Date: Thu, 16 Feb 2017 17:52:49 +0100 Subject: [PATCH 0907/2059] Fix docblock for PK generation and composite keys Ref: https://github.com/cakephp/cakephp/issues/4936#issuecomment-59808744 Where it is being used actually assumes that it can be handled like an array by casting it. ``` $id = (array)$this->_newId($primary) + $keys; ``` Could this cast be moved into the _newId() method to make clear that you can return array of primary key values? --- Table.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 8cfa54d3..b53ee445 100644 --- a/Table.php +++ b/Table.php @@ -1923,8 +1923,11 @@ protected function _insert($entity, $data) * value if possible. You can override this method if you have specific requirements * for id generation. * + * Note: The ORM will not generate primary key values for composite primary keys. + * You can overwrite _newId() in your table class. + * * @param array $primary The primary key columns to get a new ID for. - * @return mixed Either null or the new primary key value. + * @return null|string|array Either null or the primary key value or a list of primary key values. */ protected function _newId($primary) { From 291a9e673d01d49ae2107a3805cb3e976d8d13ec Mon Sep 17 00:00:00 2001 From: chinpei215 Date: Sat, 4 Feb 2017 13:56:28 +0900 Subject: [PATCH 0908/2059] Split BelongsToMany::sort() into getSort()/setSort() --- Association/BelongsToMany.php | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 32d6e4db..bfd9ff4e 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -242,20 +242,44 @@ public function foreignKey($key = null) return $this->getForeignKey(); } + /** + * Sets the sort order in which target records should be returned. + * + * @param mixed $sort A find() compatible order clause + * @return $this + */ + public function setSort($sort) + { + $this->_sort = $sort; + + return $this; + } + + /** + * Gets the sort order in which target records should be returned. + * + * @return mixed + */ + public function getSort() + { + return $this->_sort; + } + /** * Sets the sort order in which target records should be returned. * If no arguments are passed the currently configured value is returned * + * @deprecated 3.4.0 Use setSort()/getSort() instead. * @param mixed $sort A find() compatible order clause * @return mixed */ public function sort($sort = null) { if ($sort !== null) { - $this->_sort = $sort; + $this->setSort($sort); } - return $this->_sort; + return $this->getSort(); } /** @@ -554,7 +578,7 @@ public function eagerLoader(array $options) 'bindingKey' => $this->getBindingKey(), 'strategy' => $this->getStrategy(), 'associationType' => $this->type(), - 'sort' => $this->sort(), + 'sort' => $this->getSort(), 'junctionAssociationName' => $name, 'junctionProperty' => $this->_junctionProperty, 'junctionAssoc' => $this->getTarget()->association($name), @@ -1450,7 +1474,7 @@ protected function _options(array $opts) $this->setSaveStrategy($opts['saveStrategy']); } if (isset($opts['sort'])) { - $this->sort($opts['sort']); + $this->setSort($opts['sort']); } } } From 5a91b89a869f23932fda2fc2401a6568175b54b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20B=C3=BCttner?= Date: Thu, 16 Feb 2017 19:33:32 +0100 Subject: [PATCH 0909/2059] remove double spaces in phpdoc (#10243) Remove double spaces in docblocks. --- Association/BelongsToMany.php | 2 +- Association/HasMany.php | 2 +- AssociationsNormalizerTrait.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 32d6e4db..ed8acddb 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1149,7 +1149,7 @@ protected function _appendJunctionJoin($query, $conditions) * target entity contains the joint property with its primary key and any extra * information to be stored. * - * On success, the passed `$sourceEntity` will contain `$targetEntities` as value + * On success, the passed `$sourceEntity` will contain `$targetEntities` as value * in the corresponding property for this association. * * This method assumes that links between both the source entity and each of the diff --git a/Association/HasMany.php b/Association/HasMany.php index cdeede64..5000a9b5 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -381,7 +381,7 @@ function ($assoc) use ($targetEntities) { * * This method does not check link uniqueness. * - * On success, the passed `$sourceEntity` will contain `$targetEntities` as value + * On success, the passed `$sourceEntity` will contain `$targetEntities` as value * in the corresponding property for this association. * * Additional options for new links to be saved can be passed in the third argument, diff --git a/AssociationsNormalizerTrait.php b/AssociationsNormalizerTrait.php index 1c593fdf..1f0163d7 100644 --- a/AssociationsNormalizerTrait.php +++ b/AssociationsNormalizerTrait.php @@ -16,7 +16,7 @@ /** * Contains methods for parsing the associated tables array that is typically - * passed to a save operation + * passed to a save operation */ trait AssociationsNormalizerTrait { From 3479395b9597361d2cd75e510a4d04bd04afded8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Fri, 17 Feb 2017 12:54:28 +0100 Subject: [PATCH 0910/2059] Improving an error message in Association.php --- Association.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Association.php b/Association.php index 2a6a0dbe..50eedd76 100644 --- a/Association.php +++ b/Association.php @@ -414,11 +414,16 @@ public function getTarget() $className = $this->_getClassName($registryAlias, ['className' => $this->_className]); if (!$this->_targetTable instanceof $className) { - $targetClassName = get_class($this->_targetTable); + $errorMessage = '%s association "%s" of type "%s" to "%s" doesn\'t match the expected class "%s". '; + $errorMessage .= 'You can\'t have an association of the same name with a different target "className" option anywhere in your app.'; + throw new RuntimeException(sprintf( - 'Invalid Table retrieved from a registry. Requested: %s, got: %s', - $className, - $targetClassName + $errorMessage, + get_class($this->_sourceTable), + $this->getName(), + $this->type(), + get_class($this->_targetTable), + $className )); } } From 7560b2ff7c73fd748e85fa181df8248aa9ab4f37 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 17 Feb 2017 22:04:05 -0500 Subject: [PATCH 0911/2059] Add API docs for new CounterCache option. --- Behavior/CounterCacheBehavior.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 607171e7..4f6dd132 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -87,6 +87,13 @@ * ] * ] * ``` + * + * You can disable counter updates entirely by sending the `ignoreCounterCache` option + * to your save operation: + * + * ``` + * $this->Articles->save($article, ['ignoreCounterCache' => true]); + * ``` */ class CounterCacheBehavior extends Behavior { From 6d1494fd934499713433b9737ea3c9f90182e511 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 18 Feb 2017 12:35:22 -0500 Subject: [PATCH 0912/2059] Restore 'association' option in beforeMarshal event. This was accidentally dropped in 3.2 because there were no tests around the required keys of the beforeMarshal event. Refs #10247 --- Marshaller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index cbd95cfe..d7f0d178 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -105,7 +105,7 @@ protected function _buildPropertyMap($data, $options) } if (isset($options['isMerge'])) { $callback = function ($value, $entity) use ($assoc, $nested) { - $options = $nested + ['associated' => []]; + $options = $nested + ['associated' => [], 'association' => $assoc]; return $this->_mergeAssociation($entity->get($assoc->getProperty()), $assoc, $value, $options); }; From 3c75dcd33c1255d92489638d1d2405d6f581d08f Mon Sep 17 00:00:00 2001 From: chinpei215 Date: Mon, 20 Feb 2017 20:54:47 +0900 Subject: [PATCH 0913/2059] Fix memory leak in EagerLoader --- EagerLoader.php | 59 ++++++++++++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index 0b0c9717..1c694bca 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -691,30 +691,43 @@ public function associationsMap($table) return $map; } - $visitor = function ($level, $matching = false) use (&$visitor, &$map) { - /* @var \Cake\ORM\EagerLoadable $meta */ - foreach ($level as $assoc => $meta) { - $canBeJoined = $meta->canBeJoined(); - $instance = $meta->instance(); - $associations = $meta->associations(); - $forMatching = $meta->forMatching(); - $map[] = [ - 'alias' => $assoc, - 'instance' => $instance, - 'canBeJoined' => $canBeJoined, - 'entityClass' => $instance->getTarget()->getEntityClass(), - 'nestKey' => $canBeJoined ? $assoc : $meta->aliasPath(), - 'matching' => $forMatching !== null ? $forMatching : $matching, - 'targetProperty' => $meta->targetProperty() - ]; - if ($canBeJoined && $associations) { - $visitor($associations, $matching); - } + $map = $this->_buildAssociationsMap($map, $this->_matching->normalized($table), true); + $map = $this->_buildAssociationsMap($map, $this->normalized($table)); + $map = $this->_buildAssociationsMap($map, $this->_joinsMap); + + return $map; + } + + /** + * An internal method to build a map which is used for the return value of the + * associationsMap() method. + * + * @param array $map An initial array for the map. + * @param array $level An array of EagerLoadable instances. + * @param bool $matching Whether or not it is an association loaded through `matching()`. + * @return array + */ + protected function _buildAssociationsMap(array $map, array $level, $matching = false) + { + /* @var \Cake\ORM\EagerLoadable $meta */ + foreach ($level as $assoc => $meta) { + $canBeJoined = $meta->canBeJoined(); + $instance = $meta->instance(); + $associations = $meta->associations(); + $forMatching = $meta->forMatching(); + $map[] = [ + 'alias' => $assoc, + 'instance' => $instance, + 'canBeJoined' => $canBeJoined, + 'entityClass' => $instance->getTarget()->getEntityClass(), + 'nestKey' => $canBeJoined ? $assoc : $meta->aliasPath(), + 'matching' => $forMatching !== null ? $forMatching : $matching, + 'targetProperty' => $meta->targetProperty() + ]; + if ($canBeJoined && $associations) { + $map = $this->_buildAssociationsMap($map, $associations, $matching); } - }; - $visitor($this->_matching->normalized($table), true); - $visitor($this->normalized($table)); - $visitor($this->_joinsMap); + } return $map; } From 88225e05ca1889645c25f8f26c1d871f1fea4a27 Mon Sep 17 00:00:00 2001 From: chinpei215 Date: Mon, 20 Feb 2017 21:12:24 +0900 Subject: [PATCH 0914/2059] Remove type hints form _buildAssociationsMap() --- EagerLoader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EagerLoader.php b/EagerLoader.php index 1c694bca..75f91cd8 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -707,7 +707,7 @@ public function associationsMap($table) * @param bool $matching Whether or not it is an association loaded through `matching()`. * @return array */ - protected function _buildAssociationsMap(array $map, array $level, $matching = false) + protected function _buildAssociationsMap($map, $level, $matching = false) { /* @var \Cake\ORM\EagerLoadable $meta */ foreach ($level as $assoc => $meta) { From 1202d1ecf185f6f09dc7765d2351536fe8264ef9 Mon Sep 17 00:00:00 2001 From: sohelrana820 Date: Tue, 21 Feb 2017 00:33:23 +0600 Subject: [PATCH 0915/2059] Modified ORM's readme.md file. Replaced config() method name with setConfig() in readme.md file. Issue Fixed: #10265 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bb2f6cc3..9f3417e6 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ specify a driver to use: ```php use Cake\Datasource\ConnectionManager; -ConnectionManager::config('default', [ +ConnectionManager::setConfig('default', [ 'className' => 'Cake\Database\Connection', 'driver' => 'Cake\Database\Driver\Mysql', 'database' => 'test', From f241d7345d2436a4c9285d0caab8b81aa554691e Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Wed, 22 Feb 2017 21:20:18 +0100 Subject: [PATCH 0916/2059] Remove multiple empty lines --- Association/Loader/SelectLoader.php | 1 - Table.php | 2 -- 2 files changed, 3 deletions(-) diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index d8a62db2..7e7fb0bf 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -111,7 +111,6 @@ public function __construct(array $options) $this->sort = isset($options['sort']) ? $options['sort'] : null; } - /** * Returns a callable that can be used for injecting association results into a given * iterator. The options accepted by this method are the same as `Association::eagerLoader()` diff --git a/Table.php b/Table.php index 7256c548..340ad3ac 100644 --- a/Table.php +++ b/Table.php @@ -405,7 +405,6 @@ public function getAlias() return $this->_alias; } - /** * {@inheritDoc} * @deprecated 3.4.0 Use setAlias()/getAlias() instead. @@ -449,7 +448,6 @@ public function setRegistryAlias($registryAlias) return $this; } - /** * Returns the table registry key used to create this table instance. * From ff5c4329ee9fa217d96632ff20ce5e4f7a8f62e7 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 26 Feb 2017 23:34:59 -0500 Subject: [PATCH 0917/2059] Move a trait into a helper class. We thought traits were pretty cool and they are. But they result in harder to read/understand code that I'd prefer to not have long term. --- Association/DependentDeleteHelper.php | 59 +++++++++++++++++++++++++++ Association/DependentDeleteTrait.php | 24 +++-------- Association/HasMany.php | 12 +++++- Association/HasOne.php | 13 ++++-- 4 files changed, 84 insertions(+), 24 deletions(-) create mode 100644 Association/DependentDeleteHelper.php diff --git a/Association/DependentDeleteHelper.php b/Association/DependentDeleteHelper.php new file mode 100644 index 00000000..11f69c39 --- /dev/null +++ b/Association/DependentDeleteHelper.php @@ -0,0 +1,59 @@ +getDependent()) { + return true; + } + $table = $association->getTarget(); + $foreignKey = (array)$association->getForeignKey(); + $bindingKey = (array)$association->getBindingKey(); + $conditions = array_combine($foreignKey, $entity->extract($bindingKey)); + + if ($association->getCascadeCallbacks()) { + foreach ($association->find()->where($conditions)->toList() as $related) { + $table->delete($related, $options); + } + + return true; + } + $conditions = array_merge($conditions, $association->getConditions()); + + return (bool)$table->deleteAll($conditions); + } +} diff --git a/Association/DependentDeleteTrait.php b/Association/DependentDeleteTrait.php index b3614f2e..c36c64f7 100644 --- a/Association/DependentDeleteTrait.php +++ b/Association/DependentDeleteTrait.php @@ -15,11 +15,14 @@ namespace Cake\ORM\Association; use Cake\Datasource\EntityInterface; +use Cake\ORM\Association\DependentDeleteHelper; /** * Implements cascading deletes for dependent associations. * * Included by HasOne and HasMany association classes. + * + * @deprected 3.5.0 Unused in CakePHP now. This class will be removed in 4.0.0 */ trait DependentDeleteTrait { @@ -35,24 +38,7 @@ trait DependentDeleteTrait */ public function cascadeDelete(EntityInterface $entity, array $options = []) { - if (!$this->getDependent()) { - return true; - } - $table = $this->getTarget(); - $foreignKey = (array)$this->getForeignKey(); - $bindingKey = (array)$this->getBindingKey(); - $conditions = array_combine($foreignKey, $entity->extract($bindingKey)); - - if ($this->_cascadeCallbacks) { - foreach ($this->find()->where($conditions)->toList() as $related) { - $table->delete($related, $options); - } - - return true; - } - - $conditions = array_merge($conditions, $this->getConditions()); - - return $table->deleteAll($conditions); + $helper = new DependentDeleteHelper(); + return $helper->cascadeDelete($this, $entity, $options); } } diff --git a/Association/HasMany.php b/Association/HasMany.php index 8ef85db2..cd3d4789 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -20,6 +20,7 @@ use Cake\Database\Expression\QueryExpression; use Cake\Datasource\EntityInterface; use Cake\ORM\Association; +use Cake\ORM\Association\DependentDeleteHelper; use Cake\ORM\Association\Loader\SelectLoader; use Cake\ORM\Table; use InvalidArgumentException; @@ -34,8 +35,6 @@ class HasMany extends Association { - use DependentDeleteTrait; - /** * Order in which target records should be returned * @@ -655,4 +654,13 @@ public function eagerLoader(array $options) return $loader->buildEagerLoader($options); } + + /** + * {@inheritDoc} + */ + public function cascadeDelete(EntityInterface $entity, array $options = []) + { + $helper = new DependentDeleteHelper(); + return $helper->cascadeDelete($this, $entity, $options); + } } diff --git a/Association/HasOne.php b/Association/HasOne.php index 0be3521d..b2ea68ab 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -16,6 +16,7 @@ use Cake\Datasource\EntityInterface; use Cake\ORM\Association; +use Cake\ORM\Association\DependentDeleteHelper; use Cake\ORM\Association\Loader\SelectLoader; use Cake\ORM\Table; use Cake\Utility\Inflector; @@ -28,9 +29,6 @@ */ class HasOne extends Association { - - use DependentDeleteTrait; - /** * Valid strategies for this type of association * @@ -145,4 +143,13 @@ public function eagerLoader(array $options) return $loader->buildEagerLoader($options); } + + /** + * {@inheritDoc} + */ + public function cascadeDelete(EntityInterface $entity, array $options = []) + { + $helper = new DependentDeleteHelper(); + return $helper->cascadeDelete($this, $entity, $options); + } } From c3f5a2b34e5a9b93314c6631f675c19b09b810dc Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 27 Feb 2017 00:26:11 -0500 Subject: [PATCH 0918/2059] Fix since tag. --- Association/DependentDeleteHelper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/DependentDeleteHelper.php b/Association/DependentDeleteHelper.php index 11f69c39..f321e096 100644 --- a/Association/DependentDeleteHelper.php +++ b/Association/DependentDeleteHelper.php @@ -9,7 +9,7 @@ * * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) * @link http://cakephp.org CakePHP(tm) Project - * @since 3.0.0 + * @since 3.5.0 * @license http://www.opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM\Association; From 04f4f453212fe053fe343e1eef7c6a42ff766774 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 27 Feb 2017 00:56:58 -0500 Subject: [PATCH 0919/2059] Fix phpcs. --- Association/DependentDeleteTrait.php | 1 + Association/HasMany.php | 1 + Association/HasOne.php | 1 + 3 files changed, 3 insertions(+) diff --git a/Association/DependentDeleteTrait.php b/Association/DependentDeleteTrait.php index c36c64f7..3b60706d 100644 --- a/Association/DependentDeleteTrait.php +++ b/Association/DependentDeleteTrait.php @@ -39,6 +39,7 @@ trait DependentDeleteTrait public function cascadeDelete(EntityInterface $entity, array $options = []) { $helper = new DependentDeleteHelper(); + return $helper->cascadeDelete($this, $entity, $options); } } diff --git a/Association/HasMany.php b/Association/HasMany.php index cd3d4789..bb15c4e6 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -661,6 +661,7 @@ public function eagerLoader(array $options) public function cascadeDelete(EntityInterface $entity, array $options = []) { $helper = new DependentDeleteHelper(); + return $helper->cascadeDelete($this, $entity, $options); } } diff --git a/Association/HasOne.php b/Association/HasOne.php index b2ea68ab..9fcd85b1 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -150,6 +150,7 @@ public function eagerLoader(array $options) public function cascadeDelete(EntityInterface $entity, array $options = []) { $helper = new DependentDeleteHelper(); + return $helper->cascadeDelete($this, $entity, $options); } } From 854ea2604e7c27c2d00cbac861ad1557665a2b2f Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Sun, 5 Mar 2017 00:29:04 +0100 Subject: [PATCH 0920/2059] Throw an exception instead of an triggering error. Add event unload test to BehaviorRegistryTest --- BehaviorRegistry.php | 1 + 1 file changed, 1 insertion(+) diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 36965f78..d934b9b0 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -100,6 +100,7 @@ protected function _resolveClassName($class) * Throws an exception when a behavior is missing. * * Part of the template method for Cake\Core\ObjectRegistry::load() + * and Cake\Core\ObjectRegistry::unload() * * @param string $class The classname that is missing. * @param string $plugin The plugin the behavior is missing in. From 0f29dc1c4ee92970d67230e566ab14f23453a01a Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Thu, 2 Mar 2017 22:37:58 +0100 Subject: [PATCH 0921/2059] Fix the allowEmptyTranslations presistence bug --- Behavior/TranslateBehavior.php | 37 ++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index efbe5ea8..68bc84ab 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -272,6 +272,43 @@ public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $o $newOptions = [$this->_translationTable->getAlias() => ['validate' => false]]; $options['associated'] = $newOptions + $options['associated']; + // Check early if empty transaltions are present in the entity. + // If this is the case, unset them to prevent persistence. + // This only applies if $this->_config['allowEmptyTranslations'] is false + if ($this->_config['allowEmptyTranslations'] === false) { + $translations = (array)$entity->get('_translations'); + if (!empty($translations)) { + foreach ($translations as $locale => &$translation) { + $fields = $translation->extract($this->_config['fields'], false); + foreach ($fields as $field => $value) { + if (empty($value)) { + unset($translation->{$field}); + } + } + + // Workaround to check the remaining properties + $arrayEntity = $entity->toArray(); + + // If now, the current locale property is empty, + // unset it completely. + if (empty($arrayEntity['_translations'][$locale])) { + unset($entity->get('_translations')[$locale]); + } + } + + // Workaround to check the remaining properties + $arrayEntity = $entity->toArray(); + + // If now, the whole _translations property is empty, + // unset it completely and return + if (empty($arrayEntity['_translations'])) { + $entity->unsetProperty("_translations"); + + return; + } + } + } + $this->_bundleTranslatedFields($entity); $bundled = $entity->get('_i18n') ?: []; $noBundled = count($bundled) === 0; From c1246f4b1d8a494c213fc59eff4e0bc4afa278f9 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Sat, 4 Mar 2017 20:48:37 +0100 Subject: [PATCH 0922/2059] Refactor empty field handling --- Behavior/TranslateBehavior.php | 69 ++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 32 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 68bc84ab..4cafc362 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -272,41 +272,11 @@ public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $o $newOptions = [$this->_translationTable->getAlias() => ['validate' => false]]; $options['associated'] = $newOptions + $options['associated']; - // Check early if empty transaltions are present in the entity. + // Check early if empty translations are present in the entity. // If this is the case, unset them to prevent persistence. // This only applies if $this->_config['allowEmptyTranslations'] is false if ($this->_config['allowEmptyTranslations'] === false) { - $translations = (array)$entity->get('_translations'); - if (!empty($translations)) { - foreach ($translations as $locale => &$translation) { - $fields = $translation->extract($this->_config['fields'], false); - foreach ($fields as $field => $value) { - if (empty($value)) { - unset($translation->{$field}); - } - } - - // Workaround to check the remaining properties - $arrayEntity = $entity->toArray(); - - // If now, the current locale property is empty, - // unset it completely. - if (empty($arrayEntity['_translations'][$locale])) { - unset($entity->get('_translations')[$locale]); - } - } - - // Workaround to check the remaining properties - $arrayEntity = $entity->toArray(); - - // If now, the whole _translations property is empty, - // unset it completely and return - if (empty($arrayEntity['_translations'])) { - $entity->unsetProperty("_translations"); - - return; - } - } + $this->_unsetEmptyFields($entity); } $this->_bundleTranslatedFields($entity); @@ -670,6 +640,41 @@ protected function _bundleTranslatedFields($entity) $entity->set('_i18n', $contents); } + /** + * Unset empty translations to avoid persistence. + * Should only be called if $this->_config['allowEmptyTranslations'] is false + * + * @param \Cake\Datasource\EntityInterface $entity The entity to check for empty translations fields inside. + * @return void + */ + protected function _unsetEmptyFields(EntityInterface $entity) + { + $translations = (array)$entity->get('_translations'); + foreach ($translations as $locale => $translation) { + $fields = $translation->extract($this->_config['fields'], false); + foreach ($fields as $field => $value) { + if (strlen($value) === 0) { + $translation->unsetProperty($field); + } + } + + // Workaround to check the remaining properties + $arrayEntity = $entity->toArray(); + + // If now, the current locale property is empty, + // unset it completely. + if (empty($arrayEntity['_translations'][$locale])) { + unset($entity->get('_translations')[$locale]); + } + } + + // If now, the whole _translations property is empty, + // unset it completely and return + if (empty($entity->get('_translations'))) { + $entity->unsetProperty("_translations"); + } + } + /** * Returns the ids found for each of the condition arrays passed for the translations * table. Each records is indexed by the corresponding position to the conditions array From fc976d27236305baf75a9b8c88cdb36f1144ea78 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Sat, 4 Mar 2017 22:04:27 +0100 Subject: [PATCH 0923/2059] Improve empty property check --- Behavior/TranslateBehavior.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 4cafc362..7cad326c 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -658,12 +658,11 @@ protected function _unsetEmptyFields(EntityInterface $entity) } } - // Workaround to check the remaining properties - $arrayEntity = $entity->toArray(); + $translation = $translation->extract($this->_config['fields']); // If now, the current locale property is empty, // unset it completely. - if (empty($arrayEntity['_translations'][$locale])) { + if (empty(array_filter($translation))) { unset($entity->get('_translations')[$locale]); } } From 7d62efbc58491a56534bab2a5e2f86dad821f076 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Mon, 6 Mar 2017 22:12:23 +0100 Subject: [PATCH 0924/2059] Move part of the doc to the long description --- Behavior/TranslateBehavior.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 7cad326c..b8d383f9 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -642,7 +642,8 @@ protected function _bundleTranslatedFields($entity) /** * Unset empty translations to avoid persistence. - * Should only be called if $this->_config['allowEmptyTranslations'] is false + * + * Should only be called if $this->_config['allowEmptyTranslations'] is false. * * @param \Cake\Datasource\EntityInterface $entity The entity to check for empty translations fields inside. * @return void From 78f6115922789df7384cf747cda769c0d80ae37c Mon Sep 17 00:00:00 2001 From: antograssiot Date: Tue, 7 Mar 2017 06:11:11 +0100 Subject: [PATCH 0925/2059] Remove unused namespace --- Association/Loader/SelectWithPivotLoader.php | 1 - Behavior/CounterCacheBehavior.php | 2 -- 2 files changed, 3 deletions(-) diff --git a/Association/Loader/SelectWithPivotLoader.php b/Association/Loader/SelectWithPivotLoader.php index 0ab49225..c208f814 100644 --- a/Association/Loader/SelectWithPivotLoader.php +++ b/Association/Loader/SelectWithPivotLoader.php @@ -14,7 +14,6 @@ */ namespace Cake\ORM\Association\Loader; -use InvalidArgumentException; use RuntimeException; /** diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 4f6dd132..babd5e92 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -18,8 +18,6 @@ use Cake\Event\Event; use Cake\ORM\Association; use Cake\ORM\Behavior; -use Cake\Utility\Hash; -use Cake\Utility\Inflector; /** * CounterCache behavior From 0e4ea2a060f9bff26594bc2d846bd53436916cad Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 7 Mar 2017 18:18:59 +0530 Subject: [PATCH 0926/2059] Fix errors reported by phpstan. --- Association/BelongsToMany.php | 1 + Behavior/TranslateBehavior.php | 1 + 2 files changed, 2 insertions(+) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 52ed4968..c1f28360 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -432,6 +432,7 @@ public function attachTo(Query $query, array $options = []) $cond = $belongsTo->_joinCondition(['foreignKey' => $belongsTo->getForeignKey()]); $cond += $this->junctionConditions(); + $includeFields = null; if (isset($options['includeFields'])) { $includeFields = $options['includeFields']; } diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index b8d383f9..a9670239 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -607,6 +607,7 @@ protected function _bundleTranslatedFields($entity) $primaryKey = (array)$this->_table->getPrimaryKey(); $key = $entity->get(current($primaryKey)); $find = []; + $contents = []; foreach ($translations as $lang => $translation) { foreach ($fields as $field) { From 5c1432aa962847a599641a17ed82472081f70919 Mon Sep 17 00:00:00 2001 From: antograssiot Date: Wed, 8 Mar 2017 21:53:51 +0100 Subject: [PATCH 0927/2059] Another set of phpstan reported errors --- ResultSet.php | 4 ++-- Rule/ExistsIn.php | 4 ++-- Rule/ValidCount.php | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ResultSet.php b/ResultSet.php index 0700e6f4..f7cb37ac 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -177,11 +177,11 @@ public function __construct($query, $statement) { $repository = $query->repository(); $this->_statement = $statement; - $this->_driver = $query->getConnection()->driver(); + $this->_driver = $query->getConnection()->getDriver(); $this->_defaultTable = $query->repository(); $this->_calculateAssociationMap($query); $this->_hydrate = $query->isHydrationEnabled(); - $this->_entityClass = $repository->entityClass(); + $this->_entityClass = $repository->getEntityClass(); $this->_useBuffering = $query->isBufferedResultsEnabled(); $this->_defaultAlias = $this->_defaultTable->alias(); $this->_calculateColumnMap($query); diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index e370a592..c8f2faca 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -92,8 +92,8 @@ public function __invoke(EntityInterface $entity, array $options) $source = $target = $this->_repository; $isAssociation = $target instanceof Association; - $bindingKey = $isAssociation ? (array)$target->bindingKey() : (array)$target->getPrimaryKey(); - $realTarget = $isAssociation ? $target->target() : $target; + $bindingKey = $isAssociation ? (array)$target->getBindingKey() : (array)$target->getPrimaryKey(); + $realTarget = $isAssociation ? $target->getTarget() : $target; if (!empty($options['_sourceTable']) && $realTarget === $options['_sourceTable']) { return true; diff --git a/Rule/ValidCount.php b/Rule/ValidCount.php index d4f09620..509204c9 100644 --- a/Rule/ValidCount.php +++ b/Rule/ValidCount.php @@ -27,7 +27,7 @@ class ValidCount /** * The field to check * - * @var array + * @var string */ protected $_field; From 15ea756735346aeb552ba357d345e9c9c2ad0c48 Mon Sep 17 00:00:00 2001 From: Koji Tanaka Date: Wed, 1 Mar 2017 23:15:19 +0900 Subject: [PATCH 0928/2059] Update URL in *.md, *.json --- README.md | 10 +++++----- composer.json | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 9f3417e6..25bfb856 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ supports 4 association types out of the box: * belongsToMany - E.g. An article belongsToMany tags. You define associations in your table's `initialize()` method. See the -[documentation](http://book.cakephp.org/3.0/en/orm/associations.html) for +[documentation](https://book.cakephp.org/3.0/en/orm/associations.html) for complete examples. ## Reading Data @@ -66,8 +66,8 @@ foreach ($articles->find() as $article) { } ``` -You can use the [query builder](http://book.cakephp.org/3.0/en/orm/query-builder.html) to create -complex queries, and a [variety of methods](http://book.cakephp.org/3.0/en/orm/retrieving-data-and-resultsets.html) +You can use the [query builder](https://book.cakephp.org/3.0/en/orm/query-builder.html) to create +complex queries, and a [variety of methods](https://book.cakephp.org/3.0/en/orm/retrieving-data-and-resultsets.html) to access your data. ## Saving Data @@ -101,7 +101,7 @@ $articles->save($article, [ ``` The above shows how you can easily marshal and save an entity and its -associations in a simple & powerful way. Consult the [ORM documentation](http://book.cakephp.org/3.0/en/orm/saving-data.html) +associations in a simple & powerful way. Consult the [ORM documentation](https://book.cakephp.org/3.0/en/orm/saving-data.html) for more in-depth examples. ## Deleting Data @@ -116,5 +116,5 @@ $articles->delete($article); ## Additional Documentation -Consult [the CakePHP ORM documentation](http://book.cakephp.org/3.0/en/orm.html) +Consult [the CakePHP ORM documentation](https://book.cakephp.org/3.0/en/orm.html) for more in-depth documentation. diff --git a/composer.json b/composer.json index 75c43b64..062d31c6 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,7 @@ "authors": [ { "name": "CakePHP Community", - "homepage": "http://cakephp.org" + "homepage": "https://cakephp.org" } ], "autoload": { From e7eafa16fa4c27c474c1b3edd9a13b210fc0e9e4 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 14 Mar 2017 14:04:14 -0400 Subject: [PATCH 0929/2059] Remove errors when aliases don't match. This hard error makes using the `targetTable` option in associations really hard as it always triggers this condition. By reworking how we generate aliases and eagerloader names we can enable the `targetTable` option to behave as intended. The foreignKey will need to be set, but I think that is reasonble given how key generation uses the table's alias for other reasons. Refs #7125 Refs #10410 --- Association.php | 4 ++-- Association/BelongsTo.php | 2 +- EagerLoader.php | 7 ------- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/Association.php b/Association.php index 50eedd76..2ddfe6b4 100644 --- a/Association.php +++ b/Association.php @@ -1144,7 +1144,7 @@ protected function _appendFields($query, $surrogate, $options) } if ($fields) { - $query->select($query->aliasFields($fields, $target->getAlias())); + $query->select($query->aliasFields($fields, $this->_name)); } $query->addDefaultTypes($target); } @@ -1245,7 +1245,7 @@ protected function _bindNewAssociations($query, $surrogate, $options) protected function _joinCondition($options) { $conditions = []; - $tAlias = $this->getTarget()->getAlias(); + $tAlias = $this->_name; $sAlias = $this->getSource()->getAlias(); $foreignKey = (array)$options['foreignKey']; $bindingKey = (array)$this->getBindingKey(); diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 1452f31b..46074195 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -151,7 +151,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) protected function _joinCondition($options) { $conditions = []; - $tAlias = $this->getTarget()->getAlias(); + $tAlias = $this->_name; $sAlias = $this->_sourceTable->getAlias(); $foreignKey = (array)$options['foreignKey']; $bindingKey = (array)$this->getBindingKey(); diff --git a/EagerLoader.php b/EagerLoader.php index 75f91cd8..36fba45f 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -497,13 +497,6 @@ protected function _normalizeContain(Table $parent, $alias, $options, $paths) sprintf('%s is not associated with %s', $parent->getAlias(), $alias) ); } - if ($instance->getAlias() !== $alias) { - throw new InvalidArgumentException(sprintf( - "You have contained '%s' but that association was bound as '%s'.", - $alias, - $instance->getAlias() - )); - } $paths += ['aliasPath' => '', 'propertyPath' => '', 'root' => $alias]; $paths['aliasPath'] .= '.' . $alias; From 676ff0a287d422e0c42fac5e6cf647a8e470b9a8 Mon Sep 17 00:00:00 2001 From: Roberto Maldonado Date: Fri, 17 Mar 2017 00:49:35 -0300 Subject: [PATCH 0930/2059] TranslateBehavior's method TranslationField returns standard table alias for default locale --- Behavior/TranslateBehavior.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index a9670239..963b5d67 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -436,6 +436,9 @@ public function locale($locale = null) public function translationField($field) { $table = $this->_table; + if($this->locale() === $this->getConfig('defaultLocale')) { + return $table->aliasField($field); + } $associationName = $table->getAlias() . '_' . $field . '_translation'; if ($table->associations()->has($associationName)) { From dddd5d7f215f47ba6b0f12fec814a43c721b8f7f Mon Sep 17 00:00:00 2001 From: Roberto Maldonado Date: Fri, 17 Mar 2017 01:00:04 -0300 Subject: [PATCH 0931/2059] Corrected lfor lint --- Behavior/TranslateBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 963b5d67..7cfb2c01 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -436,7 +436,7 @@ public function locale($locale = null) public function translationField($field) { $table = $this->_table; - if($this->locale() === $this->getConfig('defaultLocale')) { + if ($this->locale() === $this->getConfig('defaultLocale')) { return $table->aliasField($field); } $associationName = $table->getAlias() . '_' . $field . '_translation'; From 3649630b3f464dc9a8e6af8478307a5bf6e7eb0f Mon Sep 17 00:00:00 2001 From: dereuromark Date: Sat, 18 Mar 2017 02:08:45 +0100 Subject: [PATCH 0932/2059] Fix annotations around Entity. --- Behavior/Translate/TranslateTrait.php | 2 +- Behavior/TreeBehavior.php | 22 ++++++++++++++-------- Marshaller.php | 4 ++-- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/Behavior/Translate/TranslateTrait.php b/Behavior/Translate/TranslateTrait.php index 7df90772..6c88fa31 100644 --- a/Behavior/Translate/TranslateTrait.php +++ b/Behavior/Translate/TranslateTrait.php @@ -30,7 +30,7 @@ trait TranslateTrait * it. * * @param string $language Language to return entity for. - * @return $this|\Cake\ORM\Entity + * @return $this|\Cake\Datasource\EntityInterface */ public function translation($language) { diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 412e6645..d0904eda 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -195,7 +195,7 @@ protected function _setChildrenLevel($entity) 'order' => $config['left'], ]); - /* @var \Cake\ORM\Entity $node */ + /* @var \Cake\Datasource\EntityInterface $node */ foreach ($children as $node) { $parentIdValue = $node->get($config['parent']); $depth = $depths[$parentIdValue] + 1; @@ -225,6 +225,7 @@ public function beforeDelete(Event $event, EntityInterface $entity) if ($diff > 2) { $this->_table->deleteAll(function ($exp) use ($config, $left, $right) { + /* @var \Cake\Database\Expression\QueryExpression $exp */ return $exp ->gte($config['leftField'], $left + 1) ->lte($config['leftField'], $right - 1); @@ -342,6 +343,7 @@ protected function _unmarkInternalTree() $config = $this->getConfig(); $this->_table->updateAll( function ($exp) use ($config) { + /* @var \Cake\Database\Expression\QueryExpression $exp */ $leftInverse = clone $exp; $leftInverse->type('*')->add('-1'); $rightInverse = clone $leftInverse; @@ -351,6 +353,7 @@ function ($exp) use ($config) { ->eq($config['rightField'], $rightInverse->add($config['rightField'])); }, function ($exp) use ($config) { + /* @var \Cake\Database\Expression\QueryExpression $exp */ return $exp->lt($config['leftField'], 0); } ); @@ -514,6 +517,7 @@ public function findTreeList(Query $query, array $options) public function formatTreeList(Query $query, array $options = []) { return $query->formatResults(function ($results) use ($options) { + /* @var \Cake\Collection\CollectionTrait $results */ $options += [ 'keyPath' => $this->_getPrimaryKey(), 'valuePath' => $this->_table->getDisplayField(), @@ -534,7 +538,7 @@ public function formatTreeList(Query $query, array $options = []) * without moving its children with it. * * @param \Cake\Datasource\EntityInterface $node The node to remove from the tree - * @return \Cake\ORM\Entity|false the node after being removed from the tree or + * @return \Cake\Datasource\EntityInterface|false the node after being removed from the tree or * false on error */ public function removeFromTree(EntityInterface $node) @@ -550,7 +554,7 @@ public function removeFromTree(EntityInterface $node) * Helper function containing the actual code for removeFromTree * * @param \Cake\Datasource\EntityInterface $node The node to remove from the tree - * @return \Cake\ORM\Entity|false the node after being removed from the tree or + * @return \Cake\Datasource\EntityInterface|false the node after being removed from the tree or * false on error */ protected function _removeFromTree($node) @@ -596,7 +600,7 @@ protected function _removeFromTree($node) * @param \Cake\Datasource\EntityInterface $node The node to move * @param int|bool $number How many places to move the node, or true to move to first position * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found - * @return \Cake\ORM\Entity|bool $node The node after being moved or false on failure + * @return \Cake\Datasource\EntityInterface|bool $node The node after being moved or false on failure */ public function moveUp(EntityInterface $node, $number = 1) { @@ -617,7 +621,7 @@ public function moveUp(EntityInterface $node, $number = 1) * @param \Cake\Datasource\EntityInterface $node The node to move * @param int|bool $number How many places to move the node, or true to move to first position * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found - * @return \Cake\ORM\Entity|bool $node The node after being moved or false on failure + * @return \Cake\Datasource\EntityInterface|bool $node The node after being moved or false on failure */ protected function _moveUp($node, $number) { @@ -631,6 +635,7 @@ protected function _moveUp($node, $number) ->select([$left, $right]) ->where(["$parent IS" => $nodeParent]) ->where(function ($exp) use ($config, $nodeLeft) { + /* @var \Cake\Database\Expression\QueryExpression $exp */ return $exp->lt($config['rightField'], $nodeLeft); }) ->orderDesc($config['leftField']) @@ -643,6 +648,7 @@ protected function _moveUp($node, $number) ->select([$left, $right]) ->where(["$parent IS" => $nodeParent]) ->where(function ($exp) use ($config, $nodeLeft) { + /* @var \Cake\Database\Expression\QueryExpression $exp */ return $exp->lt($config['rightField'], $nodeLeft); }) ->orderAsc($config['leftField']) @@ -684,7 +690,7 @@ protected function _moveUp($node, $number) * @param \Cake\Datasource\EntityInterface $node The node to move * @param int|bool $number How many places to move the node or true to move to last position * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found - * @return \Cake\ORM\Entity|bool the entity after being moved or false on failure + * @return \Cake\Datasource\EntityInterface|bool the entity after being moved or false on failure */ public function moveDown(EntityInterface $node, $number = 1) { @@ -705,7 +711,7 @@ public function moveDown(EntityInterface $node, $number = 1) * @param \Cake\Datasource\EntityInterface $node The node to move * @param int|bool $number How many places to move the node, or true to move to last position * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found - * @return \Cake\ORM\Entity|bool $node The node after being moved or false on failure + * @return \Cake\Datasource\EntityInterface|bool $node The node after being moved or false on failure */ protected function _moveDown($node, $number) { @@ -769,7 +775,7 @@ protected function _moveDown($node, $number) * Returns a single node from the tree from its primary key * * @param mixed $id Record id. - * @return \Cake\ORM\Entity + * @return \Cake\Datasource\EntityInterface * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found */ protected function _getNode($id) diff --git a/Marshaller.php b/Marshaller.php index d7f0d178..50de1854 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -158,7 +158,7 @@ protected function _buildPropertyMap($data, $options) * * @param array $data The data to hydrate. * @param array $options List of options - * @return \Cake\ORM\Entity + * @return \Cake\Datasource\EntityInterface * @see \Cake\ORM\Table::newEntity() * @see \Cake\ORM\Entity::$_accessible */ @@ -168,7 +168,7 @@ public function one(array $data, array $options = []) $primaryKey = (array)$this->_table->getPrimaryKey(); $entityClass = $this->_table->getEntityClass(); - /* @var Entity $entity */ + /* @var \Cake\Datasource\EntityInterface $entity */ $entity = new $entityClass(); $entity->setSource($this->_table->getRegistryAlias()); From 5fe1ad56f1d987a7c479a8f6d3877fbf5235adf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Wed, 29 Mar 2017 13:29:06 +0200 Subject: [PATCH 0933/2059] Getter/Setter/Clear for Query::contain() --- Query.php | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 140 insertions(+), 4 deletions(-) diff --git a/Query.php b/Query.php index 236c6f7f..9e6c0361 100644 --- a/Query.php +++ b/Query.php @@ -376,6 +376,123 @@ public function eagerLoader(EagerLoader $instance = null) * @return array|$this */ public function contain($associations = null, $override = false) + { + if ($associations === null) { + if ($override === true) { + $this->clearContain(); + } + + return $this->getContain(); + } + + return $this->setContain($associations, $override); + } + + /** + * Sets the list of associations that should be eagerly loaded along with this + * query. The list of associated tables passed must have been previously set as + * associations using the Table API. + * + * ### Example: + * + * ``` + * // Bring articles' author information + * $query->contain('Author'); + * + * // Also bring the category and tags associated to each article + * $query->contain(['Category', 'Tag']); + * ``` + * + * Associations can be arbitrarily nested using dot notation or nested arrays, + * this allows this object to calculate joins or any additional queries that + * must be executed to bring the required associated data. + * + * ### Example: + * + * ``` + * // Eager load the product info, and for each product load other 2 associations + * $query->contain(['Product' => ['Manufacturer', 'Distributor']); + * + * // Which is equivalent to calling + * $query->contain(['Products.Manufactures', 'Products.Distributors']); + * + * // For an author query, load his region, state and country + * $query->contain('Regions.States.Countries'); + * ``` + * + * It is possible to control the conditions and fields selected for each of the + * contained associations: + * + * ### Example: + * + * ``` + * $query->contain(['Tags' => function ($q) { + * return $q->where(['Tags.is_popular' => true]); + * }]); + * + * $query->contain(['Products.Manufactures' => function ($q) { + * return $q->select(['name'])->where(['Manufactures.active' => true]); + * }]); + * ``` + * + * Each association might define special options when eager loaded, the allowed + * options that can be set per association are: + * + * - `foreignKey`: Used to set a different field to match both tables, if set to false + * no join conditions will be generated automatically. `false` can only be used on + * joinable associations and cannot be used with hasMany or belongsToMany associations. + * - `fields`: An array with the fields that should be fetched from the association. + * - `finder`: The finder to use when loading associated records. Either the name of the + * finder as a string, or an array to define options to pass to the finder. + * - `queryBuilder`: Equivalent to passing a callable instead of an options array. + * + * ### Example: + * + * ``` + * // Set options for the hasMany articles that will be eagerly loaded for an author + * $query->contain([ + * 'Articles' => [ + * 'fields' => ['title', 'author_id'] + * ] + * ]); + * ``` + * + * Finders can be configured to use options. + * + * ``` + * // Retrieve translations for the articles, but only those for the `en` and `es` locales + * $query->contain([ + * 'Articles' => [ + * 'finder' => [ + * 'translations' => [ + * 'locales' => ['en', 'es'] + * ] + * ] + * ] + * ]); + * ``` + * + * When containing associations, it is important to include foreign key columns. + * Failing to do so will trigger exceptions. + * + * ``` + * // Use special join conditions for getting an Articles's belongsTo 'authors' + * $query->contain([ + * 'Authors' => [ + * 'foreignKey' => false, + * 'queryBuilder' => function ($q) { + * return $q->where(...); // Add full filtering conditions + * } + * ] + * ]); + * ``` + * + * @param array|string $associations List of table aliases to be queried. + * @param bool $override Whether override previous list with the one passed + * defaults to merging previous list with the new one. + * @return $this + */ + public function setContain($associations, $override = false) { $loader = $this->getEagerLoader(); if ($override === true) { @@ -383,16 +500,35 @@ public function contain($associations = null, $override = false) $this->_dirty(); } - if ($associations === null) { - return $loader->contain(); - } - $result = $loader->contain($associations); $this->_addAssociationsToTypeMap($this->repository(), $this->getTypeMap(), $result); return $this; } + /** + * Clears the contained assocations form the current query. + * + * @return $this + */ + public function clearContain() + { + $this->getEagerLoader()->clearContain(); + $this->_dirty(); + + return $this; + } + + /** + * Gets the contained associations + * + * @return array + */ + public function getContain() + { + return $this->getEagerLoader()->contain(); + } + /** * Used to recursively add contained association column types to * the query. From cab6d95a8daebae0007b8f3786077f9c7acaa75c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Wed, 29 Mar 2017 16:12:39 +0200 Subject: [PATCH 0934/2059] Updating docs and fixing typos for the Query object --- Query.php | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/Query.php b/Query.php index 9e6c0361..05b147c0 100644 --- a/Query.php +++ b/Query.php @@ -370,6 +370,7 @@ public function eagerLoader(EagerLoader $instance = null) * If called with an empty first argument and `$override` is set to true, the * previous list will be emptied. * + * @deprecated Use setContain(), getContain() and clearContain() instead. * @param array|string|null $associations List of table aliases to be queried. * @param bool $override Whether override previous list with the one passed * defaults to merging previous list with the new one. @@ -397,10 +398,10 @@ public function contain($associations = null, $override = false) * * ``` * // Bring articles' author information - * $query->contain('Author'); + * $query->setContain('Author'); * * // Also bring the category and tags associated to each article - * $query->contain(['Category', 'Tag']); + * $query->setContain(['Category', 'Tag']); * ``` * * Associations can be arbitrarily nested using dot notation or nested arrays, @@ -411,13 +412,13 @@ public function contain($associations = null, $override = false) * * ``` * // Eager load the product info, and for each product load other 2 associations - * $query->contain(['Product' => ['Manufacturer', 'Distributor']); + * $query->setContain(['Product' => ['Manufacturer', 'Distributor']); * * // Which is equivalent to calling - * $query->contain(['Products.Manufactures', 'Products.Distributors']); + * $query->setContain(['Products.Manufactures', 'Products.Distributors']); * * // For an author query, load his region, state and country - * $query->contain('Regions.States.Countries'); + * $query->setContain('Regions.States.Countries'); * ``` * * It is possible to control the conditions and fields selected for each of the @@ -426,11 +427,11 @@ public function contain($associations = null, $override = false) * ### Example: * * ``` - * $query->contain(['Tags' => function ($q) { + * $query->setContain(['Tags' => function ($q) { * return $q->where(['Tags.is_popular' => true]); * }]); * - * $query->contain(['Products.Manufactures' => function ($q) { + * $query->setContain(['Products.Manufactures' => function ($q) { * return $q->select(['name'])->where(['Manufactures.active' => true]); * }]); * ``` @@ -450,7 +451,7 @@ public function contain($associations = null, $override = false) * * ``` * // Set options for the hasMany articles that will be eagerly loaded for an author - * $query->contain([ + * $query->setContain([ * 'Articles' => [ * 'fields' => ['title', 'author_id'] * ] @@ -461,7 +462,7 @@ public function contain($associations = null, $override = false) * * ``` * // Retrieve translations for the articles, but only those for the `en` and `es` locales - * $query->contain([ + * $query->setContain([ * 'Articles' => [ * 'finder' => [ * 'translations' => [ @@ -477,7 +478,7 @@ public function contain($associations = null, $override = false) * * ``` * // Use special join conditions for getting an Articles's belongsTo 'authors' - * $query->contain([ + * $query->setContain([ * 'Authors' => [ * 'foreignKey' => false, * 'queryBuilder' => function ($q) { @@ -507,7 +508,7 @@ public function setContain($associations, $override = false) } /** - * Clears the contained assocations form the current query. + * Clears the contained associations from the current query. * * @return $this */ @@ -929,6 +930,10 @@ public function __clone() */ public function count() { + return $this->getCount(); + } + + public function getCount() { if ($this->_resultsCount === null) { $this->_resultsCount = $this->_performCount(); } From c9aaeb916b89cb948ead319e71512d56bf3f87bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sun, 2 Apr 2017 01:06:24 +0200 Subject: [PATCH 0935/2059] Requested changes to Query::contain() and related methods --- Query.php | 129 +++--------------------------------------------------- 1 file changed, 5 insertions(+), 124 deletions(-) diff --git a/Query.php b/Query.php index 05b147c0..e31131ff 100644 --- a/Query.php +++ b/Query.php @@ -370,135 +370,20 @@ public function eagerLoader(EagerLoader $instance = null) * If called with an empty first argument and `$override` is set to true, the * previous list will be emptied. * - * @deprecated Use setContain(), getContain() and clearContain() instead. * @param array|string|null $associations List of table aliases to be queried. * @param bool $override Whether override previous list with the one passed * defaults to merging previous list with the new one. * @return array|$this */ public function contain($associations = null, $override = false) - { - if ($associations === null) { - if ($override === true) { - $this->clearContain(); - } - - return $this->getContain(); - } - - return $this->setContain($associations, $override); - } - - /** - * Sets the list of associations that should be eagerly loaded along with this - * query. The list of associated tables passed must have been previously set as - * associations using the Table API. - * - * ### Example: - * - * ``` - * // Bring articles' author information - * $query->setContain('Author'); - * - * // Also bring the category and tags associated to each article - * $query->setContain(['Category', 'Tag']); - * ``` - * - * Associations can be arbitrarily nested using dot notation or nested arrays, - * this allows this object to calculate joins or any additional queries that - * must be executed to bring the required associated data. - * - * ### Example: - * - * ``` - * // Eager load the product info, and for each product load other 2 associations - * $query->setContain(['Product' => ['Manufacturer', 'Distributor']); - * - * // Which is equivalent to calling - * $query->setContain(['Products.Manufactures', 'Products.Distributors']); - * - * // For an author query, load his region, state and country - * $query->setContain('Regions.States.Countries'); - * ``` - * - * It is possible to control the conditions and fields selected for each of the - * contained associations: - * - * ### Example: - * - * ``` - * $query->setContain(['Tags' => function ($q) { - * return $q->where(['Tags.is_popular' => true]); - * }]); - * - * $query->setContain(['Products.Manufactures' => function ($q) { - * return $q->select(['name'])->where(['Manufactures.active' => true]); - * }]); - * ``` - * - * Each association might define special options when eager loaded, the allowed - * options that can be set per association are: - * - * - `foreignKey`: Used to set a different field to match both tables, if set to false - * no join conditions will be generated automatically. `false` can only be used on - * joinable associations and cannot be used with hasMany or belongsToMany associations. - * - `fields`: An array with the fields that should be fetched from the association. - * - `finder`: The finder to use when loading associated records. Either the name of the - * finder as a string, or an array to define options to pass to the finder. - * - `queryBuilder`: Equivalent to passing a callable instead of an options array. - * - * ### Example: - * - * ``` - * // Set options for the hasMany articles that will be eagerly loaded for an author - * $query->setContain([ - * 'Articles' => [ - * 'fields' => ['title', 'author_id'] - * ] - * ]); - * ``` - * - * Finders can be configured to use options. - * - * ``` - * // Retrieve translations for the articles, but only those for the `en` and `es` locales - * $query->setContain([ - * 'Articles' => [ - * 'finder' => [ - * 'translations' => [ - * 'locales' => ['en', 'es'] - * ] - * ] - * ] - * ]); - * ``` - * - * When containing associations, it is important to include foreign key columns. - * Failing to do so will trigger exceptions. - * - * ``` - * // Use special join conditions for getting an Articles's belongsTo 'authors' - * $query->setContain([ - * 'Authors' => [ - * 'foreignKey' => false, - * 'queryBuilder' => function ($q) { - * return $q->where(...); // Add full filtering conditions - * } - * ] - * ]); - * ``` - * - * @param array|string $associations List of table aliases to be queried. - * @param bool $override Whether override previous list with the one passed - * defaults to merging previous list with the new one. - * @return $this - */ - public function setContain($associations, $override = false) { $loader = $this->getEagerLoader(); if ($override === true) { - $loader->clearContain(); - $this->_dirty(); + $this->clearContain(); + } + + if ($associations === null) { + return $this->getContain(); } $result = $loader->contain($associations); @@ -930,10 +815,6 @@ public function __clone() */ public function count() { - return $this->getCount(); - } - - public function getCount() { if ($this->_resultsCount === null) { $this->_resultsCount = $this->_performCount(); } From 4c1cc256a26b7313a4335998a0f8e57b924893e5 Mon Sep 17 00:00:00 2001 From: ndm2 Date: Tue, 4 Apr 2017 17:43:21 +0200 Subject: [PATCH 0936/2059] Add loose emptiness handling for `HasMany` associations. Adds support for treating `null`, `false`, and empty strings as empty sets of associated data, similar to how `BelongsToMany` handles such values. Also introduces bailing out early on empty sets for not yet persisted entities when using the `replace` save strategy. Solves #9474 --- Association/HasMany.php | 86 ++++++++++++++++++++++++++++------------- 1 file changed, 59 insertions(+), 27 deletions(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index bb15c4e6..b7230197 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -164,63 +164,94 @@ public function saveStrategy($strategy = null) public function saveAssociated(EntityInterface $entity, array $options = []) { $targetEntities = $entity->get($this->getProperty()); - if (empty($targetEntities) && $this->_saveStrategy !== self::SAVE_REPLACE) { - return $entity; + + $isEmpty = in_array($targetEntities, [null, [], '', false], true); + if ($isEmpty) { + if ($entity->isNew() || + $this->getSaveStrategy() !== self::SAVE_REPLACE + ) { + return $entity; + } + + $targetEntities = []; } - if (!is_array($targetEntities) && !($targetEntities instanceof Traversable)) { + if (!is_array($targetEntities) && + !($targetEntities instanceof Traversable) + ) { $name = $this->getProperty(); $message = sprintf('Could not save %s, it cannot be traversed', $name); throw new InvalidArgumentException($message); } - $foreignKey = (array)$this->getForeignKey(); - $properties = array_combine( - $foreignKey, + $foreignKeyReference = array_combine( + (array)$this->getForeignKey(), $entity->extract((array)$this->getBindingKey()) ); - $target = $this->getTarget(); - $original = $targetEntities; + $options['_sourceTable'] = $this->getSource(); - $unlinkSuccessful = null; - if ($this->_saveStrategy === self::SAVE_REPLACE) { - $unlinkSuccessful = $this->_unlinkAssociated($properties, $entity, $target, $targetEntities, $options); + if ($this->_saveStrategy === self::SAVE_REPLACE && + !$this->_unlinkAssociated($foreignKeyReference, $entity, $this->getTarget(), $targetEntities, $options) + ) { + return false; } - if ($unlinkSuccessful === false) { + if (!$this->_saveTarget($foreignKeyReference, $entity, $targetEntities, $options)) { return false; } - foreach ($targetEntities as $k => $targetEntity) { - if (!($targetEntity instanceof EntityInterface)) { + return $entity; + } + + /** + * Persists each of the entities into the target table and creates links between + * the parent entity and each one of the saved target entities. + * + * @param array $foreignKeyReference The foreign key reference defining the link between the + * target entity, and the parent entity. + * @param \Cake\Datasource\EntityInterface $parentEntity The source entity containing the target + * entities to be saved. + * @param array|\Traversable $entities list of entities to persist in target table and to + * link to the parent entity + * @param array $options list of options accepted by `Table::save()`. + * @return bool `true` on success, `false` otherwise. + */ + protected function _saveTarget(array $foreignKeyReference, EntityInterface $parentEntity, $entities, array $options) + { + $foreignKey = array_keys($foreignKeyReference); + $table = $this->getTarget(); + $original = $entities; + + foreach ($entities as $k => $entity) { + if (!($entity instanceof EntityInterface)) { break; } if (!empty($options['atomic'])) { - $targetEntity = clone $targetEntity; + $entity = clone $entity; } - if ($properties !== $targetEntity->extract($foreignKey)) { - $targetEntity->set($properties, ['guard' => false]); + if ($foreignKeyReference !== $entity->extract($foreignKey)) { + $entity->set($foreignKeyReference, ['guard' => false]); } - if ($target->save($targetEntity, $options)) { - $targetEntities[$k] = $targetEntity; + if ($table->save($entity, $options)) { + $entities[$k] = $entity; continue; } if (!empty($options['atomic'])) { - $original[$k]->errors($targetEntity->errors()); + $original[$k]->errors($entity->errors()); $entity->set($this->getProperty(), $original); return false; } } - $entity->set($this->getProperty(), $targetEntities); + $parentEntity->set($this->getProperty(), $entities); - return $entity; + return true; } /** @@ -427,14 +458,15 @@ public function replace(EntityInterface $sourceEntity, array $targetEntities, ar * Deletes/sets null the related objects according to the dependency between source and targets and foreign key nullability * Skips deleting records present in $remainingEntities * - * @param array $properties array of foreignKey properties + * @param array $foreignKeyReference The foreign key reference defining the link between the + * target entity, and the parent entity. * @param \Cake\Datasource\EntityInterface $entity the entity which should have its associated entities unassigned * @param \Cake\ORM\Table $target The associated table * @param array $remainingEntities Entities that should not be deleted * @param array $options list of options accepted by `Table::delete()` * @return bool success */ - protected function _unlinkAssociated(array $properties, EntityInterface $entity, Table $target, array $remainingEntities = [], array $options = []) + protected function _unlinkAssociated(array $foreignKeyReference, EntityInterface $entity, Table $target, array $remainingEntities = [], array $options = []) { $primaryKey = (array)$target->getPrimaryKey(); $exclusions = new Collection($remainingEntities); @@ -450,18 +482,18 @@ function ($v) { ) ->toArray(); - $conditions = $properties; + $conditions = $foreignKeyReference; if (count($exclusions) > 0) { $conditions = [ 'NOT' => [ 'OR' => $exclusions ], - $properties + $foreignKeyReference ]; } - return $this->_unlink(array_keys($properties), $target, $conditions, $options); + return $this->_unlink(array_keys($foreignKeyReference), $target, $conditions, $options); } /** From 2c8c7df3a9af97760cf41e4e3c295ad6acd8f162 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Mon, 10 Apr 2017 20:34:14 +0200 Subject: [PATCH 0937/2059] Split QueryTrait::eagerLoaded() into getter/setter --- Association.php | 2 +- Association/Loader/SelectLoader.php | 2 +- Query.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Association.php b/Association.php index 2ddfe6b4..30c1ced2 100644 --- a/Association.php +++ b/Association.php @@ -916,7 +916,7 @@ public function attachTo(Query $query, array $options = []) list($finder, $opts) = $this->_extractFinder($options['finder']); $dummy = $this ->find($finder, $opts) - ->eagerLoaded(true); + ->setEagerLoaded(true); if (!empty($options['queryBuilder'])) { $dummy = $options['queryBuilder']($dummy); diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 7e7fb0bf..d48aa9d0 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -167,7 +167,7 @@ protected function _buildQuery($options) $fetchQuery = $finder() ->select($options['fields']) ->where($options['conditions']) - ->eagerLoaded(true) + ->setEagerLoaded(true) ->enableHydration($options['query']->isHydrationEnabled()); if ($useSubquery) { diff --git a/Query.php b/Query.php index 236c6f7f..fa4b041a 100644 --- a/Query.php +++ b/Query.php @@ -974,7 +974,7 @@ public function triggerBeforeFind() $table->dispatchEvent('Model.beforeFind', [ $this, new ArrayObject($this->_options), - !$this->eagerLoaded() + !$this->getEagerLoaded() ]); } } From 3e4cf8c69e5b0082450758e2a838102e77c3bb36 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Mon, 10 Apr 2017 22:16:30 +0200 Subject: [PATCH 0938/2059] Remove new setter --- Association.php | 2 +- Association/Loader/SelectLoader.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Association.php b/Association.php index 30c1ced2..2ddfe6b4 100644 --- a/Association.php +++ b/Association.php @@ -916,7 +916,7 @@ public function attachTo(Query $query, array $options = []) list($finder, $opts) = $this->_extractFinder($options['finder']); $dummy = $this ->find($finder, $opts) - ->setEagerLoaded(true); + ->eagerLoaded(true); if (!empty($options['queryBuilder'])) { $dummy = $options['queryBuilder']($dummy); diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index d48aa9d0..7e7fb0bf 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -167,7 +167,7 @@ protected function _buildQuery($options) $fetchQuery = $finder() ->select($options['fields']) ->where($options['conditions']) - ->setEagerLoaded(true) + ->eagerLoaded(true) ->enableHydration($options['query']->isHydrationEnabled()); if ($useSubquery) { From f279f211601641ea9a37f0bc995618a661853dd8 Mon Sep 17 00:00:00 2001 From: Travis Rowland Date: Mon, 17 Apr 2017 01:35:41 -0700 Subject: [PATCH 0939/2059] Changing unnecessary double-quotes to single-quotes --- Behavior/TranslateBehavior.php | 2 +- Table.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 7cfb2c01..1dc02d73 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -675,7 +675,7 @@ protected function _unsetEmptyFields(EntityInterface $entity) // If now, the whole _translations property is empty, // unset it completely and return if (empty($entity->get('_translations'))) { - $entity->unsetProperty("_translations"); + $entity->unsetProperty('_translations'); } } diff --git a/Table.php b/Table.php index 340ad3ac..f1665652 100644 --- a/Table.php +++ b/Table.php @@ -1420,7 +1420,7 @@ public function get($primaryKey, $options = []) if ($cacheConfig) { if (!$cacheKey) { $cacheKey = sprintf( - "get:%s.%s%s", + 'get:%s.%s%s', $this->getConnection()->configName(), $this->getTable(), json_encode($primaryKey) From 850c38ec67cc58a301392971a089389a120bc5a1 Mon Sep 17 00:00:00 2001 From: Travis Rowland Date: Mon, 17 Apr 2017 02:08:49 -0700 Subject: [PATCH 0940/2059] Removing some one-time use variables --- Query.php | 3 +-- Table.php | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Query.php b/Query.php index 236c6f7f..67a3812f 100644 --- a/Query.php +++ b/Query.php @@ -987,9 +987,8 @@ public function sql(ValueBinder $binder = null) $this->triggerBeforeFind(); $this->_transformQuery(); - $sql = parent::sql($binder); - return $sql; + return parent::sql($binder); } /** diff --git a/Table.php b/Table.php index 340ad3ac..3a27f06d 100644 --- a/Table.php +++ b/Table.php @@ -2401,9 +2401,8 @@ public function newEntity($data = null, array $options = []) { if ($data === null) { $class = $this->getEntityClass(); - $entity = new $class([], ['source' => $this->getRegistryAlias()]); - return $entity; + return new $class([], ['source' => $this->getRegistryAlias()]); } if (!isset($options['associated'])) { $options['associated'] = $this->_associations->keys(); From 37d7b1d0fa58415a2089fb52e688bb13f46208a5 Mon Sep 17 00:00:00 2001 From: Travis Rowland Date: Mon, 17 Apr 2017 02:13:06 -0700 Subject: [PATCH 0941/2059] Splitting some workflows for better control flow --- Marshaller.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index 50de1854..ff25582b 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -193,7 +193,8 @@ public function one(array $data, array $options = []) if ($value === '' && in_array($key, $primaryKey, true)) { // Skip marshalling '' for pk fields. continue; - } elseif (isset($propertyMap[$key])) { + } + if (isset($propertyMap[$key])) { $properties[$key] = $propertyMap[$key]($value, $entity); } else { $properties[$key] = $value; From 5d640636f988ac6453c6570fae293e3acc226342 Mon Sep 17 00:00:00 2001 From: Travis Rowland Date: Mon, 17 Apr 2017 02:17:28 -0700 Subject: [PATCH 0942/2059] Merging some unset calls for better control flow --- Rule/ExistsIn.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index c8f2faca..b3730787 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -118,8 +118,7 @@ public function __invoke(EntityInterface $entity, array $options) $schema = $source->getSchema(); foreach ($this->_fields as $i => $field) { if ($schema->column($field) && $schema->isNullable($field) && $entity->get($field) === null) { - unset($bindingKey[$i]); - unset($this->_fields[$i]); + unset($bindingKey[$i], $this->_fields[$i]); } } } From db649135c065fca60af42256ac7fef50efe8d372 Mon Sep 17 00:00:00 2001 From: Travis Rowland Date: Mon, 17 Apr 2017 02:21:28 -0700 Subject: [PATCH 0943/2059] Using short syntax for applied operations --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 340ad3ac..4757b34e 100644 --- a/Table.php +++ b/Table.php @@ -1889,7 +1889,7 @@ protected function _insert($entity, $data) $primary = array_intersect_key($data, $primary) + $primary; $filteredKeys = array_filter($primary, 'strlen'); - $data = $data + $filteredKeys; + $data += $filteredKeys; if (count($primary) > 1) { $schema = $this->getSchema(); From b0e95c013a88fd613da0ef2a79c28a0f7c1ee469 Mon Sep 17 00:00:00 2001 From: Travis Rowland Date: Mon, 17 Apr 2017 02:22:33 -0700 Subject: [PATCH 0944/2059] Prefer to use static:: method call invocation instead --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 340ad3ac..fccc1993 100644 --- a/Table.php +++ b/Table.php @@ -2717,7 +2717,7 @@ public function __debugInfo() 'entityClass' => $this->getEntityClass(), 'associations' => $associations ? $associations->keys() : false, 'behaviors' => $behaviors ? $behaviors->loaded() : false, - 'defaultConnection' => $this->defaultConnectionName(), + 'defaultConnection' => static::defaultConnectionName(), 'connectionName' => $conn ? $conn->configName() : null ]; } From ef6aed81e4506818c5345f63a08d243fde8b40a6 Mon Sep 17 00:00:00 2001 From: Travis Rowland Date: Mon, 17 Apr 2017 02:42:23 -0700 Subject: [PATCH 0945/2059] Removing default null property assignments. Class property default value is NULL if not specified; assigning it explicitly is redundant. --- EagerLoader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EagerLoader.php b/EagerLoader.php index 36fba45f..c5d042fa 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -42,7 +42,7 @@ class EagerLoader * * @var \Cake\ORM\EagerLoadable[]|\Cake\ORM\EagerLoadable|null */ - protected $_normalized = null; + protected $_normalized; /** * List of options accepted by associations in contain() From 48fb86c1c2805dd90dd81829d443b30c11ae129c Mon Sep 17 00:00:00 2001 From: Travis Rowland Date: Mon, 17 Apr 2017 02:52:38 -0700 Subject: [PATCH 0946/2059] Removing some unused/unnecessary variables and imports. --- Association/Loader/SelectLoader.php | 1 - Association/Loader/SelectWithPivotLoader.php | 1 - Marshaller.php | 1 - 3 files changed, 3 deletions(-) diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 7e7fb0bf..a5f7a58a 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -154,7 +154,6 @@ protected function _defaultOptions() */ protected function _buildQuery($options) { - $alias = $this->targetAlias; $key = $this->_linkField($options); $filter = $options['keys']; $useSubquery = $options['strategy'] === Association::STRATEGY_SUBQUERY; diff --git a/Association/Loader/SelectWithPivotLoader.php b/Association/Loader/SelectWithPivotLoader.php index c208f814..8b702458 100644 --- a/Association/Loader/SelectWithPivotLoader.php +++ b/Association/Loader/SelectWithPivotLoader.php @@ -164,7 +164,6 @@ protected function _buildResultMap($fetchQuery, $options) { $resultMap = []; $key = (array)$options['foreignKey']; - $hydrated = $fetchQuery->isHydrationEnabled(); foreach ($fetchQuery->all() as $result) { if (!isset($result[$this->junctionProperty])) { diff --git a/Marshaller.php b/Marshaller.php index 50de1854..ff995cf8 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -539,7 +539,6 @@ public function merge(EntityInterface $entity, array $data, array $options = []) } $errors = $this->_validate($data + $keys, $options, $isNew); - $schema = $this->_table->getSchema(); $options['isMerge'] = true; $propertyMap = $this->_buildPropertyMap($data, $options); $properties = $marshalledAssocs = []; From 4de40f7ea662be97740c8eda5cf63ee904984a3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Mon, 17 Apr 2017 17:23:49 +0200 Subject: [PATCH 0947/2059] Removing getContain() as requested by Mark Story --- Query.php | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/Query.php b/Query.php index d3d450f0..d77b0443 100644 --- a/Query.php +++ b/Query.php @@ -383,7 +383,7 @@ public function contain($associations = null, $override = false) } if ($associations === null) { - return $this->getContain(); + return $this->getEagerLoader()->contain(); } $result = $loader->contain($associations); @@ -405,16 +405,6 @@ public function clearContain() return $this; } - /** - * Gets the contained associations - * - * @return array - */ - public function getContain() - { - return $this->getEagerLoader()->contain(); - } - /** * Used to recursively add contained association column types to * the query. From 43de6a23a354712fecbeff620db9af0eb72efc68 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Mon, 17 Apr 2017 17:23:51 +0200 Subject: [PATCH 0948/2059] Rename getEagerLoaded to isEagerLoaded --- Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Query.php b/Query.php index fa4b041a..5987df0b 100644 --- a/Query.php +++ b/Query.php @@ -974,7 +974,7 @@ public function triggerBeforeFind() $table->dispatchEvent('Model.beforeFind', [ $this, new ArrayObject($this->_options), - !$this->getEagerLoaded() + !$this->isEagerLoaded() ]); } } From 56efe81970edae256e2506850b57d6cb6ae51c3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Fri, 21 Apr 2017 08:51:02 +0200 Subject: [PATCH 0949/2059] Split tableLocator() into a getter and a setter. --- Locator/LocatorAwareTrait.php | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index eaad3340..fcf0d7ff 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -35,12 +35,37 @@ trait LocatorAwareTrait * * @param \Cake\ORM\Locator\LocatorInterface|null $tableLocator LocatorInterface instance. * @return \Cake\ORM\Locator\LocatorInterface + * @deprecated 3.5.0 Use getter/setter instead. */ public function tableLocator(LocatorInterface $tableLocator = null) { if ($tableLocator !== null) { - $this->_tableLocator = $tableLocator; + $this->setTableLocator($tableLocator); } + + return $this->getTableLocator(); + } + + /** + * Sets the table locator. + * + * @param \Cake\ORM\Locator\LocatorInterface $tableLocator + * @return $this + */ + public function setTableLocator(LocatorInterface $tableLocator) + { + $this->_tableLocator = $tableLocator; + + return $this; + } + + /** + * Gets the table locator. + * + * @return \Cake\ORM\Locator\LocatorInterface + */ + public function getTableLocator() + { if (!$this->_tableLocator) { $this->_tableLocator = TableRegistry::locator(); } From 01a9bca0817d8bd8ff3a5dea42c5e404b780b093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Fri, 21 Apr 2017 08:54:32 +0200 Subject: [PATCH 0950/2059] Split TableRegistry::locator() into a getter and a setter. --- TableRegistry.php | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/TableRegistry.php b/TableRegistry.php index e5ecb4d5..e171210a 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -69,13 +69,24 @@ class TableRegistry * * @param \Cake\ORM\Locator\LocatorInterface|null $locator Instance of a locator to use. * @return \Cake\ORM\Locator\LocatorInterface + * @deprecated 3.5.0 Use getter/setter instead. */ public static function locator(LocatorInterface $locator = null) { if ($locator) { - static::$_locator = $locator; + static::setTableLocator($locator); } + return static::getTableLocator(); + } + + /** + * Returns a singleton instance of LocatorInterface implementation. + * + * @return \Cake\ORM\Locator\LocatorInterface + */ + public static function getTableLocator() + { if (!static::$_locator) { static::$_locator = new static::$_defaultLocatorClass; } @@ -83,6 +94,17 @@ public static function locator(LocatorInterface $locator = null) return static::$_locator; } + /** + * Sets singleton instance of LocatorInterface implementation. + * + * @param \Cake\ORM\Locator\LocatorInterface|null $locator Instance of a locator to use. + * @return void + */ + public static function setTableLocator(LocatorInterface $locator) + { + static::$_locator = $locator; + } + /** * Stores a list of options to be used when instantiating an object * with a matching alias. From c859268c5b4b66538dc69dd88843f6e8fa44f8e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Fri, 21 Apr 2017 08:56:23 +0200 Subject: [PATCH 0951/2059] Apply TableRegistry locator setter and getter. --- Locator/LocatorAwareTrait.php | 2 +- TableRegistry.php | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index fcf0d7ff..9b4c9cfa 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -67,7 +67,7 @@ public function setTableLocator(LocatorInterface $tableLocator) public function getTableLocator() { if (!$this->_tableLocator) { - $this->_tableLocator = TableRegistry::locator(); + $this->_tableLocator = TableRegistry::getTableLocator(); } return $this->_tableLocator; diff --git a/TableRegistry.php b/TableRegistry.php index e171210a..ed222a3a 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -115,7 +115,7 @@ public static function setTableLocator(LocatorInterface $locator) */ public static function config($alias = null, $options = null) { - return static::locator()->config($alias, $options); + return static::getTableLocator()->config($alias, $options); } /** @@ -129,7 +129,7 @@ public static function config($alias = null, $options = null) */ public static function get($alias, array $options = []) { - return static::locator()->get($alias, $options); + return static::getTableLocator()->get($alias, $options); } /** @@ -140,7 +140,7 @@ public static function get($alias, array $options = []) */ public static function exists($alias) { - return static::locator()->exists($alias); + return static::getTableLocator()->exists($alias); } /** @@ -152,7 +152,7 @@ public static function exists($alias) */ public static function set($alias, Table $object) { - return static::locator()->set($alias, $object); + return static::getTableLocator()->set($alias, $object); } /** @@ -163,7 +163,7 @@ public static function set($alias, Table $object) */ public static function remove($alias) { - static::locator()->remove($alias); + static::getTableLocator()->remove($alias); } /** @@ -173,7 +173,7 @@ public static function remove($alias) */ public static function clear() { - static::locator()->clear(); + static::getTableLocator()->clear(); } /** @@ -185,6 +185,6 @@ public static function clear() */ public static function __callStatic($name, $arguments) { - return static::locator()->$name(...$arguments); + return static::getTableLocator()->$name(...$arguments); } } From 72c228688505eeee5d772fc7f30860bf186c7dc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Fri, 21 Apr 2017 08:58:37 +0200 Subject: [PATCH 0952/2059] Apply LocatorAwareTrait setter and getter. --- Association.php | 2 +- Association/BelongsToMany.php | 2 +- Behavior/TranslateBehavior.php | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Association.php b/Association.php index 2ddfe6b4..c3252275 100644 --- a/Association.php +++ b/Association.php @@ -401,7 +401,7 @@ public function getTarget() $registryAlias = $this->_name; } - $tableLocator = $this->tableLocator(); + $tableLocator = $this->getTableLocator(); $config = []; $exists = $tableLocator->exists($registryAlias); diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index bfbcec80..2d86985d 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -292,7 +292,7 @@ public function junction($table = null) return $this->_junctionTable; } - $tableLocator = $this->tableLocator(); + $tableLocator = $this->getTableLocator(); if ($table === null && $this->_through) { $table = $this->_through; } elseif ($table === null) { diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 1dc02d73..46d1edc6 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -118,7 +118,7 @@ public function __construct(Table $table, array $config = []) */ public function initialize(array $config) { - $this->_translationTable = $this->tableLocator()->get($this->_config['translationTable']); + $this->_translationTable = $this->getTableLocator()->get($this->_config['translationTable']); $this->setupFieldAssociations( $this->_config['fields'], @@ -147,7 +147,7 @@ public function setupFieldAssociations($fields, $table, $model, $strategy) $targetAlias = $this->_translationTable->getAlias(); $alias = $this->_table->getAlias(); $filter = $this->_config['onlyTranslated']; - $tableLocator = $this->tableLocator(); + $tableLocator = $this->getTableLocator(); foreach ($fields as $field) { $name = $alias . '_' . $field . '_translation'; From 7cbaf430d58ce52dacb4b020ebabef2ff2282f3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Fri, 21 Apr 2017 09:00:23 +0200 Subject: [PATCH 0953/2059] CS fix. --- Locator/LocatorAwareTrait.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index 9b4c9cfa..4a95cbab 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -45,17 +45,17 @@ public function tableLocator(LocatorInterface $tableLocator = null) return $this->getTableLocator(); } - + /** * Sets the table locator. * - * @param \Cake\ORM\Locator\LocatorInterface $tableLocator + * @param \Cake\ORM\Locator\LocatorInterface $tableLocator LocatorInterface instance. * @return $this */ public function setTableLocator(LocatorInterface $tableLocator) { $this->_tableLocator = $tableLocator; - + return $this; } From 334a39a965ab3493dbd99d12c0c7445b282c3ad3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Fri, 21 Apr 2017 09:39:04 +0200 Subject: [PATCH 0954/2059] Apply getEventManager() in src. --- BehaviorRegistry.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index d934b9b0..b97133d2 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -75,7 +75,7 @@ public function __construct($table = null) public function setTable(Table $table) { $this->_table = $table; - $this->eventManager($table->eventManager()); + $this->eventManager($table->getEventManager()); } /** @@ -131,7 +131,7 @@ protected function _create($class, $alias, $config) $instance = new $class($this->_table, $config); $enable = isset($config['enabled']) ? $config['enabled'] : true; if ($enable) { - $this->eventManager()->on($instance); + $this->getEventManager()->on($instance); } $methods = $this->_getMethods($instance, $class, $alias); $this->_methodMap += $methods['methods']; From c2189b31871bb489ec79fc04e6b15a5a59fba000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Fri, 21 Apr 2017 09:50:20 +0200 Subject: [PATCH 0955/2059] Apply setEventManager() in src. --- BehaviorRegistry.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index b97133d2..3a34e54a 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -75,7 +75,10 @@ public function __construct($table = null) public function setTable(Table $table) { $this->_table = $table; - $this->eventManager($table->getEventManager()); + $eventManager = $table->getEventManager(); + if ($eventManager !== null) { + $this->setEventManager($eventManager); + } } /** From 64395c227a2230e87765237d143ff2e15c6bc62d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sun, 23 Apr 2017 23:13:20 +0200 Subject: [PATCH 0956/2059] Removing the unnecessary getEagerLoader() call --- Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Query.php b/Query.php index 0c51cddd..e82bf837 100644 --- a/Query.php +++ b/Query.php @@ -383,7 +383,7 @@ public function contain($associations = null, $override = false) } if ($associations === null) { - return $this->getEagerLoader()->contain(); + return $loader->contain(); } $result = $loader->contain($associations); From 86d3b8bef950065201ffe196174d7611629e7fda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Tue, 25 Apr 2017 08:46:19 +0200 Subject: [PATCH 0957/2059] Tweaks --- TableRegistry.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/TableRegistry.php b/TableRegistry.php index ed222a3a..40d96f4b 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -88,7 +88,7 @@ public static function locator(LocatorInterface $locator = null) public static function getTableLocator() { if (!static::$_locator) { - static::$_locator = new static::$_defaultLocatorClass; + static::$_locator = new static::$_defaultLocatorClass(); } return static::$_locator; @@ -97,12 +97,12 @@ public static function getTableLocator() /** * Sets singleton instance of LocatorInterface implementation. * - * @param \Cake\ORM\Locator\LocatorInterface|null $locator Instance of a locator to use. + * @param \Cake\ORM\Locator\LocatorInterface|null $tableLocator Instance of a locator to use. * @return void */ - public static function setTableLocator(LocatorInterface $locator) + public static function setTableLocator(LocatorInterface $tableLocator) { - static::$_locator = $locator; + static::$_locator = $tableLocator; } /** From e19bf0c21b912e310df0009994ef6107c7752a1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Tue, 25 Apr 2017 08:46:40 +0200 Subject: [PATCH 0958/2059] Update deprecation warnings. --- Locator/LocatorAwareTrait.php | 2 +- TableRegistry.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index 4a95cbab..3fbf6180 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -35,7 +35,7 @@ trait LocatorAwareTrait * * @param \Cake\ORM\Locator\LocatorInterface|null $tableLocator LocatorInterface instance. * @return \Cake\ORM\Locator\LocatorInterface - * @deprecated 3.5.0 Use getter/setter instead. + * @deprecated 3.5.0 Use getTableLocator()/setTableLocator() instead. */ public function tableLocator(LocatorInterface $tableLocator = null) { diff --git a/TableRegistry.php b/TableRegistry.php index 40d96f4b..62b56f84 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -69,7 +69,7 @@ class TableRegistry * * @param \Cake\ORM\Locator\LocatorInterface|null $locator Instance of a locator to use. * @return \Cake\ORM\Locator\LocatorInterface - * @deprecated 3.5.0 Use getter/setter instead. + * @deprecated 3.5.0 Use getTableLocator()/setTableLocator() instead. */ public static function locator(LocatorInterface $locator = null) { From b461311909cd67c1597f17400b5af45e8c314894 Mon Sep 17 00:00:00 2001 From: Travis Rowland Date: Sat, 29 Apr 2017 11:23:38 -0700 Subject: [PATCH 0959/2059] Minor updates (annotations and best-practices) (#10534) Combining conditionals and using more succinct idioms. --- Marshaller.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index d9eb3756..ccd57fd4 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -356,6 +356,9 @@ public function many(array $data, array $options = []) * @param array $data The data to convert into entities. * @param array $options List of options. * @return array An array of built entities. + * @throws \BadMethodCallException + * @throws \InvalidArgumentException + * @throws \RuntimeException */ protected function _belongsToMany(Association $assoc, array $data, $options = []) { @@ -379,7 +382,7 @@ protected function _belongsToMany(Association $assoc, array $data, $options = [] if (count($keys) === $primaryCount) { $rowConditions = []; foreach ($keys as $key => $value) { - $rowConditions[][$target->aliasfield($key)] = $value; + $rowConditions[][$target->aliasField($key)] = $value; } if ($forceNew && !$target->exists($rowConditions)) { From e80772b7026288e101d782f195e5a6357342f56d Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 5 May 2017 00:22:05 +0530 Subject: [PATCH 0960/2059] Fix some errors reported by phpstan level 2. --- Association.php | 2 +- Association/BelongsToMany.php | 2 +- Association/DependentDeleteHelper.php | 2 +- Behavior/CounterCacheBehavior.php | 5 +++-- Behavior/TranslateBehavior.php | 3 ++- Marshaller.php | 13 +++++++------ Rule/ExistsIn.php | 2 +- 7 files changed, 16 insertions(+), 13 deletions(-) diff --git a/Association.php b/Association.php index c3252275..ea809106 100644 --- a/Association.php +++ b/Association.php @@ -945,7 +945,7 @@ public function attachTo(Query $query, array $options = []) * Conditionally adds a condition to the passed Query that will make it find * records where there is no match with this association. * - * @param \Cake\Datasource\QueryInterface $query The query to modify + * @param \Cake\ORM\Query $query The query to modify * @param array $options Options array containing the `negateMatch` key. * @return void */ diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 2d86985d..e2652094 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -599,7 +599,7 @@ public function cascadeDelete(EntityInterface $entity, array $options = []) $table = $this->junction(); $hasMany = $this->getSource()->association($table->getAlias()); if ($this->_cascadeCallbacks) { - foreach ($hasMany->find('all')->where($conditions)->toList() as $related) { + foreach ($hasMany->find('all')->where($conditions)->all()->toList() as $related) { $table->delete($related, $options); } diff --git a/Association/DependentDeleteHelper.php b/Association/DependentDeleteHelper.php index f321e096..4f834e5c 100644 --- a/Association/DependentDeleteHelper.php +++ b/Association/DependentDeleteHelper.php @@ -46,7 +46,7 @@ public function cascadeDelete(Association $association, EntityInterface $entity, $conditions = array_combine($foreignKey, $entity->extract($bindingKey)); if ($association->getCascadeCallbacks()) { - foreach ($association->find()->where($conditions)->toList() as $related) { + foreach ($association->find()->where($conditions)->all()->toList() as $related) { $table->delete($related, $options); } diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index babd5e92..e9bb1d3e 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -18,6 +18,7 @@ use Cake\Event\Event; use Cake\ORM\Association; use Cake\ORM\Behavior; +use Cake\ORM\Entity; /** * CounterCache behavior @@ -198,12 +199,12 @@ protected function _processAssociations(Event $event, EntityInterface $entity) * Updates counter cache for a single association * * @param \Cake\Event\Event $event Event instance. - * @param \Cake\Datasource\EntityInterface $entity Entity + * @param \Cake\ORM\Entity $entity Entity * @param \Cake\ORM\Association $assoc The association object * @param array $settings The settings for for counter cache for this association * @return void */ - protected function _processAssociation(Event $event, EntityInterface $entity, Association $assoc, array $settings) + protected function _processAssociation(Event $event, Entity $entity, Association $assoc, array $settings) { $foreignKeys = (array)$assoc->getForeignKey(); $primaryKeys = (array)$assoc->getBindingKey(); diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 46d1edc6..b11eca65 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -328,6 +328,7 @@ public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $o 'model' => $model ]) ->enableBufferedResults(false) + ->all() ->indexBy('field'); $modified = []; @@ -704,6 +705,6 @@ protected function _findExistingTranslations($ruleSet) $query->unionAll($q); } - return $query->combine('num', 'id')->toArray(); + return $query->all()->combine('num', 'id')->toArray(); } } diff --git a/Marshaller.php b/Marshaller.php index d9eb3756..5e58fb51 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -20,6 +20,7 @@ use Cake\Database\Type; use Cake\Datasource\EntityInterface; use Cake\Datasource\InvalidPropertyInterface; +use Cake\ORM\Association\BelongsToMany; use RuntimeException; /** @@ -158,7 +159,7 @@ protected function _buildPropertyMap($data, $options) * * @param array $data The data to hydrate. * @param array $options List of options - * @return \Cake\Datasource\EntityInterface + * @return \Cake\ORM\Entity * @see \Cake\ORM\Table::newEntity() * @see \Cake\ORM\Entity::$_accessible */ @@ -168,7 +169,7 @@ public function one(array $data, array $options = []) $primaryKey = (array)$this->_table->getPrimaryKey(); $entityClass = $this->_table->getEntityClass(); - /* @var \Cake\Datasource\EntityInterface $entity */ + /* @var \Cake\ORM\Entity $entity */ $entity = new $entityClass(); $entity->setSource($this->_table->getRegistryAlias()); @@ -352,12 +353,12 @@ public function many(array $data, array $options = []) * Builds the related entities and handles the special casing * for junction table entities. * - * @param \Cake\ORM\Association $assoc The association to marshal. + * @param \Cake\ORM\BelongsToMany $assoc The association to marshal. * @param array $data The data to convert into entities. * @param array $options List of options. * @return array An array of built entities. */ - protected function _belongsToMany(Association $assoc, array $data, $options = []) + protected function _belongsToMany(BelongsToMany $assoc, array $data, $options = []) { $associated = isset($options['associated']) ? $options['associated'] : []; $forceNew = isset($options['forceNew']) ? $options['forceNew'] : false; @@ -379,7 +380,7 @@ protected function _belongsToMany(Association $assoc, array $data, $options = [] if (count($keys) === $primaryCount) { $rowConditions = []; foreach ($keys as $key => $value) { - $rowConditions[][$target->aliasfield($key)] = $value; + $rowConditions[][$target->aliasField($key)] = $value; } if ($forceNew && !$target->exists($rowConditions)) { @@ -761,7 +762,7 @@ protected function _mergeBelongsToMany($original, $assoc, $value, $options) * Merge the special _joinData property into the entity set. * * @param \Cake\Datasource\EntityInterface $original The original entity - * @param \Cake\ORM\Association $assoc The association to marshall + * @param \Cake\ORM\Association\BelongsToMany $assoc The association to marshall * @param array $value The data to hydrate * @param array $options List of options. * @return array An array of entities diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index b3730787..2af64c41 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -35,7 +35,7 @@ class ExistsIn /** * The repository where the field will be looked for * - * @var array + * @var \Cake\Datasource\RepositoryInterface */ protected $_repository; From 609d2143352a56be9b3562f570f8a5e0a561d75f Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 6 May 2017 22:43:47 +0530 Subject: [PATCH 0961/2059] Fix few more type errors reported by phpstan. --- Rule/ExistsIn.php | 2 +- Table.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 2af64c41..f35e0541 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -35,7 +35,7 @@ class ExistsIn /** * The repository where the field will be looked for * - * @var \Cake\Datasource\RepositoryInterface + * @var \Cake\Datasource\RepositoryInterface|\Cake\ORM\Association */ protected $_repository; diff --git a/Table.php b/Table.php index b2c9628e..19d6c280 100644 --- a/Table.php +++ b/Table.php @@ -169,7 +169,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc /** * Connection instance * - * @var \Cake\Datasource\ConnectionInterface + * @var \Cake\Database\Connection */ protected $_connection; @@ -494,7 +494,7 @@ public function setConnection(ConnectionInterface $connection) /** * Returns the connection instance. * - * @return \Cake\Datasource\ConnectionInterface + * @return \Cake\Database\Connection */ public function getConnection() { From 973d5345cde8066814b7fa05181d560cfa974b3d Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Wed, 10 May 2017 23:44:07 +0200 Subject: [PATCH 0962/2059] Use RepositoryInterface::getAlias instead of RepositoryInterface::alias --- Query.php | 6 +++--- ResultSet.php | 2 +- Rule/IsUnique.php | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Query.php b/Query.php index e82bf837..42e78812 100644 --- a/Query.php +++ b/Query.php @@ -1043,7 +1043,7 @@ protected function _transformQuery() } if (empty($this->_parts['from'])) { - $this->from([$this->_repository->alias() => $this->_repository->table()]); + $this->from([$this->_repository->getAlias() => $this->_repository->table()]); } $this->_addDefaultFields(); $this->getEagerLoader()->attachAssociations($this, $this->_repository, !$this->_hasFields); @@ -1067,7 +1067,7 @@ protected function _addDefaultFields() $select = $this->clause('select'); } - $aliased = $this->aliasFields($select, $this->repository()->alias()); + $aliased = $this->aliasFields($select, $this->repository()->getAlias()); $this->select($aliased, true); } @@ -1148,7 +1148,7 @@ public function update($table = null) public function delete($table = null) { $repo = $this->repository(); - $this->from([$repo->alias() => $repo->table()]); + $this->from([$repo->getAlias() => $repo->table()]); return parent::delete(); } diff --git a/ResultSet.php b/ResultSet.php index f7cb37ac..f33dc11f 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -183,7 +183,7 @@ public function __construct($query, $statement) $this->_hydrate = $query->isHydrationEnabled(); $this->_entityClass = $repository->getEntityClass(); $this->_useBuffering = $query->isBufferedResultsEnabled(); - $this->_defaultAlias = $this->_defaultTable->alias(); + $this->_defaultAlias = $this->_defaultTable->getAlias(); $this->_calculateColumnMap($query); $this->_calculateTypeMap(); $this->_autoFields = $query->isAutoFieldsEnabled(); diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index b28ed9d2..d4cdc60e 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -69,7 +69,7 @@ public function __invoke(EntityInterface $entity, array $options) } $allowMultipleNulls = $this->_options['allowMultipleNulls']; - $alias = $options['repository']->alias(); + $alias = $options['repository']->getAlias(); $conditions = $this->_alias($alias, $entity->extract($this->_fields), $allowMultipleNulls); if ($entity->isNew() === false) { $keys = (array)$options['repository']->getPrimaryKey(); From 5aba11295f40798d62dd8277d59012bf95ad658b Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Sun, 14 May 2017 17:45:21 +0200 Subject: [PATCH 0963/2059] Split TypedResultTrait::returnType into getter and setter --- Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Query.php b/Query.php index 42e78812..60c0a8fd 100644 --- a/Query.php +++ b/Query.php @@ -1091,7 +1091,7 @@ protected function _addDefaultSelectTypes() $types[$alias] = $typeMap[$value]; } if ($value instanceof TypedResultInterface) { - $types[$alias] = $value->returnType(); + $types[$alias] = $value->getReturnType(); } } $this->getSelectTypeMap()->addDefaults($types); From 66b8a8dd976cf7d0c4d361f5d59e163b78d1c60b Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 15 May 2017 00:11:01 -0400 Subject: [PATCH 0964/2059] Fix invalid SQL on paginated results. When a query with nested matching calls was reused (via pagination), the bound EagerLoadable instances for the nested relations were re-applied incorrectly. By cloning the eagerloader objects when preparing the count() query we can side-step this issue from happening as state is no longer shared between the two query executions. Refs #10633 --- EagerLoader.php | 14 ++++++++++++++ Query.php | 1 + 2 files changed, 15 insertions(+) diff --git a/EagerLoader.php b/EagerLoader.php index c5d042fa..8e34747e 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -829,4 +829,18 @@ protected function _groupKeys($statement, $collectKeys) return $keys; } + + /** + * Clone hook implementation + * + * Clone the _matching eager loader as well. + * + * @return void + */ + public function __clone() + { + if ($this->_matching) { + $this->_matching = clone $this->_matching; + } + } } diff --git a/Query.php b/Query.php index 67a3812f..9c7ad5f4 100644 --- a/Query.php +++ b/Query.php @@ -765,6 +765,7 @@ public function cleanCopy() $clone->formatResults(null, true); $clone->setSelectTypeMap(new TypeMap()); $clone->decorateResults(null, true); + $clone->setEagerLoader(clone $this->getEagerLoader()); return $clone; } From 9168748204c8dc763b8d31fad2cecb63f4db7130 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Mon, 15 May 2017 15:18:36 +0200 Subject: [PATCH 0965/2059] Updated `add*()` methods to return `$this` for method chaining. --- Table.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Table.php b/Table.php index 19d6c280..5e76b962 100644 --- a/Table.php +++ b/Table.php @@ -816,13 +816,15 @@ public function entityClass($name = null) * * @param string $name The name of the behavior. Can be a short class reference. * @param array $options The options for the behavior to use. - * @return void + * @return $this * @throws \RuntimeException If a behavior is being reloaded. * @see \Cake\ORM\Behavior */ public function addBehavior($name, array $options = []) { $this->_behaviors->load($name, $options); + + return $this; } /** @@ -837,12 +839,14 @@ public function addBehavior($name, array $options = []) * ``` * * @param string $name The alias that the behavior was added with. - * @return void + * @return $this * @see \Cake\ORM\Behavior */ public function removeBehavior($name) { $this->_behaviors->unload($name); + + return $this; } /** @@ -908,7 +912,7 @@ public function associations() * keys are used the values will be treated as association aliases. * * @param array $params Set of associations to bind (indexed by association type) - * @return void + * @return $this * @see \Cake\ORM\Table::belongsTo() * @see \Cake\ORM\Table::hasOne() * @see \Cake\ORM\Table::hasMany() @@ -925,6 +929,8 @@ public function addAssociations(array $params) $this->{$assocType}($associated, $options); } } + + return $this; } /** From 1b30be5d34112597d7452d4be59240486cbd70c9 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Mon, 15 May 2017 09:43:55 -0500 Subject: [PATCH 0966/2059] Added second signature for contain that matches the signature for matching --- EagerLoader.php | 11 ++++++++++- Query.php | 22 ++++++++++++++++++---- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index c5d042fa..ac489998 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -126,14 +126,23 @@ class EagerLoader * @param array|string $associations list of table aliases to be queried. * When this method is called multiple times it will merge previous list with * the new one. + * @param callable|null $queryBuilder The query builder callable * @return array Containments. */ - public function contain($associations = []) + public function contain($associations = [], callable $queryBuilder = null) { if (empty($associations)) { return $this->_containments; } + if ($queryBuilder) { + $associations = [ + $associations => [ + 'queryBuilder' => $queryBuilder + ] + ]; + } + $associations = (array)$associations; $associations = $this->_reformatContain($associations, $this->_containments); $this->_normalized = null; diff --git a/Query.php b/Query.php index 42e78812..c21b8ba7 100644 --- a/Query.php +++ b/Query.php @@ -352,14 +352,21 @@ public function eagerLoader(EagerLoader $instance = null) * Failing to do so will trigger exceptions. * * ``` - * // Use special join conditions for getting an Articles's belongsTo 'authors' + * // Use a query builder to add conditions to the containment + * $query->contain('Authors', function ($q) { + * return $q->where(...); // add conditions + * }); + * // Use special join conditions for multiple containments in the same method call * $query->contain([ * 'Authors' => [ * 'foreignKey' => false, * 'queryBuilder' => function ($q) { * return $q->where(...); // Add full filtering conditions * } - * ] + * ], + * 'Tags' => function ($q) { + * return $q->where(...); // add conditions + * } * ]); * ``` * @@ -371,7 +378,9 @@ public function eagerLoader(EagerLoader $instance = null) * previous list will be emptied. * * @param array|string|null $associations List of table aliases to be queried. - * @param bool $override Whether override previous list with the one passed + * @param callable|bool $override The query builder for the association, or + * if associations is an array, a bool on whether to override previous list + * with the one passed * defaults to merging previous list with the new one. * @return array|$this */ @@ -386,7 +395,12 @@ public function contain($associations = null, $override = false) return $loader->contain(); } - $result = $loader->contain($associations); + $queryBuilder = null; + if (is_callable($override)) { + $queryBuilder = $override; + } + + $result = $loader->contain($associations, $queryBuilder); $this->_addAssociationsToTypeMap($this->repository(), $this->getTypeMap(), $result); return $this; From f79d71c1ba3c5dd898514e11bcb238b79e8d4a75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20W=C3=BCrth?= Date: Thu, 18 May 2017 18:42:18 +0200 Subject: [PATCH 0967/2059] Add type & keywords to Database composer.json --- composer.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/composer.json b/composer.json index 062d31c6..d9203ff7 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,13 @@ { "name": "cakephp/orm", "description": "CakePHP ORM - Provides a flexible and powerful ORM implementing a data-mapper pattern.", + "type": "library", + "keywords": [ + "cakephp", + "orm", + "data-mapper", + "data-mapper pattern" + ], "license": "MIT", "authors": [ { From 2c32385741320c10bc2890d5c06b328218eacd98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20W=C3=BCrth?= Date: Thu, 18 May 2017 18:56:57 +0200 Subject: [PATCH 0968/2059] Add homepage to all composer.json --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index d9203ff7..fc0ca9df 100644 --- a/composer.json +++ b/composer.json @@ -8,6 +8,7 @@ "data-mapper", "data-mapper pattern" ], + "homepage": "https://cakephp.org", "license": "MIT", "authors": [ { From 594491eb2a1666c0087fd2b30459b25826189773 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20W=C3=BCrth?= Date: Thu, 18 May 2017 19:09:59 +0200 Subject: [PATCH 0969/2059] Replace homepage with specific contributor list for each sub package --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index fc0ca9df..c0682005 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ "authors": [ { "name": "CakePHP Community", - "homepage": "https://cakephp.org" + "homepage": "https://github.com/cakephp/orm/graphs/contributors" } ], "autoload": { From 7e8948d54766997f4a9d2cbc621d65534047367c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20W=C3=BCrth?= Date: Thu, 18 May 2017 19:17:27 +0200 Subject: [PATCH 0970/2059] Add support data for each sub package Use direct link for source instead of framework --- composer.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/composer.json b/composer.json index c0682005..c86cbc7f 100644 --- a/composer.json +++ b/composer.json @@ -16,6 +16,12 @@ "homepage": "https://github.com/cakephp/orm/graphs/contributors" } ], + "support": { + "issues": "https://github.com/cakephp/cakephp/issues", + "forum": "https://stackoverflow.com/tags/cakephp", + "irc": "irc://irc.freenode.org/cakephp", + "source": "https://github.com/cakephp/orm" + }, "autoload": { "psr-4": { "Cake\\ORM\\": "." From d3353071c78ec07dd4509647ba9ed36a1bfd3582 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20W=C3=BCrth?= Date: Thu, 18 May 2017 19:23:08 +0200 Subject: [PATCH 0971/2059] Add PHP requirement to all sub packages --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index c86cbc7f..d63708b1 100644 --- a/composer.json +++ b/composer.json @@ -28,6 +28,7 @@ } }, "require": { + "php": ">=5.6.0", "cakephp/collection": "~3.0", "cakephp/core": "~3.0", "cakephp/datasource": "^3.1.2", From 06230dfe0805de6b71e53fab3c4e526d4dbc5cb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20W=C3=BCrth?= Date: Thu, 18 May 2017 19:27:30 +0200 Subject: [PATCH 0972/2059] Bring key order in line for all sub packages --- composer.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index d63708b1..027c5adf 100644 --- a/composer.json +++ b/composer.json @@ -22,11 +22,6 @@ "irc": "irc://irc.freenode.org/cakephp", "source": "https://github.com/cakephp/orm" }, - "autoload": { - "psr-4": { - "Cake\\ORM\\": "." - } - }, "require": { "php": ">=5.6.0", "cakephp/collection": "~3.0", @@ -39,5 +34,10 @@ }, "suggest": { "cakephp/i18n": "If you are using Translate / Timestamp Behavior." + }, + "autoload": { + "psr-4": { + "Cake\\ORM\\": "." + } } } From 782dbec9748254841f4bf131707b5c4caf5bd079 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20W=C3=BCrth?= Date: Thu, 18 May 2017 19:33:08 +0200 Subject: [PATCH 0973/2059] Use caret operator in composer.json for all sub packages https://getcomposer.org/doc/articles/versions.md#caret --- composer.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 027c5adf..30f6ecc0 100644 --- a/composer.json +++ b/composer.json @@ -24,13 +24,13 @@ }, "require": { "php": ">=5.6.0", - "cakephp/collection": "~3.0", - "cakephp/core": "~3.0", + "cakephp/collection": "^3.0.0", + "cakephp/core": "^3.0.0", "cakephp/datasource": "^3.1.2", "cakephp/database": "^3.1.4", - "cakephp/event": "~3.0", - "cakephp/utility": "~3.0", - "cakephp/validation": "~3.0" + "cakephp/event": "^3.0.0", + "cakephp/utility": "^3.0.0", + "cakephp/validation": "^3.0.0" }, "suggest": { "cakephp/i18n": "If you are using Translate / Timestamp Behavior." From 320c1c7f7d121ba73322a783e6861c4c755383c8 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 21 May 2017 11:24:53 -0400 Subject: [PATCH 0974/2059] Improve error messages and remove redundant teardown. --- Association/BelongsToMany.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index c1f28360..89a8ed6a 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1282,13 +1282,13 @@ protected function _diffLinks($existing, $jointEntities, $targetEntities, $optio protected function _checkPersistenceStatus($sourceEntity, array $targetEntities) { if ($sourceEntity->isNew()) { - $error = 'Source entity needs to be persisted before proceeding'; + $error = 'Source entity needs to be persisted before links can be created or removed.'; throw new InvalidArgumentException($error); } foreach ($targetEntities as $entity) { if ($entity->isNew()) { - $error = 'Cannot link not persisted entities'; + $error = 'Cannot link entities that have not been persisted yet.'; throw new InvalidArgumentException($error); } } From f6fe9bc6b7d50a547d9a7bc45005529af967c592 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 21 May 2017 12:06:59 -0400 Subject: [PATCH 0975/2059] Fix multiple link() operations not persisting correctly. When an entity is used in multiple link operations, the joint record would be reused, resulting in incorrectly persisted state. Refs #10665 --- Association/BelongsToMany.php | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 89a8ed6a..35dccacc 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -797,14 +797,19 @@ protected function _saveLinks(EntityInterface $sourceEntity, $targetEntities, $o $sourceKeys = array_combine($foreignKey, $sourceEntity->extract($bindingKey)); $targetKeys = array_combine($assocForeignKey, $e->extract($targetPrimaryKey)); - if ($sourceKeys !== $joint->extract($foreignKey)) { - $joint->set($sourceKeys, ['guard' => false]); + $changedKeys = ( + $sourceKeys !== $joint->extract($foreignKey) || + $targetKeys !== $joint->extract($assocForeignKey) + ); + // Keys were changed, the junction table record _could_ be + // new. By clearing the primary key values, and marking the entity + // as new, we let save() sort out whether or not we have a new link + // or if we are updating an existing link. + if ($changedKeys) { + $joint->isNew(true); + $joint->unsetProperty($junction->getPrimaryKey()) + ->set(array_merge($sourceKeys, $targetKeys), ['guard' => false]); } - - if ($targetKeys !== $joint->extract($assocForeignKey)) { - $joint->set($targetKeys, ['guard' => false]); - } - $saved = $junction->save($joint, $options); if (!$saved && !empty($options['atomic'])) { From 22e4ab137974d501ce8a1faa49467af3cc9f7360 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Tue, 23 May 2017 09:53:05 -0500 Subject: [PATCH 0976/2059] Throwing exception if dev mixes signatures --- EagerLoader.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/EagerLoader.php b/EagerLoader.php index ac489998..da2cef97 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -128,6 +128,7 @@ class EagerLoader * the new one. * @param callable|null $queryBuilder The query builder callable * @return array Containments. + * @throws \InvalidArgumentException When using $queryBuilder with an array of $associations */ public function contain($associations = [], callable $queryBuilder = null) { @@ -136,6 +137,12 @@ public function contain($associations = [], callable $queryBuilder = null) } if ($queryBuilder) { + if (!is_string($associations)) { + throw new InvalidArgumentException( + sprintf('Cannot set containments. To use $queryBuilder, $associations must be a string') + ); + } + $associations = [ $associations => [ 'queryBuilder' => $queryBuilder From dba7c8b9b3a2ac975079ebf340f5eb0d7c3b2d30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Tue, 30 May 2017 09:40:17 +0200 Subject: [PATCH 0977/2059] Added a ValidatorAwareInterface. --- Table.php | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Table.php b/Table.php index 5e76b962..5abdbdad 100644 --- a/Table.php +++ b/Table.php @@ -37,6 +37,7 @@ use Cake\ORM\Exception\RolledbackTransactionException; use Cake\ORM\Rule\IsUnique; use Cake\Utility\Inflector; +use Cake\Validation\ValidatorAwareInterface; use Cake\Validation\ValidatorAwareTrait; use InvalidArgumentException; use RuntimeException; @@ -123,20 +124,13 @@ * * @see \Cake\Event\EventManager for reference on the events system. */ -class Table implements RepositoryInterface, EventListenerInterface, EventDispatcherInterface +class Table implements RepositoryInterface, EventListenerInterface, EventDispatcherInterface, ValidatorAwareInterface { use EventDispatcherTrait; use RulesAwareTrait; use ValidatorAwareTrait; - /** - * Name of default validation set. - * - * @var string - */ - const DEFAULT_VALIDATOR = 'default'; - /** * The alias this object is assigned to validators as. * From 14fe9ba73766bddd76de1b6a3e206a8d95f61626 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Tue, 30 May 2017 09:58:03 +0200 Subject: [PATCH 0978/2059] Use new validator methods. --- Marshaller.php | 4 ++-- SaveOptionsBuilder.php | 2 +- Table.php | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 4611f072..d174c9bb 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -232,10 +232,10 @@ protected function _validate($data, $options, $isNew) return []; } if ($options['validate'] === true) { - $options['validate'] = $this->_table->validator('default'); + $options['validate'] = $this->_table->getValidator(); } if (is_string($options['validate'])) { - $options['validate'] = $this->_table->validator($options['validate']); + $options['validate'] = $this->_table->getValidator($options['validate']); } if (!is_object($options['validate'])) { throw new RuntimeException( diff --git a/SaveOptionsBuilder.php b/SaveOptionsBuilder.php index 30f36599..e2b7785c 100644 --- a/SaveOptionsBuilder.php +++ b/SaveOptionsBuilder.php @@ -149,7 +149,7 @@ public function guard($guard) */ public function validate($validate) { - $this->_table->validator($validate); + $this->_table->getValidator($validate); $this->_options['validate'] = $validate; return $this; diff --git a/Table.php b/Table.php index 5abdbdad..d426fa4f 100644 --- a/Table.php +++ b/Table.php @@ -269,10 +269,10 @@ public function __construct(array $config = []) } if (!empty($config['validator'])) { if (!is_array($config['validator'])) { - $this->validator(static::DEFAULT_VALIDATOR, $config['validator']); + $this->setValidator(static::DEFAULT_VALIDATOR, $config['validator']); } else { foreach ($config['validator'] as $name => $validator) { - $this->validator($name, $validator); + $this->setValidator($name, $validator); } } } From 5f7c3fbcbb097d478d1ebe054de2145df8884c81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Tue, 30 May 2017 10:20:14 +0200 Subject: [PATCH 0979/2059] Added BUILD_VALIDATOR_EVENT constant. --- Table.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Table.php b/Table.php index d426fa4f..b2887ce8 100644 --- a/Table.php +++ b/Table.php @@ -138,6 +138,13 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc */ const VALIDATOR_PROVIDER_NAME = 'table'; + /** + * The name of the event dispatched when a validator has been built. + * + * @var string + */ + const BUILD_VALIDATOR_EVENT = 'Model.buildValidator'; + /** * The rules class name that is used. * From 7510c5481dc0c92492735fa6d3001995f081ed01 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Sun, 14 May 2017 19:12:54 +0200 Subject: [PATCH 0980/2059] Split EntityTrait::invalid into getter and setter --- Marshaller.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 4611f072..49ee70ca 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -186,7 +186,7 @@ public function one(array $data, array $options = []) foreach ($data as $key => $value) { if (!empty($errors[$key])) { if ($entity instanceof InvalidPropertyInterface) { - $entity->invalid($key, $value); + $entity->setInvalidField($key, $value); } continue; } @@ -550,7 +550,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) foreach ($data as $key => $value) { if (!empty($errors[$key])) { if ($entity instanceof InvalidPropertyInterface) { - $entity->invalid($key, $value); + $entity->setInvalidField($key, $value); } continue; } From 2d4948a6af167587ca22de02c47a829a0537acfc Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 2 Jun 2017 21:50:29 -0400 Subject: [PATCH 0981/2059] Update usage of I18n::locale() Don't use the deprecated method. --- Behavior/TranslateBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 1dc02d73..94d0b1ae 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -417,7 +417,7 @@ public function buildMarshalMap($marshaller, $map, $options) public function locale($locale = null) { if ($locale === null) { - return $this->_locale ?: I18n::locale(); + return $this->_locale ?: I18n::getLocale(); } return $this->_locale = (string)$locale; From f90911f0ab1c3f1d52cc2a9f2f99e972adcb7627 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Mon, 5 Jun 2017 09:40:23 +0200 Subject: [PATCH 0982/2059] Fixed a regression. --- Table.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Table.php b/Table.php index b2887ce8..d39c5df7 100644 --- a/Table.php +++ b/Table.php @@ -2707,6 +2707,14 @@ public function loadInto($entities, array $contain) return (new LazyEagerLoader)->loadInto($entities, $contain, $this); } + /** + * {@inheritDoc} + */ + protected function validationMethodExists($method) + { + return method_exists($this, $method) || $this->behaviors()->hasMethod($method); + } + /** * Returns an array that can be used to describe the internal state of this * object. From ec87f9063c1bd96fbe3cac6d45c3306afa327f1d Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 5 Jun 2017 00:35:14 +0530 Subject: [PATCH 0983/2059] Add standalone Paginator class. --- Paginator.php | 434 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 434 insertions(+) create mode 100644 Paginator.php diff --git a/Paginator.php b/Paginator.php new file mode 100644 index 00000000..6857bba7 --- /dev/null +++ b/Paginator.php @@ -0,0 +1,434 @@ + 1, + 'limit' => 20, + 'maxLimit' => 100, + 'whitelist' => ['limit', 'sort', 'page', 'direction'] + ]; + + protected $_params = []; + + protected $_pagingParams = []; + + /** + * Handles automatic pagination of model records. + * + * ### Configuring pagination + * + * When calling `paginate()` you can use the $settings parameter to pass in pagination settings. + * These settings are used to build the queries made and control other pagination settings. + * + * If your settings contain a key with the current table's alias. The data inside that key will be used. + * Otherwise the top level configuration will be used. + * + * ``` + * $settings = [ + * 'limit' => 20, + * 'maxLimit' => 100 + * ]; + * $results = $paginator->paginate($table, $settings); + * ``` + * + * The above settings will be used to paginate any Table. You can configure Table specific settings by + * keying the settings with the Table alias. + * + * ``` + * $settings = [ + * 'Articles' => [ + * 'limit' => 20, + * 'maxLimit' => 100 + * ], + * 'Comments' => [ ... ] + * ]; + * $results = $paginator->paginate($table, $settings); + * ``` + * + * This would allow you to have different pagination settings for `Articles` and `Comments` tables. + * + * ### Controlling sort fields + * + * By default CakePHP will automatically allow sorting on any column on the table object being + * paginated. Often times you will want to allow sorting on either associated columns or calculated + * fields. In these cases you will need to define a whitelist of all the columns you wish to allow + * sorting on. You can define the whitelist in the `$settings` parameter: + * + * ``` + * $settings = [ + * 'Articles' => [ + * 'finder' => 'custom', + * 'sortWhitelist' => ['title', 'author_id', 'comment_count'], + * ] + * ]; + * ``` + * + * Passing an empty array as whitelist disallows sorting altogether. + * + * ### Paginating with custom finders + * + * You can paginate with any find type defined on your table using the `finder` option. + * + * ``` + * $settings = [ + * 'Articles' => [ + * 'finder' => 'popular' + * ] + * ]; + * $results = $paginator->paginate($table, $settings); + * ``` + * + * Would paginate using the `find('popular')` method. + * + * You can also pass an already created instance of a query to this method: + * + * ``` + * $query = $this->Articles->find('popular')->matching('Tags', function ($q) { + * return $q->where(['name' => 'CakePHP']) + * }); + * $results = $paginator->paginate($query); + * ``` + * + * ### Scoping Request parameters + * + * By using request parameter scopes you can paginate multiple queries in the same controller action: + * + * ``` + * $articles = $paginator->paginate($articlesQuery, ['scope' => 'articles']); + * $tags = $paginator->paginate($tagsQuery, ['scope' => 'tags']); + * ``` + * + * Each of the above queries will use different query string parameter sets + * for pagination data. An example URL paginating both results would be: + * + * ``` + * /dashboard?articles[page]=1&tags[page]=2 + * ``` + * + * @param \Cake\Datasource\RepositoryInterface|\Cake\Datasource\QueryInterface $object The table or query to paginate. + * @param array $params Request params + * @param array $settings The settings/configuration used for pagination. + * @return \Cake\Datasource\ResultSetInterface Query results + * @throws \Cake\Network\Exception\NotFoundException + */ + public function paginate($object, array $params = null, array $settings = []) + { + $query = null; + if ($object instanceof QueryInterface) { + $query = $object; + $object = $query->repository(); + } + + if ($params !== null) { + $this->_params = $params; + } + + $alias = $object->alias(); + $options = $this->mergeOptions($alias, $settings); + $options = $this->validateSort($object, $options); + $options = $this->checkLimit($options); + + $options += ['page' => 1, 'scope' => null]; + $options['page'] = (int)$options['page'] < 1 ? 1 : (int)$options['page']; + list($finder, $options) = $this->_extractFinder($options); + + /* @var \Cake\Datasource\RepositoryInterface $object */ + if (empty($query)) { + $query = $object->find($finder, $options); + } else { + $query->applyOptions($options); + } + + $results = $query->all(); + $numResults = count($results); + $count = $numResults ? $query->count() : 0; + + $defaults = $this->getDefaults($alias, $settings); + unset($defaults[0]); + + $page = $options['page']; + $limit = $options['limit']; + $pageCount = (int)ceil($count / $limit); + $requestedPage = $page; + $page = max(min($page, $pageCount), 1); + + $order = (array)$options['order']; + $sortDefault = $directionDefault = false; + if (!empty($defaults['order']) && count($defaults['order']) == 1) { + $sortDefault = key($defaults['order']); + $directionDefault = current($defaults['order']); + } + + $paging = [ + 'finder' => $finder, + 'page' => $page, + 'current' => $numResults, + 'count' => $count, + 'perPage' => $limit, + 'prevPage' => $page > 1, + 'nextPage' => $count > ($page * $limit), + 'pageCount' => $pageCount, + 'sort' => key($order), + 'direction' => current($order), + 'limit' => $defaults['limit'] != $limit ? $limit : null, + 'sortDefault' => $sortDefault, + 'directionDefault' => $directionDefault, + 'scope' => $options['scope'], + ]; + + $this->_pagingParams = [$alias => $paging]; + + if ($requestedPage > $page) { + throw new NotFoundException(); + } + + return $results; + } + + /** + * Extracts the finder name and options out of the provided pagination options + * + * @param array $options the pagination options + * @return array An array containing in the first position the finder name and + * in the second the options to be passed to it + */ + protected function _extractFinder($options) + { + $type = !empty($options['finder']) ? $options['finder'] : 'all'; + unset($options['finder'], $options['maxLimit']); + + if (is_array($type)) { + $options = (array)current($type) + $options; + $type = key($type); + } + + return [$type, $options]; + } + + public function setParams($params) + { + $this->_params = $params; + } + + public function getPagingParams() + { + return $this->_pagingParams; + } + + /** + * Merges the various options that Pagination uses. + * Pulls settings together from the following places: + * + * - General pagination settings + * - Model specific settings. + * - Request parameters + * + * The result of this method is the aggregate of all the option sets combined together. You can change + * config value `whitelist` to modify which options/values can be set using request parameters. + * + * @param string $alias Model alias being paginated, if the general settings has a key with this value + * that key's settings will be used for pagination instead of the general ones. + * @param array $settings The settings to merge with the request data. + * @return array Array of merged options. + */ + public function mergeOptions($alias, $settings) + { + $defaults = $this->getDefaults($alias, $settings); + $scope = Hash::get($settings, 'scope', null); + $query = $this->_params; + if ($scope) { + $query = Hash::get($query, $scope, []); + } + $query = array_intersect_key($query, array_flip($this->_config['whitelist'])); + + return array_merge($defaults, $query); + } + + /** + * Get the settings for a $model. If there are no settings for a specific model, the general settings + * will be used. + * + * @param string $alias Model name to get settings for. + * @param array $settings The settings which is used for combining. + * @return array An array of pagination settings for a model, or the general settings. + */ + public function getDefaults($alias, $settings) + { + if (isset($settings[$alias])) { + $settings = $settings[$alias]; + } + + $defaults = $this->getConfig(); + $maxLimit = isset($settings['maxLimit']) ? $settings['maxLimit'] : $defaults['maxLimit']; + $limit = isset($settings['limit']) ? $settings['limit'] : $defaults['limit']; + + if ($limit > $maxLimit) { + $limit = $maxLimit; + } + + $settings['maxLimit'] = $maxLimit; + $settings['limit'] = $limit; + + return $settings + $defaults; + } + + /** + * Validate that the desired sorting can be performed on the $object. Only fields or + * virtualFields can be sorted on. The direction param will also be sanitized. Lastly + * sort + direction keys will be converted into the model friendly order key. + * + * You can use the whitelist parameter to control which columns/fields are available for sorting. + * This helps prevent users from ordering large result sets on un-indexed values. + * + * If you need to sort on associated columns or synthetic properties you will need to use a whitelist. + * + * Any columns listed in the sort whitelist will be implicitly trusted. You can use this to sort + * on synthetic columns, or columns added in custom find operations that may not exist in the schema. + * + * @param \Cake\Datasource\RepositoryInterface $object Repository object. + * @param array $options The pagination options being used for this request. + * @return array An array of options with sort + direction removed and replaced with order if possible. + */ + public function validateSort(RepositoryInterface $object, array $options) + { + if (isset($options['sort'])) { + $direction = null; + if (isset($options['direction'])) { + $direction = strtolower($options['direction']); + } + if (!in_array($direction, ['asc', 'desc'])) { + $direction = 'asc'; + } + $options['order'] = [$options['sort'] => $direction]; + } + unset($options['sort'], $options['direction']); + + if (empty($options['order'])) { + $options['order'] = []; + } + if (!is_array($options['order'])) { + return $options; + } + + $inWhitelist = false; + if (isset($options['sortWhitelist'])) { + $field = key($options['order']); + $inWhitelist = in_array($field, $options['sortWhitelist'], true); + if (!$inWhitelist) { + $options['order'] = []; + + return $options; + } + } + + $options['order'] = $this->_prefix($object, $options['order'], $inWhitelist); + + return $options; + } + + /** + * Prefixes the field with the table alias if possible. + * + * @param \Cake\Datasource\RepositoryInterface $object Repository object. + * @param array $order Order array. + * @param bool $whitelisted Whether or not the field was whitelisted + * @return array Final order array. + */ + protected function _prefix(RepositoryInterface $object, $order, $whitelisted = false) + { + $tableAlias = $object->alias(); + $tableOrder = []; + foreach ($order as $key => $value) { + if (is_numeric($key)) { + $tableOrder[] = $value; + continue; + } + $field = $key; + $alias = $tableAlias; + + if (strpos($key, '.') !== false) { + list($alias, $field) = explode('.', $key); + } + $correctAlias = ($tableAlias === $alias); + + if ($correctAlias && $whitelisted) { + // Disambiguate fields in schema. As id is quite common. + if ($object->hasField($field)) { + $field = $alias . '.' . $field; + } + $tableOrder[$field] = $value; + } elseif ($correctAlias && $object->hasField($field)) { + $tableOrder[$tableAlias . '.' . $field] = $value; + } elseif (!$correctAlias && $whitelisted) { + $tableOrder[$alias . '.' . $field] = $value; + } + } + + return $tableOrder; + } + + /** + * Check the limit parameter and ensure it's within the maxLimit bounds. + * + * @param array $options An array of options with a limit key to be checked. + * @return array An array of options for pagination + */ + public function checkLimit(array $options) + { + $options['limit'] = (int)$options['limit']; + if (empty($options['limit']) || $options['limit'] < 1) { + $options['limit'] = 1; + } + $options['limit'] = max(min($options['limit'], $options['maxLimit']), 1); + + return $options; + } +} From 7ff9f806f407758821b80c2d561bb5c326459988 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 5 Jun 2017 02:15:06 +0530 Subject: [PATCH 0984/2059] Fix return type of PaginatorComponent's config methods and add docblocks. --- Paginator.php | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/Paginator.php b/Paginator.php index 6857bba7..2754b5ae 100644 --- a/Paginator.php +++ b/Paginator.php @@ -56,8 +56,18 @@ class Paginator 'whitelist' => ['limit', 'sort', 'page', 'direction'] ]; + /** + * Request params. + * + * @var array + */ protected $_params = []; + /** + * Paging params after pagination operation is done. + * + * @var array + */ protected $_pagingParams = []; /** @@ -253,11 +263,22 @@ protected function _extractFinder($options) return [$type, $options]; } - public function setParams($params) + /** + * Set params. + * + * @param array $params + * @return void + */ + public function setParams(array $params = []) { $this->_params = $params; } + /** + * Get paging params after pagination operation. + * + * @return array + */ public function getPagingParams() { return $this->_pagingParams; From 76505fa8b89f6e7375c82d3abd119cac13d62526 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 5 Jun 2017 23:44:30 +0530 Subject: [PATCH 0985/2059] Add PageOutOfBoundsException. This avoids adding dependency on Network package in ORM package. --- Exception/PageOutOfBoundsException.php | 37 ++++++++++++++++++++++++++ Paginator.php | 6 ++--- 2 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 Exception/PageOutOfBoundsException.php diff --git a/Exception/PageOutOfBoundsException.php b/Exception/PageOutOfBoundsException.php new file mode 100644 index 00000000..ea08ed82 --- /dev/null +++ b/Exception/PageOutOfBoundsException.php @@ -0,0 +1,37 @@ +_pagingParams = [$alias => $paging]; if ($requestedPage > $page) { - throw new NotFoundException(); + throw new PageOutOfBoundsException([$requestedPage]); } return $results; From 01d1f021bb05b5316d6e96bad410737c7f59cfe4 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 6 Jun 2017 00:08:24 +0530 Subject: [PATCH 0986/2059] Pass all required variables to Paginator::mergeOptions() as arguments. --- Paginator.php | 36 +++++++----------------------------- 1 file changed, 7 insertions(+), 29 deletions(-) diff --git a/Paginator.php b/Paginator.php index d16a78e0..c620cc65 100644 --- a/Paginator.php +++ b/Paginator.php @@ -56,13 +56,6 @@ class Paginator 'whitelist' => ['limit', 'sort', 'page', 'direction'] ]; - /** - * Request params. - * - * @var array - */ - protected $_params = []; - /** * Paging params after pagination operation is done. * @@ -169,7 +162,7 @@ class Paginator * @return \Cake\Datasource\ResultSetInterface Query results * @throws \Cake\ORM\Exception\PageOutOfBoundsException */ - public function paginate($object, array $params = null, array $settings = []) + public function paginate($object, array $params = [], array $settings = []) { $query = null; if ($object instanceof QueryInterface) { @@ -177,12 +170,8 @@ public function paginate($object, array $params = null, array $settings = []) $object = $query->repository(); } - if ($params !== null) { - $this->_params = $params; - } - $alias = $object->alias(); - $options = $this->mergeOptions($alias, $settings); + $options = $this->mergeOptions($alias, $params, $settings); $options = $this->validateSort($object, $options); $options = $this->checkLimit($options); @@ -263,17 +252,6 @@ protected function _extractFinder($options) return [$type, $options]; } - /** - * Set params. - * - * @param array $params - * @return void - */ - public function setParams(array $params = []) - { - $this->_params = $params; - } - /** * Get paging params after pagination operation. * @@ -297,20 +275,20 @@ public function getPagingParams() * * @param string $alias Model alias being paginated, if the general settings has a key with this value * that key's settings will be used for pagination instead of the general ones. + * @param array $params Request params. * @param array $settings The settings to merge with the request data. * @return array Array of merged options. */ - public function mergeOptions($alias, $settings) + public function mergeOptions($alias, $params, $settings) { $defaults = $this->getDefaults($alias, $settings); $scope = Hash::get($settings, 'scope', null); - $query = $this->_params; if ($scope) { - $query = Hash::get($query, $scope, []); + $params = Hash::get($params, $scope, []); } - $query = array_intersect_key($query, array_flip($this->_config['whitelist'])); + $params = array_intersect_key($params, array_flip($this->config('whitelist'))); - return array_merge($defaults, $query); + return array_merge($defaults, $params); } /** From 89daadafb9d20bd5629bd25ae3f414db6fc18cc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Cansado=20Sola=CC=80?= Date: Tue, 6 Jun 2017 20:55:09 +0200 Subject: [PATCH 0987/2059] Update deprecated methods errors() and dirty() methods --- Behavior/TranslateBehavior.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 1dc02d73..254a2b8a 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -308,7 +308,7 @@ public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $o // entity persists. if ($noFields && $bundled && !$key) { foreach ($this->_config['fields'] as $field) { - $entity->dirty($field, true); + $entity->setDirty($field, true); } return; @@ -346,10 +346,10 @@ public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $o $entity->set('_i18n', array_merge($bundled, array_values($modified + $new))); $entity->set('_locale', $locale, ['setter' => false]); - $entity->dirty('_locale', false); + $entity->setDirty('_locale', false); foreach ($fields as $field) { - $entity->dirty($field, false); + $entity->setDirty($field, false); } } @@ -392,13 +392,13 @@ public function buildMarshalMap($marshaller, $map, $options) $translations[$language] = $this->_table->newEntity(); } $marshaller->merge($translations[$language], $fields, $options); - if ((bool)$translations[$language]->errors()) { - $errors[$language] = $translations[$language]->errors(); + if ((bool)$translations[$language]->getErrors()) { + $errors[$language] = $translations[$language]->getErrors(); } } // Set errors into the root entity, so validation errors // match the original form data position. - $entity->errors($errors); + $entity->setErrors($errors); } return $translations; @@ -614,7 +614,7 @@ protected function _bundleTranslatedFields($entity) foreach ($translations as $lang => $translation) { foreach ($fields as $field) { - if (!$translation->dirty($field)) { + if (!$translation->isDirty($field)) { continue; } $find[] = ['locale' => $lang, 'field' => $field, 'foreign_key' => $key]; From 8780bd0b066760174dbb93f837404fb7036251de Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 10 Jun 2017 02:26:11 +0530 Subject: [PATCH 0988/2059] Update docblocks. --- Paginator.php | 97 +++++++++++++++++++++++++++------------------------ 1 file changed, 52 insertions(+), 45 deletions(-) diff --git a/Paginator.php b/Paginator.php index c620cc65..1d6f2d92 100644 --- a/Paginator.php +++ b/Paginator.php @@ -9,7 +9,7 @@ * * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) * @link http://cakephp.org CakePHP(tm) Project - * @since 3.4.8 + * @since 3.5.0 * @license http://www.opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM; @@ -21,14 +21,7 @@ use Cake\Utility\Hash; /** - * This component is used to handle automatic model data pagination. The primary way to use this - * component is to call the paginate() method. There is a convenience wrapper on Controller as well. - * - * ### Configuring pagination - * - * You configure pagination when calling paginate(). See that method for more details. - * - * @link http://book.cakephp.org/3.0/en/controllers/components/pagination.html + * This class is used to handle automatic model data pagination. */ class Paginator { @@ -68,11 +61,13 @@ class Paginator * * ### Configuring pagination * - * When calling `paginate()` you can use the $settings parameter to pass in pagination settings. - * These settings are used to build the queries made and control other pagination settings. + * When calling `paginate()` you can use the $settings parameter to pass in + * pagination settings. These settings are used to build the queries made + * and control other pagination settings. * - * If your settings contain a key with the current table's alias. The data inside that key will be used. - * Otherwise the top level configuration will be used. + * If your settings contain a key with the current table's alias. The data + * inside that key will be used. Otherwise the top level configuration will + * be used. * * ``` * $settings = [ @@ -82,8 +77,8 @@ class Paginator * $results = $paginator->paginate($table, $settings); * ``` * - * The above settings will be used to paginate any Table. You can configure Table specific settings by - * keying the settings with the Table alias. + * The above settings will be used to paginate any repository. You can configure + * repository specific settings by keying the settings with the repository alias. * * ``` * $settings = [ @@ -96,13 +91,15 @@ class Paginator * $results = $paginator->paginate($table, $settings); * ``` * - * This would allow you to have different pagination settings for `Articles` and `Comments` tables. + * This would allow you to have different pagination settings for + * `Articles` and `Comments` repositories. * * ### Controlling sort fields * - * By default CakePHP will automatically allow sorting on any column on the table object being - * paginated. Often times you will want to allow sorting on either associated columns or calculated - * fields. In these cases you will need to define a whitelist of all the columns you wish to allow + * By default CakePHP will automatically allow sorting on any column on the + * repository object being paginated. Often times you will want to allow + * sorting on either associated columns or calculated fields. In these cases + * you will need to define a whitelist of all the columns you wish to allow * sorting on. You can define the whitelist in the `$settings` parameter: * * ``` @@ -118,7 +115,8 @@ class Paginator * * ### Paginating with custom finders * - * You can paginate with any find type defined on your table using the `finder` option. + * You can paginate with any find type defined on your table using the + * `finder` option. * * ``` * $settings = [ @@ -142,7 +140,8 @@ class Paginator * * ### Scoping Request parameters * - * By using request parameter scopes you can paginate multiple queries in the same controller action: + * By using request parameter scopes you can paginate multiple queries in + * the same controller action: * * ``` * $articles = $paginator->paginate($articlesQuery, ['scope' => 'articles']); @@ -179,7 +178,6 @@ public function paginate($object, array $params = [], array $settings = []) $options['page'] = (int)$options['page'] < 1 ? 1 : (int)$options['page']; list($finder, $options) = $this->_extractFinder($options); - /* @var \Cake\Datasource\RepositoryInterface $object */ if (empty($query)) { $query = $object->find($finder, $options); } else { @@ -233,11 +231,11 @@ public function paginate($object, array $params = [], array $settings = []) } /** - * Extracts the finder name and options out of the provided pagination options + * Extracts the finder name and options out of the provided pagination options. * - * @param array $options the pagination options - * @return array An array containing in the first position the finder name and - * in the second the options to be passed to it + * @param array $options the pagination options. + * @return array An array containing in the first position the finder name + * and in the second the options to be passed to it. */ protected function _extractFinder($options) { @@ -263,18 +261,20 @@ public function getPagingParams() } /** - * Merges the various options that Pagination uses. + * Merges the various options that Paginator uses. * Pulls settings together from the following places: * * - General pagination settings * - Model specific settings. * - Request parameters * - * The result of this method is the aggregate of all the option sets combined together. You can change - * config value `whitelist` to modify which options/values can be set using request parameters. + * The result of this method is the aggregate of all the option sets + * combined together. You can change config value `whitelist` to modify + * which options/values can be set using request parameters. * - * @param string $alias Model alias being paginated, if the general settings has a key with this value - * that key's settings will be used for pagination instead of the general ones. + * @param string $alias Model alias being paginated, if the general settings + * has a key with this value that key's settings will be used for + * pagination instead of the general ones. * @param array $params Request params. * @param array $settings The settings to merge with the request data. * @return array Array of merged options. @@ -292,12 +292,13 @@ public function mergeOptions($alias, $params, $settings) } /** - * Get the settings for a $model. If there are no settings for a specific model, the general settings - * will be used. + * Get the settings for a $model. If there are no settings for a specific + * repository, the general settings will be used. * * @param string $alias Model name to get settings for. * @param array $settings The settings which is used for combining. - * @return array An array of pagination settings for a model, or the general settings. + * @return array An array of pagination settings for a model, + * or the general settings. */ public function getDefaults($alias, $settings) { @@ -320,21 +321,27 @@ public function getDefaults($alias, $settings) } /** - * Validate that the desired sorting can be performed on the $object. Only fields or - * virtualFields can be sorted on. The direction param will also be sanitized. Lastly - * sort + direction keys will be converted into the model friendly order key. + * Validate that the desired sorting can be performed on the $object. + * + * Only fields or virtualFields can be sorted on. The direction param will + * also be sanitized. Lastly sort + direction keys will be converted into + * the model friendly order key. * - * You can use the whitelist parameter to control which columns/fields are available for sorting. - * This helps prevent users from ordering large result sets on un-indexed values. + * You can use the whitelist parameter to control which columns/fields are + * available for sorting. This helps prevent users from ordering large + * result sets on un-indexed values. * - * If you need to sort on associated columns or synthetic properties you will need to use a whitelist. + * If you need to sort on associated columns or synthetic properties you + * will need to use a whitelist. * - * Any columns listed in the sort whitelist will be implicitly trusted. You can use this to sort - * on synthetic columns, or columns added in custom find operations that may not exist in the schema. + * Any columns listed in the sort whitelist will be implicitly trusted. + * You can use this to sort on synthetic columns, or columns added in custom + * find operations that may not exist in the schema. * * @param \Cake\Datasource\RepositoryInterface $object Repository object. * @param array $options The pagination options being used for this request. - * @return array An array of options with sort + direction removed and replaced with order if possible. + * @return array An array of options with sort + direction removed and + * replaced with order if possible. */ public function validateSort(RepositoryInterface $object, array $options) { @@ -378,7 +385,7 @@ public function validateSort(RepositoryInterface $object, array $options) * * @param \Cake\Datasource\RepositoryInterface $object Repository object. * @param array $order Order array. - * @param bool $whitelisted Whether or not the field was whitelisted + * @param bool $whitelisted Whether or not the field was whitelisted. * @return array Final order array. */ protected function _prefix(RepositoryInterface $object, $order, $whitelisted = false) @@ -418,7 +425,7 @@ protected function _prefix(RepositoryInterface $object, $order, $whitelisted = f * Check the limit parameter and ensure it's within the maxLimit bounds. * * @param array $options An array of options with a limit key to be checked. - * @return array An array of options for pagination + * @return array An array of options for pagination. */ public function checkLimit(array $options) { From 584dcf433b59a1f5deaf0e6952bd8a8102b99394 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 10 Jun 2017 02:46:23 +0530 Subject: [PATCH 0989/2059] Add PaginatorInterface. --- Paginator.php | 2 +- PaginatorInterface.php | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 PaginatorInterface.php diff --git a/Paginator.php b/Paginator.php index 1d6f2d92..a7520694 100644 --- a/Paginator.php +++ b/Paginator.php @@ -23,7 +23,7 @@ /** * This class is used to handle automatic model data pagination. */ -class Paginator +class Paginator implements PaginatorInterface { use InstanceConfigTrait; diff --git a/PaginatorInterface.php b/PaginatorInterface.php new file mode 100644 index 00000000..9f958311 --- /dev/null +++ b/PaginatorInterface.php @@ -0,0 +1,38 @@ + Date: Sat, 10 Jun 2017 10:47:06 +0530 Subject: [PATCH 0990/2059] Avoid calling getDefaults() twice. --- Paginator.php | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/Paginator.php b/Paginator.php index a7520694..7c8d0de9 100644 --- a/Paginator.php +++ b/Paginator.php @@ -170,7 +170,8 @@ public function paginate($object, array $params = [], array $settings = []) } $alias = $object->alias(); - $options = $this->mergeOptions($alias, $params, $settings); + $defaults = $this->getDefaults($alias, $settings); + $options = $this->mergeOptions($params, $defaults); $options = $this->validateSort($object, $options); $options = $this->checkLimit($options); @@ -188,9 +189,6 @@ public function paginate($object, array $params = [], array $settings = []) $numResults = count($results); $count = $numResults ? $query->count() : 0; - $defaults = $this->getDefaults($alias, $settings); - unset($defaults[0]); - $page = $options['page']; $limit = $options['limit']; $pageCount = (int)ceil($count / $limit); @@ -272,23 +270,19 @@ public function getPagingParams() * combined together. You can change config value `whitelist` to modify * which options/values can be set using request parameters. * - * @param string $alias Model alias being paginated, if the general settings - * has a key with this value that key's settings will be used for - * pagination instead of the general ones. * @param array $params Request params. * @param array $settings The settings to merge with the request data. * @return array Array of merged options. */ - public function mergeOptions($alias, $params, $settings) + public function mergeOptions($params, $settings) { - $defaults = $this->getDefaults($alias, $settings); $scope = Hash::get($settings, 'scope', null); if ($scope) { $params = Hash::get($params, $scope, []); } $params = array_intersect_key($params, array_flip($this->config('whitelist'))); - return array_merge($defaults, $params); + return array_merge($settings, $params); } /** From f40c8130bd4f3d8ff37ef2355f6e9982008c7d8b Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 10 Jun 2017 11:04:07 +0530 Subject: [PATCH 0991/2059] Move paginator class and interface to Datasource package. --- Exception/PageOutOfBoundsException.php | 37 --- Paginator.php | 434 ------------------------- PaginatorInterface.php | 38 --- 3 files changed, 509 deletions(-) delete mode 100644 Exception/PageOutOfBoundsException.php delete mode 100644 Paginator.php delete mode 100644 PaginatorInterface.php diff --git a/Exception/PageOutOfBoundsException.php b/Exception/PageOutOfBoundsException.php deleted file mode 100644 index ea08ed82..00000000 --- a/Exception/PageOutOfBoundsException.php +++ /dev/null @@ -1,37 +0,0 @@ - 1, - 'limit' => 20, - 'maxLimit' => 100, - 'whitelist' => ['limit', 'sort', 'page', 'direction'] - ]; - - /** - * Paging params after pagination operation is done. - * - * @var array - */ - protected $_pagingParams = []; - - /** - * Handles automatic pagination of model records. - * - * ### Configuring pagination - * - * When calling `paginate()` you can use the $settings parameter to pass in - * pagination settings. These settings are used to build the queries made - * and control other pagination settings. - * - * If your settings contain a key with the current table's alias. The data - * inside that key will be used. Otherwise the top level configuration will - * be used. - * - * ``` - * $settings = [ - * 'limit' => 20, - * 'maxLimit' => 100 - * ]; - * $results = $paginator->paginate($table, $settings); - * ``` - * - * The above settings will be used to paginate any repository. You can configure - * repository specific settings by keying the settings with the repository alias. - * - * ``` - * $settings = [ - * 'Articles' => [ - * 'limit' => 20, - * 'maxLimit' => 100 - * ], - * 'Comments' => [ ... ] - * ]; - * $results = $paginator->paginate($table, $settings); - * ``` - * - * This would allow you to have different pagination settings for - * `Articles` and `Comments` repositories. - * - * ### Controlling sort fields - * - * By default CakePHP will automatically allow sorting on any column on the - * repository object being paginated. Often times you will want to allow - * sorting on either associated columns or calculated fields. In these cases - * you will need to define a whitelist of all the columns you wish to allow - * sorting on. You can define the whitelist in the `$settings` parameter: - * - * ``` - * $settings = [ - * 'Articles' => [ - * 'finder' => 'custom', - * 'sortWhitelist' => ['title', 'author_id', 'comment_count'], - * ] - * ]; - * ``` - * - * Passing an empty array as whitelist disallows sorting altogether. - * - * ### Paginating with custom finders - * - * You can paginate with any find type defined on your table using the - * `finder` option. - * - * ``` - * $settings = [ - * 'Articles' => [ - * 'finder' => 'popular' - * ] - * ]; - * $results = $paginator->paginate($table, $settings); - * ``` - * - * Would paginate using the `find('popular')` method. - * - * You can also pass an already created instance of a query to this method: - * - * ``` - * $query = $this->Articles->find('popular')->matching('Tags', function ($q) { - * return $q->where(['name' => 'CakePHP']) - * }); - * $results = $paginator->paginate($query); - * ``` - * - * ### Scoping Request parameters - * - * By using request parameter scopes you can paginate multiple queries in - * the same controller action: - * - * ``` - * $articles = $paginator->paginate($articlesQuery, ['scope' => 'articles']); - * $tags = $paginator->paginate($tagsQuery, ['scope' => 'tags']); - * ``` - * - * Each of the above queries will use different query string parameter sets - * for pagination data. An example URL paginating both results would be: - * - * ``` - * /dashboard?articles[page]=1&tags[page]=2 - * ``` - * - * @param \Cake\Datasource\RepositoryInterface|\Cake\Datasource\QueryInterface $object The table or query to paginate. - * @param array $params Request params - * @param array $settings The settings/configuration used for pagination. - * @return \Cake\Datasource\ResultSetInterface Query results - * @throws \Cake\ORM\Exception\PageOutOfBoundsException - */ - public function paginate($object, array $params = [], array $settings = []) - { - $query = null; - if ($object instanceof QueryInterface) { - $query = $object; - $object = $query->repository(); - } - - $alias = $object->alias(); - $defaults = $this->getDefaults($alias, $settings); - $options = $this->mergeOptions($params, $defaults); - $options = $this->validateSort($object, $options); - $options = $this->checkLimit($options); - - $options += ['page' => 1, 'scope' => null]; - $options['page'] = (int)$options['page'] < 1 ? 1 : (int)$options['page']; - list($finder, $options) = $this->_extractFinder($options); - - if (empty($query)) { - $query = $object->find($finder, $options); - } else { - $query->applyOptions($options); - } - - $results = $query->all(); - $numResults = count($results); - $count = $numResults ? $query->count() : 0; - - $page = $options['page']; - $limit = $options['limit']; - $pageCount = (int)ceil($count / $limit); - $requestedPage = $page; - $page = max(min($page, $pageCount), 1); - - $order = (array)$options['order']; - $sortDefault = $directionDefault = false; - if (!empty($defaults['order']) && count($defaults['order']) == 1) { - $sortDefault = key($defaults['order']); - $directionDefault = current($defaults['order']); - } - - $paging = [ - 'finder' => $finder, - 'page' => $page, - 'current' => $numResults, - 'count' => $count, - 'perPage' => $limit, - 'prevPage' => $page > 1, - 'nextPage' => $count > ($page * $limit), - 'pageCount' => $pageCount, - 'sort' => key($order), - 'direction' => current($order), - 'limit' => $defaults['limit'] != $limit ? $limit : null, - 'sortDefault' => $sortDefault, - 'directionDefault' => $directionDefault, - 'scope' => $options['scope'], - ]; - - $this->_pagingParams = [$alias => $paging]; - - if ($requestedPage > $page) { - throw new PageOutOfBoundsException([$requestedPage]); - } - - return $results; - } - - /** - * Extracts the finder name and options out of the provided pagination options. - * - * @param array $options the pagination options. - * @return array An array containing in the first position the finder name - * and in the second the options to be passed to it. - */ - protected function _extractFinder($options) - { - $type = !empty($options['finder']) ? $options['finder'] : 'all'; - unset($options['finder'], $options['maxLimit']); - - if (is_array($type)) { - $options = (array)current($type) + $options; - $type = key($type); - } - - return [$type, $options]; - } - - /** - * Get paging params after pagination operation. - * - * @return array - */ - public function getPagingParams() - { - return $this->_pagingParams; - } - - /** - * Merges the various options that Paginator uses. - * Pulls settings together from the following places: - * - * - General pagination settings - * - Model specific settings. - * - Request parameters - * - * The result of this method is the aggregate of all the option sets - * combined together. You can change config value `whitelist` to modify - * which options/values can be set using request parameters. - * - * @param array $params Request params. - * @param array $settings The settings to merge with the request data. - * @return array Array of merged options. - */ - public function mergeOptions($params, $settings) - { - $scope = Hash::get($settings, 'scope', null); - if ($scope) { - $params = Hash::get($params, $scope, []); - } - $params = array_intersect_key($params, array_flip($this->config('whitelist'))); - - return array_merge($settings, $params); - } - - /** - * Get the settings for a $model. If there are no settings for a specific - * repository, the general settings will be used. - * - * @param string $alias Model name to get settings for. - * @param array $settings The settings which is used for combining. - * @return array An array of pagination settings for a model, - * or the general settings. - */ - public function getDefaults($alias, $settings) - { - if (isset($settings[$alias])) { - $settings = $settings[$alias]; - } - - $defaults = $this->getConfig(); - $maxLimit = isset($settings['maxLimit']) ? $settings['maxLimit'] : $defaults['maxLimit']; - $limit = isset($settings['limit']) ? $settings['limit'] : $defaults['limit']; - - if ($limit > $maxLimit) { - $limit = $maxLimit; - } - - $settings['maxLimit'] = $maxLimit; - $settings['limit'] = $limit; - - return $settings + $defaults; - } - - /** - * Validate that the desired sorting can be performed on the $object. - * - * Only fields or virtualFields can be sorted on. The direction param will - * also be sanitized. Lastly sort + direction keys will be converted into - * the model friendly order key. - * - * You can use the whitelist parameter to control which columns/fields are - * available for sorting. This helps prevent users from ordering large - * result sets on un-indexed values. - * - * If you need to sort on associated columns or synthetic properties you - * will need to use a whitelist. - * - * Any columns listed in the sort whitelist will be implicitly trusted. - * You can use this to sort on synthetic columns, or columns added in custom - * find operations that may not exist in the schema. - * - * @param \Cake\Datasource\RepositoryInterface $object Repository object. - * @param array $options The pagination options being used for this request. - * @return array An array of options with sort + direction removed and - * replaced with order if possible. - */ - public function validateSort(RepositoryInterface $object, array $options) - { - if (isset($options['sort'])) { - $direction = null; - if (isset($options['direction'])) { - $direction = strtolower($options['direction']); - } - if (!in_array($direction, ['asc', 'desc'])) { - $direction = 'asc'; - } - $options['order'] = [$options['sort'] => $direction]; - } - unset($options['sort'], $options['direction']); - - if (empty($options['order'])) { - $options['order'] = []; - } - if (!is_array($options['order'])) { - return $options; - } - - $inWhitelist = false; - if (isset($options['sortWhitelist'])) { - $field = key($options['order']); - $inWhitelist = in_array($field, $options['sortWhitelist'], true); - if (!$inWhitelist) { - $options['order'] = []; - - return $options; - } - } - - $options['order'] = $this->_prefix($object, $options['order'], $inWhitelist); - - return $options; - } - - /** - * Prefixes the field with the table alias if possible. - * - * @param \Cake\Datasource\RepositoryInterface $object Repository object. - * @param array $order Order array. - * @param bool $whitelisted Whether or not the field was whitelisted. - * @return array Final order array. - */ - protected function _prefix(RepositoryInterface $object, $order, $whitelisted = false) - { - $tableAlias = $object->alias(); - $tableOrder = []; - foreach ($order as $key => $value) { - if (is_numeric($key)) { - $tableOrder[] = $value; - continue; - } - $field = $key; - $alias = $tableAlias; - - if (strpos($key, '.') !== false) { - list($alias, $field) = explode('.', $key); - } - $correctAlias = ($tableAlias === $alias); - - if ($correctAlias && $whitelisted) { - // Disambiguate fields in schema. As id is quite common. - if ($object->hasField($field)) { - $field = $alias . '.' . $field; - } - $tableOrder[$field] = $value; - } elseif ($correctAlias && $object->hasField($field)) { - $tableOrder[$tableAlias . '.' . $field] = $value; - } elseif (!$correctAlias && $whitelisted) { - $tableOrder[$alias . '.' . $field] = $value; - } - } - - return $tableOrder; - } - - /** - * Check the limit parameter and ensure it's within the maxLimit bounds. - * - * @param array $options An array of options with a limit key to be checked. - * @return array An array of options for pagination. - */ - public function checkLimit(array $options) - { - $options['limit'] = (int)$options['limit']; - if (empty($options['limit']) || $options['limit'] < 1) { - $options['limit'] = 1; - } - $options['limit'] = max(min($options['limit'], $options['maxLimit']), 1); - - return $options; - } -} diff --git a/PaginatorInterface.php b/PaginatorInterface.php deleted file mode 100644 index 9f958311..00000000 --- a/PaginatorInterface.php +++ /dev/null @@ -1,38 +0,0 @@ - Date: Sun, 11 Jun 2017 01:43:15 +0200 Subject: [PATCH 0992/2059] Use HTTPS for the cakephp.org URL --- Association.php | 4 ++-- Association/BelongsTo.php | 4 ++-- Association/BelongsToMany.php | 4 ++-- Association/DependentDeleteTrait.php | 4 ++-- Association/HasMany.php | 4 ++-- Association/HasOne.php | 4 ++-- Association/Loader/SelectLoader.php | 4 ++-- Association/Loader/SelectWithPivotLoader.php | 4 ++-- AssociationCollection.php | 4 ++-- AssociationsNormalizerTrait.php | 4 ++-- Behavior.php | 4 ++-- Behavior/CounterCacheBehavior.php | 4 ++-- Behavior/TimestampBehavior.php | 4 ++-- Behavior/Translate/TranslateTrait.php | 4 ++-- Behavior/TranslateBehavior.php | 4 ++-- Behavior/TreeBehavior.php | 4 ++-- BehaviorRegistry.php | 4 ++-- EagerLoadable.php | 4 ++-- EagerLoader.php | 4 ++-- Entity.php | 4 ++-- Exception/MissingBehaviorException.php | 2 +- Exception/MissingEntityException.php | 4 ++-- Exception/MissingTableClassException.php | 2 +- Exception/PersistenceFailedException.php | 2 +- Exception/RolledbackTransactionException.php | 2 +- LICENSE.txt | 2 +- LazyEagerLoader.php | 4 ++-- Locator/LocatorAwareTrait.php | 4 ++-- Locator/LocatorInterface.php | 4 ++-- Locator/TableLocator.php | 4 ++-- Marshaller.php | 4 ++-- PropertyMarshalInterface.php | 4 ++-- Query.php | 4 ++-- ResultSet.php | 4 ++-- Rule/ExistsIn.php | 4 ++-- Rule/IsUnique.php | 4 ++-- Rule/ValidCount.php | 4 ++-- RulesChecker.php | 4 ++-- SaveOptionsBuilder.php | 4 ++-- Table.php | 4 ++-- TableRegistry.php | 4 ++-- 41 files changed, 77 insertions(+), 77 deletions(-) diff --git a/Association.php b/Association.php index 2ddfe6b4..92a4741e 100644 --- a/Association.php +++ b/Association.php @@ -1,6 +1,6 @@ Date: Sun, 11 Jun 2017 01:48:49 +0200 Subject: [PATCH 0993/2059] Use HTTPS for the cakefoundation.org URL --- Association.php | 4 ++-- Association/BelongsTo.php | 4 ++-- Association/BelongsToMany.php | 4 ++-- Association/DependentDeleteTrait.php | 4 ++-- Association/HasMany.php | 4 ++-- Association/HasOne.php | 4 ++-- Association/Loader/SelectLoader.php | 4 ++-- Association/Loader/SelectWithPivotLoader.php | 4 ++-- AssociationCollection.php | 4 ++-- AssociationsNormalizerTrait.php | 4 ++-- Behavior.php | 4 ++-- Behavior/CounterCacheBehavior.php | 4 ++-- Behavior/TimestampBehavior.php | 4 ++-- Behavior/Translate/TranslateTrait.php | 4 ++-- Behavior/TranslateBehavior.php | 4 ++-- Behavior/TreeBehavior.php | 4 ++-- BehaviorRegistry.php | 4 ++-- EagerLoadable.php | 4 ++-- EagerLoader.php | 4 ++-- Entity.php | 4 ++-- Exception/MissingBehaviorException.php | 4 ++-- Exception/MissingEntityException.php | 4 ++-- Exception/MissingTableClassException.php | 4 ++-- Exception/PersistenceFailedException.php | 4 ++-- Exception/RolledbackTransactionException.php | 4 ++-- LICENSE.txt | 2 +- LazyEagerLoader.php | 4 ++-- Locator/LocatorAwareTrait.php | 4 ++-- Locator/LocatorInterface.php | 4 ++-- Locator/TableLocator.php | 4 ++-- Marshaller.php | 4 ++-- PropertyMarshalInterface.php | 4 ++-- Query.php | 4 ++-- ResultSet.php | 4 ++-- Rule/ExistsIn.php | 4 ++-- Rule/IsUnique.php | 4 ++-- Rule/ValidCount.php | 4 ++-- RulesChecker.php | 4 ++-- SaveOptionsBuilder.php | 4 ++-- Table.php | 4 ++-- TableRegistry.php | 4 ++-- 41 files changed, 81 insertions(+), 81 deletions(-) diff --git a/Association.php b/Association.php index 92a4741e..8f3932d4 100644 --- a/Association.php +++ b/Association.php @@ -1,13 +1,13 @@ Date: Sun, 11 Jun 2017 02:01:03 +0200 Subject: [PATCH 0994/2059] Use HTTPS for the opensource.org URL --- Association.php | 2 +- Association/BelongsTo.php | 2 +- Association/BelongsToMany.php | 2 +- Association/DependentDeleteTrait.php | 2 +- Association/HasMany.php | 2 +- Association/HasOne.php | 2 +- Association/Loader/SelectLoader.php | 2 +- Association/Loader/SelectWithPivotLoader.php | 2 +- AssociationCollection.php | 2 +- AssociationsNormalizerTrait.php | 2 +- Behavior.php | 2 +- Behavior/CounterCacheBehavior.php | 2 +- Behavior/TimestampBehavior.php | 2 +- Behavior/Translate/TranslateTrait.php | 2 +- Behavior/TranslateBehavior.php | 2 +- Behavior/TreeBehavior.php | 2 +- BehaviorRegistry.php | 2 +- EagerLoadable.php | 2 +- EagerLoader.php | 2 +- Entity.php | 2 +- Exception/MissingBehaviorException.php | 2 +- Exception/MissingEntityException.php | 2 +- Exception/MissingTableClassException.php | 2 +- Exception/PersistenceFailedException.php | 2 +- Exception/RolledbackTransactionException.php | 2 +- LazyEagerLoader.php | 2 +- Locator/LocatorAwareTrait.php | 2 +- Locator/LocatorInterface.php | 2 +- Locator/TableLocator.php | 2 +- Marshaller.php | 2 +- PropertyMarshalInterface.php | 2 +- Query.php | 2 +- ResultSet.php | 2 +- Rule/ExistsIn.php | 2 +- Rule/IsUnique.php | 2 +- Rule/ValidCount.php | 2 +- RulesChecker.php | 2 +- SaveOptionsBuilder.php | 2 +- Table.php | 2 +- TableRegistry.php | 2 +- 40 files changed, 40 insertions(+), 40 deletions(-) diff --git a/Association.php b/Association.php index 8f3932d4..2aab1365 100644 --- a/Association.php +++ b/Association.php @@ -10,7 +10,7 @@ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 3.0.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM; diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index de942e72..55b223ac 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -10,7 +10,7 @@ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 3.0.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM\Association; diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index e6dfea12..6a6840ca 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -10,7 +10,7 @@ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 3.0.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM\Association; diff --git a/Association/DependentDeleteTrait.php b/Association/DependentDeleteTrait.php index 5df025fd..fd3f8fe3 100644 --- a/Association/DependentDeleteTrait.php +++ b/Association/DependentDeleteTrait.php @@ -10,7 +10,7 @@ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 3.0.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM\Association; diff --git a/Association/HasMany.php b/Association/HasMany.php index b6384937..99b9918f 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -11,7 +11,7 @@ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 3.0.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM\Association; diff --git a/Association/HasOne.php b/Association/HasOne.php index 00423655..f97108bc 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -10,7 +10,7 @@ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 3.0.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM\Association; diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index f25eccb8..2772adcb 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -10,7 +10,7 @@ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 3.4.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM\Association\Loader; diff --git a/Association/Loader/SelectWithPivotLoader.php b/Association/Loader/SelectWithPivotLoader.php index d77655ff..5dd1918c 100644 --- a/Association/Loader/SelectWithPivotLoader.php +++ b/Association/Loader/SelectWithPivotLoader.php @@ -10,7 +10,7 @@ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 3.4.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM\Association\Loader; diff --git a/AssociationCollection.php b/AssociationCollection.php index e868e966..72e83e82 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -10,7 +10,7 @@ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 3.0.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM; diff --git a/AssociationsNormalizerTrait.php b/AssociationsNormalizerTrait.php index 86ee1282..335804b4 100644 --- a/AssociationsNormalizerTrait.php +++ b/AssociationsNormalizerTrait.php @@ -10,7 +10,7 @@ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 3.0.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM; diff --git a/Behavior.php b/Behavior.php index d78f3fb3..13134d5a 100644 --- a/Behavior.php +++ b/Behavior.php @@ -10,7 +10,7 @@ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 3.0.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM; diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 2170dd85..9e1a99fb 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -10,7 +10,7 @@ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 3.0.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM\Behavior; diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index f89ff5bf..523b2296 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -10,7 +10,7 @@ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 3.0.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM\Behavior; diff --git a/Behavior/Translate/TranslateTrait.php b/Behavior/Translate/TranslateTrait.php index a9aefb94..343740e2 100644 --- a/Behavior/Translate/TranslateTrait.php +++ b/Behavior/Translate/TranslateTrait.php @@ -10,7 +10,7 @@ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 3.0.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM\Behavior\Translate; diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index a2c05660..835afc56 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -10,7 +10,7 @@ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 3.0.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM\Behavior; diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index c947fe84..3ef896c7 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -10,7 +10,7 @@ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 3.0.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM\Behavior; diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 58919ca7..e852cdac 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -10,7 +10,7 @@ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 3.0.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM; diff --git a/EagerLoadable.php b/EagerLoadable.php index 88d2adaf..c9c4f612 100644 --- a/EagerLoadable.php +++ b/EagerLoadable.php @@ -10,7 +10,7 @@ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 3.0.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM; diff --git a/EagerLoader.php b/EagerLoader.php index 785a8884..bbff09c8 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -10,7 +10,7 @@ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 3.0.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM; diff --git a/Entity.php b/Entity.php index 4d0d0a47..a3771491 100644 --- a/Entity.php +++ b/Entity.php @@ -10,7 +10,7 @@ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 3.0.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM; diff --git a/Exception/MissingBehaviorException.php b/Exception/MissingBehaviorException.php index e08f4c51..725e5e8a 100644 --- a/Exception/MissingBehaviorException.php +++ b/Exception/MissingBehaviorException.php @@ -8,7 +8,7 @@ * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @since 3.0.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM\Exception; diff --git a/Exception/MissingEntityException.php b/Exception/MissingEntityException.php index 937e3c20..6f355279 100644 --- a/Exception/MissingEntityException.php +++ b/Exception/MissingEntityException.php @@ -12,7 +12,7 @@ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 3.0.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM\Exception; diff --git a/Exception/MissingTableClassException.php b/Exception/MissingTableClassException.php index bcc84646..544a968d 100644 --- a/Exception/MissingTableClassException.php +++ b/Exception/MissingTableClassException.php @@ -10,7 +10,7 @@ * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @since 3.0.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM\Exception; diff --git a/Exception/PersistenceFailedException.php b/Exception/PersistenceFailedException.php index 9621214d..373f779c 100644 --- a/Exception/PersistenceFailedException.php +++ b/Exception/PersistenceFailedException.php @@ -8,7 +8,7 @@ * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @since 3.4.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM\Exception; diff --git a/Exception/RolledbackTransactionException.php b/Exception/RolledbackTransactionException.php index 2511475f..ac31a9d0 100644 --- a/Exception/RolledbackTransactionException.php +++ b/Exception/RolledbackTransactionException.php @@ -8,7 +8,7 @@ * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @since 3.2.13 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM\Exception; diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index b72af70f..dbefa18b 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -10,7 +10,7 @@ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 3.1.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM; diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index b8b8f1df..f2ae6f1c 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -10,7 +10,7 @@ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 3.1.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM\Locator; diff --git a/Locator/LocatorInterface.php b/Locator/LocatorInterface.php index 856b91d5..f8dda59c 100644 --- a/Locator/LocatorInterface.php +++ b/Locator/LocatorInterface.php @@ -10,7 +10,7 @@ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 3.1.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM\Locator; diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 80e052d7..36ab5cda 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -10,7 +10,7 @@ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 3.1.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM\Locator; diff --git a/Marshaller.php b/Marshaller.php index 24f24a29..e4b8b543 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -10,7 +10,7 @@ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 3.0.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM; diff --git a/PropertyMarshalInterface.php b/PropertyMarshalInterface.php index 609a9cd5..bf2c6d56 100644 --- a/PropertyMarshalInterface.php +++ b/PropertyMarshalInterface.php @@ -10,7 +10,7 @@ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 3.4.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM; diff --git a/Query.php b/Query.php index e3b244fa..2386aab8 100644 --- a/Query.php +++ b/Query.php @@ -10,7 +10,7 @@ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 3.0.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM; diff --git a/ResultSet.php b/ResultSet.php index 0f05cbc9..dbe6811b 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -10,7 +10,7 @@ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 3.0.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM; diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index a55fde28..73810063 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -10,7 +10,7 @@ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 3.0.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM\Rule; diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index 13b080cb..f0a182a0 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -10,7 +10,7 @@ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 3.0.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM\Rule; diff --git a/Rule/ValidCount.php b/Rule/ValidCount.php index 623efe3d..33a98081 100644 --- a/Rule/ValidCount.php +++ b/Rule/ValidCount.php @@ -10,7 +10,7 @@ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 3.2.9 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM\Rule; diff --git a/RulesChecker.php b/RulesChecker.php index e5a28bc2..feea8c89 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -10,7 +10,7 @@ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 3.0.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM; diff --git a/SaveOptionsBuilder.php b/SaveOptionsBuilder.php index 8d4f1d5c..5ea63169 100644 --- a/SaveOptionsBuilder.php +++ b/SaveOptionsBuilder.php @@ -10,7 +10,7 @@ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 3.3.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM; diff --git a/Table.php b/Table.php index 9e337a2c..b347d42f 100644 --- a/Table.php +++ b/Table.php @@ -10,7 +10,7 @@ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 3.0.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM; diff --git a/TableRegistry.php b/TableRegistry.php index 05dddb13..558464db 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -10,7 +10,7 @@ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 3.0.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM; From 3dfed4caa737aef3ee8c62359c49f8cde33159de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20W=C3=BCrth?= Date: Sun, 11 Jun 2017 02:19:51 +0200 Subject: [PATCH 0995/2059] Use HTTPS for various other URLs --- Behavior/TreeBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 3ef896c7..6c43dabc 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -33,7 +33,7 @@ * order will be cached. * * For more information on what is a nested set and a how it works refer to - * http://www.sitepoint.com/hierarchical-data-database-2/ + * https://www.sitepoint.com/hierarchical-data-database-2/ */ class TreeBehavior extends Behavior { From 1062aadc59ce85da84ce86a5ade4bc5d780fcb1f Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Wed, 21 Jun 2017 22:53:07 +0200 Subject: [PATCH 0996/2059] Fix usage of contain finder options for hasMany relations --- Association/Loader/SelectLoader.php | 36 ++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 2772adcb..bbe78eb9 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -163,7 +163,14 @@ protected function _buildQuery($options) $options['fields'] = []; } - $fetchQuery = $finder() + if (isset($options['finder'])) { + list($finderName, $opts) = $this->_extractFinder($options['finder']); + $query = $finder()->find($finderName, $opts); + } else { + $query = $finder(); + } + + $fetchQuery = $query ->select($options['fields']) ->where($options['conditions']) ->eagerLoaded(true) @@ -193,6 +200,33 @@ protected function _buildQuery($options) return $fetchQuery; } + /** + * Helper method to infer the requested finder and its options. + * + * Returns the inferred options from the finder $type. + * + * ### Examples: + * + * The following will call the finder 'translations' with the value of the finder as its options: + * $query->contain(['Comments' => ['finder' => ['translations']]]); + * $query->contain(['Comments' => ['finder' => ['translations' => []]]]); + * $query->contain(['Comments' => ['finder' => ['translations' => ['locales' => ['en_US']]]]]); + * + * @param string|array $finderData The finder name or an array having the name as key + * and options as value. + * @return array + */ + protected function _extractFinder($finderData) + { + $finderData = (array)$finderData; + + if (is_numeric(key($finderData))) { + return [current($finderData), []]; + } + + return [key($finderData), current($finderData)]; + } + /** * Checks that the fetching query either has auto fields on or * has the foreignKey fields selected. From f389aa3684d12254a2bd65554202cc85e968cade Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 23 Jun 2017 01:42:17 +0530 Subject: [PATCH 0997/2059] Avoid unneeded "else". --- Association/Loader/SelectLoader.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index bbe78eb9..c10dedcd 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -163,11 +163,10 @@ protected function _buildQuery($options) $options['fields'] = []; } + $query = $finder(); if (isset($options['finder'])) { list($finderName, $opts) = $this->_extractFinder($options['finder']); - $query = $finder()->find($finderName, $opts); - } else { - $query = $finder(); + $query = $query->find($finderName, $opts); } $fetchQuery = $query From 43d1ce31fb5a299fc85b2f0cd2582d623299341e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Cansado=20Sola=CC=80?= Date: Fri, 23 Jun 2017 20:27:22 +0200 Subject: [PATCH 0998/2059] update dirty method for get/set --- Association/BelongsToMany.php | 6 +++--- Association/HasMany.php | 4 ++-- AssociationCollection.php | 2 +- Behavior/CounterCacheBehavior.php | 2 +- Behavior/TimestampBehavior.php | 4 ++-- Behavior/TranslateBehavior.php | 10 +++++----- Behavior/TreeBehavior.php | 14 +++++++------- LazyEagerLoader.php | 2 +- Marshaller.php | 4 ++-- Table.php | 2 +- 10 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 35dccacc..a1511660 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -817,7 +817,7 @@ protected function _saveLinks(EntityInterface $sourceEntity, $targetEntities, $o } $e->set($jointProperty, $joint); - $e->dirty($jointProperty, false); + $e->setDirty($jointProperty, false); } return true; @@ -942,7 +942,7 @@ function () use ($sourceEntity, $targetEntities, $options) { } $sourceEntity->set($property, array_values($existing)); - $sourceEntity->dirty($property, false); + $sourceEntity->setDirty($property, false); return true; } @@ -1199,7 +1199,7 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { ksort($targetEntities); $sourceEntity->set($property, array_values($targetEntities)); - $sourceEntity->dirty($property, false); + $sourceEntity->setDirty($property, false); return true; } diff --git a/Association/HasMany.php b/Association/HasMany.php index 8ef85db2..55b03151 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -274,7 +274,7 @@ public function link(EntityInterface $sourceEntity, array $targetEntities, array if ($ok) { $sourceEntity->set($property, $savedEntity->get($property)); - $sourceEntity->dirty($property, false); + $sourceEntity->setDirty($property, false); } return $ok; @@ -361,7 +361,7 @@ function ($assoc) use ($targetEntities) { ); } - $sourceEntity->dirty($property, false); + $sourceEntity->setDirty($property, false); } /** diff --git a/AssociationCollection.php b/AssociationCollection.php index c3ac6c66..a7d1ff59 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -250,7 +250,7 @@ protected function _saveAssociations($table, $entity, $associations, $options, $ */ protected function _save($association, $entity, $nested, $options) { - if (!$entity->dirty($association->getProperty())) { + if (!$entity->isDirty($association->getProperty())) { return true; } if (!empty($nested)) { diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index babd5e92..8706f011 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -132,7 +132,7 @@ public function beforeSave(Event $event, EntityInterface $entity, $options) if (!is_callable($config) && isset($config['ignoreDirty']) && $config['ignoreDirty'] === true && - $entity->$entityAlias->dirty($field) + $entity->$entityAlias->isDirty($field) ) { $this->_ignoreDirty[$registryAlias][$field] = true; } diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 06342bb0..7c07bb04 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -172,7 +172,7 @@ public function touch(EntityInterface $entity, $eventName = 'Model.beforeSave') foreach ($events[$eventName] as $field => $when) { if (in_array($when, ['always', 'existing'])) { $return = true; - $entity->dirty($field, false); + $entity->setDirty($field, false); $this->_updateField($entity, $field, $refresh); } } @@ -190,7 +190,7 @@ public function touch(EntityInterface $entity, $eventName = 'Model.beforeSave') */ protected function _updateField($entity, $field, $refreshTimestamp) { - if ($entity->dirty($field)) { + if ($entity->isDirty($field)) { return; } $entity->set($field, $this->timestamp(null, $refreshTimestamp)); diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 1dc02d73..ba308e30 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -308,7 +308,7 @@ public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $o // entity persists. if ($noFields && $bundled && !$key) { foreach ($this->_config['fields'] as $field) { - $entity->dirty($field, true); + $entity->setDirty($field, true); } return; @@ -346,10 +346,10 @@ public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $o $entity->set('_i18n', array_merge($bundled, array_values($modified + $new))); $entity->set('_locale', $locale, ['setter' => false]); - $entity->dirty('_locale', false); + $entity->setDirty('_locale', false); foreach ($fields as $field) { - $entity->dirty($field, false); + $entity->setDirty($field, false); } } @@ -602,7 +602,7 @@ protected function _bundleTranslatedFields($entity) { $translations = (array)$entity->get('_translations'); - if (empty($translations) && !$entity->dirty('_translations')) { + if (empty($translations) && !$entity->isDirty('_translations')) { return; } @@ -614,7 +614,7 @@ protected function _bundleTranslatedFields($entity) foreach ($translations as $lang => $translation) { foreach ($fields as $field) { - if (!$translation->dirty($field)) { + if (!$translation->isDirty($field)) { continue; } $find[] = ['locale' => $lang, 'field' => $field, 'foreign_key' => $key]; diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index d0904eda..a609af78 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -100,7 +100,7 @@ public function beforeSave(Event $event, EntityInterface $entity) $config = $this->getConfig(); $parent = $entity->get($config['parent']); $primaryKey = $this->_getPrimaryKey(); - $dirty = $entity->dirty($config['parent']); + $dirty = $entity->isDirty($config['parent']); $level = $config['level']; if ($parent && $entity->get($primaryKey) == $parent) { @@ -585,7 +585,7 @@ protected function _removeFromTree($node) $this->_table->updateAll($node->extract($fields), [$primary => $node->get($primary)]); foreach ($fields as $field) { - $node->dirty($field, false); + $node->setDirty($field, false); } return $node; @@ -675,8 +675,8 @@ protected function _moveUp($node, $number) $node->set($left, $targetLeft); $node->set($right, $targetLeft + ($nodeRight - $nodeLeft)); - $node->dirty($left, false); - $node->dirty($right, false); + $node->setDirty($left, false); + $node->setDirty($right, false); return $node; } @@ -765,8 +765,8 @@ protected function _moveDown($node, $number) $node->set($left, $targetRight - ($nodeRight - $nodeLeft)); $node->set($right, $targetRight); - $node->dirty($left, false); - $node->dirty($right, false); + $node->setDirty($left, false); + $node->setDirty($right, false); return $node; } @@ -960,7 +960,7 @@ protected function _ensureFields($entity) $entity->set($fresh->extract($fields), ['guard' => false]); foreach ($fields as $field) { - $entity->dirty($field, false); + $entity->setDirty($field, false); } } diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index de6234ae..66d32062 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -155,7 +155,7 @@ protected function _injectResults($objects, $results, $associations, $source) foreach ($associations as $assoc) { $property = $properties[$assoc]; $object->set($property, $loaded->get($property), ['useSetters' => false]); - $object->dirty($property, false); + $object->setDirty($property, false); } $injected[$k] = $object; } diff --git a/Marshaller.php b/Marshaller.php index ccd57fd4..8405f812 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -578,7 +578,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) foreach ($properties as $field => $value) { if ($value instanceof EntityInterface) { - $entity->dirty($field, $value->dirty()); + $entity->setDirty($field, $value->isDirty()); } } @@ -589,7 +589,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) if (array_key_exists($field, $properties)) { $entity->set($field, $properties[$field]); if ($properties[$field] instanceof EntityInterface) { - $entity->dirty($field, $properties[$field]->dirty()); + $entity->isDirty($field, $properties[$field]->isDirty()); } } } diff --git a/Table.php b/Table.php index a2d96f6e..750d2181 100644 --- a/Table.php +++ b/Table.php @@ -1715,7 +1715,7 @@ public function save(EntityInterface $entity, $options = []) return false; } - if ($entity->isNew() === false && !$entity->dirty()) { + if ($entity->isNew() === false && !$entity->isDirty()) { return $entity; } From d360e51cc1d46b289edd90553b9085fcfcc3a6b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Cansado=20Sola=CC=80?= Date: Fri, 23 Jun 2017 20:37:28 +0200 Subject: [PATCH 0999/2059] fix error --- Marshaller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index 8405f812..feedd34d 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -589,7 +589,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) if (array_key_exists($field, $properties)) { $entity->set($field, $properties[$field]); if ($properties[$field] instanceof EntityInterface) { - $entity->isDirty($field, $properties[$field]->isDirty()); + $entity->setDirty($field, $properties[$field]->isDirty()); } } } From c91609645abc23fc9ae0c6c4d5b68addccff54f1 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 30 Jun 2017 21:58:04 -0400 Subject: [PATCH 1000/2059] Make one()/many() more consistent with empty associations. When an association is cleaned out by a beforeMarshal hook, the entity property should not be marked as dirty as the related entity has no properties to persist. Refs #10658 --- Marshaller.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Marshaller.php b/Marshaller.php index 0511647c..c80bafb9 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -212,6 +212,14 @@ public function one(array $data, array $options = []) $entity->set($properties); } + // Don't flag clean association entities as + // dirty so we don't persist empty records. + foreach ($properties as $field => $value) { + if ($value instanceof EntityInterface) { + $entity->dirty($field, $value->dirty()); + } + } + $entity->setErrors($errors); return $entity; From dcf199be7475f2eeb0b698cc24ecd7a1e482f05d Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 30 Jun 2017 22:03:06 -0400 Subject: [PATCH 1001/2059] Reduce nesting. --- Marshaller.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index c80bafb9..6f7a20e7 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -595,11 +595,12 @@ public function merge(EntityInterface $entity, array $data, array $options = []) } foreach ((array)$options['fields'] as $field) { - if (array_key_exists($field, $properties)) { - $entity->set($field, $properties[$field]); - if ($properties[$field] instanceof EntityInterface) { - $entity->setDirty($field, $properties[$field]->isDirty()); - } + if (!array_key_exists($field, $properties)) { + continue; + } + $entity->set($field, $properties[$field]); + if ($properties[$field] instanceof EntityInterface) { + $entity->setDirty($field, $properties[$field]->isDirty()); } } From 679371ae5c30f4893e128d1d6cecb2e0ca0c04fc Mon Sep 17 00:00:00 2001 From: Mark Story Date: Wed, 5 Jul 2017 22:04:13 -0400 Subject: [PATCH 1002/2059] Reset eager loaders before triggering before find. In #10657 I made a change to reset eager loaders when creating a clone to fix state being shared across query clones. Because of method ordering this resulted in the eager loader being reset *after* the beforeFind was triggered, which dropped any contained associations added during that event. Re-ordering the method calls allows contained associations added during the beforeFind to be retained in the count() query. Refs #10813 --- Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Query.php b/Query.php index 2386aab8..e1283faf 100644 --- a/Query.php +++ b/Query.php @@ -756,6 +756,7 @@ public function applyOptions(array $options) public function cleanCopy() { $clone = clone $this; + $clone->setEagerLoader(clone $this->getEagerLoader()); $clone->triggerBeforeFind(); $clone->enableAutoFields(false); $clone->limit(null); @@ -765,7 +766,6 @@ public function cleanCopy() $clone->formatResults(null, true); $clone->setSelectTypeMap(new TypeMap()); $clone->decorateResults(null, true); - $clone->setEagerLoader(clone $this->getEagerLoader()); return $clone; } From 180f334fe565e6a17532512d8219abb54b0d852d Mon Sep 17 00:00:00 2001 From: kicaj Date: Wed, 12 Jul 2017 22:53:39 +0200 Subject: [PATCH 1003/2059] Improve API docs --- Association.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Association.php b/Association.php index 2aab1365..92ccb870 100644 --- a/Association.php +++ b/Association.php @@ -498,7 +498,7 @@ public function conditions($conditions = null) * Sets the name of the field representing the binding field with the target table. * When not manually specified the primary key of the owning side table is used. * - * @param string $key the table field to be used to link both tables together + * @param string|array $key the table field or fields to be used to link both tables together * @return $this */ public function setBindingKey($key) @@ -557,7 +557,7 @@ public function getForeignKey() /** * Sets the name of the field representing the foreign key to the target table. * - * @param string $key the key to be used to link both tables together + * @param string|array $key the key or keys to be used to link both tables together * @return $this */ public function setForeignKey($key) From 756f3f5fad5e8a3cc3363e88f1c34ce1bff1411b Mon Sep 17 00:00:00 2001 From: chinpei215 Date: Fri, 14 Jul 2017 13:34:04 +0900 Subject: [PATCH 1004/2059] Add the $previous argument to all exceptions Add _defaultCode property to Cake\Core\Exception\Exception. This allows people to pass $previous argument without $code. Also, now InvalidPrimaryKeyException, RecordNotFoundException, SocketException, RedirectException and XmlException are subclass of Cake\Core\Exception\Exception. --- Exception/PersistenceFailedException.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Exception/PersistenceFailedException.php b/Exception/PersistenceFailedException.php index 373f779c..2a3eab22 100644 --- a/Exception/PersistenceFailedException.php +++ b/Exception/PersistenceFailedException.php @@ -42,7 +42,7 @@ class PersistenceFailedException extends Exception * @param int $code The code of the error, is also the HTTP status code for the error. * @param \Exception|null $previous the previous exception. */ - public function __construct(EntityInterface $entity, $message, $code = 500, $previous = null) + public function __construct(EntityInterface $entity, $message, $code = null, $previous = null) { $this->_entity = $entity; parent::__construct($message, $code, $previous); From 8105aed24d28baa2483f3fc471efd8b8ea7a8ba3 Mon Sep 17 00:00:00 2001 From: David Yell Date: Tue, 18 Jul 2017 16:52:00 +0100 Subject: [PATCH 1005/2059] Gender non-specific Replace with gender non-specific wording --- Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Query.php b/Query.php index e1283faf..9c13ac63 100644 --- a/Query.php +++ b/Query.php @@ -197,7 +197,7 @@ public function select($fields = [], $overwrite = false) * Hints this object to associate the correct types when casting conditions * for the database. This is done by extracting the field types from the schema * associated to the passed table object. This prevents the user from repeating - * himself when specifying conditions. + * themselves when specifying conditions. * * This method returns the same query object for chaining. * From 10099ad3716f9a8d8c112f6b419dbd335b6a5038 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Mon, 24 Jul 2017 22:25:04 +0200 Subject: [PATCH 1006/2059] Fix some typos --- Association/DependentDeleteTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/DependentDeleteTrait.php b/Association/DependentDeleteTrait.php index 30d81ab6..f172d4c9 100644 --- a/Association/DependentDeleteTrait.php +++ b/Association/DependentDeleteTrait.php @@ -22,7 +22,7 @@ * * Included by HasOne and HasMany association classes. * - * @deprected 3.5.0 Unused in CakePHP now. This class will be removed in 4.0.0 + * @deprecated 3.5.0 Unused in CakePHP now. This class will be removed in 4.0.0 */ trait DependentDeleteTrait { From 86e6e9e0f91e74ad810dcf734833f8a03b073261 Mon Sep 17 00:00:00 2001 From: chinpei215 Date: Fri, 28 Jul 2017 00:33:36 +0900 Subject: [PATCH 1007/2059] Remove deprecated method calls --- Marshaller.php | 10 +++++----- ResultSet.php | 2 +- Rule/ExistsIn.php | 4 ++-- Table.php | 20 ++++++++++---------- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 6f7a20e7..6d127c15 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -70,7 +70,7 @@ protected function _buildPropertyMap($data, $options) // Is a concrete column? foreach (array_keys($data) as $prop) { - $columnType = $schema->columnType($prop); + $columnType = $schema->getColumnType($prop); if ($columnType) { $map[$prop] = function ($value, $entity) use ($columnType) { return Type::build($columnType)->marshal($value); @@ -216,7 +216,7 @@ public function one(array $data, array $options = []) // dirty so we don't persist empty records. foreach ($properties as $field => $value) { if ($value instanceof EntityInterface) { - $entity->dirty($field, $value->dirty()); + $entity->setDirty($field, $value->isDirty()); } } @@ -547,7 +547,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) if (isset($options['accessibleFields'])) { foreach ((array)$options['accessibleFields'] as $key => $value) { - $entity->accessible($key, $value); + $entity->setAccess($key, $value); } } @@ -581,7 +581,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) $properties[$key] = $value; } - $entity->errors($errors); + $entity->setErrors($errors); if (!isset($options['fields'])) { $entity->set($properties); @@ -785,7 +785,7 @@ protected function _mergeJoinData($original, $assoc, $value, $options) $extra = []; foreach ($original as $entity) { // Mark joinData as accessible so we can marshal it properly. - $entity->accessible('_joinData', true); + $entity->setAccess('_joinData', true); $joinData = $entity->get('_joinData'); if ($joinData && $joinData instanceof EntityInterface) { diff --git a/ResultSet.php b/ResultSet.php index b3197beb..18bb4d24 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -460,7 +460,7 @@ protected function _getTypes($table, $fields) } foreach (array_intersect($fields, $schema->columns()) as $col) { - $typeName = $schema->columnType($col); + $typeName = $schema->getColumnType($col); if (isset($typeMap[$typeName])) { $types[$col] = $typeMap[$typeName]; } diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index f502ca89..3a7113a3 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -117,7 +117,7 @@ public function __invoke(EntityInterface $entity, array $options) if ($this->_options['allowNullableNulls']) { $schema = $source->getSchema(); foreach ($this->_fields as $i => $field) { - if ($schema->column($field) && $schema->isNullable($field) && $entity->get($field) === null) { + if ($schema->getColumn($field) && $schema->isNullable($field) && $entity->get($field) === null) { unset($bindingKey[$i], $this->_fields[$i]); } } @@ -147,7 +147,7 @@ protected function _fieldsAreNull($entity, $source) $nulls = 0; $schema = $source->getSchema(); foreach ($this->_fields as $field) { - if ($schema->column($field) && $schema->isNullable($field) && $entity->get($field) === null) { + if ($schema->getColumn($field) && $schema->isNullable($field) && $entity->get($field) === null) { $nulls++; } } diff --git a/Table.php b/Table.php index e7d71f10..5938c640 100644 --- a/Table.php +++ b/Table.php @@ -600,7 +600,7 @@ public function schema($schema = null) * * ``` * protected function _initializeSchema(\Cake\Database\Schema\TableSchema $schema) { - * $schema->columnType('preferences', 'json'); + * $schema->setColumnType('preferences', 'json'); * return $schema; * } * ``` @@ -626,7 +626,7 @@ public function hasField($field) { $schema = $this->getSchema(); - return $schema->column($field) !== null; + return $schema->getColumn($field) !== null; } /** @@ -700,10 +700,10 @@ public function getDisplayField() $schema = $this->getSchema(); $primary = (array)$this->getPrimaryKey(); $this->_displayField = array_shift($primary); - if ($schema->column('title')) { + if ($schema->getColumn('title')) { $this->_displayField = 'title'; } - if ($schema->column('name')) { + if ($schema->getColumn('name')) { $this->_displayField = 'name'; } } @@ -1718,7 +1718,7 @@ public function save(EntityInterface $entity, $options = []) '_primary' => true ]); - if ($entity->errors()) { + if ($entity->getErrors()) { return false; } @@ -1737,7 +1737,7 @@ public function save(EntityInterface $entity, $options = []) if ($options['atomic'] || $options['_primary']) { $entity->clean(); $entity->isNew(false); - $entity->source($this->getRegistryAlias()); + $entity->setSource($this->getRegistryAlias()); } } @@ -1863,7 +1863,7 @@ protected function _onSaveSuccess($entity, $options) if (!$options['atomic'] && !$options['_primary']) { $entity->clean(); $entity->isNew(false); - $entity->source($this->getRegistryAlias()); + $entity->setSource($this->getRegistryAlias()); } return true; @@ -1901,7 +1901,7 @@ protected function _insert($entity, $data) if (count($primary) > 1) { $schema = $this->getSchema(); foreach ($primary as $k => $v) { - if (!isset($data[$k]) && empty($schema->column($k)['autoIncrement'])) { + if (!isset($data[$k]) && empty($schema->getColumn($k)['autoIncrement'])) { $msg = 'Cannot insert row, some of the primary key values are missing. '; $msg .= sprintf( 'Got (%s), expecting (%s)', @@ -1930,7 +1930,7 @@ protected function _insert($entity, $data) foreach ($primary as $key => $v) { if (!isset($data[$key])) { $id = $statement->lastInsertId($this->getTable(), $key); - $type = $schema->columnType($key); + $type = $schema->getColumnType($key); $entity->set($key, Type::build($type)->toPHP($id, $driver)); break; } @@ -1959,7 +1959,7 @@ protected function _newId($primary) if (!$primary || count((array)$primary) > 1) { return null; } - $typeName = $this->getSchema()->columnType($primary[0]); + $typeName = $this->getSchema()->getColumnType($primary[0]); $type = Type::build($typeName); return $type->newId(); From 038df65dd814d2df9731156ee1801ed686c6cd7f Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Thu, 27 Jul 2017 19:53:43 +0200 Subject: [PATCH 1008/2059] Remove deprecated driver() calls --- Association/Loader/SelectLoader.php | 2 +- EagerLoader.php | 4 ++-- Table.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index c10dedcd..e64ca361 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -254,7 +254,7 @@ protected function _assertFieldsPresent($fetchQuery, $key) $missingFields = $missingKey($select, $key); if ($missingFields) { - $driver = $fetchQuery->getConnection()->driver(); + $driver = $fetchQuery->getConnection()->getDriver(); $quoted = array_map([$driver, 'quoteIdentifier'], $key); $missingFields = $missingKey($select, $quoted); } diff --git a/EagerLoader.php b/EagerLoader.php index 701283ce..d65848a9 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -647,7 +647,7 @@ public function loadExternal($query, $statement) return $statement; } - $driver = $query->getConnection()->driver(); + $driver = $query->getConnection()->getDriver(); list($collected, $statement) = $this->_collectKeys($external, $query, $statement); foreach ($external as $meta) { @@ -803,7 +803,7 @@ protected function _collectKeys($external, $query, $statement) } if (!($statement instanceof BufferedStatement)) { - $statement = new BufferedStatement($statement, $query->getConnection()->driver()); + $statement = new BufferedStatement($statement, $query->getConnection()->getDriver()); } return [$this->_groupKeys($statement, $collectKeys), $statement]; diff --git a/Table.php b/Table.php index 5938c640..ca891a23 100644 --- a/Table.php +++ b/Table.php @@ -1926,7 +1926,7 @@ protected function _insert($entity, $data) $success = $entity; $entity->set($filteredKeys, ['guard' => false]); $schema = $this->getSchema(); - $driver = $this->getConnection()->driver(); + $driver = $this->getConnection()->getDriver(); foreach ($primary as $key => $v) { if (!isset($data[$key])) { $id = $statement->lastInsertId($this->getTable(), $key); From 7036662eb91c301dec690e4cd61f371c52761203 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Thu, 27 Jul 2017 20:15:59 +0200 Subject: [PATCH 1009/2059] Remove deprecated defaultLocale() call --- Behavior/TranslateBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 3c516efb..e1440545 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -99,7 +99,7 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface public function __construct(Table $table, array $config = []) { $config += [ - 'defaultLocale' => I18n::defaultLocale(), + 'defaultLocale' => I18n::getDefaultLocale(), 'referenceName' => $this->_referenceName($table) ]; From 12edad572eae74b388b8eaa16595b5be79200db0 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Thu, 27 Jul 2017 20:17:20 +0200 Subject: [PATCH 1010/2059] Remove _calculateTypeMap() call as it does nothing --- ResultSet.php | 1 - 1 file changed, 1 deletion(-) diff --git a/ResultSet.php b/ResultSet.php index 18bb4d24..bbdf75e0 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -185,7 +185,6 @@ public function __construct($query, $statement) $this->_useBuffering = $query->isBufferedResultsEnabled(); $this->_defaultAlias = $this->_defaultTable->getAlias(); $this->_calculateColumnMap($query); - $this->_calculateTypeMap(); $this->_autoFields = $query->isAutoFieldsEnabled(); if ($this->_useBuffering) { From b775fecd413c146a8bb8ca905ebe13d7e9af0b3c Mon Sep 17 00:00:00 2001 From: chinpei215 Date: Fri, 28 Jul 2017 20:41:52 +0900 Subject: [PATCH 1011/2059] Fix incorrect doc block --- Association/BelongsToMany.php | 2 +- Query.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index f3ce1b7b..b514d2d1 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -253,7 +253,7 @@ public function getSort() * Sets the sort order in which target records should be returned. * If no arguments are passed the currently configured value is returned * - * @deprecated 3.4.0 Use setSort()/getSort() instead. + * @deprecated 3.5.0 Use setSort()/getSort() instead. * @param mixed $sort A find() compatible order clause * @return mixed */ diff --git a/Query.php b/Query.php index 3ac20e92..f682fa8e 100644 --- a/Query.php +++ b/Query.php @@ -155,7 +155,7 @@ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface /** * Constructor * - * @param \Cake\Datasource\ConnectionInterface $connection The connection object + * @param \Cake\Database\Connection $connection The connection object * @param \Cake\ORM\Table $table The table this query is starting on */ public function __construct($connection, $table) From ac6aef4157fd00268731cefc7442fbaa29010d29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Tue, 1 Aug 2017 10:39:37 +0200 Subject: [PATCH 1012/2059] Started refactoring the ORM Cache Shell --- OrmCache.php | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 OrmCache.php diff --git a/OrmCache.php b/OrmCache.php new file mode 100644 index 00000000..f21f3dc5 --- /dev/null +++ b/OrmCache.php @@ -0,0 +1,115 @@ +_schema = $this->getSchema($connection); + } + + /** + * Build metadata. + * + * @param string|null $name The name of the table to build cache data for. + * @return bool + */ + public function build($name = null) + { + $tables = [$name]; + if (empty($name)) { + $tables = $this->_schema->listTables(); + } + + foreach ($tables as $table) { + $this->_schema->describe($table, ['forceRefresh' => true]); + } + + return true; + } + + /** + * Clear metadata. + * + * @param string|null $name The name of the table to clear cache data for. + * @return bool + */ + public function clear($name = null) + { + $tables = [$name]; + if (empty($name)) { + $tables = $this->_schema->listTables(); + } + $configName = $this->_schema->getCacheMetadata(); + + foreach ($tables as $table) { + $key = $this->_schema->cacheKey($table); + Cache::delete($key, $configName); + } + + return true; + } + + /** + * Helper method to get the schema collection. + * + * @param string $connection Connection name to get the schema for + * @return false|\Cake\Database\Schema\Collection|\Cake\Database\Schema\CachedCollection + */ + public function getSchema($connection) + { + /* @var \Cake\Database\Connection $source */ + $source = ConnectionManager::get($connection); + if (!method_exists($source, 'schemaCollection')) { + throw new RuntimeException(sprintf( + 'The "%s" connection is not compatible with orm caching, ' . + 'as it does not implement a "schemaCollection()" method.', + $connection + )); + } + + $config = $source->config(); + if (empty($config['cacheMetadata'])) { + $source->cacheMetadata(true); + } + + return $source->getSchemaCollection(); + } +} From 2cf1cfd76dc4e784818b1ff0c1d5abf2adb88f58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Tue, 1 Aug 2017 22:57:10 +0200 Subject: [PATCH 1013/2059] Adding a test for the new OrmCache class --- OrmCache.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OrmCache.php b/OrmCache.php index f21f3dc5..98f5502a 100644 --- a/OrmCache.php +++ b/OrmCache.php @@ -9,7 +9,7 @@ * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project - * @since 3.0.0 + * @since 3.5.0 * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\Orm; From 2696c94e6e127207b61cb2dfe25f13f3300c097b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Wed, 2 Aug 2017 00:01:36 +0200 Subject: [PATCH 1014/2059] Fixing the ORM cache tests --- OrmCache.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OrmCache.php b/OrmCache.php index 98f5502a..a991c538 100644 --- a/OrmCache.php +++ b/OrmCache.php @@ -12,7 +12,7 @@ * @since 3.5.0 * @license https://opensource.org/licenses/mit-license.php MIT License */ -namespace Cake\Orm; +namespace Cake\ORM; use Cake\Cache\Cache; use Cake\Datasource\ConnectionManager; From c0b52e42ad53aeb23266e0784daba0e3bbbd4415 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Wed, 2 Aug 2017 01:45:35 +0200 Subject: [PATCH 1015/2059] Adding verbose output back to the OrmCacheShell --- OrmCache.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/OrmCache.php b/OrmCache.php index a991c538..3c1ab68d 100644 --- a/OrmCache.php +++ b/OrmCache.php @@ -49,7 +49,7 @@ public function __construct($connection) * Build metadata. * * @param string|null $name The name of the table to build cache data for. - * @return bool + * @return array Returns a list build table caches */ public function build($name = null) { @@ -62,14 +62,14 @@ public function build($name = null) $this->_schema->describe($table, ['forceRefresh' => true]); } - return true; + return $tables; } /** * Clear metadata. * * @param string|null $name The name of the table to clear cache data for. - * @return bool + * @return array Returns a list of cleared table caches */ public function clear($name = null) { @@ -84,7 +84,7 @@ public function clear($name = null) Cache::delete($key, $configName); } - return true; + return $tables; } /** From 205e496690299dc58d368164dfaba16a6782dc4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Wed, 2 Aug 2017 22:45:58 +0200 Subject: [PATCH 1016/2059] Changing OrmCache getSchema() to take a connection instance --- OrmCache.php | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/OrmCache.php b/OrmCache.php index 3c1ab68d..c8a4e813 100644 --- a/OrmCache.php +++ b/OrmCache.php @@ -15,7 +15,9 @@ namespace Cake\ORM; use Cake\Cache\Cache; +use Cake\Datasource\ConnectionInterface; use Cake\Datasource\ConnectionManager; +use RuntimeException; /** * ORM Cache. @@ -31,14 +33,14 @@ class OrmCache /** * Schema * - * @var \Cake\Datasource\SchemaInterface + * @var \Cake\Database\Schema\Collection|\Cake\Database\Schema\CachedCollection */ protected $_schema; /** * Constructor * - * @param string $connection Connection name + * @param string|\Cake\Datasource\ConnectionInterface $connection Connection name to get the schema for or a connection instance */ public function __construct($connection) { @@ -90,17 +92,31 @@ public function clear($name = null) /** * Helper method to get the schema collection. * - * @param string $connection Connection name to get the schema for - * @return false|\Cake\Database\Schema\Collection|\Cake\Database\Schema\CachedCollection + * @param string|\Cake\Datasource\ConnectionInterface $connection Connection name to get the schema for or a connection instance + * @return \Cake\Database\Schema\Collection|\Cake\Database\Schema\CachedCollection */ public function getSchema($connection) { - /* @var \Cake\Database\Connection $source */ - $source = ConnectionManager::get($connection); + if (is_string($connection)) { + /* @var \Cake\Database\Connection $source */ + $source = ConnectionManager::get($connection); + } elseif (!$connection instanceof ConnectionInterface) { + throw new RuntimeException(sprtinf( + 'OrmCache::getSchema() expects `%s`, `%s` given.', + ConnectionInterface::class, + is_object($connection) ? get_class($connection) : gettype($connection) + )); + } + + if ($connection instanceof ConnectionInterface) { + $source = $connection; + $connection = $source->configName(); + } + if (!method_exists($source, 'schemaCollection')) { throw new RuntimeException(sprintf( - 'The "%s" connection is not compatible with orm caching, ' . - 'as it does not implement a "schemaCollection()" method.', + 'The "%s" connection is not compatible with schema ' . + 'caching, as it does not implement a "schemaCollection()" method.', $connection )); } From 11be2e4e975cb20b3fe4b8fcf8282f05cc887794 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Wed, 2 Aug 2017 22:58:24 +0200 Subject: [PATCH 1017/2059] Adding a test for OrmCache --- OrmCache.php | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/OrmCache.php b/OrmCache.php index c8a4e813..8f7500f4 100644 --- a/OrmCache.php +++ b/OrmCache.php @@ -17,6 +17,7 @@ use Cake\Cache\Cache; use Cake\Datasource\ConnectionInterface; use Cake\Datasource\ConnectionManager; +use InvalidArgumentException; use RuntimeException; /** @@ -98,34 +99,28 @@ public function clear($name = null) public function getSchema($connection) { if (is_string($connection)) { - /* @var \Cake\Database\Connection $source */ - $source = ConnectionManager::get($connection); + $connection = ConnectionManager::get($connection); } elseif (!$connection instanceof ConnectionInterface) { - throw new RuntimeException(sprtinf( - 'OrmCache::getSchema() expects `%s`, `%s` given.', + throw new InvalidArgumentException(sprintf( + 'SchemaCache::getSchema() expects `%s`, `%s` given.', ConnectionInterface::class, is_object($connection) ? get_class($connection) : gettype($connection) )); } - if ($connection instanceof ConnectionInterface) { - $source = $connection; - $connection = $source->configName(); - } - - if (!method_exists($source, 'schemaCollection')) { + if (!method_exists($connection, 'schemaCollection')) { throw new RuntimeException(sprintf( - 'The "%s" connection is not compatible with schema ' . - 'caching, as it does not implement a "schemaCollection()" method.', - $connection + 'The "%s" connection is not compatible with schema caching, ' . + 'as it does not implement a "schemaCollection()" method.', + $connection->configName() )); } - $config = $source->config(); + $config = $connection->config(); if (empty($config['cacheMetadata'])) { - $source->cacheMetadata(true); + $connection->cacheMetadata(true); } - return $source->getSchemaCollection(); + return $connection->getSchemaCollection(); } } From a4175963470934c82273f4d8cf0a5e01bfc45345 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Wed, 2 Aug 2017 23:10:29 +0200 Subject: [PATCH 1018/2059] Renaming the OrmCache to SchemaCache --- OrmCache.php | 126 --------------------------------------------------- 1 file changed, 126 deletions(-) delete mode 100644 OrmCache.php diff --git a/OrmCache.php b/OrmCache.php deleted file mode 100644 index 8f7500f4..00000000 --- a/OrmCache.php +++ /dev/null @@ -1,126 +0,0 @@ -_schema = $this->getSchema($connection); - } - - /** - * Build metadata. - * - * @param string|null $name The name of the table to build cache data for. - * @return array Returns a list build table caches - */ - public function build($name = null) - { - $tables = [$name]; - if (empty($name)) { - $tables = $this->_schema->listTables(); - } - - foreach ($tables as $table) { - $this->_schema->describe($table, ['forceRefresh' => true]); - } - - return $tables; - } - - /** - * Clear metadata. - * - * @param string|null $name The name of the table to clear cache data for. - * @return array Returns a list of cleared table caches - */ - public function clear($name = null) - { - $tables = [$name]; - if (empty($name)) { - $tables = $this->_schema->listTables(); - } - $configName = $this->_schema->getCacheMetadata(); - - foreach ($tables as $table) { - $key = $this->_schema->cacheKey($table); - Cache::delete($key, $configName); - } - - return $tables; - } - - /** - * Helper method to get the schema collection. - * - * @param string|\Cake\Datasource\ConnectionInterface $connection Connection name to get the schema for or a connection instance - * @return \Cake\Database\Schema\Collection|\Cake\Database\Schema\CachedCollection - */ - public function getSchema($connection) - { - if (is_string($connection)) { - $connection = ConnectionManager::get($connection); - } elseif (!$connection instanceof ConnectionInterface) { - throw new InvalidArgumentException(sprintf( - 'SchemaCache::getSchema() expects `%s`, `%s` given.', - ConnectionInterface::class, - is_object($connection) ? get_class($connection) : gettype($connection) - )); - } - - if (!method_exists($connection, 'schemaCollection')) { - throw new RuntimeException(sprintf( - 'The "%s" connection is not compatible with schema caching, ' . - 'as it does not implement a "schemaCollection()" method.', - $connection->configName() - )); - } - - $config = $connection->config(); - if (empty($config['cacheMetadata'])) { - $connection->cacheMetadata(true); - } - - return $connection->getSchemaCollection(); - } -} From 0a832456c215ea82c3f0c6080c29be4528662a7c Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 7 Aug 2017 00:08:18 -0400 Subject: [PATCH 1019/2059] Fix count errors in ORM. Fix the various count() errors in the ORM package. I had to rework how join conditions were generated as count() on a generic ExpressionInterface object fails in PHP7.2. Instead we can convert expressions into strings and then generate the appropriate SQL. I also found the link between count() and having a sql() method was pretty loose, where as the new checks are more correct. --- Association.php | 4 ++-- Marshaller.php | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Association.php b/Association.php index 92ccb870..3da887c1 100644 --- a/Association.php +++ b/Association.php @@ -419,10 +419,10 @@ public function getTarget() throw new RuntimeException(sprintf( $errorMessage, - get_class($this->_sourceTable), + $this->_sourceTable ? get_class($this->_sourceTable) : 'null', $this->getName(), $this->type(), - get_class($this->_targetTable), + $this->_targetTable ? get_class($this->_targetTable) : 'null', $className )); } diff --git a/Marshaller.php b/Marshaller.php index e4b8b543..a6d00006 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -463,7 +463,8 @@ protected function _loadAssociatedByIds($assoc, $ids) $primaryKey = array_map([$target, 'aliasField'], $primaryKey); if ($multi) { - if (count(current($ids)) !== count($primaryKey)) { + $first = current($ids); + if (!is_array($first) || count($first) !== count($primaryKey)) { return []; } $filter = new TupleComparison($primaryKey, $ids, [], 'IN'); From 44990aa24a688d50ed9de8907e0d13cd8696442e Mon Sep 17 00:00:00 2001 From: inoas Date: Fri, 11 Aug 2017 20:36:38 +0200 Subject: [PATCH 1020/2059] Note about allowNullableNulls --- Rule/ExistsIn.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 73810063..5bfa179b 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -56,6 +56,8 @@ class ExistsIn * @param object|string $repository The repository where the field will be looked for, * or the association name for the repository. * @param array $options The options that modify the rules behavior. + * Options 'allowNullableNulls' will make the rule pass if given foreign keys are set to `null`. + * Notice: allowNullableNulls cannot pass by database columns set to `NOT NULL`. */ public function __construct($fields, $repository, array $options = []) { From a4effe97da7f48975171b951578a2582eb0794b5 Mon Sep 17 00:00:00 2001 From: David Yell Date: Wed, 16 Aug 2017 16:38:31 +0100 Subject: [PATCH 1021/2059] Clarified the docblocks When reading these docblocks I found it confusing, because it's the Association class, I assumed that `getName()` would return the name of the association, such as `oneToMany` or similar. So I've just tried to clarify that the intended return is a Table class alias, as a string. --- Association.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Association.php b/Association.php index 3da887c1..c9f01acf 100644 --- a/Association.php +++ b/Association.php @@ -238,7 +238,8 @@ public function __construct($alias, array $options = []) } /** - * Sets the name for this association. + * Sets the name for this association, usually the alias + * assigned to the target associated table * * @param string $name Name to be assigned * @return $this @@ -258,7 +259,8 @@ public function setName($name) } /** - * Gets the name for this association. + * Gets the name for this association, usually the alias + * assigned to the target associated table * * @return string */ From d7f623c2993edfe5295203f42c8b848e77d0a804 Mon Sep 17 00:00:00 2001 From: dereuromark Date: Sat, 19 Aug 2017 12:50:25 +0200 Subject: [PATCH 1022/2059] Fix up behavior callbacks for clean inheritance and consistency. --- Behavior/CounterCacheBehavior.php | 7 ++++--- Behavior/TranslateBehavior.php | 5 +++-- Behavior/TreeBehavior.php | 10 +++++++--- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index e4a62f5d..36b55579 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -14,6 +14,7 @@ */ namespace Cake\ORM\Behavior; +use ArrayObject; use Cake\Datasource\EntityInterface; use Cake\Event\Event; use Cake\ORM\Association; @@ -114,7 +115,7 @@ class CounterCacheBehavior extends Behavior * @param \ArrayObject $options The options for the query * @return void */ - public function beforeSave(Event $event, EntityInterface $entity, $options) + public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options) { if (isset($options['ignoreCounterCache']) && $options['ignoreCounterCache'] === true) { return; @@ -151,7 +152,7 @@ public function beforeSave(Event $event, EntityInterface $entity, $options) * @param \ArrayObject $options The options for the query * @return void */ - public function afterSave(Event $event, EntityInterface $entity, $options) + public function afterSave(Event $event, EntityInterface $entity, ArrayObject $options) { if (isset($options['ignoreCounterCache']) && $options['ignoreCounterCache'] === true) { return; @@ -171,7 +172,7 @@ public function afterSave(Event $event, EntityInterface $entity, $options) * @param \ArrayObject $options The options for the query * @return void */ - public function afterDelete(Event $event, EntityInterface $entity, $options) + public function afterDelete(Event $event, EntityInterface $entity, ArrayObject $options) { if (isset($options['ignoreCounterCache']) && $options['ignoreCounterCache'] === true) { return; diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index e1440545..965a99c2 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -204,7 +204,7 @@ public function setupFieldAssociations($fields, $table, $model, $strategy) * @param \ArrayObject $options The options for the query * @return void */ - public function beforeFind(Event $event, Query $query, $options) + public function beforeFind(Event $event, Query $query, ArrayObject $options) { $locale = $this->locale(); @@ -359,9 +359,10 @@ public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $o * * @param \Cake\Event\Event $event The beforeSave event that was fired * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved + * @param \ArrayObject $options Options. * @return void */ - public function afterSave(Event $event, EntityInterface $entity) + public function afterSave(Event $event, EntityInterface $entity, ArrayObject $options) { $entity->unsetProperty('_i18n'); } diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index faf9f36f..598164c8 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -14,6 +14,7 @@ */ namespace Cake\ORM\Behavior; +use ArrayObject; use Cake\Database\Expression\IdentifierExpression; use Cake\Datasource\EntityInterface; use Cake\Datasource\Exception\RecordNotFoundException; @@ -91,10 +92,11 @@ public function initialize(array $config) * * @param \Cake\Event\Event $event The beforeSave event that was fired * @param \Cake\Datasource\EntityInterface $entity the entity that is going to be saved + * @param \ArrayObject $options Options. * @return void * @throws \RuntimeException if the parent to set for the node is invalid */ - public function beforeSave(Event $event, EntityInterface $entity) + public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options) { $isNew = $entity->isNew(); $config = $this->getConfig(); @@ -160,9 +162,10 @@ public function beforeSave(Event $event, EntityInterface $entity) * * @param \Cake\Event\Event $event The afterSave event that was fired * @param \Cake\Datasource\EntityInterface $entity the entity that is going to be saved + * @param \ArrayObject $options Options. * @return void */ - public function afterSave(Event $event, EntityInterface $entity) + public function afterSave(Event $event, EntityInterface $entity, ArrayObject $options) { if (!$this->_config['level'] || $entity->isNew()) { return; @@ -213,9 +216,10 @@ protected function _setChildrenLevel($entity) * * @param \Cake\Event\Event $event The beforeDelete event that was fired * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved + * @param \ArrayObject $options Options. * @return void */ - public function beforeDelete(Event $event, EntityInterface $entity) + public function beforeDelete(Event $event, EntityInterface $entity, ArrayObject $options) { $config = $this->getConfig(); $this->_ensureFields($entity); From 23fd411defa37f92253b54a80959aaa255afa977 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 19 Aug 2017 19:25:44 +0530 Subject: [PATCH 1023/2059] Add annotations for missing methods to interfaces. This prevents static analyzers like phpstan complaining when an argument is typehinted as interface and a method used on object is not present in the interface. Closes #11051 --- Association.php | 2 +- Behavior/CounterCacheBehavior.php | 5 ++--- Marshaller.php | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Association.php b/Association.php index 64df174a..15cdc772 100644 --- a/Association.php +++ b/Association.php @@ -947,7 +947,7 @@ public function attachTo(Query $query, array $options = []) * Conditionally adds a condition to the passed Query that will make it find * records where there is no match with this association. * - * @param \Cake\ORM\Query $query The query to modify + * @param \Cake\Datasource\QueryInterface $query The query to modify * @param array $options Options array containing the `negateMatch` key. * @return void */ diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index e4a62f5d..dbb54c4d 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -18,7 +18,6 @@ use Cake\Event\Event; use Cake\ORM\Association; use Cake\ORM\Behavior; -use Cake\ORM\Entity; /** * CounterCache behavior @@ -199,12 +198,12 @@ protected function _processAssociations(Event $event, EntityInterface $entity) * Updates counter cache for a single association * * @param \Cake\Event\Event $event Event instance. - * @param \Cake\ORM\Entity $entity Entity + * @param \Cake\Datasource\EntityInterface $entity Entity * @param \Cake\ORM\Association $assoc The association object * @param array $settings The settings for for counter cache for this association * @return void */ - protected function _processAssociation(Event $event, Entity $entity, Association $assoc, array $settings) + protected function _processAssociation(Event $event, EntityInterface $entity, Association $assoc, array $settings) { $foreignKeys = (array)$assoc->getForeignKey(); $primaryKeys = (array)$assoc->getBindingKey(); diff --git a/Marshaller.php b/Marshaller.php index 646aa86e..e4a18943 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -159,7 +159,7 @@ protected function _buildPropertyMap($data, $options) * * @param array $data The data to hydrate. * @param array $options List of options - * @return \Cake\ORM\Entity + * @return \Cake\Datasource\EntityInterface * @see \Cake\ORM\Table::newEntity() * @see \Cake\ORM\Entity::$_accessible */ @@ -169,7 +169,7 @@ public function one(array $data, array $options = []) $primaryKey = (array)$this->_table->getPrimaryKey(); $entityClass = $this->_table->getEntityClass(); - /* @var \Cake\ORM\Entity $entity */ + /* @var \Cake\Datasource\EntityInterface $entity */ $entity = new $entityClass(); $entity->setSource($this->_table->getRegistryAlias()); From 32a20570fb952fe36be3786b7db202d0bd005309 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 19 Aug 2017 20:05:23 +0530 Subject: [PATCH 1024/2059] Fix issues reported by phpstan in Database package. --- EagerLoader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EagerLoader.php b/EagerLoader.php index d65848a9..38330b40 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -813,7 +813,7 @@ protected function _collectKeys($external, $query, $statement) * Helper function used to iterate a statement and extract the columns * defined in $collectKeys * - * @param \Cake\Database\StatementInterface $statement The statement to read from. + * @param \Cake\Database\BufferedStatement $statement The statement to read from. * @param array $collectKeys The keys to collect * @return array */ From dee5fbb41f7caf714e7f722382a86f71593eb826 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 20 Aug 2017 11:55:28 +0530 Subject: [PATCH 1025/2059] Use where() instead of andWhere(). This avoid having to add annotation for andWhere() to QueryInterface. --- Association.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association.php b/Association.php index 15cdc772..8de4c611 100644 --- a/Association.php +++ b/Association.php @@ -956,7 +956,7 @@ protected function _appendNotMatching($query, $options) $target = $this->_targetTable; if (!empty($options['negateMatch'])) { $primaryKey = $query->aliasFields((array)$target->getPrimaryKey(), $this->_name); - $query->andWhere(function ($exp) use ($primaryKey) { + $query->where(function ($exp) use ($primaryKey) { array_map([$exp, 'isNull'], $primaryKey); return $exp; From f0be454b6b71c292c5f2f25579968f8a1c831799 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 21 Aug 2017 04:06:45 +0530 Subject: [PATCH 1026/2059] Fix errors reported by phpstan in ORM package --- Association/BelongsToMany.php | 2 +- EagerLoader.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index b514d2d1..1ca474a7 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -510,7 +510,7 @@ protected function _appendNotMatching($query, $options) $subquery = $this->_appendJunctionJoin($subquery, $conditions); $query - ->andWhere(function ($exp) use ($subquery, $conds) { + ->where(function ($exp) use ($subquery, $conds) { $identifiers = []; foreach (array_keys($conds) as $field) { $identifiers = new IdentifierExpression($field); diff --git a/EagerLoader.php b/EagerLoader.php index 38330b40..37bf9fd5 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -813,7 +813,7 @@ protected function _collectKeys($external, $query, $statement) * Helper function used to iterate a statement and extract the columns * defined in $collectKeys * - * @param \Cake\Database\BufferedStatement $statement The statement to read from. + * @param \Cake\Database\Statement\BufferedStatement $statement The statement to read from. * @param array $collectKeys The keys to collect * @return array */ From c270540597a6cde70b5d97235d942f301fca774b Mon Sep 17 00:00:00 2001 From: chinpei215 Date: Mon, 21 Aug 2017 13:46:24 +0900 Subject: [PATCH 1027/2059] Fix notMatching for BelongsToMany with composite keys --- Association/BelongsToMany.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index b514d2d1..6a9e0744 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -513,7 +513,7 @@ protected function _appendNotMatching($query, $options) ->andWhere(function ($exp) use ($subquery, $conds) { $identifiers = []; foreach (array_keys($conds) as $field) { - $identifiers = new IdentifierExpression($field); + $identifiers[] = new IdentifierExpression($field); } $identifiers = $subquery->newExpr()->add($identifiers)->setConjunction(','); $nullExp = clone $exp; From 5b290bc0394ff515627c5d0db5fd2935bee1411f Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 22 Aug 2017 07:00:58 +0530 Subject: [PATCH 1028/2059] Revert back "andWhere()" calls. --- Association.php | 2 +- Association/BelongsToMany.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Association.php b/Association.php index 8de4c611..15cdc772 100644 --- a/Association.php +++ b/Association.php @@ -956,7 +956,7 @@ protected function _appendNotMatching($query, $options) $target = $this->_targetTable; if (!empty($options['negateMatch'])) { $primaryKey = $query->aliasFields((array)$target->getPrimaryKey(), $this->_name); - $query->where(function ($exp) use ($primaryKey) { + $query->andWhere(function ($exp) use ($primaryKey) { array_map([$exp, 'isNull'], $primaryKey); return $exp; diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 1ca474a7..b514d2d1 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -510,7 +510,7 @@ protected function _appendNotMatching($query, $options) $subquery = $this->_appendJunctionJoin($subquery, $conditions); $query - ->where(function ($exp) use ($subquery, $conds) { + ->andWhere(function ($exp) use ($subquery, $conds) { $identifiers = []; foreach (array_keys($conds) as $field) { $identifiers = new IdentifierExpression($field); From 2577ba5d993befd6a7518e5480f0cbbf728f3568 Mon Sep 17 00:00:00 2001 From: mscherer Date: Fri, 25 Aug 2017 10:15:46 +0000 Subject: [PATCH 1029/2059] Add iterator annotation --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index ca891a23..30395175 100644 --- a/Table.php +++ b/Table.php @@ -885,7 +885,7 @@ public function association($name) /** * Get the associations collection for this table. * - * @return \Cake\ORM\AssociationCollection The collection of association objects. + * @return \Cake\ORM\AssociationCollection|\Cake\ORM\Association[] The collection of association objects. */ public function associations() { From 7304e680a6bf052daa78cb2cb7d54e87fa6f8554 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 29 Aug 2017 00:13:17 -0400 Subject: [PATCH 1030/2059] Update documentation to be clearer on how marshalling works Document that setters will not be triggered, and fields will not be marked dirty if the value is an identical scalar and an entity is updated. --- Table.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Table.php b/Table.php index 30395175..dd35d789 100644 --- a/Table.php +++ b/Table.php @@ -2490,6 +2490,11 @@ public function newEntities(array $data, array $options = []) * * You can use the `Model.beforeMarshal` event to modify request data * before it is converted into entities. + * + * When patching scalar values (null/booleans/string/integer/float), if the property + * presently has an identical value, the setter will not be called, and the + * property will not be marked as dirty. This is an optimization to prevent unnecessary field + * updates when persisting entities. */ public function patchEntity(EntityInterface $entity, array $data, array $options = []) { From 5c3363ae1608223fe24fe950bfc5b6a9a93ab00e Mon Sep 17 00:00:00 2001 From: Mark Story Date: Wed, 13 Sep 2017 21:30:41 -0400 Subject: [PATCH 1031/2059] Alias fields when finding dependent records to delete. Prevent ambiguous column issues by aliasing fields used in conditions. Refs #11170 --- Association/DependentDeleteHelper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/DependentDeleteHelper.php b/Association/DependentDeleteHelper.php index 4f834e5c..5bd74aff 100644 --- a/Association/DependentDeleteHelper.php +++ b/Association/DependentDeleteHelper.php @@ -41,7 +41,7 @@ public function cascadeDelete(Association $association, EntityInterface $entity, return true; } $table = $association->getTarget(); - $foreignKey = (array)$association->getForeignKey(); + $foreignKey = array_map([$association, 'aliasField'], (array)$association->getForeignKey()); $bindingKey = (array)$association->getBindingKey(); $conditions = array_combine($foreignKey, $entity->extract($bindingKey)); From bdd980ea6cb5b5032faf53f6c2624c5e84dc6186 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Tue, 12 Sep 2017 09:39:39 +0200 Subject: [PATCH 1032/2059] Pass the locator to AssociationCollection. --- AssociationCollection.php | 2 ++ Locator/TableLocator.php | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/AssociationCollection.php b/AssociationCollection.php index ee6618fe..3d3bd38d 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -16,6 +16,7 @@ use ArrayIterator; use Cake\Datasource\EntityInterface; +use Cake\ORM\Locator\LocatorAwareTrait; use InvalidArgumentException; use IteratorAggregate; @@ -29,6 +30,7 @@ class AssociationCollection implements IteratorAggregate { use AssociationsNormalizerTrait; + use LocatorAwareTrait; /** * Stored associations diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 36ab5cda..cb766a44 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -16,6 +16,7 @@ use Cake\Core\App; use Cake\Datasource\ConnectionManager; +use Cake\ORM\AssociationCollection; use Cake\ORM\Table; use Cake\Utility\Inflector; use RuntimeException; @@ -211,6 +212,11 @@ public function get($alias, array $options = []) } $options['connection'] = ConnectionManager::get($connectionName); } + if (empty($options['associations'])) { + $associations = new AssociationCollection(); + $associations->setTableLocator($this); + $options['associations'] = $associations; + } $options['registryAlias'] = $alias; $this->_instances[$alias] = $this->_create($options); From 2c4a6cd1e72181b4ee3a66f44b883d3d96a9dae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Tue, 12 Sep 2017 09:48:01 +0200 Subject: [PATCH 1033/2059] Add `AssociationCollection::load` method. --- AssociationCollection.php | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/AssociationCollection.php b/AssociationCollection.php index 3d3bd38d..02d2822a 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -56,6 +56,30 @@ public function add($alias, Association $association) return $this->_items[strtolower($alias)] = $association; } + /** + * Creates and adds the Association object to this collection. + * + * @param string $className The name of association class. + * @param string $associated The alias for the target table. + * @param array $options List of options to configure the association definition. + * @return \Cake\ORM\Association + * @throws InvalidArgumentException + */ + public function load($className, $associated, array $options = []) + { + $options += [ + 'tableLocator' => $this->getTableLocator() + ]; + + $association = new $className($associated, $options); + if (!$association instanceof Association) { + $message = sprintf('The association must extend `%s` class, `%s` given.', Association::class, get_class($association)); + throw new InvalidArgumentException($message); + } + + return $this->add($association->getName(), $association); + } + /** * Fetch an attached association by name. * From e3179d60058eb346c31bf0b7c85b35aef517abe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Tue, 12 Sep 2017 09:51:31 +0200 Subject: [PATCH 1034/2059] Use `AssociationCollection::load()` to load association. --- Table.php | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/Table.php b/Table.php index dd35d789..3a2b1671 100644 --- a/Table.php +++ b/Table.php @@ -967,9 +967,8 @@ public function addAssociations(array $params) public function belongsTo($associated, array $options = []) { $options += ['sourceTable' => $this]; - $association = new BelongsTo($associated, $options); - return $this->_associations->add($association->getName(), $association); + return $this->_associations->load(BelongsTo::class, $associated, $options); } /** @@ -1011,9 +1010,8 @@ public function belongsTo($associated, array $options = []) public function hasOne($associated, array $options = []) { $options += ['sourceTable' => $this]; - $association = new HasOne($associated, $options); - return $this->_associations->add($association->getName(), $association); + return $this->_associations->load(HasOne::class, $associated, $options); } /** @@ -1061,9 +1059,8 @@ public function hasOne($associated, array $options = []) public function hasMany($associated, array $options = []) { $options += ['sourceTable' => $this]; - $association = new HasMany($associated, $options); - return $this->_associations->add($association->getName(), $association); + return $this->_associations->load(HasMany::class, $associated, $options); } /** @@ -1113,9 +1110,8 @@ public function hasMany($associated, array $options = []) public function belongsToMany($associated, array $options = []) { $options += ['sourceTable' => $this]; - $association = new BelongsToMany($associated, $options); - return $this->_associations->add($association->getName(), $association); + return $this->_associations->load(BelongsToMany::class, $associated, $options); } /** From b5fb06ca9671e2a4ad9ec9185929b89fca5caadf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Wed, 13 Sep 2017 15:11:54 +0200 Subject: [PATCH 1035/2059] Pass the locator to the constructor. --- AssociationCollection.php | 16 ++++++++++++++++ Locator/TableLocator.php | 3 +-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/AssociationCollection.php b/AssociationCollection.php index 02d2822a..7f9988f8 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -17,6 +17,7 @@ use ArrayIterator; use Cake\Datasource\EntityInterface; use Cake\ORM\Locator\LocatorAwareTrait; +use Cake\ORM\Locator\LocatorInterface; use InvalidArgumentException; use IteratorAggregate; @@ -39,6 +40,21 @@ class AssociationCollection implements IteratorAggregate */ protected $_items = []; + /** + * Constructor. + * + * Sets the default table locator for associations. + * If no locator is provided, the global one will be used. + * + * @param \Cake\ORM\Locator\LocatorInterface|null $tableLocator Table locator instance. + */ + public function __construct(LocatorInterface $tableLocator = null) + { + if ($tableLocator !== null) { + $this->_tableLocator = $tableLocator; + } + } + /** * Add an association to the collection * diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index cb766a44..578b241f 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -213,8 +213,7 @@ public function get($alias, array $options = []) $options['connection'] = ConnectionManager::get($connectionName); } if (empty($options['associations'])) { - $associations = new AssociationCollection(); - $associations->setTableLocator($this); + $associations = new AssociationCollection($this); $options['associations'] = $associations; } From efcc2dfab6a2db48040b85814dbeee645e8656e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Fri, 21 Jul 2017 12:54:37 +0200 Subject: [PATCH 1036/2059] Deprecate TableRegistry in favor of TableLocator. Add getInstance()/setInstance() methods for global TableLocator access. --- Locator/TableLocator.php | 32 ++++++++++++++++++++++++++++++++ TableRegistry.php | 11 +++++------ 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 36ab5cda..a6574542 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -55,6 +55,13 @@ class TableLocator implements LocatorInterface */ protected $_options = []; + /** + * LocatorInterface implementation instance. + * + * @var \Cake\ORM\Locator\LocatorInterface + */ + protected static $_instance; + /** * Stores a list of options to be used when instantiating an object * with a matching alias. @@ -299,4 +306,29 @@ public function remove($alias) $this->_fallbacked[$alias] ); } + + /** + * Returns a singleton instance of LocatorInterface implementation. + * + * @return \Cake\ORM\Locator\LocatorInterface + */ + public static function getInstance() + { + if (!static::$_instance) { + static::$_instance = new static(); + } + + return static::$_instance; + } + + /** + * Sets singleton instance of LocatorInterface implementation. + * + * @param \Cake\ORM\Locator\LocatorInterface|null $tableLocator Instance of a locator to use. + * @return void + */ + public static function setInstance(LocatorInterface $tableLocator) + { + static::$_instance = $tableLocator; + } } diff --git a/TableRegistry.php b/TableRegistry.php index 217de563..52cc3b58 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -15,6 +15,7 @@ namespace Cake\ORM; use Cake\ORM\Locator\LocatorInterface; +use Cake\ORM\Locator\TableLocator; /** * Provides a registry/factory for Table objects. @@ -46,6 +47,8 @@ * ``` * $table = TableRegistry::get('Users', $config); * ``` + * + * @deprecated 3.5.0 Use \Cake\ORM\Locator\TableLocator instead. */ class TableRegistry { @@ -87,11 +90,7 @@ public static function locator(LocatorInterface $locator = null) */ public static function getTableLocator() { - if (!static::$_locator) { - static::$_locator = new static::$_defaultLocatorClass(); - } - - return static::$_locator; + return TableLocator::getInstance(); } /** @@ -102,7 +101,7 @@ public static function getTableLocator() */ public static function setTableLocator(LocatorInterface $tableLocator) { - static::$_locator = $tableLocator; + TableLocator::setInstance($tableLocator); } /** From b797389e4d21b8ebbbd4603b435933cfd6c461df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Fri, 21 Jul 2017 13:10:07 +0200 Subject: [PATCH 1037/2059] Remove TableRegistry calls from the core. --- Locator/LocatorAwareTrait.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index b857518c..da75990c 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -14,8 +14,6 @@ */ namespace Cake\ORM\Locator; -use Cake\ORM\TableRegistry; - /** * Contains method for setting and accessing LocatorInterface instance */ @@ -67,7 +65,7 @@ public function setTableLocator(LocatorInterface $tableLocator) public function getTableLocator() { if (!$this->_tableLocator) { - $this->_tableLocator = TableRegistry::getTableLocator(); + $this->_tableLocator = TableLocator::getInstance(); } return $this->_tableLocator; From 6af0bc82cc866c31def6e222e6d436dc37f547a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Fri, 21 Jul 2017 14:29:23 +0200 Subject: [PATCH 1038/2059] Remove TableRegistry mentions and examples. --- README.md | 10 +++++----- Table.php | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 25bfb856..bea6e97d 100644 --- a/README.md +++ b/README.md @@ -58,9 +58,9 @@ complete examples. Once you've defined some table classes you can read existing data in your tables: ```php -use Cake\ORM\TableRegistry; +use Cake\ORM\Locator\LocatorAwareTrait; -$articles = TableRegistry::get('Articles'); +$articles = $this->getTableLocator()->get('Articles'); foreach ($articles->find() as $article) { echo $article->title; } @@ -76,7 +76,7 @@ Table objects provide ways to convert request data into entities, and then persi those entities to the database: ```php -use Cake\ORM\TableRegistry; +use Cake\ORM\Locator\LocatorAwareTrait; $data = [ 'title' => 'My first article', @@ -91,7 +91,7 @@ $data = [ ] ]; -$articles = TableRegistry::get('Articles'); +$articles = $this->getTableLocator()->get('Articles'); $article = $articles->newEntity($data, [ 'associated' => ['Tags', 'Comments'] ]); @@ -109,7 +109,7 @@ for more in-depth examples. Once you have a reference to an entity, you can use it to delete data: ```php -$articles = TableRegistry::get('Articles'); +$articles = $this->getTableLocator()->get('Articles'); $article = $articles->get(2); $articles->delete($article); ``` diff --git a/Table.php b/Table.php index dd35d789..da3254f1 100644 --- a/Table.php +++ b/Table.php @@ -297,10 +297,10 @@ public function __construct(array $config = []) * Get the default connection name. * * This method is used to get the fallback connection name if an - * instance is created through the TableRegistry without a connection. + * instance is created through the TableLocator without a connection. * * @return string - * @see \Cake\ORM\TableRegistry::get() + * @see \Cake\ORM\Locator\TableLocator::get() */ public static function defaultConnectionName() { From a5803554e197a26b210d5ab1b3a09afe7a940096 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Thu, 21 Sep 2017 10:23:02 +0200 Subject: [PATCH 1039/2059] Depracation version update. --- TableRegistry.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TableRegistry.php b/TableRegistry.php index 52cc3b58..66352751 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -48,7 +48,7 @@ * $table = TableRegistry::get('Users', $config); * ``` * - * @deprecated 3.5.0 Use \Cake\ORM\Locator\TableLocator instead. + * @deprecated 3.6.0 Use \Cake\ORM\Locator\TableLocator instead. */ class TableRegistry { From 69e094d806f1f971fbbcc600af4461583e9d54cc Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 22 Sep 2017 00:24:14 +0530 Subject: [PATCH 1040/2059] Rename AssociationCollection::type() to AssociationCollection::getByType() Closes #11220 --- AssociationCollection.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/AssociationCollection.php b/AssociationCollection.php index ee6618fe..dda07c32 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -114,8 +114,22 @@ public function keys() * @param string|array $class The type of associations you want. * For example 'BelongsTo' or array like ['BelongsTo', 'HasOne'] * @return array An array of Association objects. + * @deprecated 3.6.0 Use getByType() instead. */ public function type($class) + { + return $this->getByType($class); + } + + /** + * Get an array of associations matching a specific type. + * + * @param string|array $class The type of associations you want. + * For example 'BelongsTo' or array like ['BelongsTo', 'HasOne'] + * @return array An array of Association objects. + * @since 3.5.3 + */ + public function getByType($class) { $class = array_map('strtolower', (array)$class); From 0a4a8bedca7d2b7762484fe473e581082752546f Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 22 Sep 2017 03:46:43 +0530 Subject: [PATCH 1041/2059] Update version in deprecated tag. --- AssociationCollection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AssociationCollection.php b/AssociationCollection.php index dda07c32..4f063c27 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -114,7 +114,7 @@ public function keys() * @param string|array $class The type of associations you want. * For example 'BelongsTo' or array like ['BelongsTo', 'HasOne'] * @return array An array of Association objects. - * @deprecated 3.6.0 Use getByType() instead. + * @deprecated 3.5.3 Use getByType() instead. */ public function type($class) { From 47263a8887fb98a6c4f166485559a9ba9a85849d Mon Sep 17 00:00:00 2001 From: dereuromark Date: Mon, 25 Sep 2017 20:55:11 +0200 Subject: [PATCH 1042/2059] Improve doc blocks for IDE understanding. --- Association.php | 1 + Behavior.php | 1 + Behavior/TranslateBehavior.php | 21 +++++++++++++-------- Table.php | 2 ++ 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/Association.php b/Association.php index 15cdc772..d93247f9 100644 --- a/Association.php +++ b/Association.php @@ -1191,6 +1191,7 @@ protected function _formatAssociationResults($query, $surrogate, $options) $extracted = new ResultSetDecorator($callable($extracted)); } + /* @var \Cake\Collection\CollectionInterface $results */ return $results->insert($property, $extracted); }, Query::PREPEND); } diff --git a/Behavior.php b/Behavior.php index 13134d5a..7f434d77 100644 --- a/Behavior.php +++ b/Behavior.php @@ -109,6 +109,7 @@ * * @see \Cake\ORM\Table::addBehavior() * @see \Cake\Event\EventManager + * @mixin \Cake\Core\InstanceConfigTrait */ class Behavior implements EventListenerInterface { diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index e1440545..9f4c3667 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -213,17 +213,19 @@ public function beforeFind(Event $event, Query $query, $options) } $conditions = function ($field, $locale, $query, $select) { - return function ($q) use ($field, $locale, $query, $select) { - $q->where([$q->repository()->aliasField('locale') => $locale]); + return function ($query) use ($field, $locale, $query, $select) { + /* @var \Cake\Datasource\QueryInterface $query */ + $query->where([$query->repository()->aliasField('locale') => $locale]); + /* @var \Cake\ORM\Query $query */ if ($query->isAutoFieldsEnabled() || in_array($field, $select, true) || in_array($this->_table->aliasField($field), $select, true) ) { - $q->select(['id', 'content']); + $query->select(['id', 'content']); } - return $q; + return $query; }; }; @@ -381,12 +383,13 @@ public function buildMarshalMap($marshaller, $map, $options) return [ '_translations' => function ($value, $entity) use ($marshaller, $options) { + /* @var \Cake\Datasource\EntityInterface $entity */ $translations = $entity->get('_translations'); foreach ($this->_config['fields'] as $field) { $options['validate'] = $this->_config['validator']; $errors = []; if (!is_array($value)) { - return; + return null; } foreach ($value as $language => $fields) { if (!isset($translations[$language])) { @@ -477,12 +480,13 @@ public function findTranslations(Query $query, array $options) $targetAlias = $this->_translationTable->getAlias(); return $query - ->contain([$targetAlias => function ($q) use ($locales, $targetAlias) { + ->contain([$targetAlias => function ($query) use ($locales, $targetAlias) { if ($locales) { - $q->where(["$targetAlias.locale IN" => $locales]); + /* @var \Cake\Datasource\QueryInterface $query */ + $query->where(["$targetAlias.locale IN" => $locales]); } - return $q; + return $query; }]) ->formatResults([$this, 'groupTranslations'], $query::PREPEND); } @@ -545,6 +549,7 @@ protected function _rowMapper($results, $locale) $row['_locale'] = $locale; if ($hydrated) { + /** @var \Cake\Datasource\EntityInterface $row */ $row->clean(); } diff --git a/Table.php b/Table.php index dd35d789..43343490 100644 --- a/Table.php +++ b/Table.php @@ -1289,6 +1289,7 @@ public function findList(Query $query, array $options) ); return $query->formatResults(function ($results) use ($options) { + /* @var \Cake\Collection\CollectionInterface $results */ return $results->combine( $options['keyField'], $options['valueField'], @@ -1338,6 +1339,7 @@ public function findThreaded(Query $query, array $options) $options = $this->_setFieldMatchers($options, ['keyField', 'parentField']); return $query->formatResults(function ($results) use ($options) { + /* @var \Cake\Collection\CollectionInterface $results */ return $results->nest($options['keyField'], $options['parentField'], $options['nestingKey']); }); } From 085861bc9c7c53dccae998d0c404a1e2fcbfedfc Mon Sep 17 00:00:00 2001 From: dereuromark Date: Mon, 25 Sep 2017 20:58:57 +0200 Subject: [PATCH 1043/2059] Fix CS. --- Behavior/TranslateBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 9f4c3667..805abbf5 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -549,7 +549,7 @@ protected function _rowMapper($results, $locale) $row['_locale'] = $locale; if ($hydrated) { - /** @var \Cake\Datasource\EntityInterface $row */ + /* @var \Cake\Datasource\EntityInterface $row */ $row->clean(); } From 6ec658056a3c73672c86504fd7cb61c7ab2ad1b4 Mon Sep 17 00:00:00 2001 From: dereuromark Date: Mon, 25 Sep 2017 21:17:36 +0200 Subject: [PATCH 1044/2059] Fix callable checks. Add concrete for interface annotations as long as they are still used in parallel. --- Behavior/CounterCacheBehavior.php | 4 ++-- Query.php | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index dbb54c4d..87806a7c 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -227,7 +227,7 @@ protected function _processAssociation(Event $event, EntityInterface $entity, As continue; } - if (!is_string($config) && is_callable($config)) { + if (is_callable($config)) { $count = $config($event, $entity, $this->_table, false); } else { $count = $this->_getCount($config, $countConditions); @@ -236,7 +236,7 @@ protected function _processAssociation(Event $event, EntityInterface $entity, As $assoc->getTarget()->updateAll([$field => $count], $updateConditions); if (isset($updateOriginalConditions)) { - if (!is_string($config) && is_callable($config)) { + if (is_callable($config)) { $count = $config($event, $entity, $this->_table, true); } else { $count = $this->_getCount($config, $countOriginalConditions); diff --git a/Query.php b/Query.php index f682fa8e..5871271d 100644 --- a/Query.php +++ b/Query.php @@ -65,6 +65,7 @@ * with the first rows of the query and each of the items, then the second rows and so on. * @method \Cake\Collection\CollectionInterface chunk($size) Groups the results in arrays of $size rows each. * @method bool isEmpty() Returns true if this query found no results. + * @mixin \Cake\Datasource\QueryTrait */ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface { @@ -423,7 +424,7 @@ public function clearContain() * Used to recursively add contained association column types to * the query. * - * @param \Cake\ORM\Table $table The table instance to pluck associations from. + * @param \Cake\Datasource\RepositoryInterface $table The table instance to pluck associations from. * @param \Cake\Database\TypeMap $typeMap The typemap to check for columns in. * This typemap is indirectly mutated via Cake\ORM\Query::addDefaultTypes() * @param array $associations The nested tree of associations to walk. @@ -432,6 +433,7 @@ public function clearContain() protected function _addAssociationsToTypeMap($table, $typeMap, $associations) { foreach ($associations as $name => $nested) { + /* @var \Cake\ORM\Table $table */ $association = $table->association($name); if (!$association) { continue; @@ -998,6 +1000,7 @@ public function triggerBeforeFind() if (!$this->_beforeFindFired && $this->_type === 'select') { $table = $this->repository(); $this->_beforeFindFired = true; + /* @var \Cake\Event\EventDispatcherInterface $table */ $table->dispatchEvent('Model.beforeFind', [ $this, new ArrayObject($this->_options), From 1ff113a45a3fa33d3658eea3d91eb8cddd533fbd Mon Sep 17 00:00:00 2001 From: dereuromark Date: Mon, 25 Sep 2017 21:24:31 +0200 Subject: [PATCH 1045/2059] Add missing method to interface via annotations --- Behavior/TranslateBehavior.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 805abbf5..dce8e009 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -213,19 +213,19 @@ public function beforeFind(Event $event, Query $query, $options) } $conditions = function ($field, $locale, $query, $select) { - return function ($query) use ($field, $locale, $query, $select) { - /* @var \Cake\Datasource\QueryInterface $query */ - $query->where([$query->repository()->aliasField('locale') => $locale]); + return function ($q) use ($field, $locale, $query, $select) { + /* @var \Cake\Datasource\QueryInterface $q */ + $q->where([$q->repository()->aliasField('locale') => $locale]); /* @var \Cake\ORM\Query $query */ if ($query->isAutoFieldsEnabled() || in_array($field, $select, true) || in_array($this->_table->aliasField($field), $select, true) ) { - $query->select(['id', 'content']); + $q->select(['id', 'content']); } - return $query; + return $q; }; }; From 46f49d3fcd7bf71d14114817194cef6e7a6a28dc Mon Sep 17 00:00:00 2001 From: dereuromark Date: Mon, 25 Sep 2017 21:59:27 +0200 Subject: [PATCH 1046/2059] Fix phpstan --- Query.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Query.php b/Query.php index 5871271d..d071a6a2 100644 --- a/Query.php +++ b/Query.php @@ -424,7 +424,7 @@ public function clearContain() * Used to recursively add contained association column types to * the query. * - * @param \Cake\Datasource\RepositoryInterface $table The table instance to pluck associations from. + * @param \Cake\ORM\Table|\Cake\Datasource\RepositoryInterface $table The table instance to pluck associations from. * @param \Cake\Database\TypeMap $typeMap The typemap to check for columns in. * This typemap is indirectly mutated via Cake\ORM\Query::addDefaultTypes() * @param array $associations The nested tree of associations to walk. @@ -433,7 +433,6 @@ public function clearContain() protected function _addAssociationsToTypeMap($table, $typeMap, $associations) { foreach ($associations as $name => $nested) { - /* @var \Cake\ORM\Table $table */ $association = $table->association($name); if (!$association) { continue; From 0d7297f97cde056a32693a025fc73bf4c50e085a Mon Sep 17 00:00:00 2001 From: dereuromark Date: Mon, 25 Sep 2017 22:26:09 +0200 Subject: [PATCH 1047/2059] Use interface const instead of magic strings for LEFT/RIGHT/INNER join types. --- Association.php | 3 ++- Association/BelongsToMany.php | 5 +++-- Association/HasMany.php | 3 ++- Behavior/TranslateBehavior.php | 5 +++-- EagerLoader.php | 3 ++- Query.php | 6 +++--- 6 files changed, 15 insertions(+), 10 deletions(-) diff --git a/Association.php b/Association.php index 15cdc772..a9ae98a7 100644 --- a/Association.php +++ b/Association.php @@ -19,6 +19,7 @@ use Cake\Core\ConventionsTrait; use Cake\Database\Expression\IdentifierExpression; use Cake\Datasource\EntityInterface; +use Cake\Datasource\QueryInterface; use Cake\Datasource\ResultSetDecorator; use Cake\ORM\Locator\LocatorAwareTrait; use Cake\Utility\Inflector; @@ -158,7 +159,7 @@ abstract class Association * * @var string */ - protected $_joinType = 'LEFT'; + protected $_joinType = QueryInterface::JOIN_TYPE_LEFT; /** * The property name that should be filled with data from the target table diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 6a9e0744..b94be203 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -18,6 +18,7 @@ use Cake\Database\ExpressionInterface; use Cake\Database\Expression\IdentifierExpression; use Cake\Datasource\EntityInterface; +use Cake\Datasource\QueryInterface; use Cake\ORM\Association; use Cake\ORM\Association\Loader\SelectWithPivotLoader; use Cake\ORM\Query; @@ -55,7 +56,7 @@ class BelongsToMany extends Association * * @var string */ - protected $_joinType = 'INNER'; + protected $_joinType = QueryInterface::JOIN_TYPE_INNER; /** * The strategy name to be used to fetch associated records. @@ -1120,7 +1121,7 @@ protected function _appendJunctionJoin($query, $conditions) $name => [ 'table' => $this->junction()->getTable(), 'conditions' => $conditions, - 'type' => 'INNER' + 'type' => QueryInterface::JOIN_TYPE_INNER ] ]; diff --git a/Association/HasMany.php b/Association/HasMany.php index 236da021..c7d1b4d3 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -19,6 +19,7 @@ use Cake\Database\Expression\FieldInterface; use Cake\Database\Expression\QueryExpression; use Cake\Datasource\EntityInterface; +use Cake\Datasource\QueryInterface; use Cake\ORM\Association; use Cake\ORM\Association\DependentDeleteHelper; use Cake\ORM\Association\Loader\SelectLoader; @@ -47,7 +48,7 @@ class HasMany extends Association * * @var string */ - protected $_joinType = 'INNER'; + protected $_joinType = QueryInterface::JOIN_TYPE_INNER; /** * The strategy name to be used to fetch associated records. diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index e1440545..32f0580c 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -17,6 +17,7 @@ use ArrayObject; use Cake\Collection\Collection; use Cake\Datasource\EntityInterface; +use Cake\Datasource\QueryInterface; use Cake\Event\Event; use Cake\I18n\I18n; use Cake\ORM\Behavior; @@ -173,7 +174,7 @@ public function setupFieldAssociations($fields, $table, $model, $strategy) $this->_table->hasOne($name, [ 'targetTable' => $fieldTable, 'foreignKey' => 'foreign_key', - 'joinType' => $filter ? 'INNER' : 'LEFT', + 'joinType' => $filter ? QueryInterface::JOIN_TYPE_INNER : QueryInterface::JOIN_TYPE_LEFT, 'conditions' => $conditions, 'propertyName' => $field . '_translation' ]); @@ -246,7 +247,7 @@ public function beforeFind(Event $event, Query $query, $options) ); if ($changeFilter) { - $filter = $options['filterByCurrentLocale'] ? 'INNER' : 'LEFT'; + $filter = $options['filterByCurrentLocale'] ? QueryInterface::JOIN_TYPE_INNER : QueryInterface::JOIN_TYPE_LEFT; $contain[$name]['joinType'] = $filter; } } diff --git a/EagerLoader.php b/EagerLoader.php index 37bf9fd5..77f80736 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -16,6 +16,7 @@ use Cake\Database\Statement\BufferedStatement; use Cake\Database\Statement\CallbackStatement; +use Cake\Datasource\QueryInterface; use Closure; use InvalidArgumentException; @@ -238,7 +239,7 @@ public function setMatching($assoc, callable $builder = null, $options = []) } if (!isset($options['joinType'])) { - $options['joinType'] = 'INNER'; + $options['joinType'] = QueryInterface::JOIN_TYPE_INNER; } $assocs = explode('.', $assoc); diff --git a/Query.php b/Query.php index f682fa8e..9d42cbe4 100644 --- a/Query.php +++ b/Query.php @@ -573,7 +573,7 @@ public function leftJoinWith($assoc, callable $builder = null) { $result = $this->getEagerLoader() ->setMatching($assoc, $builder, [ - 'joinType' => 'LEFT', + 'joinType' => QueryInterface::JOIN_TYPE_LEFT, 'fields' => false ]) ->getMatching(); @@ -622,7 +622,7 @@ public function innerJoinWith($assoc, callable $builder = null) { $result = $this->getEagerLoader() ->setMatching($assoc, $builder, [ - 'joinType' => 'INNER', + 'joinType' => QueryInterface::JOIN_TYPE_INNER, 'fields' => false ]) ->getMatching(); @@ -686,7 +686,7 @@ public function notMatching($assoc, callable $builder = null) { $result = $this->getEagerLoader() ->setMatching($assoc, $builder, [ - 'joinType' => 'LEFT', + 'joinType' => QueryInterface::JOIN_TYPE_LEFT, 'fields' => false, 'negateMatch' => true ]) From 7c176ee72eb4cf9e8c2b27015ebb4fb2c7715c64 Mon Sep 17 00:00:00 2001 From: dereuromark Date: Mon, 25 Sep 2017 23:07:19 +0200 Subject: [PATCH 1048/2059] Fix string security issue for callable. --- Behavior/CounterCacheBehavior.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 87806a7c..39e064be 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -18,6 +18,7 @@ use Cake\Event\Event; use Cake\ORM\Association; use Cake\ORM\Behavior; +use RuntimeException; /** * CounterCache behavior @@ -202,6 +203,7 @@ protected function _processAssociations(Event $event, EntityInterface $entity) * @param \Cake\ORM\Association $assoc The association object * @param array $settings The settings for for counter cache for this association * @return void + * @throws \RuntimeException If invalid callable is passed. */ protected function _processAssociation(Event $event, EntityInterface $entity, Association $assoc, array $settings) { @@ -228,6 +230,9 @@ protected function _processAssociation(Event $event, EntityInterface $entity, As } if (is_callable($config)) { + if (is_string($config)) { + throw new RuntimeException('You must not use a string as callable.'); + } $count = $config($event, $entity, $this->_table, false); } else { $count = $this->_getCount($config, $countConditions); @@ -237,6 +242,9 @@ protected function _processAssociation(Event $event, EntityInterface $entity, As if (isset($updateOriginalConditions)) { if (is_callable($config)) { + if (is_string($config)) { + throw new RuntimeException('You must not use a string as callable.'); + } $count = $config($event, $entity, $this->_table, true); } else { $count = $this->_getCount($config, $countOriginalConditions); From 582cd48c59573309d22c1c02afb78a6c07ec04cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Tue, 26 Sep 2017 15:33:09 +0200 Subject: [PATCH 1049/2059] Revert TableLocator singleton. --- Locator/LocatorAwareTrait.php | 4 +++- Locator/TableLocator.php | 32 -------------------------------- TableRegistry.php | 17 ++++++++++++----- 3 files changed, 15 insertions(+), 38 deletions(-) diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index da75990c..b857518c 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -14,6 +14,8 @@ */ namespace Cake\ORM\Locator; +use Cake\ORM\TableRegistry; + /** * Contains method for setting and accessing LocatorInterface instance */ @@ -65,7 +67,7 @@ public function setTableLocator(LocatorInterface $tableLocator) public function getTableLocator() { if (!$this->_tableLocator) { - $this->_tableLocator = TableLocator::getInstance(); + $this->_tableLocator = TableRegistry::getTableLocator(); } return $this->_tableLocator; diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index a6574542..36ab5cda 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -55,13 +55,6 @@ class TableLocator implements LocatorInterface */ protected $_options = []; - /** - * LocatorInterface implementation instance. - * - * @var \Cake\ORM\Locator\LocatorInterface - */ - protected static $_instance; - /** * Stores a list of options to be used when instantiating an object * with a matching alias. @@ -306,29 +299,4 @@ public function remove($alias) $this->_fallbacked[$alias] ); } - - /** - * Returns a singleton instance of LocatorInterface implementation. - * - * @return \Cake\ORM\Locator\LocatorInterface - */ - public static function getInstance() - { - if (!static::$_instance) { - static::$_instance = new static(); - } - - return static::$_instance; - } - - /** - * Sets singleton instance of LocatorInterface implementation. - * - * @param \Cake\ORM\Locator\LocatorInterface|null $tableLocator Instance of a locator to use. - * @return void - */ - public static function setInstance(LocatorInterface $tableLocator) - { - static::$_instance = $tableLocator; - } } diff --git a/TableRegistry.php b/TableRegistry.php index 66352751..bba3ad9e 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -15,7 +15,6 @@ namespace Cake\ORM; use Cake\ORM\Locator\LocatorInterface; -use Cake\ORM\Locator\TableLocator; /** * Provides a registry/factory for Table objects. @@ -47,8 +46,6 @@ * ``` * $table = TableRegistry::get('Users', $config); * ``` - * - * @deprecated 3.6.0 Use \Cake\ORM\Locator\TableLocator instead. */ class TableRegistry { @@ -90,7 +87,11 @@ public static function locator(LocatorInterface $locator = null) */ public static function getTableLocator() { - return TableLocator::getInstance(); + if (!static::$_locator) { + static::$_locator = new static::$_defaultLocatorClass(); + } + + return static::$_locator; } /** @@ -101,7 +102,7 @@ public static function getTableLocator() */ public static function setTableLocator(LocatorInterface $tableLocator) { - TableLocator::setInstance($tableLocator); + static::$_locator = $tableLocator; } /** @@ -111,6 +112,7 @@ public static function setTableLocator(LocatorInterface $tableLocator) * @param string|null $alias Name of the alias * @param array|null $options list of options for the alias * @return array The config data. + * @deprecated 3.6.0 Use \Cake\ORM\Locator\TableLocator::config() instead. */ public static function config($alias = null, $options = null) { @@ -125,6 +127,7 @@ public static function config($alias = null, $options = null) * @param string $alias The alias name you want to get. * @param array $options The options you want to build the table with. * @return \Cake\ORM\Table + * @deprecated 3.6.0 Use \Cake\ORM\Locator\TableLocator::get() instead. */ public static function get($alias, array $options = []) { @@ -136,6 +139,7 @@ public static function get($alias, array $options = []) * * @param string $alias The alias to check for. * @return bool + * @deprecated 3.6.0 Use \Cake\ORM\Locator\TableLocator::exists() instead. */ public static function exists($alias) { @@ -148,6 +152,7 @@ public static function exists($alias) * @param string $alias The alias to set. * @param \Cake\ORM\Table $object The table to set. * @return \Cake\ORM\Table + * @deprecated 3.6.0 Use \Cake\ORM\Locator\TableLocator::set() instead. */ public static function set($alias, Table $object) { @@ -159,6 +164,7 @@ public static function set($alias, Table $object) * * @param string $alias The alias to remove. * @return void + * @deprecated 3.6.0 Use \Cake\ORM\Locator\TableLocator::remove() instead. */ public static function remove($alias) { @@ -169,6 +175,7 @@ public static function remove($alias) * Clears the registry of configuration and instances. * * @return void + * @deprecated 3.6.0 Use \Cake\ORM\Locator\TableLocator::clear() instead. */ public static function clear() { From 109617240da5fcc49ec13034fd878ff997b478a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sun, 8 Oct 2017 21:26:51 +0200 Subject: [PATCH 1050/2059] Adding get_var_type() to basics.php This will replace the recurring ternary checks on this. --- Marshaller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index e4a18943..07bf155f 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -247,7 +247,7 @@ protected function _validate($data, $options, $isNew) } if (!is_object($options['validate'])) { throw new RuntimeException( - sprintf('validate must be a boolean, a string or an object. Got %s.', gettype($options['validate'])) + sprintf('validate must be a boolean, a string or an object. Got %s.', get_var_type($options['validate'])) ); } From c242ec5c455c8cb9640501d61ab535cc4ae4691b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sun, 8 Oct 2017 21:37:33 +0200 Subject: [PATCH 1051/2059] Changing function name from underscore to camel case --- Marshaller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index 07bf155f..a0819c10 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -247,7 +247,7 @@ protected function _validate($data, $options, $isNew) } if (!is_object($options['validate'])) { throw new RuntimeException( - sprintf('validate must be a boolean, a string or an object. Got %s.', get_var_type($options['validate'])) + sprintf('validate must be a boolean, a string or an object. Got %s.', getVarType($options['validate'])) ); } From ec8054a4b4399a9c499c1fc56ae0b670c5fd5a4d Mon Sep 17 00:00:00 2001 From: Jon McAndrew Date: Mon, 9 Oct 2017 13:13:10 -0400 Subject: [PATCH 1052/2059] Fixed issue Multi-Tree Deleting Nodes outside the Scope GitHub Issue #11289 --- Behavior/TreeBehavior.php | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index faf9f36f..60af9b32 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -224,12 +224,16 @@ public function beforeDelete(Event $event, EntityInterface $entity) $diff = $right - $left + 1; if ($diff > 2) { - $this->_table->deleteAll(function ($exp) use ($config, $left, $right) { - /* @var \Cake\Database\Expression\QueryExpression $exp */ - return $exp - ->gte($config['leftField'], $left + 1) - ->lte($config['leftField'], $right - 1); - }); + /* @var \Cake\Database\Expression\QueryExpression $expression */ + $expression = $this->_scope($this->_table->find()) + ->where(function ($exp) use ($config, $left, $right) { + return $exp + ->gte($config['leftField'], $left + 1) + ->lte($config['leftField'], $right - 1); + }) + ->clause('where'); + + $this->_table->deleteAll($expression); } $this->_sync($diff, '-', "> {$right}"); From 3813d4cdf9eae1adfbe8faef861d68bea26883ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Mon, 9 Oct 2017 21:39:33 +0200 Subject: [PATCH 1053/2059] Renaming getVarType() to getTypeName() --- Marshaller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index a0819c10..3326baba 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -247,7 +247,7 @@ protected function _validate($data, $options, $isNew) } if (!is_object($options['validate'])) { throw new RuntimeException( - sprintf('validate must be a boolean, a string or an object. Got %s.', getVarType($options['validate'])) + sprintf('validate must be a boolean, a string or an object. Got %s.', getTypeName($options['validate'])) ); } From cab7bd2a2a5ec836497ca7bb65f061aa651d6c27 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 9 Oct 2017 22:22:45 -0400 Subject: [PATCH 1054/2059] Use a subquery to delete child records. Picking off the where clause is risky as the default find() method may contain associations or joins. --- Behavior/TreeBehavior.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 60af9b32..1cacae32 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -222,18 +222,19 @@ public function beforeDelete(Event $event, EntityInterface $entity) $left = $entity->get($config['left']); $right = $entity->get($config['right']); $diff = $right - $left + 1; + $primaryKey = $this->_getPrimaryKey(); if ($diff > 2) { /* @var \Cake\Database\Expression\QueryExpression $expression */ - $expression = $this->_scope($this->_table->find()) + $finder = $this->_scope($this->_table->find()) + ->select([$primaryKey], true) ->where(function ($exp) use ($config, $left, $right) { return $exp ->gte($config['leftField'], $left + 1) ->lte($config['leftField'], $right - 1); - }) - ->clause('where'); + }); - $this->_table->deleteAll($expression); + $this->_table->deleteAll([$primaryKey . ' IN' => $finder]); } $this->_sync($diff, '-', "> {$right}"); From 0b8ecc8e17a29ae40b23eb93e58adb3297cd5631 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 10 Oct 2017 21:57:04 -0400 Subject: [PATCH 1055/2059] Don't use a subquery for deletes. MySQL doesn't like deleting from a table that it is also selecting records from. Use lower level query APIs to get the desired results. --- Behavior/TreeBehavior.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 1cacae32..a35f8887 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -222,19 +222,18 @@ public function beforeDelete(Event $event, EntityInterface $entity) $left = $entity->get($config['left']); $right = $entity->get($config['right']); $diff = $right - $left + 1; - $primaryKey = $this->_getPrimaryKey(); if ($diff > 2) { - /* @var \Cake\Database\Expression\QueryExpression $expression */ - $finder = $this->_scope($this->_table->find()) - ->select([$primaryKey], true) + $query = $this->_scope($this->_table->query()) + ->delete() ->where(function ($exp) use ($config, $left, $right) { + /* @var \Cake\Database\Expression\QueryExpression $exp */ return $exp ->gte($config['leftField'], $left + 1) ->lte($config['leftField'], $right - 1); }); - - $this->_table->deleteAll([$primaryKey . ' IN' => $finder]); + $statement = $query->execute(); + $statement->closeCursor(); } $this->_sync($diff, '-', "> {$right}"); From b2ed231b338fe26057d4a7794ac48e5403922c0f Mon Sep 17 00:00:00 2001 From: Dustin Haggard Date: Wed, 25 Oct 2017 12:18:34 -0400 Subject: [PATCH 1056/2059] Added support for dot syntax on Table::association() Reference: #11366 --- Table.php | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index 6234a4fb..e2a7087c 100644 --- a/Table.php +++ b/Table.php @@ -872,14 +872,27 @@ public function hasBehavior($name) } /** - * Returns an association object configured for the specified alias if any + * Returns an association object configured for the specified alias if any. + * + * The name argument also supports dot syntax to access deeper associations. + * + * ``` + * $users = $this->association('Articles.Comments.Users'); + * ``` * * @param string $name the alias used for the association. * @return \Cake\ORM\Association|null Either the association or null. */ public function association($name) { - return $this->_associations->get($name); + list($name, $next) = array_pad(explode('.', $name, 2), 2, null); + $result = $this->_associations->get($name); + + if ($result !== null && $next !== null) { + $result = $result->getTarget()->association($next); + } + + return $result; } /** From 22a2312b59825bf0f867b9df7a163897d737d722 Mon Sep 17 00:00:00 2001 From: Dustin Haggard Date: Thu, 26 Oct 2017 11:38:43 -0400 Subject: [PATCH 1057/2059] Deprecated Table::association() --- Table.php | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/Table.php b/Table.php index e2a7087c..eb8ed351 100644 --- a/Table.php +++ b/Table.php @@ -871,28 +871,44 @@ public function hasBehavior($name) return $this->_behaviors->has($name); } + /** + * Returns an association object configured for the specified alias if any + * + * @deprecated 3.6.0 Use getAssociation() instead. + * @param string $name the alias used for the association. + * @return \Cake\ORM\Association|null Either the association or null. + */ + public function association($name) + { + return $this->getAssociation($name); + } + /** * Returns an association object configured for the specified alias if any. * * The name argument also supports dot syntax to access deeper associations. * * ``` - * $users = $this->association('Articles.Comments.Users'); + * $users = $this->getAssociation('Articles.Comments.Users'); * ``` * * @param string $name the alias used for the association. * @return \Cake\ORM\Association|null Either the association or null. */ - public function association($name) + public function getAssociation($name) { - list($name, $next) = array_pad(explode('.', $name, 2), 2, null); - $result = $this->_associations->get($name); + if (strpos($name, '.') !== false) { + list($name, $next) = array_pad(explode('.', $name, 2), 2, null); + $result = $this->_associations->get($name); + + if ($result !== null && $next !== null) { + $result = $result->getTarget()->association($next); + } - if ($result !== null && $next !== null) { - $result = $result->getTarget()->association($next); + return $result; } - return $result; + return $this->_associations->get($name); } /** From b10e597240cd7ab106902516e7e39391ca3b3727 Mon Sep 17 00:00:00 2001 From: Dustin Haggard Date: Fri, 27 Oct 2017 10:01:26 -0400 Subject: [PATCH 1058/2059] Return early if dot syntax is not being used. --- Table.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Table.php b/Table.php index eb8ed351..11fbca4b 100644 --- a/Table.php +++ b/Table.php @@ -897,18 +897,18 @@ public function association($name) */ public function getAssociation($name) { - if (strpos($name, '.') !== false) { - list($name, $next) = array_pad(explode('.', $name, 2), 2, null); - $result = $this->_associations->get($name); + if (strpos($name, '.') === false) { + return $this->_associations->get($name); + } - if ($result !== null && $next !== null) { - $result = $result->getTarget()->association($next); - } + list($name, $next) = array_pad(explode('.', $name, 2), 2, null); + $result = $this->_associations->get($name); - return $result; + if ($result !== null && $next !== null) { + $result = $result->getTarget()->association($next); } - return $this->_associations->get($name); + return $result; } /** From c51ba3d305451b2f9e64aa94dd7eca88f5db1e71 Mon Sep 17 00:00:00 2001 From: Dustin Haggard Date: Fri, 27 Oct 2017 10:12:33 -0400 Subject: [PATCH 1059/2059] Remove call to deprecated association() method --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 11fbca4b..1eab9a1d 100644 --- a/Table.php +++ b/Table.php @@ -905,7 +905,7 @@ public function getAssociation($name) $result = $this->_associations->get($name); if ($result !== null && $next !== null) { - $result = $result->getTarget()->association($next); + $result = $result->getTarget()->getAssociation($next); } return $result; From d58f3d2ce4a738a0ae90d8fb040704b7aed1942e Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 27 Oct 2017 22:05:18 -0400 Subject: [PATCH 1060/2059] Fix other uses of deprecated database features. --- Association/Loader/SelectLoader.php | 2 +- Behavior/TreeBehavior.php | 2 +- LazyEagerLoader.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index e64ca361..30c20944 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -398,7 +398,7 @@ protected function _buildSubquery($query) $filterQuery->mapReduce(null, null, true); $filterQuery->formatResults(null, true); $filterQuery->contain([], true); - $filterQuery->valueBinder(new ValueBinder()); + $filterQuery->setValueBinder(new ValueBinder()); if (!$filterQuery->clause('limit')) { $filterQuery->limit(null); diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index a277c433..35701679 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -353,7 +353,7 @@ protected function _unmarkInternalTree() function ($exp) use ($config) { /* @var \Cake\Database\Expression\QueryExpression $exp */ $leftInverse = clone $exp; - $leftInverse->type('*')->add('-1'); + $leftInverse->setConjunction('*')->add('-1'); $rightInverse = clone $leftInverse; return $exp diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 48d3da19..3fc743b0 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -88,7 +88,7 @@ protected function _getQuery($objects, $contain, $source) return $exp->in($source->aliasField($primaryKey), $keys->toList()); } - $types = array_intersect_key($q->defaultTypes(), array_flip($primaryKey)); + $types = array_intersect_key($q->getDefaultTypes(), array_flip($primaryKey)); $primaryKey = array_map([$source, 'aliasField'], $primaryKey); return new TupleComparison($primaryKey, $keys->toList(), $types, 'IN'); From 638f83972294a7ed09eb9a73ae9ad9ed6eac499f Mon Sep 17 00:00:00 2001 From: Dustin Haggard Date: Fri, 3 Nov 2017 06:35:02 -0400 Subject: [PATCH 1061/2059] Trigger a deprecation warning --- Table.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Table.php b/Table.php index 1eab9a1d..98ffb9b9 100644 --- a/Table.php +++ b/Table.php @@ -880,6 +880,8 @@ public function hasBehavior($name) */ public function association($name) { + deprecationWarning('Use Table::getAssociation() instead.'); + return $this->getAssociation($name); } From 208cd5a08289ade8c77278fd607513ff66451177 Mon Sep 17 00:00:00 2001 From: Dustin Haggard Date: Tue, 7 Nov 2017 07:10:49 -0500 Subject: [PATCH 1062/2059] Updated instance of Table::association() Replaced all instances of Table::association() with Table::getAssociation() in the framework source and tests. --- Association/BelongsToMany.php | 44 +++++++++++++++---------------- Association/HasMany.php | 2 +- Behavior/CounterCacheBehavior.php | 4 +-- Behavior/TranslateBehavior.php | 2 +- EagerLoader.php | 2 +- Marshaller.php | 2 +- Query.php | 2 +- Rule/ExistsIn.php | 2 +- SaveOptionsBuilder.php | 2 +- 9 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index b94be203..3114e4dd 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -346,14 +346,14 @@ protected function _generateTargetAssociations($junction, $source, $target) $junctionAlias = $junction->getAlias(); $sAlias = $source->getAlias(); - if (!$target->association($junctionAlias)) { + if (!$target->getAssociation($junctionAlias)) { $target->hasMany($junctionAlias, [ 'targetTable' => $junction, 'foreignKey' => $this->getTargetForeignKey(), 'strategy' => $this->_strategy, ]); } - if (!$target->association($sAlias)) { + if (!$target->getAssociation($sAlias)) { $target->belongsToMany($sAlias, [ 'sourceTable' => $target, 'targetTable' => $source, @@ -383,7 +383,7 @@ protected function _generateTargetAssociations($junction, $source, $target) protected function _generateSourceAssociations($junction, $source) { $junctionAlias = $junction->getAlias(); - if (!$source->association($junctionAlias)) { + if (!$source->getAssociation($junctionAlias)) { $source->hasMany($junctionAlias, [ 'targetTable' => $junction, 'foreignKey' => $this->getForeignKey(), @@ -413,13 +413,13 @@ protected function _generateJunctionAssociations($junction, $source, $target) $tAlias = $target->getAlias(); $sAlias = $source->getAlias(); - if (!$junction->association($tAlias)) { + if (!$junction->getAssociation($tAlias)) { $junction->belongsTo($tAlias, [ 'foreignKey' => $this->getTargetForeignKey(), 'targetTable' => $target ]); } - if (!$junction->association($sAlias)) { + if (!$junction->getAssociation($sAlias)) { $junction->belongsTo($sAlias, [ 'foreignKey' => $this->getForeignKey(), 'targetTable' => $source @@ -453,7 +453,7 @@ public function attachTo(Query $query, array $options = []) } $junction = $this->junction(); - $belongsTo = $junction->association($this->getSource()->getAlias()); + $belongsTo = $junction->getAssociation($this->getSource()->getAlias()); $cond = $belongsTo->_joinCondition(['foreignKey' => $belongsTo->getForeignKey()]); $cond += $this->junctionConditions(); @@ -463,7 +463,7 @@ public function attachTo(Query $query, array $options = []) } // Attach the junction table as well we need it to populate _joinData. - $assoc = $this->_targetTable->association($junction->getAlias()); + $assoc = $this->_targetTable->getAssociation($junction->getAlias()); $newOptions = array_intersect_key($options, ['joinType' => 1, 'fields' => 1]); $newOptions += [ 'conditions' => $cond, @@ -492,7 +492,7 @@ protected function _appendNotMatching($query, $options) $options['conditions'] = []; } $junction = $this->junction(); - $belongsTo = $junction->association($this->getSource()->getAlias()); + $belongsTo = $junction->getAssociation($this->getSource()->getAlias()); $conds = $belongsTo->_joinCondition(['foreignKey' => $belongsTo->getForeignKey()]); $subquery = $this->find() @@ -504,7 +504,7 @@ protected function _appendNotMatching($query, $options) $subquery = $options['queryBuilder']($subquery); } - $assoc = $junction->association($this->getTarget()->getAlias()); + $assoc = $junction->getAssociation($this->getTarget()->getAlias()); $conditions = $assoc->_joinCondition([ 'foreignKey' => $this->getTargetForeignKey() ]); @@ -567,7 +567,7 @@ public function eagerLoader(array $options) 'sort' => $this->getSort(), 'junctionAssociationName' => $name, 'junctionProperty' => $this->_junctionProperty, - 'junctionAssoc' => $this->getTarget()->association($name), + 'junctionAssoc' => $this->getTarget()->getAssociation($name), 'junctionConditions' => $this->junctionConditions(), 'finder' => function () { return $this->_appendJunctionJoin($this->find(), []); @@ -598,7 +598,7 @@ public function cascadeDelete(EntityInterface $entity, array $options = []) } $table = $this->junction(); - $hasMany = $this->getSource()->association($table->getAlias()); + $hasMany = $this->getSource()->getAssociation($table->getAlias()); if ($this->_cascadeCallbacks) { foreach ($hasMany->find('all')->where($conditions)->all()->toList() as $related) { $table->delete($related, $options); @@ -806,7 +806,7 @@ protected function _saveLinks(EntityInterface $sourceEntity, $targetEntities, $o $target = $this->getTarget(); $junction = $this->junction(); $entityClass = $junction->getEntityClass(); - $belongsTo = $junction->association($target->getAlias()); + $belongsTo = $junction->getAssociation($target->getAlias()); $foreignKey = (array)$this->getForeignKey(); $assocForeignKey = (array)$belongsTo->getForeignKey(); $targetPrimaryKey = (array)$target->getPrimaryKey(); @@ -863,7 +863,7 @@ protected function _saveLinks(EntityInterface $sourceEntity, $targetEntities, $o * * ``` * $newTags = $tags->find('relevant')->toArray(); - * $articles->association('tags')->link($article, $newTags); + * $articles->getAssociation('tags')->link($article, $newTags); * ``` * * `$article->get('tags')` will contain all tags in `$newTags` after liking @@ -913,7 +913,7 @@ function () use ($sourceEntity, $targetEntities, $options) { * ``` * $article->tags = [$tag1, $tag2, $tag3, $tag4]; * $tags = [$tag1, $tag2, $tag3]; - * $articles->association('tags')->unlink($article, $tags); + * $articles->getAssociation('tags')->unlink($article, $tags); * ``` * * `$article->get('tags')` will contain only `[$tag4]` after deleting in the database @@ -1097,7 +1097,7 @@ public function find($type = null, array $options = []) return $query; } - $belongsTo = $this->junction()->association($this->getTarget()->getAlias()); + $belongsTo = $this->junction()->getAssociation($this->getTarget()->getAlias()); $conditions = $belongsTo->_joinCondition([ 'foreignKey' => $this->getTargetForeignKey() ]); @@ -1125,7 +1125,7 @@ protected function _appendJunctionJoin($query, $conditions) ] ]; - $assoc = $this->getTarget()->association($name); + $assoc = $this->getTarget()->getAssociation($name); $query ->addDefaultTypes($assoc->getTarget()) ->join($matching + $joins, [], true); @@ -1168,7 +1168,7 @@ protected function _appendJunctionJoin($query, $conditions) * $article->tags = [$tag1, $tag2, $tag3, $tag4]; * $articles->save($article); * $tags = [$tag1, $tag3]; - * $articles->association('tags')->replaceLinks($article, $tags); + * $articles->getAssociation('tags')->replaceLinks($article, $tags); * ``` * * `$article->get('tags')` will contain only `[$tag1, $tag3]` at the end @@ -1195,7 +1195,7 @@ public function replaceLinks(EntityInterface $sourceEntity, array $targetEntitie return $this->junction()->getConnection()->transactional( function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { $foreignKey = array_map([$this->_junctionTable, 'aliasField'], (array)$this->getForeignKey()); - $hasMany = $this->getSource()->association($this->_junctionTable->getAlias()); + $hasMany = $this->getSource()->getAssociation($this->_junctionTable->getAlias()); $existing = $hasMany->find('all') ->where(array_combine($foreignKey, $primaryValue)); @@ -1247,7 +1247,7 @@ protected function _diffLinks($existing, $jointEntities, $targetEntities, $optio { $junction = $this->junction(); $target = $this->getTarget(); - $belongsTo = $junction->association($target->getAlias()); + $belongsTo = $junction->getAssociation($target->getAlias()); $foreignKey = (array)$this->getForeignKey(); $assocForeignKey = (array)$belongsTo->getForeignKey(); @@ -1367,8 +1367,8 @@ protected function _collectJointEntities($sourceEntity, $targetEntities) return $result; } - $belongsTo = $junction->association($target->getAlias()); - $hasMany = $source->association($junction->getAlias()); + $belongsTo = $junction->getAssociation($target->getAlias()); + $hasMany = $source->getAssociation($junction->getAlias()); $foreignKey = (array)$this->getForeignKey(); $assocForeignKey = (array)$belongsTo->getForeignKey(); $sourceKey = $sourceEntity->extract((array)$source->getPrimaryKey()); @@ -1398,7 +1398,7 @@ protected function _junctionAssociationName() { if (!$this->_junctionAssociationName) { $this->_junctionAssociationName = $this->getTarget() - ->association($this->junction()->getAlias()) + ->getAssociation($this->junction()->getAlias()) ->getName(); } diff --git a/Association/HasMany.php b/Association/HasMany.php index c7d1b4d3..454a8034 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -424,7 +424,7 @@ function ($assoc) use ($targetEntities) { * $author->articles = [$article1, $article2, $article3, $article4]; * $authors->save($author); * $articles = [$article1, $article3]; - * $authors->association('articles')->replace($author, $articles); + * $authors->getAssociation('articles')->replace($author, $articles); * ``` * * `$author->get('articles')` will contain only `[$article1, $article3]` at the end diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 17abc550..2971b362 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -122,7 +122,7 @@ public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $o } foreach ($this->_config as $assoc => $settings) { - $assoc = $this->_table->association($assoc); + $assoc = $this->_table->getAssociation($assoc); foreach ($settings as $field => $config) { if (is_int($field)) { continue; @@ -191,7 +191,7 @@ public function afterDelete(Event $event, EntityInterface $entity, ArrayObject $ protected function _processAssociations(Event $event, EntityInterface $entity) { foreach ($this->_config as $assoc => $settings) { - $assoc = $this->_table->association($assoc); + $assoc = $this->_table->getAssociation($assoc); $this->_processAssociation($event, $entity, $assoc, $settings); } } diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index c61d4aec..eb00b216 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -696,7 +696,7 @@ protected function _unsetEmptyFields(EntityInterface $entity) */ protected function _findExistingTranslations($ruleSet) { - $association = $this->_table->association($this->_translationTable->getAlias()); + $association = $this->_table->getAssociation($this->_translationTable->getAlias()); $query = $association->find() ->select(['id', 'num' => 0]) diff --git a/EagerLoader.php b/EagerLoader.php index 77f80736..54dff151 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -508,7 +508,7 @@ public function externalAssociations(Table $repository) protected function _normalizeContain(Table $parent, $alias, $options, $paths) { $defaults = $this->_containOptions; - $instance = $parent->association($alias); + $instance = $parent->getAssociation($alias); if (!$instance) { throw new InvalidArgumentException( sprintf('%s is not associated with %s', $parent->getAlias(), $alias) diff --git a/Marshaller.php b/Marshaller.php index 3326baba..eabd4c78 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -88,7 +88,7 @@ protected function _buildPropertyMap($data, $options) $key = $nested; $nested = []; } - $assoc = $this->_table->association($key); + $assoc = $this->_table->getAssociation($key); // If the key is not a special field like _ids or _joinData // it is a missing association that we should error on. if (!$assoc) { diff --git a/Query.php b/Query.php index d592999d..61c502ee 100644 --- a/Query.php +++ b/Query.php @@ -433,7 +433,7 @@ public function clearContain() protected function _addAssociationsToTypeMap($table, $typeMap, $associations) { foreach ($associations as $name => $nested) { - $association = $table->association($name); + $association = $table->getAssociation($name); if (!$association) { continue; } diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 1eea45e5..bf0e4d37 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -80,7 +80,7 @@ public function __construct($fields, $repository, array $options = []) public function __invoke(EntityInterface $entity, array $options) { if (is_string($this->_repository)) { - $repository = $options['repository']->association($this->_repository); + $repository = $options['repository']->getAssociation($this->_repository); if (!$repository) { throw new RuntimeException(sprintf( "ExistsIn rule for '%s' is invalid. '%s' is not associated with '%s'.", diff --git a/SaveOptionsBuilder.php b/SaveOptionsBuilder.php index 97e1ed2f..f3ea0b95 100644 --- a/SaveOptionsBuilder.php +++ b/SaveOptionsBuilder.php @@ -107,7 +107,7 @@ protected function _associated(Table $table, array $associations) } $this->_checkAssociation($table, $key); if (isset($associated['associated'])) { - $this->_associated($table->association($key)->getTarget(), $associated['associated']); + $this->_associated($table->getAssociation($key)->getTarget(), $associated['associated']); continue; } } From 7a139f3bd2f05d415535494b39574a26a6ec919c Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 16 Nov 2017 16:15:56 -0500 Subject: [PATCH 1063/2059] Add deprecation warnings for BelongsToMany --- Association/BelongsToMany.php | 12 ++++++++++++ Association/DependentDeleteTrait.php | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 3114e4dd..c83191f6 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -194,6 +194,10 @@ public function getTargetForeignKey() */ public function targetForeignKey($key = null) { + deprecationWarning( + 'BelongToMany::targetForeignKey() is deprecated. ' . + 'Use setTargetForeignKey()/getTargetForeignKey() instead.' + ); if ($key !== null) { $this->setTargetForeignKey($key); } @@ -260,6 +264,10 @@ public function getSort() */ public function sort($sort = null) { + deprecationWarning( + 'BelongToMany::sort() is deprecated. ' . + 'Use setSort()/getSort() instead.' + ); if ($sort !== null) { $this->setSort($sort); } @@ -664,6 +672,10 @@ public function getSaveStrategy() */ public function saveStrategy($strategy = null) { + deprecationWarning( + 'BelongsToMany::saveStrategy() is deprecated. ' . + 'Use setSaveStrategy()/getSaveStrategy() instead.' + ); if ($strategy !== null) { $this->setSaveStrategy($strategy); } diff --git a/Association/DependentDeleteTrait.php b/Association/DependentDeleteTrait.php index f172d4c9..aab6048a 100644 --- a/Association/DependentDeleteTrait.php +++ b/Association/DependentDeleteTrait.php @@ -38,6 +38,10 @@ trait DependentDeleteTrait */ public function cascadeDelete(EntityInterface $entity, array $options = []) { + deprecationWarning( + 'The DependentDeleteTrait is deprecated. ' . + 'You should use Cake\ORM\Association\DependentDeleteHelper instead.' + ); $helper = new DependentDeleteHelper(); return $helper->cascadeDelete($this, $entity, $options); From 73a5da0e9cf5594fe42d892ff8997e9a7da9ed61 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 16 Nov 2017 16:17:39 -0500 Subject: [PATCH 1064/2059] Add deprecation warnings to HasMany --- Association/HasMany.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Association/HasMany.php b/Association/HasMany.php index 454a8034..4335327b 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -141,6 +141,10 @@ public function getSaveStrategy() */ public function saveStrategy($strategy = null) { + deprecationWarning( + 'HasMany::saveStrategy() is deprecated. ' . + 'Use setSaveStrategy()/getSaveStrategy() instead.' + ); if ($strategy !== null) { $this->setSaveStrategy($strategy); } @@ -630,6 +634,10 @@ public function getSort() */ public function sort($sort = null) { + deprecationWarning( + 'HasMany::sort() is deprecated. ' . + 'Use setSort()/getSort() instead.' + ); if ($sort !== null) { $this->setSort($sort); } From 58020333f25a7dae981e88fc7fcdd2d93d37291c Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 16 Nov 2017 16:39:07 -0500 Subject: [PATCH 1065/2059] Update deprecated method usage in ORM\Association. --- Association.php | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/Association.php b/Association.php index b2beab5f..9466a301 100644 --- a/Association.php +++ b/Association.php @@ -279,6 +279,10 @@ public function getName() */ public function name($name = null) { + deprecationWarning( + get_called_class() . '::name() is deprecated. ' . + 'Use setName()/getName() instead.' + ); if ($name !== null) { $this->setName($name); } @@ -319,6 +323,10 @@ public function getCascadeCallbacks() */ public function cascadeCallbacks($cascadeCallbacks = null) { + deprecationWarning( + get_called_class() . '::cascadeCallbacks() is deprecated. ' . + 'Use setCascadeCallbacks()/getCascadeCallbacks() instead.' + ); if ($cascadeCallbacks !== null) { $this->setCascadeCallbacks($cascadeCallbacks); } @@ -369,6 +377,10 @@ public function getSource() */ public function source(Table $table = null) { + deprecationWarning( + get_called_class() . '::source() is deprecated. ' . + 'Use setSource()/getSource() instead.' + ); if ($table === null) { return $this->_sourceTable; } @@ -445,6 +457,10 @@ public function getTarget() */ public function target(Table $table = null) { + deprecationWarning( + get_called_class() . '::target() is deprecated. ' . + 'Use setTarget()/getTarget() instead.' + ); if ($table !== null) { $this->setTarget($table); } @@ -490,6 +506,10 @@ public function getConditions() */ public function conditions($conditions = null) { + deprecationWarning( + get_called_class() . '::conditions() is deprecated. ' . + 'Use setConditions()/getConditions() instead.' + ); if ($conditions !== null) { $this->setConditions($conditions); } @@ -540,6 +560,10 @@ public function getBindingKey() */ public function bindingKey($key = null) { + deprecationWarning( + get_called_class() . '::bindingKey() is deprecated. ' . + 'Use setBindingKey()/getBindingKey() instead.' + ); if ($key !== null) { $this->setBindingKey($key); } @@ -580,6 +604,10 @@ public function setForeignKey($key) */ public function foreignKey($key = null) { + deprecationWarning( + get_called_class() . '::foreignKey() is deprecated. ' . + 'Use setForeignKey()/getForeignKey() instead.' + ); if ($key !== null) { $this->setForeignKey($key); } @@ -632,6 +660,10 @@ public function getDependent() */ public function dependent($dependent = null) { + deprecationWarning( + get_called_class() . '::dependent() is deprecated. ' . + 'Use setDependent()/getDependent() instead.' + ); if ($dependent !== null) { $this->setDependent($dependent); } @@ -685,6 +717,10 @@ public function getJoinType() */ public function joinType($type = null) { + deprecationWarning( + get_called_class() . '::joinType() is deprecated. ' . + 'Use setJoinType()/getJoinType() instead.' + ); if ($type !== null) { $this->setJoinType($type); } @@ -740,6 +776,10 @@ public function getProperty() */ public function property($name = null) { + deprecationWarning( + get_called_class() . '::property() is deprecated. ' . + 'Use setProperty()/getProperty() instead.' + ); if ($name !== null) { $this->setProperty($name); } @@ -805,6 +845,10 @@ public function getStrategy() */ public function strategy($name = null) { + deprecationWarning( + get_called_class() . '::strategy() is deprecated. ' . + 'Use setStrategy()/getStrategy() instead.' + ); if ($name !== null) { $this->setStrategy($name); } @@ -846,6 +890,10 @@ public function setFinder($finder) */ public function finder($finder = null) { + deprecationWarning( + get_called_class() . '::finder() is deprecated. ' . + 'Use setFinder()/getFinder() instead.' + ); if ($finder !== null) { $this->setFinder($finder); } From c4797e8ea81c169c846d5457c1ef8311ccb383d9 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 16 Nov 2017 17:02:33 -0500 Subject: [PATCH 1066/2059] Add dperecation warnings and fix tests in AssociationCollection --- AssociationCollection.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/AssociationCollection.php b/AssociationCollection.php index 44f8782f..ebc7c370 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -160,6 +160,10 @@ public function keys() */ public function type($class) { + deprecationWarning( + 'AssociationCollection::type() is deprecated. ' . + 'Use getByType() instead.' + ); return $this->getByType($class); } From 4897f1f3d76e8cf99f473deba503eec3c78b6094 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 16 Nov 2017 18:09:36 -0500 Subject: [PATCH 1067/2059] Add deprecation warnings to EagerLoader & EagerLoadable. --- EagerLoadable.php | 8 ++++++++ EagerLoader.php | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/EagerLoadable.php b/EagerLoadable.php index c9c4f612..05f71366 100644 --- a/EagerLoadable.php +++ b/EagerLoadable.php @@ -228,6 +228,10 @@ public function setCanBeJoined($possible) public function canBeJoined($possible = null) { if ($possible !== null) { + deprecationWarning( + 'Using EagerLoadable::canBeJoined() as a setter is deprecated. ' . + 'Use setCanBeJoined() instead.' + ); $this->setCanBeJoined($possible); } @@ -272,6 +276,10 @@ public function getConfig() */ public function config(array $config = null) { + deprecationWarning( + 'EagerLoadable::config() is deprecated. ' . + 'Use setConfig()/getConfig() instead.' + ); if ($config !== null) { $this->setConfig($config); } diff --git a/EagerLoader.php b/EagerLoader.php index 54dff151..ac26a517 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -208,6 +208,10 @@ public function isAutoFieldsEnabled() */ public function autoFields($enable = null) { + deprecationWarning( + 'EagerLoader::autoFields() is deprecated. ' . + 'Use enableAutoFields()/isAutoFieldsEnabled() instead.' + ); if ($enable !== null) { $this->enableAutoFields($enable); } @@ -295,6 +299,10 @@ public function getMatching() */ public function matching($assoc = null, callable $builder = null, $options = []) { + deprecationWarning( + 'EagerLoader::matching() is deprecated. ' . + 'Use setMatch()/getMatching() instead.' + ); if ($assoc !== null) { $this->setMatching($assoc, $builder, $options); } From f1df6ef2da38175784595d8293dadd654e16e63b Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 17 Nov 2017 08:05:26 -0500 Subject: [PATCH 1068/2059] Fix phpcs error. --- AssociationCollection.php | 1 + 1 file changed, 1 insertion(+) diff --git a/AssociationCollection.php b/AssociationCollection.php index ebc7c370..5098116c 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -164,6 +164,7 @@ public function type($class) 'AssociationCollection::type() is deprecated. ' . 'Use getByType() instead.' ); + return $this->getByType($class); } From 76252814e1e962a0f323e03ada0e3267471e709c Mon Sep 17 00:00:00 2001 From: mosaxiv Date: Sun, 19 Nov 2017 21:36:57 +0900 Subject: [PATCH 1069/2059] Updated PHPDoc --- Marshaller.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index e4a18943..d701e56c 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -338,7 +338,7 @@ protected function _marshalAssociation($assoc, $value, $options) * * @param array $data The data to hydrate. * @param array $options List of options - * @return array An array of hydrated records. + * @return \Cake\Datasource\EntityInterface[] An array of hydrated records. * @see \Cake\ORM\Table::newEntities() * @see \Cake\ORM\Entity::$_accessible */ @@ -361,7 +361,7 @@ public function many(array $data, array $options = []) * Builds the related entities and handles the special casing * for junction table entities. * - * @param \Cake\ORM\BelongsToMany $assoc The association to marshal. + * @param \Cake\ORM\Association\BelongsToMany $assoc The association to marshal. * @param array $data The data to convert into entities. * @param array $options List of options. * @return array An array of built entities. From 358c7a70f644f3ea9bf07d87a8c0dccadc3a38d1 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 19 Nov 2017 17:02:32 -0500 Subject: [PATCH 1070/2059] Add missing core dependencies and bump versions. Most packages contain deprecation warnings now, which mean they rely on cakephp/core. They will also need at 3.6.0 as that will be the first version with deprecationWarning() in it. --- composer.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index 30f6ecc0..f8e20729 100644 --- a/composer.json +++ b/composer.json @@ -25,12 +25,12 @@ "require": { "php": ">=5.6.0", "cakephp/collection": "^3.0.0", - "cakephp/core": "^3.0.0", + "cakephp/core": "^3.6.0", "cakephp/datasource": "^3.1.2", "cakephp/database": "^3.1.4", - "cakephp/event": "^3.0.0", - "cakephp/utility": "^3.0.0", - "cakephp/validation": "^3.0.0" + "cakephp/event": "^3.6.0", + "cakephp/utility": "^3.6.0", + "cakephp/validation": "^3.6.0" }, "suggest": { "cakephp/i18n": "If you are using Translate / Timestamp Behavior." From bf15e489cdb235011db3382da25c5fb5a0a09429 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 20 Nov 2017 00:25:22 -0500 Subject: [PATCH 1071/2059] Add deprecations for less frequently used ORM methods. I'll add warnings for the remaining table methods in a separate pull request as many tests fail with the additional warnings. --- Locator/LocatorAwareTrait.php | 4 ++++ Locator/TableLocator.php | 4 ++++ Marshaller.php | 6 ++++++ Query.php | 12 ++++++++++++ ResultSet.php | 3 +++ TableRegistry.php | 4 ++++ 6 files changed, 33 insertions(+) diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index b857518c..2d26e519 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -39,6 +39,10 @@ trait LocatorAwareTrait */ public function tableLocator(LocatorInterface $tableLocator = null) { + deprecationWarning( + get_called_class() . '::tableLocator() is deprecated. ' . + 'Use getTableLocator()/setTableLocator() instead.' + ); if ($tableLocator !== null) { $this->setTableLocator($tableLocator); } diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 578b241f..1d912216 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -119,6 +119,10 @@ public function getConfig($alias = null) */ public function config($alias = null, $options = null) { + deprecationWarning( + 'TableLocator::config() is deprecated. ' . + 'Use getConfig()/setConfig() instead.' + ); if ($alias !== null) { if (is_string($alias) && $options === null) { return $this->getConfig($alias); diff --git a/Marshaller.php b/Marshaller.php index eabd4c78..e2b037f3 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -266,6 +266,9 @@ protected function _prepareDataAndOptions($data, $options) $options += ['validate' => true]; if (!isset($options['fields']) && isset($options['fieldList'])) { + deprecationWarning( + 'The `fieldList` option for marshalling is deprecated. Use the `fields` option instead.' + ); $options['fields'] = $options['fieldList']; unset($options['fieldList']); } @@ -494,6 +497,9 @@ protected function _loadAssociatedByIds($assoc, $ids) */ protected function _loadBelongsToMany($assoc, $ids) { + deprecationWarning( + 'Marshaller::_loadBelongsToMany() is deprecated. Use _loadAssociatedByIds() instead.' + ); return $this->_loadAssociatedByIds($assoc, $ids); } diff --git a/Query.php b/Query.php index 61c502ee..cd934fb9 100644 --- a/Query.php +++ b/Query.php @@ -258,6 +258,10 @@ public function getEagerLoader() */ public function eagerLoader(EagerLoader $instance = null) { + deprecationWarning( + 'Query::eagerLoader() is deprecated. ' . + 'Use setEagerLoader()/getEagerLoader() instead.' + ); if ($instance !== null) { return $this->setEagerLoader($instance); } @@ -949,6 +953,10 @@ public function isHydrationEnabled() */ public function hydrate($enable = null) { + deprecationWarning( + 'Query::hydrate() is deprecated. ' . + 'Use enableHydration()/isHydrationEnabled() instead.' + ); if ($enable === null) { return $this->isHydrationEnabled(); } @@ -1279,6 +1287,10 @@ public function isAutoFieldsEnabled() */ public function autoFields($value = null) { + deprecationWarning( + 'Query::autoFields() is deprecated. ' . + 'Use enableAutoFields()/isAutoFieldsEnabled() instead.' + ); if ($value === null) { return $this->isAutoFieldsEnabled(); } diff --git a/ResultSet.php b/ResultSet.php index bbdf75e0..8708ba48 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -432,6 +432,7 @@ protected function _calculateColumnMap($query) */ protected function _calculateTypeMap() { + deprecationWarning('ResultSet::_calculateTypeMap() is deprecated, and will be removed in 4.0.0.'); } /** @@ -606,6 +607,8 @@ protected function _groupResult($row) */ protected function _castValues($alias, $values) { + deprecationWarning('ResultSet::_castValues() is deprecated, and will be removed in 4.0.0.'); + return $values; } diff --git a/TableRegistry.php b/TableRegistry.php index 217de563..a3bf4f5a 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -73,6 +73,10 @@ class TableRegistry */ public static function locator(LocatorInterface $locator = null) { + deprecationWarning( + 'TableRegistry::locator() is deprecated. ' . + 'Use setTableLocator()/getTableLocator() instead.' + ); if ($locator) { static::setTableLocator($locator); } From 60e9c81c68b07c4652be2c75312a352fb6f89052 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Mon, 20 Nov 2017 09:51:01 +0100 Subject: [PATCH 1072/2059] Add deprecation warnings to TableRegistry. --- TableRegistry.php | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/TableRegistry.php b/TableRegistry.php index bba3ad9e..b6b72041 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -73,6 +73,11 @@ class TableRegistry */ public static function locator(LocatorInterface $locator = null) { + deprecationWarning( + 'TableRegistry::locator() is deprecated. ' . + 'Use getTableLocator()/setTableLocator() instead.' + ); + if ($locator) { static::setTableLocator($locator); } @@ -116,6 +121,11 @@ public static function setTableLocator(LocatorInterface $tableLocator) */ public static function config($alias = null, $options = null) { + deprecationWarning( + 'TableRegistry::config() is deprecated. ' . + 'Use \Cake\ORM\Locator\TableLocator::config() instead.' + ); + return static::getTableLocator()->config($alias, $options); } @@ -131,6 +141,11 @@ public static function config($alias = null, $options = null) */ public static function get($alias, array $options = []) { + deprecationWarning( + 'TableRegistry::get() is deprecated. ' . + 'Use \Cake\ORM\Locator\TableLocator::get() instead.' + ); + return static::getTableLocator()->get($alias, $options); } @@ -143,6 +158,11 @@ public static function get($alias, array $options = []) */ public static function exists($alias) { + deprecationWarning( + 'TableRegistry::exists() is deprecated. ' . + 'Use \Cake\ORM\Locator\TableLocator::exists() instead.' + ); + return static::getTableLocator()->exists($alias); } @@ -156,6 +176,11 @@ public static function exists($alias) */ public static function set($alias, Table $object) { + deprecationWarning( + 'TableRegistry::set() is deprecated. ' . + 'Use \Cake\ORM\Locator\TableLocator::set() instead.' + ); + return static::getTableLocator()->set($alias, $object); } @@ -168,6 +193,11 @@ public static function set($alias, Table $object) */ public static function remove($alias) { + deprecationWarning( + 'TableRegistry::remove() is deprecated. ' . + 'Use \Cake\ORM\Locator\TableLocator::remove() instead.' + ); + static::getTableLocator()->remove($alias); } @@ -179,6 +209,11 @@ public static function remove($alias) */ public static function clear() { + deprecationWarning( + 'TableRegistry::clear() is deprecated. ' . + 'Use \Cake\ORM\Locator\TableLocator::clear() instead.' + ); + static::getTableLocator()->clear(); } @@ -191,6 +226,11 @@ public static function clear() */ public static function __callStatic($name, $arguments) { + deprecationWarning( + 'TableRegistry::' . $name . '() is deprecated. ' . + 'Use \Cake\ORM\Locator\TableLocator::' . $name . '() instead.' + ); + return static::getTableLocator()->$name(...$arguments); } } From 2008a5544620798a292c87c19d58f5d4f353041d Mon Sep 17 00:00:00 2001 From: mscherer Date: Mon, 20 Nov 2017 12:23:14 +0100 Subject: [PATCH 1073/2059] Follow up on #11439 regarding entity phpdoc typehinting. --- Marshaller.php | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index d701e56c..3cdf2541 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -106,6 +106,7 @@ protected function _buildPropertyMap($data, $options) } if (isset($options['isMerge'])) { $callback = function ($value, $entity) use ($assoc, $nested) { + /* @var \Cake\Datasource\EntityInterface $entity */ $options = $nested + ['associated' => [], 'association' => $assoc]; return $this->_mergeAssociation($entity->get($assoc->getProperty()), $assoc, $value, $options); @@ -364,7 +365,7 @@ public function many(array $data, array $options = []) * @param \Cake\ORM\Association\BelongsToMany $assoc The association to marshal. * @param array $data The data to convert into entities. * @param array $options List of options. - * @return array An array of built entities. + * @return \Cake\Datasource\EntityInterface[] An array of built entities. * @throws \BadMethodCallException * @throws \InvalidArgumentException * @throws \RuntimeException @@ -408,6 +409,7 @@ protected function _belongsToMany(BelongsToMany $assoc, array $data, $options = if (!empty($conditions)) { $query = $target->find(); $query->andWhere(function ($exp) use ($conditions) { + /* @var \Cake\Database\Expression\QueryExpression $exp */ return $exp->or_($conditions); }); @@ -458,7 +460,7 @@ protected function _belongsToMany(BelongsToMany $assoc, array $data, $options = * * @param \Cake\ORM\Association $assoc The association class for the belongsToMany association. * @param array $ids The list of ids to load. - * @return array An array of entities. + * @return \Cake\Datasource\EntityInterface[] An array of entities. */ protected function _loadAssociatedByIds($assoc, $ids) { @@ -489,7 +491,7 @@ protected function _loadAssociatedByIds($assoc, $ids) * * @param \Cake\ORM\Association $assoc The association class for the belongsToMany association. * @param array $ids The list of ids to load. - * @return array An array of entities. + * @return \Cake\Datasource\EntityInterface[] An array of entities. * @deprecated Use _loadAssociatedByIds() */ protected function _loadBelongsToMany($assoc, $ids) @@ -633,11 +635,11 @@ public function merge(EntityInterface $entity, array $data, array $options = []) * the accessible fields list in the entity will be used. * - accessibleFields: A list of fields to allow or deny in entity accessible fields. * - * @param array|\Traversable $entities the entities that will get the + * @param \Cake\Datasource\EntityInterface[]|\Traversable $entities the entities that will get the * data merged in * @param array $data list of arrays to be merged into the entities * @param array $options List of options. - * @return array + * @return \Cake\Datasource\EntityInterface[] * @see \Cake\ORM\Entity::$_accessible */ public function mergeMany($entities, array $data, array $options = []) @@ -684,6 +686,7 @@ public function mergeMany($entities, array $data, array $options = []) return count(array_filter($keys, 'strlen')) === count($primary); }) ->reduce(function ($query, $keys) use ($primary) { + /* @var \Cake\ORM\Query $query */ $fields = array_map([$this->_table, 'aliasField'], $primary); return $query->orWhere($query->newExpr()->and_(array_combine($fields, $keys))); @@ -748,7 +751,7 @@ protected function _mergeAssociation($original, $assoc, $value, $options) * @param \Cake\ORM\Association $assoc The association to marshall * @param array $value The data to hydrate * @param array $options List of options. - * @return array + * @return \Cake\Datasource\EntityInterface[] */ protected function _mergeBelongsToMany($original, $assoc, $value, $options) { @@ -778,7 +781,7 @@ protected function _mergeBelongsToMany($original, $assoc, $value, $options) * @param \Cake\ORM\Association\BelongsToMany $assoc The association to marshall * @param array $value The data to hydrate * @param array $options List of options. - * @return array An array of entities + * @return \Cake\Datasource\EntityInterface[] An array of entities */ protected function _mergeJoinData($original, $assoc, $value, $options) { From 91579313b1598794652fb6748096624a2ab16d52 Mon Sep 17 00:00:00 2001 From: mscherer Date: Mon, 20 Nov 2017 15:59:33 +0100 Subject: [PATCH 1074/2059] Fix more doc blocks. --- Marshaller.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 3cdf2541..82779123 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -290,7 +290,7 @@ protected function _prepareDataAndOptions($data, $options) * @param \Cake\ORM\Association $assoc The association to marshall * @param array $value The data to hydrate * @param array $options List of options. - * @return mixed + * @return \Cake\Datasource\EntityInterface|\Cake\Datasource\EntityInterface[]|null */ protected function _marshalAssociation($assoc, $value, $options) { @@ -715,11 +715,11 @@ public function mergeMany($entities, array $data, array $options = []) /** * Creates a new sub-marshaller and merges the associated data. * - * @param \Cake\Datasource\EntityInterface $original The original entity + * @param \Cake\Datasource\EntityInterface|\Cake\Datasource\EntityInterface[] $original The original entity * @param \Cake\ORM\Association $assoc The association to merge * @param array $value The data to hydrate * @param array $options List of options. - * @return mixed + * @return \Cake\Datasource\EntityInterface|\Cake\Datasource\EntityInterface[]|null */ protected function _mergeAssociation($original, $assoc, $value, $options) { From c8369e8d863a0fe7233d9584c54d9da86aa09f6b Mon Sep 17 00:00:00 2001 From: mscherer Date: Mon, 20 Nov 2017 19:37:01 +0100 Subject: [PATCH 1075/2059] Fix more doc blocks. --- Association/Loader/SelectLoader.php | 7 ++++--- Association/Loader/SelectWithPivotLoader.php | 6 +++--- ResultSet.php | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index e64ca361..d22e6c5d 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -163,6 +163,7 @@ protected function _buildQuery($options) $options['fields'] = []; } + /* @var \Cake\ORM\Query $query */ $query = $finder(); if (isset($options['finder'])) { list($finderName, $opts) = $this->_extractFinder($options['finder']); @@ -275,7 +276,7 @@ protected function _assertFieldsPresent($fetchQuery, $key) * filtering needs to be done using a subquery. * * @param \Cake\ORM\Query $query Target table's query - * @param string $key the fields that should be used for filtering + * @param string|array $key the fields that should be used for filtering * @param \Cake\ORM\Query $subquery The Subquery to use for filtering * @return \Cake\ORM\Query */ @@ -312,8 +313,8 @@ protected function _addFilteringJoin($query, $key, $subquery) * target table query given a filter key and some filtering values. * * @param \Cake\ORM\Query $query Target table's query - * @param string|array $key the fields that should be used for filtering - * @param mixed $filter the value that should be used to match for $key + * @param string|array $key The fields that should be used for filtering + * @param mixed $filter The value that should be used to match for $key * @return \Cake\ORM\Query */ protected function _addFilteringCondition($query, $key, $filter) diff --git a/Association/Loader/SelectWithPivotLoader.php b/Association/Loader/SelectWithPivotLoader.php index 5dd1918c..733a8c7f 100644 --- a/Association/Loader/SelectWithPivotLoader.php +++ b/Association/Loader/SelectWithPivotLoader.php @@ -48,7 +48,7 @@ class SelectWithPivotLoader extends SelectLoader /** * Custom conditions for the junction association * - * @var mixed + * @var string|array|\Cake\Database\ExpressionInterface|callable|null */ protected $junctionConditions; @@ -111,7 +111,7 @@ protected function _buildQuery($options) } $query - ->where($this->junctionConditions) + ->where($this->junctionConditions) ->select($joinFields); $query @@ -133,7 +133,7 @@ protected function _buildQuery($options) * which the filter should be applied * * @param array $options the options to use for getting the link field. - * @return string + * @return array|string */ protected function _linkField($options) { diff --git a/ResultSet.php b/ResultSet.php index bbdf75e0..6068c836 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -491,7 +491,7 @@ protected function _fetchResult() /** * Correctly nests results keys including those coming from associations * - * @param mixed $row Array containing columns and values or false if there is no results + * @param array $row Array containing columns and values or false if there is no results * @return array Results */ protected function _groupResult($row) From 20f5680a90a8b88bf4f6d4c6f9c63f81759f72c4 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 20 Nov 2017 20:33:02 -0500 Subject: [PATCH 1076/2059] PHPcs fixes. --- Marshaller.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Marshaller.php b/Marshaller.php index e2b037f3..2ab548e9 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -500,6 +500,7 @@ protected function _loadBelongsToMany($assoc, $ids) deprecationWarning( 'Marshaller::_loadBelongsToMany() is deprecated. Use _loadAssociatedByIds() instead.' ); + return $this->_loadAssociatedByIds($assoc, $ids); } From 18de21e8dc6bd7331abf0830506c05340790993e Mon Sep 17 00:00:00 2001 From: mscherer Date: Tue, 21 Nov 2017 16:31:55 +0100 Subject: [PATCH 1077/2059] More phpstan related fixes. --- Association.php | 2 +- Association/BelongsToMany.php | 4 +++- Table.php | 6 +++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Association.php b/Association.php index b2beab5f..865e6788 100644 --- a/Association.php +++ b/Association.php @@ -1059,7 +1059,7 @@ public function exists($conditions) * @param mixed $conditions Conditions to be used, accepts anything Query::where() * can take. * @see \Cake\ORM\Table::updateAll() - * @return bool Success Returns true if one or more rows are affected. + * @return int Count Returns the affected rows. */ public function updateAll($fields, $conditions) { diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index b94be203..14b2f1aa 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -609,7 +609,9 @@ public function cascadeDelete(EntityInterface $entity, array $options = []) $conditions = array_merge($conditions, $hasMany->getConditions()); - return $table->deleteAll($conditions); + $table->deleteAll($conditions); + + return true; } /** diff --git a/Table.php b/Table.php index 43343490..f4b17fb2 100644 --- a/Table.php +++ b/Table.php @@ -170,7 +170,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc /** * Connection instance * - * @var \Cake\Database\Connection + * @var \Cake\Database\Connection|\Cake\Datasource\ConnectionInterface */ protected $_connection; @@ -482,7 +482,7 @@ public function registryAlias($registryAlias = null) /** * Sets the connection instance. * - * @param \Cake\Datasource\ConnectionInterface $connection The connection instance + * @param \Cake\Database\Connection|\Cake\Datasource\ConnectionInterface $connection The connection instance * @return $this */ public function setConnection(ConnectionInterface $connection) @@ -495,7 +495,7 @@ public function setConnection(ConnectionInterface $connection) /** * Returns the connection instance. * - * @return \Cake\Database\Connection + * @return \Cake\Database\Connection|\Cake\Datasource\ConnectionInterface */ public function getConnection() { From eec8c5338e87b5838c721ca3e9f1f03ba58e88b4 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 20 Nov 2017 01:07:06 -0500 Subject: [PATCH 1078/2059] Add deprecation warnings for commonly used methods on Table. --- Table.php | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index 98ffb9b9..f16220cc 100644 --- a/Table.php +++ b/Table.php @@ -370,6 +370,10 @@ public function getTable() */ public function table($table = null) { + deprecationWarning( + get_called_class() . '::table() is deprecated. ' . + 'Use setTable()/getTable() instead.' + ); if ($table !== null) { $this->setTable($table); } @@ -412,6 +416,10 @@ public function getAlias() */ public function alias($alias = null) { + deprecationWarning( + get_called_class() . '::alias() is deprecated. ' . + 'Use setAlias()/getAlias() instead.' + ); if ($alias !== null) { $this->setAlias($alias); } @@ -472,6 +480,10 @@ public function getRegistryAlias() */ public function registryAlias($registryAlias = null) { + deprecationWarning( + get_called_class() . '::registryAlias() is deprecated. ' . + 'Use setRegistryAlias()/getRegistryAlias() instead.' + ); if ($registryAlias !== null) { $this->setRegistryAlias($registryAlias); } @@ -511,6 +523,10 @@ public function getConnection() */ public function connection(ConnectionInterface $connection = null) { + deprecationWarning( + get_called_class() . '::connection() is deprecated. ' . + 'Use setConnection()/getConnection() instead.' + ); if ($connection !== null) { $this->setConnection($connection); } @@ -582,6 +598,10 @@ public function setSchema($schema) */ public function schema($schema = null) { + deprecationWarning( + get_called_class() . '::schema() is deprecated. ' . + 'Use setSchema()/getSchema() instead.' + ); if ($schema !== null) { $this->setSchema($schema); } @@ -669,6 +689,10 @@ public function getPrimaryKey() */ public function primaryKey($key = null) { + deprecationWarning( + get_called_class() . '::primaryKey() is deprecated. ' . + 'Use setPrimaryKey()/getPrimaryKey() instead.' + ); if ($key !== null) { $this->setPrimaryKey($key); } @@ -720,6 +744,10 @@ public function getDisplayField() */ public function displayField($key = null) { + deprecationWarning( + get_called_class() . '::displayKey() is deprecated. ' . + 'Use setDisplayKey()/getDisplayKey() instead.' + ); if ($key !== null) { return $this->setDisplayField($key); } @@ -790,6 +818,10 @@ public function setEntityClass($name) */ public function entityClass($name = null) { + deprecationWarning( + get_called_class() . '::entityClass() is deprecated. ' . + 'Use setEntityClass()/getEntityClass() instead.' + ); if ($name !== null) { $this->setEntityClass($name); } @@ -1291,7 +1323,7 @@ public function findList(Query $query, array $options) if (isset($options['idField'])) { $options['keyField'] = $options['idField']; unset($options['idField']); - trigger_error('Option "idField" is deprecated, use "keyField" instead.', E_USER_DEPRECATED); + deprecationWarning('Option "idField" is deprecated, use "keyField" instead.'); } if (!$query->clause('select') && @@ -1360,7 +1392,7 @@ public function findThreaded(Query $query, array $options) if (isset($options['idField'])) { $options['keyField'] = $options['idField']; unset($options['idField']); - trigger_error('Option "idField" is deprecated, use "keyField" instead.', E_USER_DEPRECATED); + deprecationWarning('Option "idField" is deprecated, use "keyField" instead.'); } $options = $this->_setFieldMatchers($options, ['keyField', 'parentField']); From 8c40e39cdc00dc72710f80b3248ae46d7d10999d Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 20 Nov 2017 01:07:31 -0500 Subject: [PATCH 1079/2059] Fix use of deprecated methods. --- Query.php | 8 ++++---- ResultSet.php | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Query.php b/Query.php index cd934fb9..542da697 100644 --- a/Query.php +++ b/Query.php @@ -1068,7 +1068,7 @@ protected function _transformQuery() } if (empty($this->_parts['from'])) { - $this->from([$this->_repository->getAlias() => $this->_repository->table()]); + $this->from([$this->_repository->getAlias() => $this->_repository->getTable()]); } $this->_addDefaultFields(); $this->getEagerLoader()->attachAssociations($this, $this->_repository, !$this->_hasFields); @@ -1156,7 +1156,7 @@ protected function _dirty() */ public function update($table = null) { - $table = $table ?: $this->repository()->table(); + $table = $table ?: $this->repository()->getTable(); return parent::update($table); } @@ -1173,7 +1173,7 @@ public function update($table = null) public function delete($table = null) { $repo = $this->repository(); - $this->from([$repo->getAlias() => $repo->table()]); + $this->from([$repo->getAlias() => $repo->getTable()]); return parent::delete(); } @@ -1193,7 +1193,7 @@ public function delete($table = null) */ public function insert(array $columns, array $types = []) { - $table = $this->repository()->table(); + $table = $this->repository()->getTable(); $this->into($table); return parent::insert($columns, $types); diff --git a/ResultSet.php b/ResultSet.php index 8708ba48..d5e9f90b 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -585,7 +585,7 @@ protected function _groupResult($row) $results[$defaultAlias]['_matchingData'] = $results['_matchingData']; } - $options['source'] = $this->_defaultTable->registryAlias(); + $options['source'] = $this->_defaultTable->getRegistryAlias(); if (isset($results[$defaultAlias])) { $results = $results[$defaultAlias]; } From d88f4386c345995f5d3f844357a5cd4031cbcab8 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 21 Nov 2017 23:47:04 -0500 Subject: [PATCH 1080/2059] Don't use deprecated entity methods. --- Association/HasMany.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index 4335327b..4d2bdef1 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -247,7 +247,7 @@ protected function _saveTarget(array $foreignKeyReference, EntityInterface $pare } if (!empty($options['atomic'])) { - $original[$k]->errors($entity->errors()); + $original[$k]->setErrors($entity->getErrors()); $entity->set($this->getProperty(), $original); return false; From 7720b48d54e88e6ec527b8bb58c1ee8283184d9a Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 29 Nov 2017 19:57:28 +0530 Subject: [PATCH 1081/2059] Fix errors reported by phpstan. --- Association.php | 2 +- EagerLoadable.php | 2 +- Query.php | 2 +- TableRegistry.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Association.php b/Association.php index 865e6788..f9300a2f 100644 --- a/Association.php +++ b/Association.php @@ -1431,7 +1431,7 @@ abstract public function isOwningSide(Table $side); * the saving operation to the target table. * * @param \Cake\Datasource\EntityInterface $entity the data to be saved - * @param array|\ArrayObject $options The options for saving associated data. + * @param array $options The options for saving associated data. * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns * the saved entity * @see \Cake\ORM\Table::save() diff --git a/EagerLoadable.php b/EagerLoadable.php index c9c4f612..1e7a1d48 100644 --- a/EagerLoadable.php +++ b/EagerLoadable.php @@ -35,7 +35,7 @@ class EagerLoadable /** * A list of other associations to load from this level. * - * @var \Cake\Orm\EagerLoadable[] + * @var \Cake\ORM\EagerLoadable[] */ protected $_associations = []; diff --git a/Query.php b/Query.php index d592999d..47cb2c78 100644 --- a/Query.php +++ b/Query.php @@ -424,7 +424,7 @@ public function clearContain() * Used to recursively add contained association column types to * the query. * - * @param \Cake\ORM\Table|\Cake\Datasource\RepositoryInterface $table The table instance to pluck associations from. + * @param \Cake\ORM\Table $table The table instance to pluck associations from. * @param \Cake\Database\TypeMap $typeMap The typemap to check for columns in. * This typemap is indirectly mutated via Cake\ORM\Query::addDefaultTypes() * @param array $associations The nested tree of associations to walk. diff --git a/TableRegistry.php b/TableRegistry.php index 217de563..38d589e5 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -97,7 +97,7 @@ public static function getTableLocator() /** * Sets singleton instance of LocatorInterface implementation. * - * @param \Cake\ORM\Locator\LocatorInterface|null $tableLocator Instance of a locator to use. + * @param \Cake\ORM\Locator\LocatorInterface $tableLocator Instance of a locator to use. * @return void */ public static function setTableLocator(LocatorInterface $tableLocator) From 65c0b22687dbd034ac5da49ca8bc27e2a6d7573d Mon Sep 17 00:00:00 2001 From: Dustin Haggard Date: Thu, 30 Nov 2017 09:29:16 -0500 Subject: [PATCH 1082/2059] Add Table::addBehaviors() method. This method mirrors Table::addAssociations() and lets you add many behaviors at the same time. --- Table.php | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/Table.php b/Table.php index e91dbc1c..521e4d76 100644 --- a/Table.php +++ b/Table.php @@ -860,6 +860,38 @@ public function addBehavior($name, array $options = []) return $this; } + /** + * Add Many Behaviors + * + * Adds an array of behaviors to the table's behavior collection. + * + * Example: + * + * ``` + * $this->addBehaviors([ + * 'Timestamp', + * 'Tree' => ['level' => 'level'], + * ]); + * ``` + * + * @param array $behaviors All of the behaviors to load. + * @return $this + * @throws \RuntimeException If a behavior is being reloaded. + */ + public function addBehaviors(array $behaviors) + { + foreach ($behaviors as $name => $options) { + if (is_int($name) === true) { + $name = $options; + $options = []; + } + + $this->addBehavior($name, $options); + } + + return $this; + } + /** * Removes a behavior from this table's behavior registry. * From e09185bfa3fcbdb6aa43dab935131f3a7c0ee468 Mon Sep 17 00:00:00 2001 From: Dustin Haggard Date: Thu, 30 Nov 2017 11:50:28 -0500 Subject: [PATCH 1083/2059] Added changes from PR review --- Table.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Table.php b/Table.php index 521e4d76..0ca044b5 100644 --- a/Table.php +++ b/Table.php @@ -861,8 +861,6 @@ public function addBehavior($name, array $options = []) } /** - * Add Many Behaviors - * * Adds an array of behaviors to the table's behavior collection. * * Example: @@ -881,7 +879,7 @@ public function addBehavior($name, array $options = []) public function addBehaviors(array $behaviors) { foreach ($behaviors as $name => $options) { - if (is_int($name) === true) { + if (is_int($name)) { $name = $options; $options = []; } From 363b49a90bb9dba37309937e627e9c6e7058ca3f Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 30 Nov 2017 21:26:31 -0500 Subject: [PATCH 1084/2059] Deprecate orWhere(). I made time to circle back on this as I'd really like to not have this method in the future. It is easy to replace after I thought through the problem a bit more. --- Marshaller.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 839b0ab7..25e6a562 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -685,19 +685,20 @@ public function mergeMany($entities, array $data, array $options = []) unset($indexed[$key]); } - $maybeExistentQuery = (new Collection($indexed)) + $conditions = (new Collection($indexed)) ->map(function ($data, $key) { return explode(';', $key); }) ->filter(function ($keys) use ($primary) { return count(array_filter($keys, 'strlen')) === count($primary); }) - ->reduce(function ($query, $keys) use ($primary) { - /* @var \Cake\ORM\Query $query */ + ->reduce(function ($conditions, $keys) use ($primary) { $fields = array_map([$this->_table, 'aliasField'], $primary); + $conditions['OR'][] = array_combine($fields, $keys); - return $query->orWhere($query->newExpr()->and_(array_combine($fields, $keys))); - }, $this->_table->find()); + return $conditions; + }, ['OR' => []]); + $maybeExistentQuery = $this->_table->find()->where($conditions); if (!empty($indexed) && count($maybeExistentQuery->clause('where'))) { foreach ($maybeExistentQuery as $entity) { From 3e7c7a61d97f4c505ecb1c8c1955af620643b537 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 3 Dec 2017 01:05:16 +0530 Subject: [PATCH 1085/2059] Add BehaviorRegistry::className(). This helps in behavior class resolution since core behaviors are under ORM/Behavior while in app they are under Model/Behavior. --- BehaviorRegistry.php | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index b0fbe8bf..5c305072 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -84,12 +84,11 @@ public function setTable(Table $table) /** * Resolve a behavior classname. * - * Part of the template method for Cake\Core\ObjectRegistry::load() - * * @param string $class Partial classname to resolve. * @return string|false Either the correct classname or false. + * @since 3.5.7 */ - protected function _resolveClassName($class) + public static function className($class) { $result = App::className($class, 'Model/Behavior', 'Behavior'); if (!$result) { @@ -99,6 +98,19 @@ protected function _resolveClassName($class) return $result; } + /** + * Resolve a behavior classname. + * + * Part of the template method for Cake\Core\ObjectRegistry::load() + * + * @param string $class Partial classname to resolve. + * @return string|false Either the correct classname or false. + */ + protected function _resolveClassName($class) + { + return static::className($class); + } + /** * Throws an exception when a behavior is missing. * From 4f09453f255abb3277ecb9c92eab50b9c14a4eb3 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 5 Dec 2017 11:21:21 +0530 Subject: [PATCH 1086/2059] Make BehaviorRegistry::className() return null on failure. --- BehaviorRegistry.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 5c305072..a72389e6 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -85,7 +85,7 @@ public function setTable(Table $table) * Resolve a behavior classname. * * @param string $class Partial classname to resolve. - * @return string|false Either the correct classname or false. + * @return string|null Either the correct classname or null. * @since 3.5.7 */ public static function className($class) @@ -95,7 +95,7 @@ public static function className($class) $result = App::className($class, 'ORM/Behavior', 'Behavior'); } - return $result; + return $result ?: null; } /** @@ -108,7 +108,7 @@ public static function className($class) */ protected function _resolveClassName($class) { - return static::className($class); + return static::className($class) ?: false; } /** From 1a7cd631682d75d4242bc92dc8677735787efd06 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 11 Dec 2017 12:09:10 +0530 Subject: [PATCH 1087/2059] Fix some more phpstan reported errors. --- Association/BelongsTo.php | 3 +-- Association/BelongsToMany.php | 3 +-- Association/HasMany.php | 3 +-- Association/HasOne.php | 3 +-- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 55b223ac..a179353b 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -111,8 +111,7 @@ public function type() * `$options` * * @param \Cake\Datasource\EntityInterface $entity an entity from the source table - * @param array|\ArrayObject $options options to be passed to the save method in - * the target table + * @param array $options options to be passed to the save method in the target table * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns * the saved entity * @see \Cake\ORM\Table::save() diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 14b2f1aa..177530c2 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -689,8 +689,7 @@ public function saveStrategy($strategy = null) * not deleted. * * @param \Cake\Datasource\EntityInterface $entity an entity from the source table - * @param array|\ArrayObject $options options to be passed to the save method in - * the target table + * @param array $options options to be passed to the save method in the target table * @throws \InvalidArgumentException if the property representing the association * in the parent entity cannot be traversed * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns diff --git a/Association/HasMany.php b/Association/HasMany.php index c7d1b4d3..c18f78fc 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -155,8 +155,7 @@ public function saveStrategy($strategy = null) * `$options` * * @param \Cake\Datasource\EntityInterface $entity an entity from the source table - * @param array|\ArrayObject $options options to be passed to the save method in - * the target table + * @param array $options options to be passed to the save method in the target table * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns * the saved entity * @see \Cake\ORM\Table::save() diff --git a/Association/HasOne.php b/Association/HasOne.php index 06e9e0da..5bb78a62 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -95,8 +95,7 @@ public function type() * `$options` * * @param \Cake\Datasource\EntityInterface $entity an entity from the source table - * @param array|\ArrayObject $options options to be passed to the save method in - * the target table + * @param array $options options to be passed to the save method in the target table * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns * the saved entity * @see \Cake\ORM\Table::save() From d3a5f44e8929cf66e406bbd2d2de37923c3d5c72 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 11 Dec 2017 20:57:12 +0530 Subject: [PATCH 1088/2059] Update inline docblocks. --- Marshaller.php | 8 ++++---- Table.php | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 82779123..c916e904 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -106,7 +106,7 @@ protected function _buildPropertyMap($data, $options) } if (isset($options['isMerge'])) { $callback = function ($value, $entity) use ($assoc, $nested) { - /* @var \Cake\Datasource\EntityInterface $entity */ + /** @var \Cake\Datasource\EntityInterface $entity */ $options = $nested + ['associated' => [], 'association' => $assoc]; return $this->_mergeAssociation($entity->get($assoc->getProperty()), $assoc, $value, $options); @@ -170,7 +170,7 @@ public function one(array $data, array $options = []) $primaryKey = (array)$this->_table->getPrimaryKey(); $entityClass = $this->_table->getEntityClass(); - /* @var \Cake\Datasource\EntityInterface $entity */ + /** @var \Cake\Datasource\EntityInterface $entity */ $entity = new $entityClass(); $entity->setSource($this->_table->getRegistryAlias()); @@ -409,7 +409,7 @@ protected function _belongsToMany(BelongsToMany $assoc, array $data, $options = if (!empty($conditions)) { $query = $target->find(); $query->andWhere(function ($exp) use ($conditions) { - /* @var \Cake\Database\Expression\QueryExpression $exp */ + /** @var \Cake\Database\Expression\QueryExpression $exp */ return $exp->or_($conditions); }); @@ -686,7 +686,7 @@ public function mergeMany($entities, array $data, array $options = []) return count(array_filter($keys, 'strlen')) === count($primary); }) ->reduce(function ($query, $keys) use ($primary) { - /* @var \Cake\ORM\Query $query */ + /** @var \Cake\ORM\Query $query */ $fields = array_map([$this->_table, 'aliasField'], $primary); return $query->orWhere($query->newExpr()->and_(array_combine($fields, $keys))); diff --git a/Table.php b/Table.php index f4b17fb2..844463a3 100644 --- a/Table.php +++ b/Table.php @@ -1289,7 +1289,7 @@ public function findList(Query $query, array $options) ); return $query->formatResults(function ($results) use ($options) { - /* @var \Cake\Collection\CollectionInterface $results */ + /** @var \Cake\Collection\CollectionInterface $results */ return $results->combine( $options['keyField'], $options['valueField'], @@ -1339,7 +1339,7 @@ public function findThreaded(Query $query, array $options) $options = $this->_setFieldMatchers($options, ['keyField', 'parentField']); return $query->formatResults(function ($results) use ($options) { - /* @var \Cake\Collection\CollectionInterface $results */ + /** @var \Cake\Collection\CollectionInterface $results */ return $results->nest($options['keyField'], $options['parentField'], $options['nestingKey']); }); } From d6e09633e107cf95ce58b892f1491c21b57716b6 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 12 Dec 2017 01:28:11 +0530 Subject: [PATCH 1089/2059] More phpstan related fixes. --- Marshaller.php | 9 ++++++--- Table.php | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index c916e904..dbe3da44 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -240,19 +240,22 @@ protected function _validate($data, $options, $isNew) if (!$options['validate']) { return []; } + + $validator = null; if ($options['validate'] === true) { - $options['validate'] = $this->_table->getValidator(); + $validator = $this->_table->getValidator(); } if (is_string($options['validate'])) { - $options['validate'] = $this->_table->getValidator($options['validate']); + $validator = $this->_table->getValidator($options['validate']); } + $options['validate'] = $validator; if (!is_object($options['validate'])) { throw new RuntimeException( sprintf('validate must be a boolean, a string or an object. Got %s.', gettype($options['validate'])) ); } - return $options['validate']->errors($data, $isNew); + return $validator->errors($data, $isNew); } /** diff --git a/Table.php b/Table.php index 844463a3..e42125f0 100644 --- a/Table.php +++ b/Table.php @@ -170,7 +170,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc /** * Connection instance * - * @var \Cake\Database\Connection|\Cake\Datasource\ConnectionInterface + * @var \Cake\Database\Connection */ protected $_connection; @@ -495,7 +495,7 @@ public function setConnection(ConnectionInterface $connection) /** * Returns the connection instance. * - * @return \Cake\Database\Connection|\Cake\Datasource\ConnectionInterface + * @return \Cake\Database\Connection */ public function getConnection() { From d3130d7e145b93ed328565fe40cf44e830595787 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 12 Dec 2017 23:33:33 +0530 Subject: [PATCH 1090/2059] Fix incorrect validator object checking. --- Marshaller.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index dbe3da44..4fb7f832 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -244,12 +244,13 @@ protected function _validate($data, $options, $isNew) $validator = null; if ($options['validate'] === true) { $validator = $this->_table->getValidator(); - } - if (is_string($options['validate'])) { + } elseif (is_string($options['validate'])) { $validator = $this->_table->getValidator($options['validate']); + } elseif (is_object($options['validate'])) { + $validator = $options['validate']; } - $options['validate'] = $validator; - if (!is_object($options['validate'])) { + + if ($validator === null) { throw new RuntimeException( sprintf('validate must be a boolean, a string or an object. Got %s.', gettype($options['validate'])) ); From d9c225cb87e709ce202a785f9e4af71fa741f275 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 13 Dec 2017 13:49:35 +0530 Subject: [PATCH 1091/2059] Fix errors reported by phpstan. --- Locator/LocatorInterface.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Locator/LocatorInterface.php b/Locator/LocatorInterface.php index f8dda59c..f34e6cad 100644 --- a/Locator/LocatorInterface.php +++ b/Locator/LocatorInterface.php @@ -18,6 +18,9 @@ /** * Registries for Table objects should implement this interface. + * + * @method array getConfig() + * @method $this setConfig($alias, $options = null) */ interface LocatorInterface { From b1b34273d99d7d008b9a9143c9882f1e3c8bd5ac Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 16 Dec 2017 13:54:19 +0530 Subject: [PATCH 1092/2059] Fix few errors reported by phpstan level 3 --- Association/BelongsTo.php | 2 +- Association/BelongsToMany.php | 2 +- Association/HasMany.php | 2 +- Association/HasOne.php | 2 +- Rule/ExistsIn.php | 4 ++-- Table.php | 12 ++++++++---- 6 files changed, 14 insertions(+), 10 deletions(-) diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index a179353b..651f81e3 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -182,7 +182,7 @@ protected function _joinCondition($options) /** * {@inheritDoc} * - * @return callable + * @return \Closure */ public function eagerLoader(array $options) { diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 177530c2..89e35eb6 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -551,7 +551,7 @@ protected function _joinCondition($options) /** * {@inheritDoc} * - * @return callable + * @return \Closure */ public function eagerLoader(array $options) { diff --git a/Association/HasMany.php b/Association/HasMany.php index c18f78fc..f7bab7b2 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -668,7 +668,7 @@ protected function _options(array $opts) /** * {@inheritDoc} * - * @return callable + * @return \Closure */ public function eagerLoader(array $options) { diff --git a/Association/HasOne.php b/Association/HasOne.php index 5bb78a62..88e6b49f 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -125,7 +125,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) /** * {@inheritDoc} * - * @return callable + * @return \Closure */ public function eagerLoader(array $options) { diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 1eea45e5..47afd05e 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -35,7 +35,7 @@ class ExistsIn /** * The repository where the field will be looked for * - * @var \Cake\Datasource\RepositoryInterface|\Cake\ORM\Association + * @var \Cake\Datasource\RepositoryInterface|\Cake\ORM\Association|string */ protected $_repository; @@ -53,7 +53,7 @@ class ExistsIn * Set to true to accept composite foreign keys where one or more nullable columns are null. * * @param string|array $fields The field or fields to check existence as primary key. - * @param object|string $repository The repository where the field will be looked for, + * @param \Cake\Datasource\RepositoryInterface|\Cake\ORM\Association|string $repository The repository where the field will be looked for, * or the association name for the repository. * @param array $options The options that modify the rules behavior. * Options 'allowNullableNulls' will make the rule pass if given foreign keys are set to `null`. diff --git a/Table.php b/Table.php index e42125f0..fd422266 100644 --- a/Table.php +++ b/Table.php @@ -968,8 +968,9 @@ public function belongsTo($associated, array $options = []) { $options += ['sourceTable' => $this]; $association = new BelongsTo($associated, $options); + $this->_associations->add($association->getName(), $association); - return $this->_associations->add($association->getName(), $association); + return $association; } /** @@ -1012,8 +1013,9 @@ public function hasOne($associated, array $options = []) { $options += ['sourceTable' => $this]; $association = new HasOne($associated, $options); + $this->_associations->add($association->getName(), $association); - return $this->_associations->add($association->getName(), $association); + return $association; } /** @@ -1062,8 +1064,9 @@ public function hasMany($associated, array $options = []) { $options += ['sourceTable' => $this]; $association = new HasMany($associated, $options); + $this->_associations->add($association->getName(), $association); - return $this->_associations->add($association->getName(), $association); + return $association; } /** @@ -1114,8 +1117,9 @@ public function belongsToMany($associated, array $options = []) { $options += ['sourceTable' => $this]; $association = new BelongsToMany($associated, $options); + $this->_associations->add($association->getName(), $association); - return $this->_associations->add($association->getName(), $association); + return $association; } /** From 34fcb8c9780fe4524fd8fd6f86db89546c065722 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 18 Dec 2017 12:25:39 +0530 Subject: [PATCH 1093/2059] Fix few errors reported by phpstan. --- LazyEagerLoader.php | 1 + 1 file changed, 1 insertion(+) diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 48d3da19..ff9fc6b1 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -76,6 +76,7 @@ protected function _getQuery($objects, $contain, $source) return $entity->{$method}($primaryKey); }); + /** @var \Cake\ORM\Query $query */ $query = $source ->find() ->select((array)$primaryKey) From 67b5bb62394f067f18afbaaf8534a096e0247e32 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 18 Dec 2017 19:35:54 +0530 Subject: [PATCH 1094/2059] Fix more errors reported by phpstan. --- Association/Loader/SelectLoader.php | 5 +++-- Locator/TableLocator.php | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index d22e6c5d..289b4bfa 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -116,7 +116,7 @@ public function __construct(array $options) * iterator. The options accepted by this method are the same as `Association::eagerLoader()` * * @param array $options Same options as `Association::eagerLoader()` - * @return callable + * @return \Closure */ public function buildEagerLoader(array $options) { @@ -294,13 +294,14 @@ protected function _addFilteringJoin($query, $key, $subquery) } $subquery->select($filter, true); + $conditions = null; if (is_array($key)) { $conditions = $this->_createTupleCondition($query, $key, $filter, '='); } else { $filter = current($filter); } - $conditions = isset($conditions) ? $conditions : $query->newExpr([$key => $filter]); + $conditions = $conditions ?: $query->newExpr([$key => $filter]); return $query->innerJoin( [$aliasedTable => $subquery], diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 36ab5cda..8bc683da 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -227,7 +227,7 @@ public function get($alias, array $options = []) * * @param string $alias The alias name you want to get. * @param array $options Table options array. - * @return string + * @return string|false */ protected function _getClassName($alias, array $options = []) { From d7ce0f42afdd2f0a8cc9a96ccb4b180ad7518456 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 22 Dec 2017 23:31:01 +0530 Subject: [PATCH 1095/2059] Fix docblock of TranslateBehavior::buildMarshalMap() Translations under `_translations` are marshaled by default. You don't need to set 'translations' to `true`. --- Behavior/TranslateBehavior.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index ec895d9d..8ec2e398 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -370,9 +370,9 @@ public function afterSave(Event $event, EntityInterface $entity) } /** - * Add in _translations marshalling handlers if translation marshalling is - * enabled. You need to specifically enable translation marshalling by adding - * `'translations' => true` to the options provided to `Table::newEntity()` or `Table::patchEntity()`. + * Add in `_translations` marshalling handlers. You can disable marshalling + * of translations by setting `'translations' => false` in the options + * provided to `Table::newEntity()` or `Table::patchEntity()`. * * {@inheritDoc} */ From c4b0ccd04cb4071443cc0b3503175eaae69a1747 Mon Sep 17 00:00:00 2001 From: Johannes Jordan Date: Fri, 24 Nov 2017 19:21:06 +0100 Subject: [PATCH 1096/2059] only update count if valid count is given Callback might refuse the count update --- Behavior/CounterCacheBehavior.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 2971b362..8bfdc153 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -238,8 +238,10 @@ protected function _processAssociation(Event $event, EntityInterface $entity, As } else { $count = $this->_getCount($config, $countConditions); } - - $assoc->getTarget()->updateAll([$field => $count], $updateConditions); + if (!is_null($count)) { + $assoc->getTarget()->updateAll([$field => $count], + $updateConditions); + } if (isset($updateOriginalConditions)) { if (is_callable($config)) { @@ -250,7 +252,10 @@ protected function _processAssociation(Event $event, EntityInterface $entity, As } else { $count = $this->_getCount($config, $countOriginalConditions); } - $assoc->getTarget()->updateAll([$field => $count], $updateOriginalConditions); + if (!is_null($count)) { + $assoc->getTarget()->updateAll([$field => $count], + $updateOriginalConditions); + } } } } From a20822414f831c8a74f3ee6f295b908d0ee69c32 Mon Sep 17 00:00:00 2001 From: Johannes Jordan Date: Tue, 28 Nov 2017 15:46:54 +0100 Subject: [PATCH 1097/2059] use false instead of null --- Behavior/CounterCacheBehavior.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 8bfdc153..026db553 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -238,7 +238,7 @@ protected function _processAssociation(Event $event, EntityInterface $entity, As } else { $count = $this->_getCount($config, $countConditions); } - if (!is_null($count)) { + if ($count !== false) { $assoc->getTarget()->updateAll([$field => $count], $updateConditions); } @@ -252,7 +252,7 @@ protected function _processAssociation(Event $event, EntityInterface $entity, As } else { $count = $this->_getCount($config, $countOriginalConditions); } - if (!is_null($count)) { + if ($count !== false) { $assoc->getTarget()->updateAll([$field => $count], $updateOriginalConditions); } From 35f86c27fb07ba7edc89ff522b4d1291e3bd5cae Mon Sep 17 00:00:00 2001 From: Johannes Jordan Date: Wed, 29 Nov 2017 17:03:39 +0100 Subject: [PATCH 1098/2059] fix style --- Behavior/CounterCacheBehavior.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 026db553..90c5ab01 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -239,8 +239,7 @@ protected function _processAssociation(Event $event, EntityInterface $entity, As $count = $this->_getCount($config, $countConditions); } if ($count !== false) { - $assoc->getTarget()->updateAll([$field => $count], - $updateConditions); + $assoc->getTarget()->updateAll([$field => $count], updateConditions); } if (isset($updateOriginalConditions)) { @@ -253,8 +252,7 @@ protected function _processAssociation(Event $event, EntityInterface $entity, As $count = $this->_getCount($config, $countOriginalConditions); } if ($count !== false) { - $assoc->getTarget()->updateAll([$field => $count], - $updateOriginalConditions); + $assoc->getTarget()->updateAll([$field => $count], updateOriginalConditions); } } } From 25118dfa929c80be48f01c3e1fd184cc1ad834d6 Mon Sep 17 00:00:00 2001 From: Johannes Jordan Date: Wed, 29 Nov 2017 17:06:04 +0100 Subject: [PATCH 1099/2059] fix the fix --- Behavior/CounterCacheBehavior.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 90c5ab01..fcbf9bbd 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -239,7 +239,7 @@ protected function _processAssociation(Event $event, EntityInterface $entity, As $count = $this->_getCount($config, $countConditions); } if ($count !== false) { - $assoc->getTarget()->updateAll([$field => $count], updateConditions); + $assoc->getTarget()->updateAll([$field => $count], $updateConditions); } if (isset($updateOriginalConditions)) { @@ -252,7 +252,7 @@ protected function _processAssociation(Event $event, EntityInterface $entity, As $count = $this->_getCount($config, $countOriginalConditions); } if ($count !== false) { - $assoc->getTarget()->updateAll([$field => $count], updateOriginalConditions); + $assoc->getTarget()->updateAll([$field => $count], $updateOriginalConditions); } } } From dcbad5c60320c96769de02d552c98d3a2a9d2e56 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 26 Dec 2017 16:00:44 -0500 Subject: [PATCH 1100/2059] Add docs for return false case. --- Behavior/CounterCacheBehavior.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index fcbf9bbd..082013e8 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -63,6 +63,7 @@ * * Counter cache using lambda function returning the count * This is equivalent to example #2 + * * ``` * [ * 'Users' => [ @@ -77,6 +78,9 @@ * ] * ``` * + * When using a lambda function you can return `false` to disable updating the counter value + * for the current operation. + * * Ignore updating the field if it is dirty * ``` * [ From 70fefc016e7c8614e6e399ec3030d9581ac379b8 Mon Sep 17 00:00:00 2001 From: Hideki Kinjyo Date: Sat, 30 Dec 2017 14:52:29 +0900 Subject: [PATCH 1101/2059] Check DateTimeType using immutable in TimestampBehavior --- Behavior/TimestampBehavior.php | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 2c675665..1c0da13f 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -14,11 +14,11 @@ */ namespace Cake\ORM\Behavior; +use Cake\Database\Type; use Cake\Datasource\EntityInterface; use Cake\Event\Event; -use Cake\I18n\Time; use Cake\ORM\Behavior; -use DateTime; +use DateTimeInterface; use UnexpectedValueException; class TimestampBehavior extends Behavior @@ -57,7 +57,7 @@ class TimestampBehavior extends Behavior /** * Current timestamp * - * @var \DateTime + * @var \DateTimeInterface */ protected $_ts; @@ -130,19 +130,22 @@ public function implementedEvents() * If an explicit date time is passed, the config option `refreshTimestamp` is * automatically set to false. * - * @param \DateTime|null $ts Timestamp + * @param \DateTimeInterface|null $ts Timestamp * @param bool $refreshTimestamp If true timestamp is refreshed. - * @return \DateTime + * @return \DateTimeInterface */ - public function timestamp(DateTime $ts = null, $refreshTimestamp = false) + public function timestamp(DateTimeInterface $ts = null, $refreshTimestamp = false) { + $class = Type::build('datetime') + ->getDateTimeClassName(); + if ($ts) { if ($this->_config['refreshTimestamp']) { $this->_config['refreshTimestamp'] = false; } - $this->_ts = new Time($ts); + $this->_ts = new $class($ts); } elseif ($this->_ts === null || $refreshTimestamp) { - $this->_ts = new Time(); + $this->_ts = new $class(); } return $this->_ts; From cad206c278e5f1b1820551604ecddb5fae910788 Mon Sep 17 00:00:00 2001 From: Hideki Kinjyo Date: Sat, 30 Dec 2017 17:57:41 +0900 Subject: [PATCH 1102/2059] Fix phpstan --- Behavior/TimestampBehavior.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 1c0da13f..58ba19ba 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -136,8 +136,9 @@ public function implementedEvents() */ public function timestamp(DateTimeInterface $ts = null, $refreshTimestamp = false) { - $class = Type::build('datetime') - ->getDateTimeClassName(); + /** @var \Cake\Database\Type\DateTimeType $type */ + $type = Type::build('datetime'); + $class = $type->getDateTimeClassName(); if ($ts) { if ($this->_config['refreshTimestamp']) { From cf7be747335dfbf01f24237b56843bff34517021 Mon Sep 17 00:00:00 2001 From: Hideki Kinjyo Date: Sun, 31 Dec 2017 13:15:13 +0900 Subject: [PATCH 1103/2059] Revert timestamp() not to check useImmutable --- Behavior/TimestampBehavior.php | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 58ba19ba..2c675665 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -14,11 +14,11 @@ */ namespace Cake\ORM\Behavior; -use Cake\Database\Type; use Cake\Datasource\EntityInterface; use Cake\Event\Event; +use Cake\I18n\Time; use Cake\ORM\Behavior; -use DateTimeInterface; +use DateTime; use UnexpectedValueException; class TimestampBehavior extends Behavior @@ -57,7 +57,7 @@ class TimestampBehavior extends Behavior /** * Current timestamp * - * @var \DateTimeInterface + * @var \DateTime */ protected $_ts; @@ -130,23 +130,19 @@ public function implementedEvents() * If an explicit date time is passed, the config option `refreshTimestamp` is * automatically set to false. * - * @param \DateTimeInterface|null $ts Timestamp + * @param \DateTime|null $ts Timestamp * @param bool $refreshTimestamp If true timestamp is refreshed. - * @return \DateTimeInterface + * @return \DateTime */ - public function timestamp(DateTimeInterface $ts = null, $refreshTimestamp = false) + public function timestamp(DateTime $ts = null, $refreshTimestamp = false) { - /** @var \Cake\Database\Type\DateTimeType $type */ - $type = Type::build('datetime'); - $class = $type->getDateTimeClassName(); - if ($ts) { if ($this->_config['refreshTimestamp']) { $this->_config['refreshTimestamp'] = false; } - $this->_ts = new $class($ts); + $this->_ts = new Time($ts); } elseif ($this->_ts === null || $refreshTimestamp) { - $this->_ts = new $class(); + $this->_ts = new Time(); } return $this->_ts; From 5f3727eb38104903d89db774ca6f27d1d292b9c4 Mon Sep 17 00:00:00 2001 From: Hideki Kinjyo Date: Sun, 31 Dec 2017 13:38:49 +0900 Subject: [PATCH 1104/2059] Modify _updateField to check using immutable-datetimetype --- Behavior/TimestampBehavior.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 2c675665..f3322f15 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -14,6 +14,7 @@ */ namespace Cake\ORM\Behavior; +use Cake\Database\Type; use Cake\Datasource\EntityInterface; use Cake\Event\Event; use Cake\I18n\Time; @@ -193,6 +194,14 @@ protected function _updateField($entity, $field, $refreshTimestamp) if ($entity->isDirty($field)) { return; } - $entity->set($field, $this->timestamp(null, $refreshTimestamp)); + + $ts = $this->timestamp(null, $refreshTimestamp); + + $columnType = $this->getTable()->getSchema()->getColumnType($field); + /** @var \Cake\Database\Type\DateTimeType $type */ + $type = Type::build($columnType); + $class = $type->getDateTimeClassName(); + + $entity->set($field, new $class($ts)); } } From fae02fbbd0ce6d703c2f0886eeca926b3f2dc60a Mon Sep 17 00:00:00 2001 From: Anto Date: Fri, 5 Jan 2018 20:10:41 +0100 Subject: [PATCH 1105/2059] Remove deprecated function calls --- Association/BelongsToMany.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index fe358766..9887f411 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -784,7 +784,7 @@ protected function _saveTarget(EntityInterface $parentEntity, $entities, $option // Saving the new linked entity failed, copy errors back into the // original entity if applicable and abort. if (!empty($options['atomic'])) { - $original[$k]->errors($entity->errors()); + $original[$k]->setErrors($entity->getErrors()); } if (!$saved) { return false; From 431f138cc90d1f4b1f568af8611d7efbaa8f63ec Mon Sep 17 00:00:00 2001 From: dereuromark Date: Mon, 8 Jan 2018 21:02:11 +0100 Subject: [PATCH 1106/2059] Simplify documentation. Use good defaults. --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 25bfb856..9da1f507 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,8 @@ ConnectionManager::setConfig('default', [ 'database' => 'test', 'username' => 'root', 'password' => 'secret', - 'cacheMetadata' => false // If set to `true` you need to install the optional "cakephp/cache" package. + 'cacheMetadata' => true, + 'quoteIdentifiers' => false, ]); ``` From bbfe61fb494c088e6a7e8f79b263b5fd70346379 Mon Sep 17 00:00:00 2001 From: dereuromark Date: Tue, 9 Jan 2018 11:37:03 +0100 Subject: [PATCH 1107/2059] Add meta data cache doc for ORM split. --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 9da1f507..8629095b 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,22 @@ $article = $articles->get(2); $articles->delete($article); ``` +## Meta Data Cache + +It is recommended to enable meta data cache for production systems to avoid performance issues. +For e.g. file system strategy your bootstrap file could look like this: +```php +use Cake\Cache\Engine\FileEngine; + +$cacheConfig = [ + 'className' => FileEngine::class, + 'duration' => '+1 year', + 'serialize' => true, + 'prefix' => 'orm_', +], +Cache::setConfig('_cake_model_', $cacheConfig); +``` + ## Additional Documentation Consult [the CakePHP ORM documentation](https://book.cakephp.org/3.0/en/orm.html) From 8a38ea73562fa2dcebcefa455c0183099c30ffbb Mon Sep 17 00:00:00 2001 From: Mark Story Date: Wed, 10 Jan 2018 21:50:58 -0500 Subject: [PATCH 1108/2059] Fix notice errors when scope is unset. Use `getConfig()` instead of reading from potentially unset indicies. Refs #11565 --- Behavior/TreeBehavior.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index a35f8887..ee93abd9 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -932,13 +932,13 @@ protected function _sync($shift, $dir, $conditions, $mark = false) */ protected function _scope($query) { - $config = $this->getConfig(); + $scope = $this->getConfig('scope'); - if (is_array($config['scope'])) { - return $query->where($config['scope']); + if (is_array($scope)) { + return $query->where($scope); } - if (is_callable($config['scope'])) { - return $config['scope']($query); + if (is_callable($scope)) { + return $scope($query); } return $query; From a11141c74524a83334dd4e35fdb989d878a2a39f Mon Sep 17 00:00:00 2001 From: Dustin Haggard Date: Tue, 16 Jan 2018 02:48:11 -0500 Subject: [PATCH 1109/2059] Added the getBehavior() method. Adds a `getBehavior()` shortcut method to the table. --- Table.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Table.php b/Table.php index b323f338..8662f142 100644 --- a/Table.php +++ b/Table.php @@ -922,6 +922,17 @@ public function behaviors() return $this->_behaviors; } + /** + * Get a behavior from the registry. + * + * @param string $name The behavior alias to get from the registry. + * @return \Cake\ORM\Behavior|null + */ + public function getBehavior($name) + { + return $this->_behaviors->get($name); + } + /** * Check if a behavior with the given alias has been loaded. * From 3293ef251c6d8c9e91f52cab90fa44f090fb22f6 Mon Sep 17 00:00:00 2001 From: dereuromark Date: Tue, 16 Jan 2018 11:28:04 +0100 Subject: [PATCH 1110/2059] Use a stricter get+has pattern for associations on tables. --- Table.php | 54 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/Table.php b/Table.php index b323f338..e613cfd9 100644 --- a/Table.php +++ b/Table.php @@ -934,7 +934,7 @@ public function hasBehavior($name) } /** - * Returns an association object configured for the specified alias if any + * Returns an association object configured for the specified alias if any. * * @deprecated 3.6.0 Use getAssociation() instead. * @param string $name the alias used for the association. @@ -942,9 +942,9 @@ public function hasBehavior($name) */ public function association($name) { - deprecationWarning('Use Table::getAssociation() instead.'); + deprecationWarning('Use Table::getAssociation() and Table::hasAssocation() instead.'); - return $this->getAssociation($name); + return $this->findAssociation($name); } /** @@ -956,10 +956,54 @@ public function association($name) * $users = $this->getAssociation('Articles.Comments.Users'); * ``` * - * @param string $name the alias used for the association. - * @return \Cake\ORM\Association|null Either the association or null. + * Note that this method requires the association to be present or otherwise + * throws an exception. + * If you are not sure, use hasAssociation() before calling this method. + * + * @param string $name The alias used for the association. + * @return \Cake\ORM\Association The association. + * @throws \InvalidArgumentException */ public function getAssociation($name) + { + $association = $this->findAssociation($name); + if (!$association) { + throw new InvalidArgumentException('Association does not exist: ' . $name); + } + + return $association; + } + + /** + * Checks whether a specific association exists on this Table instance. + * + * The name argument also supports dot syntax to access deeper associations. + * + * ``` + * $hasUsers = $this->hasAssociation('Articles.Comments.Users'); + * ``` + * + * @param string $name The alias used for the association. + * @return bool + */ + public function hasAssociation($name) + { + return $this->findAssociation($name) !== null; + } + + /** + * Returns an association object configured for the specified alias if any. + * + * The name argument also supports dot syntax to access deeper associations. + * + * ``` + * $users = $this->getAssociation('Articles.Comments.Users'); + * ``` + * + * @param string $name The alias used for the association. + * @return \Cake\ORM\Association|null Either the association or null. + */ + protected function findAssociation($name) { if (strpos($name, '.') === false) { return $this->_associations->get($name); From 1986f8b70e86a6af769f1d65ec9f0c2334ea4385 Mon Sep 17 00:00:00 2001 From: dereuromark Date: Tue, 16 Jan 2018 14:20:36 +0100 Subject: [PATCH 1111/2059] Adjust deprecation texts. --- Table.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index e613cfd9..27359b11 100644 --- a/Table.php +++ b/Table.php @@ -936,7 +936,7 @@ public function hasBehavior($name) /** * Returns an association object configured for the specified alias if any. * - * @deprecated 3.6.0 Use getAssociation() instead. + * @deprecated 3.6.0 Use getAssociation() and Table::hasAssocation() instead. * @param string $name the alias used for the association. * @return \Cake\ORM\Association|null Either the association or null. */ @@ -948,7 +948,7 @@ public function association($name) } /** - * Returns an association object configured for the specified alias if any. + * Returns an association object configured for the specified alias. * * The name argument also supports dot syntax to access deeper associations. * From 7c62b3344e85e47eccd88f91b6180560cc90991d Mon Sep 17 00:00:00 2001 From: Dustin Haggard Date: Wed, 17 Jan 2018 12:40:07 -0500 Subject: [PATCH 1112/2059] Removed nullable return from Table::getBehavior() --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 8662f142..e46167af 100644 --- a/Table.php +++ b/Table.php @@ -926,7 +926,7 @@ public function behaviors() * Get a behavior from the registry. * * @param string $name The behavior alias to get from the registry. - * @return \Cake\ORM\Behavior|null + * @return \Cake\ORM\Behavior */ public function getBehavior($name) { From 1c1e0b4adeea8f6e5b95074c52aa8c26595396d1 Mon Sep 17 00:00:00 2001 From: Dustin Haggard Date: Fri, 19 Jan 2018 07:40:07 -0500 Subject: [PATCH 1113/2059] Throw an exception if the behavior does not exist. --- Table.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Table.php b/Table.php index e46167af..1d2e8255 100644 --- a/Table.php +++ b/Table.php @@ -927,9 +927,18 @@ public function behaviors() * * @param string $name The behavior alias to get from the registry. * @return \Cake\ORM\Behavior + * @throws \InvalidArgumentException If the behavior does not exist. */ public function getBehavior($name) { + if ($this->hasBehavior($name) === false) { + throw new InvalidArgumentException(sprintf( + 'The %s behavior is not defined on %s.', + $name, + get_class($this) + )); + } + return $this->_behaviors->get($name); } From 94af81528d837ccfca4caf2bb379df48d474afc4 Mon Sep 17 00:00:00 2001 From: mark_story Date: Fri, 19 Jan 2018 15:02:36 -0500 Subject: [PATCH 1114/2059] Improve error message when associations are not defined. --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 27359b11..64656e59 100644 --- a/Table.php +++ b/Table.php @@ -968,7 +968,7 @@ public function getAssociation($name) { $association = $this->findAssociation($name); if (!$association) { - throw new InvalidArgumentException('Association does not exist: ' . $name); + throw new InvalidArgumentException("The {$name} association is not defined on {$this->getAlias()}."); } return $association; From 046a5741171dc52249705037a209b7d0277aece1 Mon Sep 17 00:00:00 2001 From: mark_story Date: Fri, 19 Jan 2018 15:02:56 -0500 Subject: [PATCH 1115/2059] Use hasAssociation() instead of getAssociation() --- Association/BelongsToMany.php | 10 +++++----- Marshaller.php | 5 +++-- Query.php | 4 ++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 9887f411..58ee737d 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -354,14 +354,14 @@ protected function _generateTargetAssociations($junction, $source, $target) $junctionAlias = $junction->getAlias(); $sAlias = $source->getAlias(); - if (!$target->getAssociation($junctionAlias)) { + if (!$target->hasAssociation($junctionAlias)) { $target->hasMany($junctionAlias, [ 'targetTable' => $junction, 'foreignKey' => $this->getTargetForeignKey(), 'strategy' => $this->_strategy, ]); } - if (!$target->getAssociation($sAlias)) { + if (!$target->hasAssociation($sAlias)) { $target->belongsToMany($sAlias, [ 'sourceTable' => $target, 'targetTable' => $source, @@ -391,7 +391,7 @@ protected function _generateTargetAssociations($junction, $source, $target) protected function _generateSourceAssociations($junction, $source) { $junctionAlias = $junction->getAlias(); - if (!$source->getAssociation($junctionAlias)) { + if (!$source->hasAssociation($junctionAlias)) { $source->hasMany($junctionAlias, [ 'targetTable' => $junction, 'foreignKey' => $this->getForeignKey(), @@ -421,13 +421,13 @@ protected function _generateJunctionAssociations($junction, $source, $target) $tAlias = $target->getAlias(); $sAlias = $source->getAlias(); - if (!$junction->getAssociation($tAlias)) { + if (!$junction->hasAssociation($tAlias)) { $junction->belongsTo($tAlias, [ 'foreignKey' => $this->getTargetForeignKey(), 'targetTable' => $target ]); } - if (!$junction->getAssociation($sAlias)) { + if (!$junction->hasAssociation($sAlias)) { $junction->belongsTo($sAlias, [ 'foreignKey' => $this->getForeignKey(), 'targetTable' => $source diff --git a/Marshaller.php b/Marshaller.php index 870bf624..45ae6136 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -88,10 +88,9 @@ protected function _buildPropertyMap($data, $options) $key = $nested; $nested = []; } - $assoc = $this->_table->getAssociation($key); // If the key is not a special field like _ids or _joinData // it is a missing association that we should error on. - if (!$assoc) { + if (!$this->_table->hasAssociation($key)) { if (substr($key, 0, 1) !== '_') { throw new \InvalidArgumentException(sprintf( 'Cannot marshal data for "%s" association. It is not associated with "%s".', @@ -101,6 +100,8 @@ protected function _buildPropertyMap($data, $options) } continue; } + $assoc = $this->_table->getAssociation($key); + if (isset($options['forceNew'])) { $nested['forceNew'] = $options['forceNew']; } diff --git a/Query.php b/Query.php index e0045148..801413a4 100644 --- a/Query.php +++ b/Query.php @@ -437,10 +437,10 @@ public function clearContain() protected function _addAssociationsToTypeMap($table, $typeMap, $associations) { foreach ($associations as $name => $nested) { - $association = $table->getAssociation($name); - if (!$association) { + if (!$table->hasAssociation($name)) { continue; } + $association = $table->getAssociation($name); $target = $association->getTarget(); $primary = (array)$target->getPrimaryKey(); if (empty($primary) || $typeMap->type($target->aliasField($primary[0])) === null) { From e51118342eb05de337d2204c91449e9c679a2d9f Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 20 Jan 2018 14:07:25 -0500 Subject: [PATCH 1116/2059] Fix another getAssociation that should be hasAssociation(). --- Rule/ExistsIn.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 2cef92e0..49e47141 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -80,8 +80,7 @@ public function __construct($fields, $repository, array $options = []) public function __invoke(EntityInterface $entity, array $options) { if (is_string($this->_repository)) { - $repository = $options['repository']->getAssociation($this->_repository); - if (!$repository) { + if (!$options['repository']->hasAssociation($this->_repository)) { throw new RuntimeException(sprintf( "ExistsIn rule for '%s' is invalid. '%s' is not associated with '%s'.", implode(', ', $this->_fields), @@ -89,6 +88,7 @@ public function __invoke(EntityInterface $entity, array $options) get_class($options['repository']) )); } + $repository = $options['repository']->getAssociation($this->_repository); $this->_repository = $repository; } From 6242937da5021bcaf818c67b3effdf04b1cf8c3c Mon Sep 17 00:00:00 2001 From: Mark Sch Date: Sat, 20 Jan 2018 21:00:03 +0100 Subject: [PATCH 1117/2059] Improve getBehavior() nullable check --- Table.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index 1d2e8255..37caf543 100644 --- a/Table.php +++ b/Table.php @@ -931,7 +931,8 @@ public function behaviors() */ public function getBehavior($name) { - if ($this->hasBehavior($name) === false) { + $behavior = $this->_behaviors->get($name); + if ($behavior === null) { throw new InvalidArgumentException(sprintf( 'The %s behavior is not defined on %s.', $name, @@ -939,7 +940,7 @@ public function getBehavior($name) )); } - return $this->_behaviors->get($name); + return $behavior; } /** From c7a1f8e20e0db2882de82724bf2129d46615e731 Mon Sep 17 00:00:00 2001 From: dereuromark Date: Tue, 23 Jan 2018 13:20:09 +0100 Subject: [PATCH 1118/2059] Fix saveOrFail() to include useful messages on output, e.g. CLI/tests. --- Exception/PersistenceFailedException.php | 29 ++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/Exception/PersistenceFailedException.php b/Exception/PersistenceFailedException.php index 2a3eab22..9c79079f 100644 --- a/Exception/PersistenceFailedException.php +++ b/Exception/PersistenceFailedException.php @@ -45,9 +45,37 @@ class PersistenceFailedException extends Exception public function __construct(EntityInterface $entity, $message, $code = null, $previous = null) { $this->_entity = $entity; + if (is_array($message)) { + $errors = []; + foreach ($entity->getErrors() as $field => $error) { + $errors[] = $field . ': "' . $this->buildError($error) . '"'; + } + if ($errors) { + $message[] = implode(', ', $errors); + $this->_messageTemplate = 'Entity %s failure (%s).'; + } + } parent::__construct($message, $code, $previous); } + /** + * @param string|array $error Error message. + * @return string + */ + protected function buildError($error) + { + if (!is_array($error)) { + return $error; + } + + $errors = []; + foreach ($error as $key => $message) { + $errors[] = is_int($key) ? $message : $key; + } + + return implode(', ', $errors); + } + /** * Get the passed in entity * @@ -57,4 +85,5 @@ public function getEntity() { return $this->_entity; } + } From 8e8d4be990930007a0e0eab1ede4206b87bc91b6 Mon Sep 17 00:00:00 2001 From: stickler-ci Date: Tue, 23 Jan 2018 12:25:44 +0000 Subject: [PATCH 1119/2059] Fixing style errors. --- Exception/PersistenceFailedException.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Exception/PersistenceFailedException.php b/Exception/PersistenceFailedException.php index 9c79079f..4bfb1db0 100644 --- a/Exception/PersistenceFailedException.php +++ b/Exception/PersistenceFailedException.php @@ -85,5 +85,4 @@ public function getEntity() { return $this->_entity; } - } From 6f9bf6e8a6f335d0b22ba4b59df016fc47a13b29 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 26 Jan 2018 21:46:56 -0500 Subject: [PATCH 1120/2059] Fix invalid SQL generation from leftJoinWith() & auto-fields When no fields are selected, and auto-fields is enabled, `$fields` gets set to false which ends up as '' in SQL causing invalid field lists. Refs #11663 --- Association.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Association.php b/Association.php index f9300a2f..05a266a5 100644 --- a/Association.php +++ b/Association.php @@ -1143,7 +1143,8 @@ protected function _appendFields($query, $surrogate, $options) } if ($autoFields === true) { - $fields = array_merge((array)$fields, $target->getSchema()->columns()); + $fields = array_filter((array)$fields); + $fields = array_merge($fields, $target->getSchema()->columns()); } if ($fields) { From 60cff8a7a2f334c261c028976b0f734e5d5fd8eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Thu, 15 Feb 2018 11:15:09 +0100 Subject: [PATCH 1121/2059] Update docs on table locators usage. --- README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/README.md b/README.md index b696b9be..9cbcbac4 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,38 @@ ConnectionManager::setConfig('default', [ Once a 'default' connection is registered, it will be used by all the Table mappers if no explicit connection is defined. +## Using Table Locator + +In order to access table instances you need to use a *Table Locator*. + +```php +use Cake\ORM\Locator\TableLocator; + +$locator = new TableLocator(); +$articles = $locator->get('Articles'); +``` + +You can also use a trait for easy access to the locator instance: + +```php +use Cake\ORM\Locator\LocatorAwareTrait; + +$articles = $this->getTableLocator()->get('Articles'); +``` + +By default classes using `LocatorAwareTrait` will share a global locator instance. +You can inject your own locator instance into the object: + +```php +use Cake\ORM\Locator\TableLocator; +use Cake\ORM\Locator\LocatorAwareTrait; + +$locator = new TableLocator(); +$this->setTableLocator($locator); + +$articles = $this->getTableLocator()->get('Articles'); +``` + ## Creating Associations In your table classes you can define the relations between your tables. CakePHP's ORM From 9dcfdd58aad6011d1f34df2006f5a046f7bbbdb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Thu, 15 Feb 2018 11:17:00 +0100 Subject: [PATCH 1122/2059] Remove deprecation warnings for commonly used TableRegistry methods. --- TableRegistry.php | 29 ++--------------------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/TableRegistry.php b/TableRegistry.php index 2e375acf..5c952687 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -116,13 +116,13 @@ public static function setTableLocator(LocatorInterface $tableLocator) * @param string|null $alias Name of the alias * @param array|null $options list of options for the alias * @return array The config data. - * @deprecated 3.6.0 Use \Cake\ORM\Locator\TableLocator::config() instead. + * @deprecated 3.6.0 Use \Cake\ORM\Locator\TableLocator::getConfig()/setConfig() instead. */ public static function config($alias = null, $options = null) { deprecationWarning( 'TableRegistry::config() is deprecated. ' . - 'Use \Cake\ORM\Locator\TableLocator::config() instead.' + 'Use \Cake\ORM\Locator\TableLocator::getConfig()/setConfig() instead.' ); return static::getTableLocator()->config($alias, $options); @@ -140,11 +140,6 @@ public static function config($alias = null, $options = null) */ public static function get($alias, array $options = []) { - deprecationWarning( - 'TableRegistry::get() is deprecated. ' . - 'Use \Cake\ORM\Locator\TableLocator::get() instead.' - ); - return static::getTableLocator()->get($alias, $options); } @@ -157,11 +152,6 @@ public static function get($alias, array $options = []) */ public static function exists($alias) { - deprecationWarning( - 'TableRegistry::exists() is deprecated. ' . - 'Use \Cake\ORM\Locator\TableLocator::exists() instead.' - ); - return static::getTableLocator()->exists($alias); } @@ -175,11 +165,6 @@ public static function exists($alias) */ public static function set($alias, Table $object) { - deprecationWarning( - 'TableRegistry::set() is deprecated. ' . - 'Use \Cake\ORM\Locator\TableLocator::set() instead.' - ); - return static::getTableLocator()->set($alias, $object); } @@ -192,11 +177,6 @@ public static function set($alias, Table $object) */ public static function remove($alias) { - deprecationWarning( - 'TableRegistry::remove() is deprecated. ' . - 'Use \Cake\ORM\Locator\TableLocator::remove() instead.' - ); - static::getTableLocator()->remove($alias); } @@ -208,11 +188,6 @@ public static function remove($alias) */ public static function clear() { - deprecationWarning( - 'TableRegistry::clear() is deprecated. ' . - 'Use \Cake\ORM\Locator\TableLocator::clear() instead.' - ); - static::getTableLocator()->clear(); } From aef02d32508cc5534753bf25a9ad03c363095dd5 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 16 Feb 2018 21:58:19 -0500 Subject: [PATCH 1123/2059] Do not allow updates to tables with no primary key Raise exceptions instead of allowing updates to tables without primary keys. Refs #11726 --- Table.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Table.php b/Table.php index fd422266..895510e7 100644 --- a/Table.php +++ b/Table.php @@ -1989,6 +1989,13 @@ protected function _update($entity, $data) return $entity; } + if (empty($primaryColumns)) { + $entityClass = get_class($entity); + $table = $this->getTable(); + $message = "Cannot update `$entityClass` the `$table` has no primary key."; + throw new InvalidArgumentException($message); + } + if (!$entity->has($primaryColumns)) { $message = 'All primary key value(s) are needed for updating, '; $message .= get_class($entity) . ' is missing ' . implode(', ', $primaryColumns); From 4d1451148f034bb2df248c9a05b480255d62c365 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 17 Feb 2018 10:32:58 -0500 Subject: [PATCH 1124/2059] Improve error and use count() instead of empty(). --- Table.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index 895510e7..78d39636 100644 --- a/Table.php +++ b/Table.php @@ -1989,10 +1989,10 @@ protected function _update($entity, $data) return $entity; } - if (empty($primaryColumns)) { + if (count($primaryColumns) === 0) { $entityClass = get_class($entity); $table = $this->getTable(); - $message = "Cannot update `$entityClass` the `$table` has no primary key."; + $message = "Cannot update `$entityClass`. The `$table` has no primary key."; throw new InvalidArgumentException($message); } From 089d4e2d9590beee703ef2c08903937e1ab9165c Mon Sep 17 00:00:00 2001 From: dereuromark Date: Tue, 20 Feb 2018 01:24:21 +0100 Subject: [PATCH 1125/2059] Always use consistent class usage to make comparison and detection/refactor possible. --- Association/BelongsToMany.php | 2 +- Table.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 58ee737d..506801ab 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1430,7 +1430,7 @@ protected function _junctionTableName($name = null) { if ($name === null) { if (empty($this->_junctionTableName)) { - $tablesNames = array_map('\Cake\Utility\Inflector::underscore', [ + $tablesNames = array_map('Cake\Utility\Inflector::underscore', [ $this->getSource()->getTable(), $this->getTarget()->getTable() ]); diff --git a/Table.php b/Table.php index c605e1dd..34d10d10 100644 --- a/Table.php +++ b/Table.php @@ -763,7 +763,7 @@ public function displayField($key = null) public function getEntityClass() { if (!$this->_entityClass) { - $default = '\Cake\ORM\Entity'; + $default = Entity::class; $self = get_called_class(); $parts = explode('\\', $self); @@ -772,7 +772,7 @@ public function getEntityClass() } $alias = Inflector::singularize(substr(array_pop($parts), 0, -5)); - $name = implode('\\', array_slice($parts, 0, -1)) . '\Entity\\' . $alias; + $name = implode('\\', array_slice($parts, 0, -1)) . '\\Entity\\' . $alias; if (!class_exists($name)) { return $this->_entityClass = $default; } From efcb0ab21a9c54bb08e5ff9530e22a99fa0cc46e Mon Sep 17 00:00:00 2001 From: dereuromark Date: Tue, 20 Feb 2018 11:02:37 +0100 Subject: [PATCH 1126/2059] Fix annotation. --- Locator/LocatorInterface.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Locator/LocatorInterface.php b/Locator/LocatorInterface.php index f34e6cad..08cddd6a 100644 --- a/Locator/LocatorInterface.php +++ b/Locator/LocatorInterface.php @@ -19,7 +19,7 @@ /** * Registries for Table objects should implement this interface. * - * @method array getConfig() + * @method array getConfig($alias) * @method $this setConfig($alias, $options = null) */ interface LocatorInterface From a1a091a1dcb1d5a6603924306859c6bdb141928e Mon Sep 17 00:00:00 2001 From: mark_story Date: Tue, 27 Feb 2018 13:20:28 -0500 Subject: [PATCH 1127/2059] Fix documented types for setConditions() Refs #11767 --- Association.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association.php b/Association.php index 05a266a5..6fc96697 100644 --- a/Association.php +++ b/Association.php @@ -456,7 +456,7 @@ public function target(Table $table = null) * Sets a list of conditions to be always included when fetching records from * the target association. * - * @param array $conditions list of conditions to be used + * @param array|callable $conditions list of conditions to be used * @see \Cake\Database\Query::where() for examples on the format of the array * @return $this */ From 8d5bf50b66aa84474d655935ece2f2d612c05e70 Mon Sep 17 00:00:00 2001 From: saeideng Date: Tue, 27 Feb 2018 23:17:06 +0330 Subject: [PATCH 1128/2059] document can be return callable --- Association.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Association.php b/Association.php index 6fc96697..e028bfff 100644 --- a/Association.php +++ b/Association.php @@ -120,7 +120,7 @@ abstract class Association * A list of conditions to be always included when fetching records from * the target association * - * @var array + * @var array|callable */ protected $_conditions = []; @@ -472,7 +472,7 @@ public function setConditions($conditions) * the target association. * * @see \Cake\Database\Query::where() for examples on the format of the array - * @return array + * @return array|callable */ public function getConditions() { From 126471b62566dbb2a0cc50a16bebe38422763c93 Mon Sep 17 00:00:00 2001 From: Bryan Crowe Date: Wed, 28 Feb 2018 08:34:55 -0500 Subject: [PATCH 1129/2059] Use variadic isset --- EagerLoader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EagerLoader.php b/EagerLoader.php index 77f80736..bbb51690 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -398,7 +398,7 @@ protected function _reformatContain($associations, $original) $pointer += [$table => []]; - if (isset($options['queryBuilder']) && isset($pointer[$table]['queryBuilder'])) { + if (isset($options['queryBuilder'], $pointer[$table]['queryBuilder'])) { $first = $pointer[$table]['queryBuilder']; $second = $options['queryBuilder']; $options['queryBuilder'] = function ($query) use ($first, $second) { From 930df116129647d74408f5092ca06d05236ebba3 Mon Sep 17 00:00:00 2001 From: Eugene Ritter Date: Sat, 3 Mar 2018 20:17:44 -0600 Subject: [PATCH 1130/2059] selectAllExcept() pull request. #1125 Select columns from a table except columns in exclusion list. --- Query.php | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Query.php b/Query.php index 801413a4..062a704b 100644 --- a/Query.php +++ b/Query.php @@ -17,6 +17,7 @@ use ArrayObject; use Cake\Database\ExpressionInterface; use Cake\Database\Query as DatabaseQuery; +use Cake\Database\Schema\TableSchema; use Cake\Database\TypedResultInterface; use Cake\Database\TypeMap; use Cake\Database\ValueBinder; @@ -194,6 +195,30 @@ public function select($fields = [], $overwrite = false) return parent::select($fields, $overwrite); } + /** + * Select all the columns associated to $table except the $excludedFields. + * Excluded fields should not be aliased names. + * + * + * @param Table|Association $table The table to use to get an array of columns + * @param array $excludedFields The un-aliased column names you do not want selected from $table + * @param bool $overwrite Whether to reset/remove previous selected fields + * @return Query + */ + public function selectAllExcept($table, array $excludedFields, $overwrite = false) + { + if ($table instanceof Association) { + $table = $table->getTarget(); + } + $aliasedFields = []; + if ($table instanceof Table) { + $fields = array_diff($table->getSchema()->columns(), $excludedFields); + $aliasedFields = $this->aliasFields($fields); + } + + return $this->select($aliasedFields, $overwrite); + } + /** * Hints this object to associate the correct types when casting conditions * for the database. This is done by extracting the field types from the schema From f7b54f28c5f130c7f1a5ebd194f2c05b9f31e2b1 Mon Sep 17 00:00:00 2001 From: Gene Ritter Date: Mon, 5 Mar 2018 12:13:54 -0600 Subject: [PATCH 1131/2059] Updated doc block with FCQN Updated test to include a reset being passed Add a test to catch invalid argument exception. --- Query.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Query.php b/Query.php index 062a704b..1bd5d3ef 100644 --- a/Query.php +++ b/Query.php @@ -17,7 +17,6 @@ use ArrayObject; use Cake\Database\ExpressionInterface; use Cake\Database\Query as DatabaseQuery; -use Cake\Database\Schema\TableSchema; use Cake\Database\TypedResultInterface; use Cake\Database\TypeMap; use Cake\Database\ValueBinder; @@ -200,22 +199,25 @@ public function select($fields = [], $overwrite = false) * Excluded fields should not be aliased names. * * - * @param Table|Association $table The table to use to get an array of columns + * @param \Cake\ORM\Table|\Cake\ORM\Association $table The table to use to get an array of columns * @param array $excludedFields The un-aliased column names you do not want selected from $table * @param bool $overwrite Whether to reset/remove previous selected fields * @return Query + * @throws \InvalidArgumentException If Association|Table is not passed in first argument */ public function selectAllExcept($table, array $excludedFields, $overwrite = false) { if ($table instanceof Association) { $table = $table->getTarget(); } - $aliasedFields = []; - if ($table instanceof Table) { - $fields = array_diff($table->getSchema()->columns(), $excludedFields); - $aliasedFields = $this->aliasFields($fields); + + if (!($table instanceof Table)) { + throw new \InvalidArgumentException('You must provide either an Association or a Table object'); } + $fields = array_diff($table->getSchema()->columns(), $excludedFields); + $aliasedFields = $this->aliasFields($fields); + return $this->select($aliasedFields, $overwrite); } From e2be33bb665705dd72f245336608f56708d36570 Mon Sep 17 00:00:00 2001 From: mark_story Date: Wed, 7 Mar 2018 13:27:16 -0500 Subject: [PATCH 1132/2059] Don't hard fail on missing columns. If timestamp behavior is attached to a table that doesn't have the required columns, it should not hard fail in this way. This failure mode can be percieved as a compatibility break as it happens deep into runtime, and not during configuration/attach time. Refs #11800 --- Behavior/TimestampBehavior.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index f3322f15..f98f99a4 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -198,6 +198,10 @@ protected function _updateField($entity, $field, $refreshTimestamp) $ts = $this->timestamp(null, $refreshTimestamp); $columnType = $this->getTable()->getSchema()->getColumnType($field); + if (!$columnType) { + return; + } + /** @var \Cake\Database\Type\DateTimeType $type */ $type = Type::build($columnType); $class = $type->getDateTimeClassName(); From 16f3fca02ffc5df40cf31ea0139f0ae7b69f4539 Mon Sep 17 00:00:00 2001 From: Eugene Ritter Date: Wed, 7 Mar 2018 20:36:26 -0600 Subject: [PATCH 1133/2059] Updated documentation explaining the behavior when you call selectAllExcept 2x in the same query. --- Query.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Query.php b/Query.php index 1bd5d3ef..f61295ad 100644 --- a/Query.php +++ b/Query.php @@ -195,8 +195,12 @@ public function select($fields = [], $overwrite = false) } /** - * Select all the columns associated to $table except the $excludedFields. - * Excluded fields should not be aliased names. + * All the fields associated with the passed table except the excluded + * fields will be added to the select clause of the query. Passed excluded fields should not be aliased. + * After the first call to this method, a second call cannot be used to remove fields that have already + * been added to the query by the first. If you need to change the list after the first call, + * pass overwrite boolean true which will reset the select clause removing all previous additions. + * * * * @param \Cake\ORM\Table|\Cake\ORM\Association $table The table to use to get an array of columns From 4333e10227619939864675839121325244999393 Mon Sep 17 00:00:00 2001 From: ndm2 Date: Mon, 12 Mar 2018 15:42:36 +0100 Subject: [PATCH 1134/2059] Split TranslateBehavior::locale() into getter/setter. --- Behavior/TranslateBehavior.php | 62 ++++++++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 6 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index dd9d5114..840aaf2d 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -77,6 +77,8 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface protected $_defaultConfig = [ 'implementedFinders' => ['translations' => 'findTranslations'], 'implementedMethods' => [ + 'setLocale' => 'setLocale', + 'getLocale' => 'getLocale', 'locale' => 'locale', 'translationField' => 'translationField' ], @@ -207,7 +209,7 @@ public function setupFieldAssociations($fields, $table, $model, $strategy) */ public function beforeFind(Event $event, Query $query, ArrayObject $options) { - $locale = $this->locale(); + $locale = $this->getLocale(); if ($locale === $this->getConfig('defaultLocale')) { return; @@ -271,7 +273,7 @@ public function beforeFind(Event $event, Query $query, ArrayObject $options) */ public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options) { - $locale = $entity->get('_locale') ?: $this->locale(); + $locale = $entity->get('_locale') ?: $this->getLocale(); $newOptions = [$this->_translationTable->getAlias() => ['validate' => false]]; $options['associated'] = $newOptions + $options['associated']; @@ -412,21 +414,69 @@ public function buildMarshalMap($marshaller, $map, $options) ]; } + /** + * Sets the locale that should be used for all future find and save operations on + * the table where this behavior is attached to. + * + * When fetching records, the behavior will include the content for the locale set + * via this method, and likewise when saving data, it will save the data in that + * locale. + * + * Note that in case an entity has a `_locale` property set, that locale will win + * over the locale set via this method (and over the globally configured one for + * that matter)! + * + * @param string|null $locale The locale to use for fetching and saving records. Pass `null` + * in order to unset the current locale, and to make the behavior fall back to using the + * globally configured locale. + * @return $this + * @see \Cake\ORM\Behavior\TranslateBehavior::getLocale() + * @link https://book.cakephp.org/3.0/en/orm/behaviors/translate.html#retrieving-one-language-without-using-i18n-locale + * @link https://book.cakephp.org/3.0/en/orm/behaviors/translate.html#saving-in-another-language + */ + public function setLocale($locale) + { + $this->_locale = $locale; + + return $this; + } + + /** + * Returns the current locale. + * + * If no locale has been explicitly set via `setLocale()`, this method will return + * the currently configured global locale. + * + * @return string + * @see \Cake\I18n\I18n::getLocale() + * @see \Cake\ORM\Behavior\TranslateBehavior::setLocale() + */ + public function getLocale() + { + return $this->_locale ?: I18n::getLocale(); + } + /** * Sets all future finds for the bound table to also fetch translated fields for * the passed locale. If no value is passed, it returns the currently configured * locale * + * @deprecated 3.6.0 Use setLocale()/getLocale() instead. * @param string|null $locale The locale to use for fetching translated records * @return string */ public function locale($locale = null) { - if ($locale === null) { - return $this->_locale ?: I18n::getLocale(); + deprecationWarning( + get_called_class() . '::locale() is deprecated. ' . + 'Use setLocale()/getLocale() instead.' + ); + + if ($locale !== null) { + $this->setLocale($locale); } - return $this->_locale = (string)$locale; + return $this->getLocale(); } /** @@ -442,7 +492,7 @@ public function locale($locale = null) public function translationField($field) { $table = $this->_table; - if ($this->locale() === $this->getConfig('defaultLocale')) { + if ($this->getLocale() === $this->getConfig('defaultLocale')) { return $table->aliasField($field); } $associationName = $table->getAlias() . '_' . $field . '_translation'; From 1eb58a5776bf26f13a206d6d08ac29aa8996c9e6 Mon Sep 17 00:00:00 2001 From: saeideng Date: Tue, 13 Mar 2018 23:30:19 +0330 Subject: [PATCH 1135/2059] fix method name Refs:https://github.com/cakephp/cakephp/issues/11817 --- Table.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index cc9c2001..ce75881f 100644 --- a/Table.php +++ b/Table.php @@ -745,8 +745,8 @@ public function getDisplayField() public function displayField($key = null) { deprecationWarning( - get_called_class() . '::displayKey() is deprecated. ' . - 'Use setDisplayKey()/getDisplayKey() instead.' + get_called_class() . '::displayField() is deprecated. ' . + 'Use setDisplayField()/getDisplayField() instead.' ); if ($key !== null) { return $this->setDisplayField($key); From 2cd4cfb0de13d3db8865bc606b58f8e7441d6b83 Mon Sep 17 00:00:00 2001 From: mscherer Date: Wed, 14 Mar 2018 01:17:13 +0100 Subject: [PATCH 1136/2059] Remove combined getter/setter on QueryTrait. --- Association.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association.php b/Association.php index 5cef487c..9c03c2b9 100644 --- a/Association.php +++ b/Association.php @@ -1216,7 +1216,7 @@ protected function _appendFields($query, $surrogate, $options) */ protected function _formatAssociationResults($query, $surrogate, $options) { - $formatters = $surrogate->formatResults(); + $formatters = $surrogate->getResultFormatters(); if (!$formatters || empty($options['propertyPath'])) { return; From 0dfc6fd270934da54d9482c13e1f28d906bc1c99 Mon Sep 17 00:00:00 2001 From: mscherer Date: Wed, 14 Mar 2018 11:16:45 +0100 Subject: [PATCH 1137/2059] Deprecate getter code smell parts contain() in favor of getContain(). --- Association.php | 6 ++++-- EagerLoader.php | 26 +++++++++++++++++++++++--- LazyEagerLoader.php | 2 +- Query.php | 25 ++++++++++++++++++++----- 4 files changed, 48 insertions(+), 11 deletions(-) diff --git a/Association.php b/Association.php index 9c03c2b9..dd94f287 100644 --- a/Association.php +++ b/Association.php @@ -1262,7 +1262,7 @@ protected function _formatAssociationResults($query, $surrogate, $options) protected function _bindNewAssociations($query, $surrogate, $options) { $loader = $surrogate->getEagerLoader(); - $contain = $loader->contain(); + $contain = $loader->getContain(); $matching = $loader->getMatching(); if (!$contain && !$matching) { @@ -1275,7 +1275,9 @@ protected function _bindNewAssociations($query, $surrogate, $options) } $eagerLoader = $query->getEagerLoader(); - $eagerLoader->contain($newContain); + if ($newContain) { + $eagerLoader->contain($newContain); + } foreach ($matching as $alias => $value) { $eagerLoader->setMatching( diff --git a/EagerLoader.php b/EagerLoader.php index ca6546e3..449b9b02 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -113,6 +113,8 @@ class EagerLoader * this allows this object to calculate joins or any additional queries that * must be executed to bring the required associated data. * + * The getter part is deprecated as of 3.6.0. Use getContain() instead. + * * Accepted options per passed association: * * - foreignKey: Used to set a different field to match both tables, if set to false @@ -134,7 +136,12 @@ class EagerLoader public function contain($associations = [], callable $queryBuilder = null) { if (empty($associations)) { - return $this->_containments; + deprecationWarning( + 'Using EagerLoader::contain() as getter is deprecated. ' . + 'Use getContain() instead.' + ); + + return $this->getContain(); } if ($queryBuilder) { @@ -160,6 +167,19 @@ public function contain($associations = [], callable $queryBuilder = null) return $this->_containments = $associations; } + /** + * Gets the list of associations that should be eagerly loaded along for a + * specific table using when a query is provided. The list of associated tables + * passed to this method must have been previously set as associations using the + * Table API. + * + * @return array Containments. + */ + public function getContain() + { + return $this->_containments; + } + /** * Remove any existing non-matching based containments. * @@ -276,7 +296,7 @@ public function getMatching() $this->_matching = new static(); } - return $this->_matching->contain(); + return $this->_matching->getContain(); } /** @@ -705,7 +725,7 @@ public function associationsMap($table) { $map = []; - if (!$this->getMatching() && !$this->contain() && empty($this->_joinsMap)) { + if (!$this->getMatching() && !$this->getContain() && empty($this->_joinsMap)) { return $map; } diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 3fc743b0..d22965b4 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -51,7 +51,7 @@ public function loadInto($entities, array $contain, Table $source) $entities = new Collection($entities); $query = $this->_getQuery($entities, $contain, $source); - $associations = array_keys($query->contain()); + $associations = array_keys($query->getContain()); $entities = $this->_injectResults($entities, $query, $associations, $source); diff --git a/Query.php b/Query.php index f61295ad..5a080323 100644 --- a/Query.php +++ b/Query.php @@ -408,7 +408,7 @@ public function eagerLoader(EagerLoader $instance = null) * * If called with no arguments, this function will return an array with * with the list of previously configured associations to be contained in the - * result. + * result. This getter part is deprecated as of 3.6.0. Use getContain() instead. * * If called with an empty first argument and `$override` is set to true, the * previous list will be emptied. @@ -428,7 +428,12 @@ public function contain($associations = null, $override = false) } if ($associations === null) { - return $loader->contain(); + deprecationWarning( + 'Using Query::contain() as getter is deprecated. ' . + 'Use getContain() instead.' + ); + + return $loader->getContain(); } $queryBuilder = null; @@ -436,12 +441,22 @@ public function contain($associations = null, $override = false) $queryBuilder = $override; } - $result = $loader->contain($associations, $queryBuilder); - $this->_addAssociationsToTypeMap($this->repository(), $this->getTypeMap(), $result); + if ($associations) { + $loader->contain($associations, $queryBuilder); + } + $this->_addAssociationsToTypeMap($this->repository(), $this->getTypeMap(), $loader->getContain()); return $this; } + /** + * @return array + */ + public function getContain() + { + return $this->getEagerLoader()->getContain(); + } + /** * Clears the contained associations from the current query. * @@ -1258,7 +1273,7 @@ public function __debugInfo() 'buffered' => $this->_useBufferedResults, 'formatters' => count($this->_formatters), 'mapReducers' => count($this->_mapReduce), - 'contain' => $eagerLoader ? $eagerLoader->contain() : [], + 'contain' => $eagerLoader ? $eagerLoader->getContain() : [], 'matching' => $eagerLoader ? $eagerLoader->getMatching() : [], 'extraOptions' => $this->_options, 'repository' => $this->_repository From 0baa1c1d872fbd17885c3a636b2b8d90a5224da7 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 19 Mar 2018 02:50:14 +0530 Subject: [PATCH 1138/2059] Fix errors reported by phpstan. --- Association.php | 2 +- Table.php | 25 ++++++++++++++++++++----- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Association.php b/Association.php index dd94f287..eda333f5 100644 --- a/Association.php +++ b/Association.php @@ -502,7 +502,7 @@ public function getConditions() * @deprecated 3.4.0 Use setConditions()/getConditions() instead. * @param array|null $conditions list of conditions to be used * @see \Cake\Database\Query::where() for examples on the format of the array - * @return array + * @return array|callable */ public function conditions($conditions = null) { diff --git a/Table.php b/Table.php index ce75881f..98771ccc 100644 --- a/Table.php +++ b/Table.php @@ -749,7 +749,9 @@ public function displayField($key = null) 'Use setDisplayField()/getDisplayField() instead.' ); if ($key !== null) { - return $this->setDisplayField($key); + $this->setDisplayField($key); + + return $key; } return $this->getDisplayField(); @@ -931,6 +933,7 @@ public function behaviors() */ public function getBehavior($name) { + /** @var \Cake\ORM\Behavior $behavior */ $behavior = $this->_behaviors->get($name); if ($behavior === null) { throw new InvalidArgumentException(sprintf( @@ -1126,7 +1129,10 @@ public function belongsTo($associated, array $options = []) { $options += ['sourceTable' => $this]; - return $this->_associations->load(BelongsTo::class, $associated, $options); + /** @var \Cake\ORM\Association\BelongsTo $association */ + $association = $this->_associations->load(BelongsTo::class, $associated, $options); + + return $association; } /** @@ -1169,7 +1175,10 @@ public function hasOne($associated, array $options = []) { $options += ['sourceTable' => $this]; - return $this->_associations->load(HasOne::class, $associated, $options); + /** @var \Cake\ORM\Association\HasOne $association */ + $association = $this->_associations->load(HasOne::class, $associated, $options); + + return $association; } /** @@ -1218,7 +1227,10 @@ public function hasMany($associated, array $options = []) { $options += ['sourceTable' => $this]; - return $this->_associations->load(HasMany::class, $associated, $options); + /** @var \Cake\ORM\Association\HasMany $association */ + $association = $this->_associations->load(HasMany::class, $associated, $options); + + return $association; } /** @@ -1269,7 +1281,10 @@ public function belongsToMany($associated, array $options = []) { $options += ['sourceTable' => $this]; - return $this->_associations->load(BelongsToMany::class, $associated, $options); + /** @var \Cake\ORM\Association\BelongsToMany $association */ + $association = $this->_associations->load(BelongsToMany::class, $associated, $options); + + return $association; } /** From 479e24a27f958c4be13404fc256ed670909a5315 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Mon, 19 Mar 2018 13:37:32 -0500 Subject: [PATCH 1139/2059] Fixed TimestampBehavior failing when type isn't DateTimeType --- Behavior/TimestampBehavior.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index f98f99a4..48d0539e 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -204,6 +204,13 @@ protected function _updateField($entity, $field, $refreshTimestamp) /** @var \Cake\Database\Type\DateTimeType $type */ $type = Type::build($columnType); + + if (!$type instanceof Type\DateTimeType) { + $entity->set($field, (string)$ts); + + return; + } + $class = $type->getDateTimeClassName(); $entity->set($field, new $class($ts)); From d9017b97193a73762e5dceaed3acc75142b197d2 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Mon, 19 Mar 2018 16:40:51 -0500 Subject: [PATCH 1140/2059] Added deprecation warning when using non-DateTimeTypes in TimestampBehavior --- Behavior/TimestampBehavior.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 48d0539e..5cbcfcf5 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -206,6 +206,7 @@ protected function _updateField($entity, $field, $refreshTimestamp) $type = Type::build($columnType); if (!$type instanceof Type\DateTimeType) { + deprecationWarning('TimestampBehavior support for column types other than DateTimeType will be removed in 4.0.'); $entity->set($field, (string)$ts); return; From 8d27af1f794c0d82f36c415211965b3ec1dca3dc Mon Sep 17 00:00:00 2001 From: Mark Sch Date: Tue, 20 Mar 2018 16:34:21 +0100 Subject: [PATCH 1141/2059] Improve doc block to be more precise. --- Table.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index 78d39636..557f60ed 100644 --- a/Table.php +++ b/Table.php @@ -2024,9 +2024,9 @@ protected function _update($entity, $data) * any one of the records fails to save due to failed validation or database * error. * - * @param array|\Cake\ORM\ResultSet $entities Entities to save. + * @param \Cake\Datasource\EntityInterface[]|\Cake\ORM\ResultSet $entities Entities to save. * @param array|\ArrayAccess $options Options used when calling Table::save() for each entity. - * @return bool|array|\Cake\ORM\ResultSet False on failure, entities list on success. + * @return bool|\Cake\Datasource\EntityInterface[]|\Cake\ORM\ResultSet False on failure, entities list on success. */ public function saveMany($entities, $options = []) { From efcbb67d1483277057daaf0400291f4e6d580dd4 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 1 Apr 2018 23:31:34 -0400 Subject: [PATCH 1142/2059] Don't mutate ExistsIn fields. We should not mutate fields when applying an ExistsIn rule as it causes problems with saveMany(). Thanks to @Nic0tiN for the test case Refs #11880 --- Rule/ExistsIn.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 47afd05e..ba1a4c8e 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -92,6 +92,7 @@ public function __invoke(EntityInterface $entity, array $options) $this->_repository = $repository; } + $fields = $this->_fields; $source = $target = $this->_repository; $isAssociation = $target instanceof Association; $bindingKey = $isAssociation ? (array)$target->getBindingKey() : (array)$target->getPrimaryKey(); @@ -118,9 +119,9 @@ public function __invoke(EntityInterface $entity, array $options) if ($this->_options['allowNullableNulls']) { $schema = $source->getSchema(); - foreach ($this->_fields as $i => $field) { + foreach ($fields as $i => $field) { if ($schema->getColumn($field) && $schema->isNullable($field) && $entity->get($field) === null) { - unset($bindingKey[$i], $this->_fields[$i]); + unset($bindingKey[$i], $fields[$i]); } } } @@ -131,7 +132,7 @@ public function __invoke(EntityInterface $entity, array $options) ); $conditions = array_combine( $primary, - $entity->extract($this->_fields) + $entity->extract($fields) ); return $target->exists($conditions); From 22b5f1c289df87df98eb5e2713bfc67f5e8455ef Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 1 Apr 2018 22:43:52 +0530 Subject: [PATCH 1143/2059] Add QueryTrait::getRepository(). Deprecate using QueryInterface::repository() as getter. --- Behavior/TranslateBehavior.php | 2 +- EagerLoader.php | 2 +- Query.php | 28 ++++++++++++++++------------ ResultSet.php | 4 ++-- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 840aaf2d..173db6d3 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -218,7 +218,7 @@ public function beforeFind(Event $event, Query $query, ArrayObject $options) $conditions = function ($field, $locale, $query, $select) { return function ($q) use ($field, $locale, $query, $select) { /* @var \Cake\Datasource\QueryInterface $q */ - $q->where([$q->repository()->aliasField('locale') => $locale]); + $q->where([$q->getRepository()->aliasField('locale') => $locale]); /* @var \Cake\ORM\Query $query */ if ($query->isAutoFieldsEnabled() || diff --git a/EagerLoader.php b/EagerLoader.php index 449b9b02..749f4366 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -671,7 +671,7 @@ protected function _resolveJoins($associations, $matching = []) */ public function loadExternal($query, $statement) { - $external = $this->externalAssociations($query->repository()); + $external = $this->externalAssociations($query->getRepository()); if (empty($external)) { return $statement; } diff --git a/Query.php b/Query.php index 5a080323..210201bf 100644 --- a/Query.php +++ b/Query.php @@ -444,7 +444,11 @@ public function contain($associations = null, $override = false) if ($associations) { $loader->contain($associations, $queryBuilder); } - $this->_addAssociationsToTypeMap($this->repository(), $this->getTypeMap(), $loader->getContain()); + $this->_addAssociationsToTypeMap( + $this->getRepository(), + $this->getTypeMap(), + $loader->getContain() + ); return $this; } @@ -551,7 +555,7 @@ protected function _addAssociationsToTypeMap($table, $typeMap, $associations) public function matching($assoc, callable $builder = null) { $result = $this->getEagerLoader()->setMatching($assoc, $builder)->getMatching(); - $this->_addAssociationsToTypeMap($this->repository(), $this->getTypeMap(), $result); + $this->_addAssociationsToTypeMap($this->getRepository(), $this->getTypeMap(), $result); $this->_dirty(); return $this; @@ -628,7 +632,7 @@ public function leftJoinWith($assoc, callable $builder = null) 'fields' => false ]) ->getMatching(); - $this->_addAssociationsToTypeMap($this->repository(), $this->getTypeMap(), $result); + $this->_addAssociationsToTypeMap($this->getRepository(), $this->getTypeMap(), $result); $this->_dirty(); return $this; @@ -677,7 +681,7 @@ public function innerJoinWith($assoc, callable $builder = null) 'fields' => false ]) ->getMatching(); - $this->_addAssociationsToTypeMap($this->repository(), $this->getTypeMap(), $result); + $this->_addAssociationsToTypeMap($this->getRepository(), $this->getTypeMap(), $result); $this->_dirty(); return $this; @@ -742,7 +746,7 @@ public function notMatching($assoc, callable $builder = null) 'negateMatch' => true ]) ->getMatching(); - $this->_addAssociationsToTypeMap($this->repository(), $this->getTypeMap(), $result); + $this->_addAssociationsToTypeMap($this->getRepository(), $this->getTypeMap(), $result); $this->_dirty(); return $this; @@ -1051,7 +1055,7 @@ public function all() public function triggerBeforeFind() { if (!$this->_beforeFindFired && $this->_type === 'select') { - $table = $this->repository(); + $table = $this->getRepository(); $this->_beforeFindFired = true; /* @var \Cake\Event\EventDispatcherInterface $table */ $table->dispatchEvent('Model.beforeFind', [ @@ -1134,11 +1138,11 @@ protected function _addDefaultFields() if (!count($select) || $this->_autoFields === true) { $this->_hasFields = false; - $this->select($this->repository()->getSchema()->columns()); + $this->select($this->getRepository()->getSchema()->columns()); $select = $this->clause('select'); } - $aliased = $this->aliasFields($select, $this->repository()->getAlias()); + $aliased = $this->aliasFields($select, $this->getRepository()->getAlias()); $this->select($aliased, true); } @@ -1175,7 +1179,7 @@ protected function _addDefaultSelectTypes() */ public function find($finder, array $options = []) { - return $this->repository()->callFinder($finder, $this, $options); + return $this->getRepository()->callFinder($finder, $this, $options); } /** @@ -1202,7 +1206,7 @@ protected function _dirty() */ public function update($table = null) { - $table = $table ?: $this->repository()->getTable(); + $table = $table ?: $this->getRepository()->getTable(); return parent::update($table); } @@ -1218,7 +1222,7 @@ public function update($table = null) */ public function delete($table = null) { - $repo = $this->repository(); + $repo = $this->getRepository(); $this->from([$repo->getAlias() => $repo->getTable()]); return parent::delete(); @@ -1239,7 +1243,7 @@ public function delete($table = null) */ public function insert(array $columns, array $types = []) { - $table = $this->repository()->getTable(); + $table = $this->getRepository()->getTable(); $this->into($table); return parent::insert($columns, $types); diff --git a/ResultSet.php b/ResultSet.php index 3ad2cb1d..725d583c 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -175,10 +175,10 @@ class ResultSet implements ResultSetInterface */ public function __construct($query, $statement) { - $repository = $query->repository(); + $repository = $query->getRepository(); $this->_statement = $statement; $this->_driver = $query->getConnection()->getDriver(); - $this->_defaultTable = $query->repository(); + $this->_defaultTable = $query->getRepository(); $this->_calculateAssociationMap($query); $this->_hydrate = $query->isHydrationEnabled(); $this->_entityClass = $repository->getEntityClass(); From d825cf6777d714936bbbdd333e25f895e0124639 Mon Sep 17 00:00:00 2001 From: Dominik Schmelz Date: Mon, 16 Apr 2018 21:21:29 +0200 Subject: [PATCH 1144/2059] Add missing phpDoc --- Behavior/TimestampBehavior.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 5cbcfcf5..63778c2e 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -22,6 +22,10 @@ use DateTime; use UnexpectedValueException; +/** + * Class TimestampBehavior + * @package Cake\ORM\Behavior + */ class TimestampBehavior extends Behavior { From 47ff6fb257c10bd550a1337cb1ece690c3c63026 Mon Sep 17 00:00:00 2001 From: Dominik Schmelz Date: Mon, 16 Apr 2018 21:23:44 +0200 Subject: [PATCH 1145/2059] Add missing @throws in phpDocs --- Association/BelongsToMany.php | 9 +++------ Association/HasMany.php | 1 + Behavior.php | 1 + Behavior/TreeBehavior.php | 6 ++++-- Table.php | 2 ++ 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 506801ab..48a7ef7f 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -886,9 +886,8 @@ protected function _saveLinks(EntityInterface $sourceEntity, $targetEntities, $o * @param array $targetEntities list of entities belonging to the `target` side * of this association * @param array $options list of options to be passed to the internal `save` call - * @throws \InvalidArgumentException when any of the values in $targetEntities is - * detected to not be already persisted * @return bool true on success, false otherwise + * @throws \Exception */ public function link(EntityInterface $sourceEntity, array $targetEntities, array $options = []) { @@ -937,9 +936,8 @@ function () use ($sourceEntity, $targetEntities, $options) { * this association * @param array|bool $options list of options to be passed to the internal `delete` call, * or a `boolean` - * @throws \InvalidArgumentException if non persisted entities are passed or if - * any of them is lacking a primary key value * @return bool Success + * @throws \Exception */ public function unlink(EntityInterface $sourceEntity, array $targetEntities, $options = []) { @@ -1191,9 +1189,8 @@ protected function _appendJunctionJoin($query, $conditions) * @param array $targetEntities list of entities from the target table to be linked * @param array $options list of options to be passed to the internal `save`/`delete` calls * when persisting/updating new links, or deleting existing ones - * @throws \InvalidArgumentException if non persisted entities are passed or if - * any of them is lacking a primary key value * @return bool success + * @throws \Exception */ public function replaceLinks(EntityInterface $sourceEntity, array $targetEntities, array $options = []) { diff --git a/Association/HasMany.php b/Association/HasMany.php index 725c2ebb..57dd4f8f 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -282,6 +282,7 @@ protected function _saveTarget(array $foreignKeyReference, EntityInterface $pare * of this association * @param array $options list of options to be passed to the internal `save` call * @return bool true on success, false otherwise + * @throws \Exception */ public function link(EntityInterface $sourceEntity, array $targetEntities, array $options = []) { diff --git a/Behavior.php b/Behavior.php index 7f434d77..21119b64 100644 --- a/Behavior.php +++ b/Behavior.php @@ -365,6 +365,7 @@ public function implementedMethods() * declared on Cake\ORM\Behavior * * @return array + * @throws \ReflectionException */ protected function _reflectionCache() { diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index ff67b406..e6af8c4d 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -548,6 +548,7 @@ public function formatTreeList(Query $query, array $options = []) * @param \Cake\Datasource\EntityInterface $node The node to remove from the tree * @return \Cake\Datasource\EntityInterface|false the node after being removed from the tree or * false on error + * @throws \Exception */ public function removeFromTree(EntityInterface $node) { @@ -607,8 +608,8 @@ protected function _removeFromTree($node) * * @param \Cake\Datasource\EntityInterface $node The node to move * @param int|bool $number How many places to move the node, or true to move to first position - * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found * @return \Cake\Datasource\EntityInterface|bool $node The node after being moved or false on failure + * @throws \Exception */ public function moveUp(EntityInterface $node, $number = 1) { @@ -697,8 +698,8 @@ protected function _moveUp($node, $number) * * @param \Cake\Datasource\EntityInterface $node The node to move * @param int|bool $number How many places to move the node or true to move to last position - * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found * @return \Cake\Datasource\EntityInterface|bool the entity after being moved or false on failure + * @throws \Exception */ public function moveDown(EntityInterface $node, $number = 1) { @@ -813,6 +814,7 @@ protected function _getNode($id) * parent column. * * @return void + * @throws \Exception */ public function recover() { diff --git a/Table.php b/Table.php index 5c46fbbd..c16d140b 100644 --- a/Table.php +++ b/Table.php @@ -1616,6 +1616,7 @@ public function get($primaryKey, $options = []) * @param callable $worker The worker that will run inside the transaction. * @param bool $atomic Whether to execute the worker inside a database transaction. * @return mixed + * @throws \Exception */ protected function _executeTransaction(callable $worker, $atomic = true) { @@ -2192,6 +2193,7 @@ protected function _update($entity, $data) * @param \Cake\Datasource\EntityInterface[]|\Cake\ORM\ResultSet $entities Entities to save. * @param array|\ArrayAccess $options Options used when calling Table::save() for each entity. * @return bool|\Cake\Datasource\EntityInterface[]|\Cake\ORM\ResultSet False on failure, entities list on success. + * @throws \Exception */ public function saveMany($entities, $options = []) { From a82c0a8437965b4224dd9d1e24aa9f97c295699c Mon Sep 17 00:00:00 2001 From: Dominik Schmelz Date: Mon, 16 Apr 2018 23:04:15 +0200 Subject: [PATCH 1146/2059] Fix all phpDocs @throws FQN --- Association/BelongsToMany.php | 3 +-- Association/Loader/SelectLoader.php | 2 +- AssociationCollection.php | 2 +- Behavior.php | 2 ++ Table.php | 10 +++++++--- 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 48a7ef7f..fc4b84d7 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -702,10 +702,9 @@ public function saveStrategy($strategy = null) * * @param \Cake\Datasource\EntityInterface $entity an entity from the source table * @param array $options options to be passed to the save method in the target table - * @throws \InvalidArgumentException if the property representing the association - * in the parent entity cannot be traversed * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns * the saved entity + * @throws \Exception * @see \Cake\ORM\Table::save() * @see \Cake\ORM\Association\BelongsToMany::replaceLinks() */ diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index bff533c7..2c01816d 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -235,7 +235,7 @@ protected function _extractFinder($finderData) * @param \Cake\ORM\Query $fetchQuery The association fetching query * @param array $key The foreign key fields to check * @return void - * @throws InvalidArgumentException + * @throws \InvalidArgumentException */ protected function _assertFieldsPresent($fetchQuery, $key) { diff --git a/AssociationCollection.php b/AssociationCollection.php index 5098116c..574648f0 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -79,7 +79,7 @@ public function add($alias, Association $association) * @param string $associated The alias for the target table. * @param array $options List of options to configure the association definition. * @return \Cake\ORM\Association - * @throws InvalidArgumentException + * @throws \InvalidArgumentException */ public function load($className, $associated, array $options = []) { diff --git a/Behavior.php b/Behavior.php index 21119b64..ab11b674 100644 --- a/Behavior.php +++ b/Behavior.php @@ -315,6 +315,7 @@ public function implementedEvents() * method list. See core behaviors for examples * * @return array + * @throws \ReflectionException */ public function implementedFinders() { @@ -346,6 +347,7 @@ public function implementedFinders() * method list. See core behaviors for examples * * @return array + * @throws \ReflectionException */ public function implementedMethods() { diff --git a/Table.php b/Table.php index c16d140b..0773e100 100644 --- a/Table.php +++ b/Table.php @@ -852,7 +852,7 @@ public function entityClass($name = null) * @param string $name The name of the behavior. Can be a short class reference. * @param array $options The options for the behavior to use. * @return $this - * @throws \RuntimeException If a behavior is being reloaded. + * @throws \Exception * @see \Cake\ORM\Behavior */ public function addBehavior($name, array $options = []) @@ -905,6 +905,7 @@ public function addBehaviors(array $behaviors) * * @param string $name The alias that the behavior was added with. * @return $this + * @throws \Exception * @see \Cake\ORM\Behavior */ public function removeBehavior($name) @@ -1673,6 +1674,7 @@ protected function _transactionCommitted($atomic, $primary) * is persisted. * @param array $options The options to use when saving. * @return \Cake\Datasource\EntityInterface An entity. + * @throws \Exception */ public function findOrCreate($search, callable $callback = null, $options = []) { @@ -1873,8 +1875,10 @@ public function exists($conditions) * $articles->save($entity, ['associated' => false]); * ``` * - * @throws \Cake\ORM\Exception\RolledbackTransactionException If the transaction - * is aborted in the afterSave event. + * @param \Cake\Datasource\EntityInterface $entity + * @param array $options + * @return bool|\Cake\Datasource\EntityInterface|mixed + * @throws \Exception */ public function save(EntityInterface $entity, $options = []) { From ca0e2b45faa25205572f75e1ea21008aa99f9a07 Mon Sep 17 00:00:00 2001 From: saeid Date: Fri, 20 Apr 2018 00:24:48 +0430 Subject: [PATCH 1147/2059] Update requirements in subsplits. --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index f8e20729..14328939 100644 --- a/composer.json +++ b/composer.json @@ -24,10 +24,10 @@ }, "require": { "php": ">=5.6.0", - "cakephp/collection": "^3.0.0", + "cakephp/collection": "^3.6.0", "cakephp/core": "^3.6.0", - "cakephp/datasource": "^3.1.2", - "cakephp/database": "^3.1.4", + "cakephp/datasource": "^3.6.0", + "cakephp/database": "^3.6.0", "cakephp/event": "^3.6.0", "cakephp/utility": "^3.6.0", "cakephp/validation": "^3.6.0" From 6aa97677980715db96105b83d773765584533fdd Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 21 Apr 2018 11:12:44 +0530 Subject: [PATCH 1148/2059] Use new method. --- ResultSet.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ResultSet.php b/ResultSet.php index 725d583c..5fc721b8 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -447,7 +447,7 @@ protected function _getTypes($table, $fields) { $types = []; $schema = $table->getSchema(); - $map = array_keys(Type::map() + ['string' => 1, 'text' => 1, 'boolean' => 1]); + $map = array_keys(Type::getMap() + ['string' => 1, 'text' => 1, 'boolean' => 1]); $typeMap = array_combine( $map, array_map(['Cake\Database\Type', 'build'], $map) From 57909ea5db2035a985cdba7af07dc6723debb70e Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 21 Apr 2018 11:17:23 +0530 Subject: [PATCH 1149/2059] Remove deprecate and unused methods. --- ResultSet.php | 46 ---------------------------------------------- 1 file changed, 46 deletions(-) diff --git a/ResultSet.php b/ResultSet.php index 5fc721b8..2ab0b59a 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -423,52 +423,6 @@ protected function _calculateColumnMap($query) $this->_map = $map; } - /** - * Creates a map of Type converter classes for each of the columns that should - * be fetched by this object. - * - * @deprecated 3.2.0 Not used anymore. Type casting is done at the statement level - * @return void - */ - protected function _calculateTypeMap() - { - deprecationWarning('ResultSet::_calculateTypeMap() is deprecated, and will be removed in 4.0.0.'); - } - - /** - * Returns the Type classes for each of the passed fields belonging to the - * table. - * - * @param \Cake\ORM\Table $table The table from which to get the schema - * @param array $fields The fields whitelist to use for fields in the schema. - * @return array - */ - protected function _getTypes($table, $fields) - { - $types = []; - $schema = $table->getSchema(); - $map = array_keys(Type::getMap() + ['string' => 1, 'text' => 1, 'boolean' => 1]); - $typeMap = array_combine( - $map, - array_map(['Cake\Database\Type', 'build'], $map) - ); - - foreach (['string', 'text'] as $t) { - if (get_class($typeMap[$t]) === 'Cake\Database\Type') { - unset($typeMap[$t]); - } - } - - foreach (array_intersect($fields, $schema->columns()) as $col) { - $typeName = $schema->getColumnType($col); - if (isset($typeMap[$typeName])) { - $types[$col] = $typeMap[$typeName]; - } - } - - return $types; - } - /** * Helper function to fetch the next result from the statement or * seeded results. From c9be9f25906a201341c2f7a072c640255be600eb Mon Sep 17 00:00:00 2001 From: Dominik Schmelz Date: Tue, 24 Apr 2018 10:14:32 +0200 Subject: [PATCH 1150/2059] Remove @package from classes --- Behavior/TimestampBehavior.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 63778c2e..68e24af5 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -24,7 +24,6 @@ /** * Class TimestampBehavior - * @package Cake\ORM\Behavior */ class TimestampBehavior extends Behavior { From a82e0d5f9c793fbe09bfef0d3f562e0141094d80 Mon Sep 17 00:00:00 2001 From: Dominik Schmelz Date: Tue, 24 Apr 2018 10:40:16 +0200 Subject: [PATCH 1151/2059] Added correct generalized @throws as well as specialized @throws --- Association/BelongsToMany.php | 4 ++++ Behavior/TreeBehavior.php | 2 ++ Table.php | 2 ++ 3 files changed, 8 insertions(+) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index fc4b84d7..5bd89cd3 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -704,6 +704,7 @@ public function saveStrategy($strategy = null) * @param array $options options to be passed to the save method in the target table * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns * the saved entity + * @throws \InvalidArgumentException if the property representing the association in the parent entity cannot be traversed * @throws \Exception * @see \Cake\ORM\Table::save() * @see \Cake\ORM\Association\BelongsToMany::replaceLinks() @@ -886,6 +887,7 @@ protected function _saveLinks(EntityInterface $sourceEntity, $targetEntities, $o * of this association * @param array $options list of options to be passed to the internal `save` call * @return bool true on success, false otherwise + * @throws \InvalidArgumentException when any of the values in $targetEntities is detected to not be already persisted * @throws \Exception */ public function link(EntityInterface $sourceEntity, array $targetEntities, array $options = []) @@ -936,6 +938,7 @@ function () use ($sourceEntity, $targetEntities, $options) { * @param array|bool $options list of options to be passed to the internal `delete` call, * or a `boolean` * @return bool Success + * @throws \InvalidArgumentException if non persisted entities are passed or if any of them is lacking a primary key value * @throws \Exception */ public function unlink(EntityInterface $sourceEntity, array $targetEntities, $options = []) @@ -1190,6 +1193,7 @@ protected function _appendJunctionJoin($query, $conditions) * when persisting/updating new links, or deleting existing ones * @return bool success * @throws \Exception + * @throws \InvalidArgumentException if non persisted entities are passed or if any of them is lacking a primary key value */ public function replaceLinks(EntityInterface $sourceEntity, array $targetEntities, array $options = []) { diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index e6af8c4d..a624c4c3 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -609,6 +609,7 @@ protected function _removeFromTree($node) * @param \Cake\Datasource\EntityInterface $node The node to move * @param int|bool $number How many places to move the node, or true to move to first position * @return \Cake\Datasource\EntityInterface|bool $node The node after being moved or false on failure + * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found * @throws \Exception */ public function moveUp(EntityInterface $node, $number = 1) @@ -699,6 +700,7 @@ protected function _moveUp($node, $number) * @param \Cake\Datasource\EntityInterface $node The node to move * @param int|bool $number How many places to move the node or true to move to last position * @return \Cake\Datasource\EntityInterface|bool the entity after being moved or false on failure + * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found * @throws \Exception */ public function moveDown(EntityInterface $node, $number = 1) diff --git a/Table.php b/Table.php index 0773e100..c298b320 100644 --- a/Table.php +++ b/Table.php @@ -853,6 +853,7 @@ public function entityClass($name = null) * @param array $options The options for the behavior to use. * @return $this * @throws \Exception + * @throws \RuntimeException If a behavior is being reloaded. * @see \Cake\ORM\Behavior */ public function addBehavior($name, array $options = []) @@ -1878,6 +1879,7 @@ public function exists($conditions) * @param \Cake\Datasource\EntityInterface $entity * @param array $options * @return bool|\Cake\Datasource\EntityInterface|mixed + * @throws \Cake\ORM\Exception\RolledbackTransactionException If the transaction is aborted in the afterSave event. * @throws \Exception */ public function save(EntityInterface $entity, $options = []) From dc52e854f911a7bc2ff7bc80db8b648d886d1a2a Mon Sep 17 00:00:00 2001 From: Dominik Schmelz Date: Tue, 24 Apr 2018 13:15:51 +0200 Subject: [PATCH 1152/2059] Revert documentation of base Exceptions --- Association/BelongsToMany.php | 16 ++++++++-------- Association/HasMany.php | 1 - Behavior/TreeBehavior.php | 8 ++------ Table.php | 6 ------ 4 files changed, 10 insertions(+), 21 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 5bd89cd3..506801ab 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -702,10 +702,10 @@ public function saveStrategy($strategy = null) * * @param \Cake\Datasource\EntityInterface $entity an entity from the source table * @param array $options options to be passed to the save method in the target table + * @throws \InvalidArgumentException if the property representing the association + * in the parent entity cannot be traversed * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns * the saved entity - * @throws \InvalidArgumentException if the property representing the association in the parent entity cannot be traversed - * @throws \Exception * @see \Cake\ORM\Table::save() * @see \Cake\ORM\Association\BelongsToMany::replaceLinks() */ @@ -886,9 +886,9 @@ protected function _saveLinks(EntityInterface $sourceEntity, $targetEntities, $o * @param array $targetEntities list of entities belonging to the `target` side * of this association * @param array $options list of options to be passed to the internal `save` call + * @throws \InvalidArgumentException when any of the values in $targetEntities is + * detected to not be already persisted * @return bool true on success, false otherwise - * @throws \InvalidArgumentException when any of the values in $targetEntities is detected to not be already persisted - * @throws \Exception */ public function link(EntityInterface $sourceEntity, array $targetEntities, array $options = []) { @@ -937,9 +937,9 @@ function () use ($sourceEntity, $targetEntities, $options) { * this association * @param array|bool $options list of options to be passed to the internal `delete` call, * or a `boolean` + * @throws \InvalidArgumentException if non persisted entities are passed or if + * any of them is lacking a primary key value * @return bool Success - * @throws \InvalidArgumentException if non persisted entities are passed or if any of them is lacking a primary key value - * @throws \Exception */ public function unlink(EntityInterface $sourceEntity, array $targetEntities, $options = []) { @@ -1191,9 +1191,9 @@ protected function _appendJunctionJoin($query, $conditions) * @param array $targetEntities list of entities from the target table to be linked * @param array $options list of options to be passed to the internal `save`/`delete` calls * when persisting/updating new links, or deleting existing ones + * @throws \InvalidArgumentException if non persisted entities are passed or if + * any of them is lacking a primary key value * @return bool success - * @throws \Exception - * @throws \InvalidArgumentException if non persisted entities are passed or if any of them is lacking a primary key value */ public function replaceLinks(EntityInterface $sourceEntity, array $targetEntities, array $options = []) { diff --git a/Association/HasMany.php b/Association/HasMany.php index 57dd4f8f..725c2ebb 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -282,7 +282,6 @@ protected function _saveTarget(array $foreignKeyReference, EntityInterface $pare * of this association * @param array $options list of options to be passed to the internal `save` call * @return bool true on success, false otherwise - * @throws \Exception */ public function link(EntityInterface $sourceEntity, array $targetEntities, array $options = []) { diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index a624c4c3..ff67b406 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -548,7 +548,6 @@ public function formatTreeList(Query $query, array $options = []) * @param \Cake\Datasource\EntityInterface $node The node to remove from the tree * @return \Cake\Datasource\EntityInterface|false the node after being removed from the tree or * false on error - * @throws \Exception */ public function removeFromTree(EntityInterface $node) { @@ -608,9 +607,8 @@ protected function _removeFromTree($node) * * @param \Cake\Datasource\EntityInterface $node The node to move * @param int|bool $number How many places to move the node, or true to move to first position - * @return \Cake\Datasource\EntityInterface|bool $node The node after being moved or false on failure * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found - * @throws \Exception + * @return \Cake\Datasource\EntityInterface|bool $node The node after being moved or false on failure */ public function moveUp(EntityInterface $node, $number = 1) { @@ -699,9 +697,8 @@ protected function _moveUp($node, $number) * * @param \Cake\Datasource\EntityInterface $node The node to move * @param int|bool $number How many places to move the node or true to move to last position - * @return \Cake\Datasource\EntityInterface|bool the entity after being moved or false on failure * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found - * @throws \Exception + * @return \Cake\Datasource\EntityInterface|bool the entity after being moved or false on failure */ public function moveDown(EntityInterface $node, $number = 1) { @@ -816,7 +813,6 @@ protected function _getNode($id) * parent column. * * @return void - * @throws \Exception */ public function recover() { diff --git a/Table.php b/Table.php index c298b320..8f9af061 100644 --- a/Table.php +++ b/Table.php @@ -852,7 +852,6 @@ public function entityClass($name = null) * @param string $name The name of the behavior. Can be a short class reference. * @param array $options The options for the behavior to use. * @return $this - * @throws \Exception * @throws \RuntimeException If a behavior is being reloaded. * @see \Cake\ORM\Behavior */ @@ -906,7 +905,6 @@ public function addBehaviors(array $behaviors) * * @param string $name The alias that the behavior was added with. * @return $this - * @throws \Exception * @see \Cake\ORM\Behavior */ public function removeBehavior($name) @@ -1618,7 +1616,6 @@ public function get($primaryKey, $options = []) * @param callable $worker The worker that will run inside the transaction. * @param bool $atomic Whether to execute the worker inside a database transaction. * @return mixed - * @throws \Exception */ protected function _executeTransaction(callable $worker, $atomic = true) { @@ -1675,7 +1672,6 @@ protected function _transactionCommitted($atomic, $primary) * is persisted. * @param array $options The options to use when saving. * @return \Cake\Datasource\EntityInterface An entity. - * @throws \Exception */ public function findOrCreate($search, callable $callback = null, $options = []) { @@ -1880,7 +1876,6 @@ public function exists($conditions) * @param array $options * @return bool|\Cake\Datasource\EntityInterface|mixed * @throws \Cake\ORM\Exception\RolledbackTransactionException If the transaction is aborted in the afterSave event. - * @throws \Exception */ public function save(EntityInterface $entity, $options = []) { @@ -2199,7 +2194,6 @@ protected function _update($entity, $data) * @param \Cake\Datasource\EntityInterface[]|\Cake\ORM\ResultSet $entities Entities to save. * @param array|\ArrayAccess $options Options used when calling Table::save() for each entity. * @return bool|\Cake\Datasource\EntityInterface[]|\Cake\ORM\ResultSet False on failure, entities list on success. - * @throws \Exception */ public function saveMany($entities, $options = []) { From 165c1af63f4ed9606a37d178294d7ea2e507e739 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 25 Apr 2018 19:56:03 +0530 Subject: [PATCH 1153/2059] Revert "Fix up behavior callbacks for clean inheritance and consistency." This reverts commit 9e93097b504583a84a827a2c4a41a24e16650470. Changing argument typehints is not backwards compatible and causes errors when these methods are overridden in extending classes. --- Behavior/CounterCacheBehavior.php | 7 +++---- Behavior/TranslateBehavior.php | 5 ++--- Behavior/TreeBehavior.php | 10 +++------- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 082013e8..b89a42d3 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -14,7 +14,6 @@ */ namespace Cake\ORM\Behavior; -use ArrayObject; use Cake\Datasource\EntityInterface; use Cake\Event\Event; use Cake\ORM\Association; @@ -119,7 +118,7 @@ class CounterCacheBehavior extends Behavior * @param \ArrayObject $options The options for the query * @return void */ - public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options) + public function beforeSave(Event $event, EntityInterface $entity, $options) { if (isset($options['ignoreCounterCache']) && $options['ignoreCounterCache'] === true) { return; @@ -156,7 +155,7 @@ public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $o * @param \ArrayObject $options The options for the query * @return void */ - public function afterSave(Event $event, EntityInterface $entity, ArrayObject $options) + public function afterSave(Event $event, EntityInterface $entity, $options) { if (isset($options['ignoreCounterCache']) && $options['ignoreCounterCache'] === true) { return; @@ -176,7 +175,7 @@ public function afterSave(Event $event, EntityInterface $entity, ArrayObject $op * @param \ArrayObject $options The options for the query * @return void */ - public function afterDelete(Event $event, EntityInterface $entity, ArrayObject $options) + public function afterDelete(Event $event, EntityInterface $entity, $options) { if (isset($options['ignoreCounterCache']) && $options['ignoreCounterCache'] === true) { return; diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 173db6d3..55244e69 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -207,7 +207,7 @@ public function setupFieldAssociations($fields, $table, $model, $strategy) * @param \ArrayObject $options The options for the query * @return void */ - public function beforeFind(Event $event, Query $query, ArrayObject $options) + public function beforeFind(Event $event, Query $query, $options) { $locale = $this->getLocale(); @@ -364,10 +364,9 @@ public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $o * * @param \Cake\Event\Event $event The beforeSave event that was fired * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved - * @param \ArrayObject $options Options. * @return void */ - public function afterSave(Event $event, EntityInterface $entity, ArrayObject $options) + public function afterSave(Event $event, EntityInterface $entity) { $entity->unsetProperty('_i18n'); } diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index ff67b406..9c4c44f4 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -14,7 +14,6 @@ */ namespace Cake\ORM\Behavior; -use ArrayObject; use Cake\Database\Expression\IdentifierExpression; use Cake\Datasource\EntityInterface; use Cake\Datasource\Exception\RecordNotFoundException; @@ -92,11 +91,10 @@ public function initialize(array $config) * * @param \Cake\Event\Event $event The beforeSave event that was fired * @param \Cake\Datasource\EntityInterface $entity the entity that is going to be saved - * @param \ArrayObject $options Options. * @return void * @throws \RuntimeException if the parent to set for the node is invalid */ - public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options) + public function beforeSave(Event $event, EntityInterface $entity) { $isNew = $entity->isNew(); $config = $this->getConfig(); @@ -162,10 +160,9 @@ public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $o * * @param \Cake\Event\Event $event The afterSave event that was fired * @param \Cake\Datasource\EntityInterface $entity the entity that is going to be saved - * @param \ArrayObject $options Options. * @return void */ - public function afterSave(Event $event, EntityInterface $entity, ArrayObject $options) + public function afterSave(Event $event, EntityInterface $entity) { if (!$this->_config['level'] || $entity->isNew()) { return; @@ -216,10 +213,9 @@ protected function _setChildrenLevel($entity) * * @param \Cake\Event\Event $event The beforeDelete event that was fired * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved - * @param \ArrayObject $options Options. * @return void */ - public function beforeDelete(Event $event, EntityInterface $entity, ArrayObject $options) + public function beforeDelete(Event $event, EntityInterface $entity) { $config = $this->getConfig(); $this->_ensureFields($entity); From e0fcb90e23bf348e9b017fd8f3cab474f8da46a7 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 26 Apr 2018 23:21:12 +0530 Subject: [PATCH 1154/2059] Fix few errors reported on phpstan level 3. --- Association/Loader/SelectLoader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index bff533c7..e6ac7fcb 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -301,7 +301,7 @@ protected function _addFilteringJoin($query, $key, $subquery) $filter = current($filter); } - $conditions = $conditions ?: $query->newExpr([$key => $filter]); + $conditions = $conditions ?: $query->newExpr([(string)$key => $filter]); return $query->innerJoin( [$aliasedTable => $subquery], From beb1920bec99fa5236e9391a3aab4fdda02ecbed Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 27 Apr 2018 23:51:24 +0530 Subject: [PATCH 1155/2059] Update usage to "Type" to "TypeFactory". --- Behavior/TimestampBehavior.php | 7 ++++--- Marshaller.php | 4 ++-- ResultSet.php | 2 +- Table.php | 6 +++--- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 5cbcfcf5..e52f6034 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -14,7 +14,8 @@ */ namespace Cake\ORM\Behavior; -use Cake\Database\Type; +use Cake\Database\Type\DateTimeType; +use Cake\Database\TypeFactory; use Cake\Datasource\EntityInterface; use Cake\Event\Event; use Cake\I18n\Time; @@ -203,9 +204,9 @@ protected function _updateField($entity, $field, $refreshTimestamp) } /** @var \Cake\Database\Type\DateTimeType $type */ - $type = Type::build($columnType); + $type = TypeFactory::build($columnType); - if (!$type instanceof Type\DateTimeType) { + if (!$type instanceof DateTimeType) { deprecationWarning('TimestampBehavior support for column types other than DateTimeType will be removed in 4.0.'); $entity->set($field, (string)$ts); diff --git a/Marshaller.php b/Marshaller.php index 45ae6136..3803d7c4 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -17,7 +17,7 @@ use ArrayObject; use Cake\Collection\Collection; use Cake\Database\Expression\TupleComparison; -use Cake\Database\Type; +use Cake\Database\TypeFactory; use Cake\Datasource\EntityInterface; use Cake\Datasource\InvalidPropertyInterface; use Cake\ORM\Association\BelongsToMany; @@ -73,7 +73,7 @@ protected function _buildPropertyMap($data, $options) $columnType = $schema->getColumnType($prop); if ($columnType) { $map[$prop] = function ($value, $entity) use ($columnType) { - return Type::build($columnType)->marshal($value); + return TypeFactory::build($columnType)->marshal($value); }; } } diff --git a/ResultSet.php b/ResultSet.php index 2ab0b59a..8c2fdeaa 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -17,7 +17,7 @@ use Cake\Collection\Collection; use Cake\Collection\CollectionTrait; use Cake\Database\Exception; -use Cake\Database\Type; +use Cake\Database\TypeFactory; use Cake\Datasource\EntityInterface; use Cake\Datasource\ResultSetInterface; use SplFixedArray; diff --git a/Table.php b/Table.php index 5c46fbbd..65b619e9 100644 --- a/Table.php +++ b/Table.php @@ -18,7 +18,7 @@ use BadMethodCallException; use Cake\Core\App; use Cake\Database\Schema\TableSchema; -use Cake\Database\Type; +use Cake\Database\TypeFactory ; use Cake\Datasource\ConnectionInterface; use Cake\Datasource\EntityInterface; use Cake\Datasource\Exception\InvalidPrimaryKeyException; @@ -2102,7 +2102,7 @@ protected function _insert($entity, $data) if (!isset($data[$key])) { $id = $statement->lastInsertId($this->getTable(), $key); $type = $schema->getColumnType($key); - $entity->set($key, Type::build($type)->toPHP($id, $driver)); + $entity->set($key, TypeFactory::build($type)->toPHP($id, $driver)); break; } } @@ -2131,7 +2131,7 @@ protected function _newId($primary) return null; } $typeName = $this->getSchema()->getColumnType($primary[0]); - $type = Type::build($typeName); + $type = TypeFactory::build($typeName); return $type->newId(); } From fe7de4d1b985ed81d6305447f4a5e37777f8e50e Mon Sep 17 00:00:00 2001 From: stickler-ci Date: Fri, 27 Apr 2018 18:35:50 +0000 Subject: [PATCH 1156/2059] Fixing style errors. --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 65b619e9..a3b256f8 100644 --- a/Table.php +++ b/Table.php @@ -18,7 +18,7 @@ use BadMethodCallException; use Cake\Core\App; use Cake\Database\Schema\TableSchema; -use Cake\Database\TypeFactory ; +use Cake\Database\TypeFactory; use Cake\Datasource\ConnectionInterface; use Cake\Datasource\EntityInterface; use Cake\Datasource\Exception\InvalidPrimaryKeyException; From ce0520aa7570b68630e66450987369d21446e64d Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 28 Apr 2018 00:10:45 +0530 Subject: [PATCH 1157/2059] Fix CS error. --- Behavior/TimestampBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index e52f6034..0f7754d3 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -14,8 +14,8 @@ */ namespace Cake\ORM\Behavior; -use Cake\Database\Type\DateTimeType; use Cake\Database\TypeFactory; +use Cake\Database\Type\DateTimeType; use Cake\Datasource\EntityInterface; use Cake\Event\Event; use Cake\I18n\Time; From 6d0e1bd650b31020573c8abb95953d8ac9575416 Mon Sep 17 00:00:00 2001 From: Vladimir Reznichenko Date: Fri, 27 Apr 2018 20:41:57 +0200 Subject: [PATCH 1158/2059] SCA with Php Inspections (EA Ultimate): random fixes (CS, control flow) --- Behavior/TreeBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 9c4c44f4..70714be2 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -905,7 +905,7 @@ protected function _sync($shift, $dir, $conditions, $mark = false) $exp = $query->newExpr(); $movement = clone $exp; - $movement->add($field)->add("$shift")->setConjunction($dir); + $movement->add($field)->add((string)$shift)->setConjunction($dir); $inverse = clone $exp; $movement = $mark ? From 06c3aa4fe09c2795e507229405271d5734c8673f Mon Sep 17 00:00:00 2001 From: Vladimir Reznichenko Date: Fri, 27 Apr 2018 20:46:44 +0200 Subject: [PATCH 1159/2059] SCA with Php Inspections (EA Ultimate): tweak misused functions calls --- Association/HasMany.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index 725c2ebb..84999d02 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -481,7 +481,7 @@ function ($ent) use ($primaryKey) { ) ->filter( function ($v) { - return !in_array(null, array_values($v), true); + return !in_array(null, $v, true); } ) ->toArray(); From c8ca1df595aa618f7c3488f712b2bedd925de8cc Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Mon, 30 Apr 2018 14:15:54 -0500 Subject: [PATCH 1160/2059] Fixed bug preventing afterSaveCommit from being fired during findOrCreate --- Table.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Table.php b/Table.php index 8f9af061..f42ed184 100644 --- a/Table.php +++ b/Table.php @@ -1680,9 +1680,7 @@ public function findOrCreate($search, callable $callback = null, $options = []) 'defaults' => true ]; - return $this->_executeTransaction(function () use ($search, $callback, $options) { - return $this->_processFindOrCreate($search, $callback, $options); - }, $options['atomic']); + return $this->_processFindOrCreate($search, $callback, $options); } /** From 4b4ef32d926675569ac00087ec03b9ddabc055fa Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Tue, 1 May 2018 15:50:50 -0500 Subject: [PATCH 1161/2059] Re-wrapped _processFindOrCreate in transaction, manually dispatched afterSaveCommit --- Table.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index f42ed184..8134fd99 100644 --- a/Table.php +++ b/Table.php @@ -1677,10 +1677,19 @@ public function findOrCreate($search, callable $callback = null, $options = []) { $options += [ 'atomic' => true, - 'defaults' => true + 'defaults' => true, + '_primary' => true, ]; - return $this->_processFindOrCreate($search, $callback, $options); + $entity = $this->_executeTransaction(function () use ($search, $callback, $options) { + return $this->_processFindOrCreate($search, $callback, $options); + }, $options['atomic']); + + if ($entity && $this->_transactionCommitted($options['atomic'], $options['_primary'])) { + $this->dispatchEvent('Model.afterSaveCommit', compact('entity', 'options')); + } + + return $entity; } /** From cf7379c613c02482bad7efd4f50de7bfcbf26098 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Tue, 1 May 2018 16:25:00 -0500 Subject: [PATCH 1162/2059] Removed unnecessary _primary option --- Table.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Table.php b/Table.php index 8134fd99..bc1ac97e 100644 --- a/Table.php +++ b/Table.php @@ -1678,14 +1678,13 @@ public function findOrCreate($search, callable $callback = null, $options = []) $options += [ 'atomic' => true, 'defaults' => true, - '_primary' => true, ]; $entity = $this->_executeTransaction(function () use ($search, $callback, $options) { return $this->_processFindOrCreate($search, $callback, $options); }, $options['atomic']); - if ($entity && $this->_transactionCommitted($options['atomic'], $options['_primary'])) { + if ($entity && $this->_transactionCommitted($options['atomic'], true)) { $this->dispatchEvent('Model.afterSaveCommit', compact('entity', 'options')); } From 422391d3c312be7f6beb1ce17c7ab0eef205921e Mon Sep 17 00:00:00 2001 From: Tomas Saghy Date: Mon, 7 May 2018 17:02:18 +0200 Subject: [PATCH 1163/2059] removed duplicate 'className' check --- Locator/TableLocator.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 8c29e777..745e8a06 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -191,10 +191,6 @@ public function get($alias, array $options = []) $options += $this->_config[$alias]; } - if (empty($options['className'])) { - $options['className'] = Inflector::camelize($alias); - } - $className = $this->_getClassName($alias, $options); if ($className) { $options['className'] = $className; From 43851f72dea71af0eec625d1a6e23e6da0d72379 Mon Sep 17 00:00:00 2001 From: Tomas Saghy Date: Tue, 8 May 2018 07:42:24 +0200 Subject: [PATCH 1164/2059] Update TableLocator.php --- Locator/TableLocator.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 745e8a06..27b907b0 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -195,6 +195,7 @@ public function get($alias, array $options = []) if ($className) { $options['className'] = $className; } else { + $options['className'] = Inflector::camelize($alias); if (!isset($options['table']) && strpos($options['className'], '\\') === false) { list(, $table) = pluginSplit($options['className']); $options['table'] = Inflector::underscore($table); From 653c75e9fb2bb13afded62d4112455e314f49fdd Mon Sep 17 00:00:00 2001 From: Tomas Saghy Date: Tue, 8 May 2018 11:18:50 +0200 Subject: [PATCH 1165/2059] Update TableLocator.php --- Locator/TableLocator.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 27b907b0..95b2ce83 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -195,7 +195,9 @@ public function get($alias, array $options = []) if ($className) { $options['className'] = $className; } else { - $options['className'] = Inflector::camelize($alias); + if (empty($options['className'])) { + $options['className'] = Inflector::camelize($alias); + } if (!isset($options['table']) && strpos($options['className'], '\\') === false) { list(, $table) = pluginSplit($options['className']); $options['table'] = Inflector::underscore($table); From 7820b6a56e76f51049cb75d63f7c9f33b0335549 Mon Sep 17 00:00:00 2001 From: stickler-ci Date: Tue, 8 May 2018 09:19:49 +0000 Subject: [PATCH 1166/2059] Fixing style errors. --- Locator/TableLocator.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 95b2ce83..7597cef8 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -195,8 +195,8 @@ public function get($alias, array $options = []) if ($className) { $options['className'] = $className; } else { - if (empty($options['className'])) { - $options['className'] = Inflector::camelize($alias); + if (empty($options['className'])) { + $options['className'] = Inflector::camelize($alias); } if (!isset($options['table']) && strpos($options['className'], '\\') === false) { list(, $table) = pluginSplit($options['className']); From a57af73d924f63ba9c921d49e8448eec6dad01ba Mon Sep 17 00:00:00 2001 From: Tomas Saghy Date: Fri, 11 May 2018 16:35:19 +0200 Subject: [PATCH 1167/2059] Remove @inheritDoc to correctly work with phpstan --- Query.php | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/Query.php b/Query.php index 210201bf..746f2578 100644 --- a/Query.php +++ b/Query.php @@ -170,7 +170,35 @@ public function __construct($connection, $table) } /** - * {@inheritDoc} + * Adds new fields to be returned by a `SELECT` statement when this query is + * executed. Fields can be passed as an array of strings, array of expression + * objects, a single expression or a single string. + * + * If an array is passed, keys will be used to alias fields using the value as the + * real field to be aliased. It is possible to alias strings, Expression objects or + * even other Query objects. + * + * If a callable function is passed, the returning array of the function will + * be used as the list of fields. + * + * By default this function will append any passed argument to the list of fields + * to be selected, unless the second argument is set to true. + * + * ### Examples: + * + * ``` + * $query->select(['id', 'title']); // Produces SELECT id, title + * $query->select(['author' => 'author_id']); // Appends author: SELECT id, title, author_id as author + * $query->select('id', true); // Resets the list: SELECT id + * $query->select(['total' => $countQuery]); // SELECT id, (SELECT ...) AS total + * $query->select(function ($query) { + * return ['article_id', 'total' => $query->count('*')]; + * }) + * ``` + * + * By default no fields are selected, if you have an instance of `Cake\ORM\Query` and try to append + * fields you should also call `Cake\ORM\Query::enableAutoFields()` to select the default fields + * from the table. * * If you pass an instance of a `Cake\ORM\Table` or `Cake\ORM\Association` class, * all the fields in the schema of the table or the association will be added to From d0bea49424713fe1fd532e3d7171e04b814460b7 Mon Sep 17 00:00:00 2001 From: saeideng Date: Mon, 14 May 2018 01:52:36 +0430 Subject: [PATCH 1168/2059] fix deprecationWarning message --- Table.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index bc1ac97e..a4979a8c 100644 --- a/Table.php +++ b/Table.php @@ -960,13 +960,13 @@ public function hasBehavior($name) /** * Returns an association object configured for the specified alias if any. * - * @deprecated 3.6.0 Use getAssociation() and Table::hasAssocation() instead. + * @deprecated 3.6.0 Use getAssociation() and Table::hasAssociation() instead. * @param string $name the alias used for the association. * @return \Cake\ORM\Association|null Either the association or null. */ public function association($name) { - deprecationWarning('Use Table::getAssociation() and Table::hasAssocation() instead.'); + deprecationWarning('Use Table::getAssociation() and Table::hasAssociation() instead.'); return $this->findAssociation($name); } From 6a72b49dd5ea96ca2edfd17792429a86e34399cf Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 19 May 2018 10:52:30 +0530 Subject: [PATCH 1169/2059] Remove deprecated code for association classes. --- Association.php | 263 --------------------------- Association/BelongsToMany.php | 64 ------- Association/DependentDeleteTrait.php | 49 ----- Association/HasMany.php | 43 ----- AssociationCollection.php | 18 -- 5 files changed, 437 deletions(-) delete mode 100644 Association/DependentDeleteTrait.php diff --git a/Association.php b/Association.php index eda333f5..d81dab9b 100644 --- a/Association.php +++ b/Association.php @@ -270,26 +270,6 @@ public function getName() return $this->_name; } - /** - * Sets the name for this association. - * - * @deprecated 3.4.0 Use setName()/getName() instead. - * @param string|null $name Name to be assigned - * @return string - */ - public function name($name = null) - { - deprecationWarning( - get_called_class() . '::name() is deprecated. ' . - 'Use setName()/getName() instead.' - ); - if ($name !== null) { - $this->setName($name); - } - - return $this->getName(); - } - /** * Sets whether or not cascaded deletes should also fire callbacks. * @@ -313,27 +293,6 @@ public function getCascadeCallbacks() return $this->_cascadeCallbacks; } - /** - * Sets whether or not cascaded deletes should also fire callbacks. If no - * arguments are passed, the current configured value is returned - * - * @deprecated 3.4.0 Use setCascadeCallbacks()/getCascadeCallbacks() instead. - * @param bool|null $cascadeCallbacks cascade callbacks switch value - * @return bool - */ - public function cascadeCallbacks($cascadeCallbacks = null) - { - deprecationWarning( - get_called_class() . '::cascadeCallbacks() is deprecated. ' . - 'Use setCascadeCallbacks()/getCascadeCallbacks() instead.' - ); - if ($cascadeCallbacks !== null) { - $this->setCascadeCallbacks($cascadeCallbacks); - } - - return $this->getCascadeCallbacks(); - } - /** * The class name of the target table object * @@ -367,27 +326,6 @@ public function getSource() return $this->_sourceTable; } - /** - * Sets the table instance for the source side of the association. If no arguments - * are passed, the current configured table instance is returned - * - * @deprecated 3.4.0 Use setSource()/getSource() instead. - * @param \Cake\ORM\Table|null $table the instance to be assigned as source side - * @return \Cake\ORM\Table - */ - public function source(Table $table = null) - { - deprecationWarning( - get_called_class() . '::source() is deprecated. ' . - 'Use setSource()/getSource() instead.' - ); - if ($table === null) { - return $this->_sourceTable; - } - - return $this->_sourceTable = $table; - } - /** * Sets the table instance for the target side of the association. * @@ -447,27 +385,6 @@ public function getTarget() return $this->_targetTable; } - /** - * Sets the table instance for the target side of the association. If no arguments - * are passed, the current configured table instance is returned - * - * @deprecated 3.4.0 Use setTarget()/getTarget() instead. - * @param \Cake\ORM\Table|null $table the instance to be assigned as target side - * @return \Cake\ORM\Table - */ - public function target(Table $table = null) - { - deprecationWarning( - get_called_class() . '::target() is deprecated. ' . - 'Use setTarget()/getTarget() instead.' - ); - if ($table !== null) { - $this->setTarget($table); - } - - return $this->getTarget(); - } - /** * Sets a list of conditions to be always included when fetching records from * the target association. @@ -495,28 +412,6 @@ public function getConditions() return $this->_conditions; } - /** - * Sets a list of conditions to be always included when fetching records from - * the target association. If no parameters are passed the current list is returned - * - * @deprecated 3.4.0 Use setConditions()/getConditions() instead. - * @param array|null $conditions list of conditions to be used - * @see \Cake\Database\Query::where() for examples on the format of the array - * @return array|callable - */ - public function conditions($conditions = null) - { - deprecationWarning( - get_called_class() . '::conditions() is deprecated. ' . - 'Use setConditions()/getConditions() instead.' - ); - if ($conditions !== null) { - $this->setConditions($conditions); - } - - return $this->getConditions(); - } - /** * Sets the name of the field representing the binding field with the target table. * When not manually specified the primary key of the owning side table is used. @@ -548,29 +443,6 @@ public function getBindingKey() return $this->_bindingKey; } - /** - * Sets the name of the field representing the binding field with the target table. - * When not manually specified the primary key of the owning side table is used. - * - * If no parameters are passed the current field is returned - * - * @deprecated 3.4.0 Use setBindingKey()/getBindingKey() instead. - * @param string|null $key the table field to be used to link both tables together - * @return string|array - */ - public function bindingKey($key = null) - { - deprecationWarning( - get_called_class() . '::bindingKey() is deprecated. ' . - 'Use setBindingKey()/getBindingKey() instead.' - ); - if ($key !== null) { - $this->setBindingKey($key); - } - - return $this->getBindingKey(); - } - /** * Gets the name of the field representing the foreign key to the target table. * @@ -594,27 +466,6 @@ public function setForeignKey($key) return $this; } - /** - * Sets the name of the field representing the foreign key to the target table. - * If no parameters are passed the current field is returned - * - * @deprecated 3.4.0 Use setForeignKey()/getForeignKey() instead. - * @param string|null $key the key to be used to link both tables together - * @return string|array - */ - public function foreignKey($key = null) - { - deprecationWarning( - get_called_class() . '::foreignKey() is deprecated. ' . - 'Use setForeignKey()/getForeignKey() instead.' - ); - if ($key !== null) { - $this->setForeignKey($key); - } - - return $this->getForeignKey(); - } - /** * Sets whether the records on the target table are dependent on the source table. * @@ -646,31 +497,6 @@ public function getDependent() return $this->_dependent; } - /** - * Sets whether the records on the target table are dependent on the source table. - * - * This is primarily used to indicate that records should be removed if the owning record in - * the source table is deleted. - * - * If no parameters are passed the current setting is returned. - * - * @deprecated 3.4.0 Use setDependent()/getDependent() instead. - * @param bool|null $dependent Set the dependent mode. Use null to read the current state. - * @return bool - */ - public function dependent($dependent = null) - { - deprecationWarning( - get_called_class() . '::dependent() is deprecated. ' . - 'Use setDependent()/getDependent() instead.' - ); - if ($dependent !== null) { - $this->setDependent($dependent); - } - - return $this->getDependent(); - } - /** * Whether this association can be expressed directly in a query join * @@ -707,27 +533,6 @@ public function getJoinType() return $this->_joinType; } - /** - * Sets the type of join to be used when adding the association to a query. - * If no arguments are passed, the currently configured type is returned. - * - * @deprecated 3.4.0 Use setJoinType()/getJoinType() instead. - * @param string|null $type the join type to be used (e.g. INNER) - * @return string - */ - public function joinType($type = null) - { - deprecationWarning( - get_called_class() . '::joinType() is deprecated. ' . - 'Use setJoinType()/getJoinType() instead.' - ); - if ($type !== null) { - $this->setJoinType($type); - } - - return $this->getJoinType(); - } - /** * Sets the property name that should be filled with data from the target table * in the source table record. @@ -765,28 +570,6 @@ public function getProperty() return $this->_propertyName; } - /** - * Sets the property name that should be filled with data from the target table - * in the source table record. - * If no arguments are passed, the currently configured type is returned. - * - * @deprecated 3.4.0 Use setProperty()/getProperty() instead. - * @param string|null $name The name of the association property. Use null to read the current value. - * @return string - */ - public function property($name = null) - { - deprecationWarning( - get_called_class() . '::property() is deprecated. ' . - 'Use setProperty()/getProperty() instead.' - ); - if ($name !== null) { - $this->setProperty($name); - } - - return $this->getProperty(); - } - /** * Returns default property name based on association name. * @@ -832,30 +615,6 @@ public function getStrategy() return $this->_strategy; } - /** - * Sets the strategy name to be used to fetch associated records. Keep in mind - * that some association types might not implement but a default strategy, - * rendering any changes to this setting void. - * If no arguments are passed, the currently configured strategy is returned. - * - * @deprecated 3.4.0 Use setStrategy()/getStrategy() instead. - * @param string|null $name The strategy type. Use null to read the current value. - * @return string - * @throws \InvalidArgumentException When an invalid strategy is provided. - */ - public function strategy($name = null) - { - deprecationWarning( - get_called_class() . '::strategy() is deprecated. ' . - 'Use setStrategy()/getStrategy() instead.' - ); - if ($name !== null) { - $this->setStrategy($name); - } - - return $this->getStrategy(); - } - /** * Gets the default finder to use for fetching rows from the target table. * @@ -879,28 +638,6 @@ public function setFinder($finder) return $this; } - /** - * Sets the default finder to use for fetching rows from the target table. - * If no parameters are passed, it will return the currently configured - * finder name. - * - * @deprecated 3.4.0 Use setFinder()/getFinder() instead. - * @param string|null $finder the finder name to use - * @return string - */ - public function finder($finder = null) - { - deprecationWarning( - get_called_class() . '::finder() is deprecated. ' . - 'Use setFinder()/getFinder() instead.' - ); - if ($finder !== null) { - $this->setFinder($finder); - } - - return $this->getFinder(); - } - /** * Override this function to initialize any concrete association class, it will * get passed the original list of options used in the constructor diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 506801ab..e453d918 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -184,27 +184,6 @@ public function getTargetForeignKey() return $this->_targetForeignKey; } - /** - * Sets the name of the field representing the foreign key to the target table. - * If no parameters are passed current field is returned - * - * @deprecated 3.4.0 Use setTargetForeignKey()/getTargetForeignKey() instead. - * @param string|null $key the key to be used to link both tables together - * @return string - */ - public function targetForeignKey($key = null) - { - deprecationWarning( - 'BelongToMany::targetForeignKey() is deprecated. ' . - 'Use setTargetForeignKey()/getTargetForeignKey() instead.' - ); - if ($key !== null) { - $this->setTargetForeignKey($key); - } - - return $this->getTargetForeignKey(); - } - /** * Whether this association can be expressed directly in a query join * @@ -254,27 +233,6 @@ public function getSort() return $this->_sort; } - /** - * Sets the sort order in which target records should be returned. - * If no arguments are passed the currently configured value is returned - * - * @deprecated 3.5.0 Use setSort()/getSort() instead. - * @param mixed $sort A find() compatible order clause - * @return mixed - */ - public function sort($sort = null) - { - deprecationWarning( - 'BelongToMany::sort() is deprecated. ' . - 'Use setSort()/getSort() instead.' - ); - if ($sort !== null) { - $this->setSort($sort); - } - - return $this->getSort(); - } - /** * {@inheritDoc} */ @@ -663,28 +621,6 @@ public function getSaveStrategy() return $this->_saveStrategy; } - /** - * Sets the strategy that should be used for saving. If called with no - * arguments, it will return the currently configured strategy - * - * @deprecated 3.4.0 Use setSaveStrategy()/getSaveStrategy() instead. - * @param string|null $strategy the strategy name to be used - * @throws \InvalidArgumentException if an invalid strategy name is passed - * @return string the strategy to be used for saving - */ - public function saveStrategy($strategy = null) - { - deprecationWarning( - 'BelongsToMany::saveStrategy() is deprecated. ' . - 'Use setSaveStrategy()/getSaveStrategy() instead.' - ); - if ($strategy !== null) { - $this->setSaveStrategy($strategy); - } - - return $this->getSaveStrategy(); - } - /** * Takes an entity from the source table and looks if there is a field * matching the property name for this association. The found entity will be diff --git a/Association/DependentDeleteTrait.php b/Association/DependentDeleteTrait.php deleted file mode 100644 index aab6048a..00000000 --- a/Association/DependentDeleteTrait.php +++ /dev/null @@ -1,49 +0,0 @@ -cascadeDelete($this, $entity, $options); - } -} diff --git a/Association/HasMany.php b/Association/HasMany.php index 84999d02..89730efc 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -130,28 +130,6 @@ public function getSaveStrategy() return $this->_saveStrategy; } - /** - * Sets the strategy that should be used for saving. If called with no - * arguments, it will return the currently configured strategy - * - * @deprecated 3.4.0 Use setSaveStrategy()/getSaveStrategy() instead. - * @param string|null $strategy the strategy name to be used - * @throws \InvalidArgumentException if an invalid strategy name is passed - * @return string the strategy to be used for saving - */ - public function saveStrategy($strategy = null) - { - deprecationWarning( - 'HasMany::saveStrategy() is deprecated. ' . - 'Use setSaveStrategy()/getSaveStrategy() instead.' - ); - if ($strategy !== null) { - $this->setSaveStrategy($strategy); - } - - return $this->getSaveStrategy(); - } - /** * Takes an entity from the source table and looks if there is a field * matching the property name for this association. The found entity will be @@ -623,27 +601,6 @@ public function getSort() return $this->_sort; } - /** - * Sets the sort order in which target records should be returned. - * If no arguments are passed the currently configured value is returned - * - * @deprecated 3.4.0 Use setSort()/getSort() instead. - * @param mixed $sort A find() compatible order clause - * @return mixed - */ - public function sort($sort = null) - { - deprecationWarning( - 'HasMany::sort() is deprecated. ' . - 'Use setSort()/getSort() instead.' - ); - if ($sort !== null) { - $this->setSort($sort); - } - - return $this->getSort(); - } - /** * {@inheritDoc} */ diff --git a/AssociationCollection.php b/AssociationCollection.php index 574648f0..c3ea52a3 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -150,24 +150,6 @@ public function keys() return array_keys($this->_items); } - /** - * Get an array of associations matching a specific type. - * - * @param string|array $class The type of associations you want. - * For example 'BelongsTo' or array like ['BelongsTo', 'HasOne'] - * @return array An array of Association objects. - * @deprecated 3.5.3 Use getByType() instead. - */ - public function type($class) - { - deprecationWarning( - 'AssociationCollection::type() is deprecated. ' . - 'Use getByType() instead.' - ); - - return $this->getByType($class); - } - /** * Get an array of associations matching a specific type. * From d966528d7535fe08d18ff92a3ae5111f254991db Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 19 May 2018 11:02:06 +0530 Subject: [PATCH 1170/2059] Remove deprecated code from behavior classes. --- Behavior/TimestampBehavior.php | 6 ++---- Behavior/TranslateBehavior.php | 23 ----------------------- 2 files changed, 2 insertions(+), 27 deletions(-) diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 2a52e193..1e01520b 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -21,6 +21,7 @@ use Cake\I18n\Time; use Cake\ORM\Behavior; use DateTime; +use RuntimeException; use UnexpectedValueException; /** @@ -210,10 +211,7 @@ protected function _updateField($entity, $field, $refreshTimestamp) $type = TypeFactory::build($columnType); if (!$type instanceof DateTimeType) { - deprecationWarning('TimestampBehavior support for column types other than DateTimeType will be removed in 4.0.'); - $entity->set($field, (string)$ts); - - return; + throw new RuntimeException('TimestampBehavior only supports columns of type DateTimeType.'); } $class = $type->getDateTimeClassName(); diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 55244e69..8badf160 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -455,29 +455,6 @@ public function getLocale() return $this->_locale ?: I18n::getLocale(); } - /** - * Sets all future finds for the bound table to also fetch translated fields for - * the passed locale. If no value is passed, it returns the currently configured - * locale - * - * @deprecated 3.6.0 Use setLocale()/getLocale() instead. - * @param string|null $locale The locale to use for fetching translated records - * @return string - */ - public function locale($locale = null) - { - deprecationWarning( - get_called_class() . '::locale() is deprecated. ' . - 'Use setLocale()/getLocale() instead.' - ); - - if ($locale !== null) { - $this->setLocale($locale); - } - - return $this->getLocale(); - } - /** * Returns a fully aliased field name for translated fields. * From 1b58cba1ef27fa62c6a49d609b1a1015abddb08a Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 19 May 2018 11:06:22 +0530 Subject: [PATCH 1171/2059] Remove deprecated code from eager loading classes. --- EagerLoadable.php | 38 +--------------------------- EagerLoader.php | 64 +---------------------------------------------- 2 files changed, 2 insertions(+), 100 deletions(-) diff --git a/EagerLoadable.php b/EagerLoadable.php index 9d93f5b1..aa3aab96 100644 --- a/EagerLoadable.php +++ b/EagerLoadable.php @@ -219,22 +219,10 @@ public function setCanBeJoined($possible) /** * Gets whether or not this level can be fetched using a join. * - * If called with arguments it sets the value. - * As of 3.4.0 the setter part is deprecated, use setCanBeJoined() instead. - * - * @param bool|null $possible The value to set. * @return bool */ - public function canBeJoined($possible = null) + public function canBeJoined() { - if ($possible !== null) { - deprecationWarning( - 'Using EagerLoadable::canBeJoined() as a setter is deprecated. ' . - 'Use setCanBeJoined() instead.' - ); - $this->setCanBeJoined($possible); - } - return $this->_canBeJoined; } @@ -263,30 +251,6 @@ public function getConfig() return $this->_config; } - /** - * Sets the list of options to pass to the association object for loading - * the records. - * - * If called with no arguments it returns the current - * value. - * - * @deprecated 3.4.0 Use setConfig()/getConfig() instead. - * @param array|null $config The value to set. - * @return array - */ - public function config(array $config = null) - { - deprecationWarning( - 'EagerLoadable::config() is deprecated. ' . - 'Use setConfig()/getConfig() instead.' - ); - if ($config !== null) { - $this->setConfig($config); - } - - return $this->getConfig(); - } - /** * Gets whether or not this level was meant for a * "matching" fetch operation. diff --git a/EagerLoader.php b/EagerLoader.php index 749f4366..ae4cb8d2 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -113,8 +113,6 @@ class EagerLoader * this allows this object to calculate joins or any additional queries that * must be executed to bring the required associated data. * - * The getter part is deprecated as of 3.6.0. Use getContain() instead. - * * Accepted options per passed association: * * - foreignKey: Used to set a different field to match both tables, if set to false @@ -133,17 +131,8 @@ class EagerLoader * @return array Containments. * @throws \InvalidArgumentException When using $queryBuilder with an array of $associations */ - public function contain($associations = [], callable $queryBuilder = null) + public function contain($associations, callable $queryBuilder = null) { - if (empty($associations)) { - deprecationWarning( - 'Using EagerLoader::contain() as getter is deprecated. ' . - 'Use getContain() instead.' - ); - - return $this->getContain(); - } - if ($queryBuilder) { if (!is_string($associations)) { throw new InvalidArgumentException( @@ -219,26 +208,6 @@ public function isAutoFieldsEnabled() return $this->_autoFields; } - /** - * Sets/Gets whether or not contained associations will load fields automatically. - * - * @deprecated 3.4.0 Use enableAutoFields()/isAutoFieldsEnabled() instead. - * @param bool|null $enable The value to set. - * @return bool The current value. - */ - public function autoFields($enable = null) - { - deprecationWarning( - 'EagerLoader::autoFields() is deprecated. ' . - 'Use enableAutoFields()/isAutoFieldsEnabled() instead.' - ); - if ($enable !== null) { - $this->enableAutoFields($enable); - } - - return $this->isAutoFieldsEnabled(); - } - /** * Adds a new association to the list that will be used to filter the results of * any given query based on the results of finding records for that association. @@ -299,37 +268,6 @@ public function getMatching() return $this->_matching->getContain(); } - /** - * Adds a new association to the list that will be used to filter the results of - * any given query based on the results of finding records for that association. - * You can pass a dot separated path of associations to this method as its first - * parameter, this will translate in setting all those associations with the - * `matching` option. - * - * If called with no arguments it will return the current tree of associations to - * be matched. - * - * @deprecated 3.4.0 Use setMatching()/getMatching() instead. - * @param string|null $assoc A single association or a dot separated path of associations. - * @param callable|null $builder the callback function to be used for setting extra - * options to the filtering query - * @param array $options Extra options for the association matching, such as 'joinType' - * and 'fields' - * @return array The resulting containments array - */ - public function matching($assoc = null, callable $builder = null, $options = []) - { - deprecationWarning( - 'EagerLoader::matching() is deprecated. ' . - 'Use setMatch()/getMatching() instead.' - ); - if ($assoc !== null) { - $this->setMatching($assoc, $builder, $options); - } - - return $this->getMatching(); - } - /** * Returns the fully normalized array of associations that should be eagerly * loaded for a table. The normalized array will restructure the original array From 53c256e363d54bee614689c6e147ac0e952c5e56 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 19 May 2018 11:48:15 +0530 Subject: [PATCH 1172/2059] Removed deprecated methods for table locator. --- Locator/LocatorAwareTrait.php | 21 --------------------- Locator/LocatorInterface.php | 11 ----------- Locator/TableLocator.php | 34 ---------------------------------- TableRegistry.php | 10 +++++++++- 4 files changed, 9 insertions(+), 67 deletions(-) diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index 2d26e519..3924e553 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -29,27 +29,6 @@ trait LocatorAwareTrait */ protected $_tableLocator; - /** - * Sets the table locator. - * If no parameters are passed, it will return the currently used locator. - * - * @param \Cake\ORM\Locator\LocatorInterface|null $tableLocator LocatorInterface instance. - * @return \Cake\ORM\Locator\LocatorInterface - * @deprecated 3.5.0 Use getTableLocator()/setTableLocator() instead. - */ - public function tableLocator(LocatorInterface $tableLocator = null) - { - deprecationWarning( - get_called_class() . '::tableLocator() is deprecated. ' . - 'Use getTableLocator()/setTableLocator() instead.' - ); - if ($tableLocator !== null) { - $this->setTableLocator($tableLocator); - } - - return $this->getTableLocator(); - } - /** * Sets the table locator. * diff --git a/Locator/LocatorInterface.php b/Locator/LocatorInterface.php index 08cddd6a..124ca09a 100644 --- a/Locator/LocatorInterface.php +++ b/Locator/LocatorInterface.php @@ -24,17 +24,6 @@ */ interface LocatorInterface { - - /** - * Stores a list of options to be used when instantiating an object - * with a matching alias. - * - * @param string|null $alias Name of the alias - * @param array|null $options list of options for the alias - * @return array The config data. - */ - public function config($alias = null, $options = null); - /** * Get a table instance from the registry. * diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 7597cef8..55b19a59 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -100,40 +100,6 @@ public function getConfig($alias = null) return isset($this->_config[$alias]) ? $this->_config[$alias] : []; } - /** - * Stores a list of options to be used when instantiating an object - * with a matching alias. - * - * The options that can be stored are those that are recognized by `get()` - * If second argument is omitted, it will return the current settings - * for $alias. - * - * If no arguments are passed it will return the full configuration array for - * all aliases - * - * @deprecated 3.4.0 Use setConfig()/getConfig() instead. - * @param string|array|null $alias Name of the alias - * @param array|null $options list of options for the alias - * @return array The config data. - * @throws \RuntimeException When you attempt to configure an existing table instance. - */ - public function config($alias = null, $options = null) - { - deprecationWarning( - 'TableLocator::config() is deprecated. ' . - 'Use getConfig()/setConfig() instead.' - ); - if ($alias !== null) { - if (is_string($alias) && $options === null) { - return $this->getConfig($alias); - } - - $this->setConfig($alias, $options); - } - - return $this->getConfig($alias); - } - /** * Get a table instance from the registry. * diff --git a/TableRegistry.php b/TableRegistry.php index 5c952687..3270d003 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -125,7 +125,15 @@ public static function config($alias = null, $options = null) 'Use \Cake\ORM\Locator\TableLocator::getConfig()/setConfig() instead.' ); - return static::getTableLocator()->config($alias, $options); + if ($alias !== null) { + if (is_string($alias) && $options === null) { + return static::getTableLocator()->getConfig($alias); + } + + static::getTableLocator()->setConfig($alias, $options); + } + + return static::getTableLocator()->getConfig($alias); } /** From 2d7bdb32f25b8c1fa7ef58aeeb11d77989a49f0e Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 19 May 2018 11:56:22 +0530 Subject: [PATCH 1173/2059] Remove deprecated code from Marshaller. --- Marshaller.php | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 3803d7c4..94418ff2 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -141,7 +141,6 @@ protected function _buildPropertyMap($data, $options) * - validate: Set to false to disable validation. Can also be a string of the validator ruleset to be applied. * Defaults to true/default. * - associated: Associations listed here will be marshalled as well. Defaults to null. - * - fieldList: (deprecated) Since 3.4.0. Use fields instead. * - fields: A whitelist of fields to be assigned to the entity. If not present, * the accessible fields list in the entity will be used. Defaults to null. * - accessibleFields: A list of fields to allow or deny in entity accessible fields. Defaults to null @@ -271,14 +270,6 @@ protected function _prepareDataAndOptions($data, $options) { $options += ['validate' => true]; - if (!isset($options['fields']) && isset($options['fieldList'])) { - deprecationWarning( - 'The `fieldList` option for marshalling is deprecated. Use the `fields` option instead.' - ); - $options['fields'] = $options['fieldList']; - unset($options['fieldList']); - } - $tableName = $this->_table->getAlias(); if (isset($data[$tableName])) { $data += $data[$tableName]; @@ -337,7 +328,6 @@ protected function _marshalAssociation($assoc, $value, $options) * - validate: Set to false to disable validation. Can also be a string of the validator ruleset to be applied. * Defaults to true/default. * - associated: Associations listed here will be marshalled as well. Defaults to null. - * - fieldList: (deprecated) Since 3.4.0. Use fields instead * - fields: A whitelist of fields to be assigned to the entity. If not present, * the accessible fields list in the entity will be used. Defaults to null. * - accessibleFields: A list of fields to allow or deny in entity accessible fields. Defaults to null @@ -494,23 +484,6 @@ protected function _loadAssociatedByIds($assoc, $ids) return $target->find()->where($filter)->toArray(); } - /** - * Loads a list of belongs to many from ids. - * - * @param \Cake\ORM\Association $assoc The association class for the belongsToMany association. - * @param array $ids The list of ids to load. - * @return \Cake\Datasource\EntityInterface[] An array of entities. - * @deprecated Use _loadAssociatedByIds() - */ - protected function _loadBelongsToMany($assoc, $ids) - { - deprecationWarning( - 'Marshaller::_loadBelongsToMany() is deprecated. Use _loadAssociatedByIds() instead.' - ); - - return $this->_loadAssociatedByIds($assoc, $ids); - } - /** * Merges `$data` into `$entity` and recursively does the same for each one of * the association names passed in `$options`. When merging associations, if an @@ -527,7 +500,6 @@ protected function _loadBelongsToMany($assoc, $ids) * - associated: Associations listed here will be marshalled as well. * - validate: Whether or not to validate data before hydrating the entities. Can * also be set to a string to use a specific validator. Defaults to true/default. - * - fieldList: (deprecated) Since 3.4.0. Use fields instead * - fields: A whitelist of fields to be assigned to the entity. If not present * the accessible fields list in the entity will be used. * - accessibleFields: A list of fields to allow or deny in entity accessible fields. @@ -642,7 +614,6 @@ public function merge(EntityInterface $entity, array $data, array $options = []) * - validate: Whether or not to validate data before hydrating the entities. Can * also be set to a string to use a specific validator. Defaults to true/default. * - associated: Associations listed here will be marshalled as well. - * - fieldList: (deprecated) Since 3.4.0. Use fields instead * - fields: A whitelist of fields to be assigned to the entity. If not present, * the accessible fields list in the entity will be used. * - accessibleFields: A list of fields to allow or deny in entity accessible fields. From 746429c674b9544ca0c06e6bc9d24b79c0ab2e87 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 19 May 2018 12:00:18 +0530 Subject: [PATCH 1174/2059] Remove deprecated code from ORM\Query. --- Query.php | 83 ++----------------------------------------------------- 1 file changed, 3 insertions(+), 80 deletions(-) diff --git a/Query.php b/Query.php index 746f2578..c2d187a3 100644 --- a/Query.php +++ b/Query.php @@ -22,6 +22,7 @@ use Cake\Database\ValueBinder; use Cake\Datasource\QueryInterface; use Cake\Datasource\QueryTrait; +use InvalidArgumentException; use JsonSerializable; use RuntimeException; @@ -305,29 +306,6 @@ public function getEagerLoader() return $this->_eagerLoader; } - /** - * Sets the instance of the eager loader class to use for loading associations - * and storing containments. If called with no arguments, it will return the - * currently configured instance. - * - * @deprecated 3.4.0 Use setEagerLoader()/getEagerLoader() instead. - * @param \Cake\ORM\EagerLoader|null $instance The eager loader to use. Pass null - * to get the current eagerloader. - * @return \Cake\ORM\EagerLoader|$this - */ - public function eagerLoader(EagerLoader $instance = null) - { - deprecationWarning( - 'Query::eagerLoader() is deprecated. ' . - 'Use setEagerLoader()/getEagerLoader() instead.' - ); - if ($instance !== null) { - return $this->setEagerLoader($instance); - } - - return $this->getEagerLoader(); - } - /** * Sets the list of associations that should be eagerly loaded along with this * query. The list of associated tables passed must have been previously set as @@ -434,10 +412,6 @@ public function eagerLoader(EagerLoader $instance = null) * ]); * ``` * - * If called with no arguments, this function will return an array with - * with the list of previously configured associations to be contained in the - * result. This getter part is deprecated as of 3.6.0. Use getContain() instead. - * * If called with an empty first argument and `$override` is set to true, the * previous list will be emptied. * @@ -455,13 +429,8 @@ public function contain($associations = null, $override = false) $this->clearContain(); } - if ($associations === null) { - deprecationWarning( - 'Using Query::contain() as getter is deprecated. ' . - 'Use getContain() instead.' - ); - - return $loader->getContain(); + if ($associations === null && $override === false) { + throw new InvalidArgumentException('$associations can be null only $override is true.'); } $queryBuilder = null; @@ -1019,29 +988,6 @@ public function isHydrationEnabled() return $this->_hydrate; } - /** - * Toggle hydrating entities. - * - * If set to false array results will be returned. - * - * @deprecated 3.4.0 Use enableHydration()/isHydrationEnabled() instead. - * @param bool|null $enable Use a boolean to set the hydration mode. - * Null will fetch the current hydration mode. - * @return bool|$this A boolean when reading, and $this when setting the mode. - */ - public function hydrate($enable = null) - { - deprecationWarning( - 'Query::hydrate() is deprecated. ' . - 'Use enableHydration()/isHydrationEnabled() instead.' - ); - if ($enable === null) { - return $this->isHydrationEnabled(); - } - - return $this->enableHydration($enable); - } - /** * {@inheritDoc} * @@ -1353,29 +1299,6 @@ public function isAutoFieldsEnabled() return $this->_autoFields; } - /** - * Get/Set whether or not the ORM should automatically append fields. - * - * By default calling select() will disable auto-fields. You can re-enable - * auto-fields with this method. - * - * @deprecated 3.4.0 Use enableAutoFields()/isAutoFieldsEnabled() instead. - * @param bool|null $value The value to set or null to read the current value. - * @return bool|$this Either the current value or the query object. - */ - public function autoFields($value = null) - { - deprecationWarning( - 'Query::autoFields() is deprecated. ' . - 'Use enableAutoFields()/isAutoFieldsEnabled() instead.' - ); - if ($value === null) { - return $this->isAutoFieldsEnabled(); - } - - return $this->enableAutoFields($value); - } - /** * Decorates the results iterator with MapReduce routines and formatters * From ea107a5347b97873aac02ded77947a8ef51589ec Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 19 May 2018 12:03:48 +0530 Subject: [PATCH 1175/2059] Remove deprecated code from Table class. --- Table.php | 193 ------------------------------------------------------ 1 file changed, 193 deletions(-) diff --git a/Table.php b/Table.php index 97a522fe..e94ea352 100644 --- a/Table.php +++ b/Table.php @@ -361,26 +361,6 @@ public function getTable() return $this->_table; } - /** - * Returns the database table name or sets a new one. - * - * @deprecated 3.4.0 Use setTable()/getTable() instead. - * @param string|null $table the new table name - * @return string - */ - public function table($table = null) - { - deprecationWarning( - get_called_class() . '::table() is deprecated. ' . - 'Use setTable()/getTable() instead.' - ); - if ($table !== null) { - $this->setTable($table); - } - - return $this->getTable(); - } - /** * Sets the table alias. * @@ -410,23 +390,6 @@ public function getAlias() return $this->_alias; } - /** - * {@inheritDoc} - * @deprecated 3.4.0 Use setAlias()/getAlias() instead. - */ - public function alias($alias = null) - { - deprecationWarning( - get_called_class() . '::alias() is deprecated. ' . - 'Use setAlias()/getAlias() instead.' - ); - if ($alias !== null) { - $this->setAlias($alias); - } - - return $this->getAlias(); - } - /** * Alias a field with the table's current alias. * @@ -471,26 +434,6 @@ public function getRegistryAlias() return $this->_registryAlias; } - /** - * Returns the table registry key used to create this table instance or sets one. - * - * @deprecated 3.4.0 Use setRegistryAlias()/getRegistryAlias() instead. - * @param string|null $registryAlias the key used to access this object - * @return string - */ - public function registryAlias($registryAlias = null) - { - deprecationWarning( - get_called_class() . '::registryAlias() is deprecated. ' . - 'Use setRegistryAlias()/getRegistryAlias() instead.' - ); - if ($registryAlias !== null) { - $this->setRegistryAlias($registryAlias); - } - - return $this->getRegistryAlias(); - } - /** * Sets the connection instance. * @@ -514,26 +457,6 @@ public function getConnection() return $this->_connection; } - /** - * Returns the connection instance or sets a new one - * - * @deprecated 3.4.0 Use setConnection()/getConnection() instead. - * @param \Cake\Datasource\ConnectionInterface|null $connection The new connection instance - * @return \Cake\Datasource\ConnectionInterface - */ - public function connection(ConnectionInterface $connection = null) - { - deprecationWarning( - get_called_class() . '::connection() is deprecated. ' . - 'Use setConnection()/getConnection() instead.' - ); - if ($connection !== null) { - $this->setConnection($connection); - } - - return $this->getConnection(); - } - /** * Returns the schema table object describing this table's properties. * @@ -583,32 +506,6 @@ public function setSchema($schema) return $this; } - /** - * Returns the schema table object describing this table's properties. - * - * If a TableSchema is passed, it will be used for this table - * instead of the default one. - * - * If an array is passed, a new TableSchema will be constructed - * out of it and used as the schema for this table. - * - * @deprecated 3.4.0 Use setSchema()/getSchema() instead. - * @param array|\Cake\Database\Schema\TableSchema|null $schema New schema to be used for this table - * @return \Cake\Database\Schema\TableSchema - */ - public function schema($schema = null) - { - deprecationWarning( - get_called_class() . '::schema() is deprecated. ' . - 'Use setSchema()/getSchema() instead.' - ); - if ($schema !== null) { - $this->setSchema($schema); - } - - return $this->getSchema(); - } - /** * Override this function in order to alter the schema used by this table. * This function is only called after fetching the schema out of the database. @@ -680,26 +577,6 @@ public function getPrimaryKey() return $this->_primaryKey; } - /** - * Returns the primary key field name or sets a new one - * - * @deprecated 3.4.0 Use setPrimaryKey()/getPrimaryKey() instead. - * @param string|array|null $key Sets a new name to be used as primary key - * @return string|array - */ - public function primaryKey($key = null) - { - deprecationWarning( - get_called_class() . '::primaryKey() is deprecated. ' . - 'Use setPrimaryKey()/getPrimaryKey() instead.' - ); - if ($key !== null) { - $this->setPrimaryKey($key); - } - - return $this->getPrimaryKey(); - } - /** * Sets the display field. * @@ -735,28 +612,6 @@ public function getDisplayField() return $this->_displayField; } - /** - * Returns the display field or sets a new one - * - * @deprecated 3.4.0 Use setDisplayField()/getDisplayField() instead. - * @param string|null $key sets a new name to be used as display field - * @return string - */ - public function displayField($key = null) - { - deprecationWarning( - get_called_class() . '::displayField() is deprecated. ' . - 'Use setDisplayField()/getDisplayField() instead.' - ); - if ($key !== null) { - $this->setDisplayField($key); - - return $key; - } - - return $this->getDisplayField(); - } - /** * Returns the class used to hydrate rows for this table. * @@ -809,28 +664,6 @@ public function setEntityClass($name) return $this; } - /** - * Returns the class used to hydrate rows for this table or sets - * a new one - * - * @deprecated 3.4.0 Use setEntityClass()/getEntityClass() instead. - * @param string|null $name The name of the class to use - * @throws \Cake\ORM\Exception\MissingEntityException when the entity class cannot be found - * @return string - */ - public function entityClass($name = null) - { - deprecationWarning( - get_called_class() . '::entityClass() is deprecated. ' . - 'Use setEntityClass()/getEntityClass() instead.' - ); - if ($name !== null) { - $this->setEntityClass($name); - } - - return $this->getEntityClass(); - } - /** * Add a behavior. * @@ -957,20 +790,6 @@ public function hasBehavior($name) return $this->_behaviors->has($name); } - /** - * Returns an association object configured for the specified alias if any. - * - * @deprecated 3.6.0 Use getAssociation() and Table::hasAssocation() instead. - * @param string $name the alias used for the association. - * @return \Cake\ORM\Association|null Either the association or null. - */ - public function association($name) - { - deprecationWarning('Use Table::getAssociation() and Table::hasAssocation() instead.'); - - return $this->findAssociation($name); - } - /** * Returns an association object configured for the specified alias. * @@ -1430,12 +1249,6 @@ public function findList(Query $query, array $options) 'groupField' => null ]; - if (isset($options['idField'])) { - $options['keyField'] = $options['idField']; - unset($options['idField']); - deprecationWarning('Option "idField" is deprecated, use "keyField" instead.'); - } - if (!$query->clause('select') && !is_object($options['keyField']) && !is_object($options['valueField']) && @@ -1499,12 +1312,6 @@ public function findThreaded(Query $query, array $options) 'nestingKey' => 'children' ]; - if (isset($options['idField'])) { - $options['keyField'] = $options['idField']; - unset($options['idField']); - deprecationWarning('Option "idField" is deprecated, use "keyField" instead.'); - } - $options = $this->_setFieldMatchers($options, ['keyField', 'parentField']); return $query->formatResults(function ($results) use ($options) { From 40592194813f22a4a7dc5ad82e703e5563a90b94 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 19 May 2018 12:05:17 +0530 Subject: [PATCH 1176/2059] Remove deprecated code from ResultSet. --- ResultSet.php | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/ResultSet.php b/ResultSet.php index 8c2fdeaa..6f96244e 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -33,14 +33,6 @@ class ResultSet implements ResultSetInterface use CollectionTrait; - /** - * Original query from where results were generated - * - * @var \Cake\ORM\Query - * @deprecated 3.1.6 Due to a memory leak, this property cannot be used anymore - */ - protected $_query; - /** * Database statement holding the results * @@ -550,22 +542,6 @@ protected function _groupResult($row) return $results; } - /** - * Casts all values from a row brought from a table to the correct - * PHP type. - * - * @param string $alias The table object alias - * @param array $values The values to cast - * @deprecated 3.2.0 Not used anymore. Type casting is done at the statement level - * @return array - */ - protected function _castValues($alias, $values) - { - deprecationWarning('ResultSet::_castValues() is deprecated, and will be removed in 4.0.0.'); - - return $values; - } - /** * Returns an array that can be used to describe the internal state of this * object. From 9c68216ced3d5b73ff03d96e98d9aaa7d6f074dd Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 20 May 2018 11:17:56 +0530 Subject: [PATCH 1177/2059] Removed unneeded null value for argument. --- Query.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Query.php b/Query.php index c2d187a3..f061f4a7 100644 --- a/Query.php +++ b/Query.php @@ -415,24 +415,20 @@ public function getEagerLoader() * If called with an empty first argument and `$override` is set to true, the * previous list will be emptied. * - * @param array|string|null $associations List of table aliases to be queried. + * @param array|string $associations List of table aliases to be queried. * @param callable|bool $override The query builder for the association, or * if associations is an array, a bool on whether to override previous list * with the one passed * defaults to merging previous list with the new one. * @return array|$this */ - public function contain($associations = null, $override = false) + public function contain($associations, $override = false) { $loader = $this->getEagerLoader(); if ($override === true) { $this->clearContain(); } - if ($associations === null && $override === false) { - throw new InvalidArgumentException('$associations can be null only $override is true.'); - } - $queryBuilder = null; if (is_callable($override)) { $queryBuilder = $override; From 0d9c0837d37e5b8a1847409618f29c7f3d5a12f7 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 20 May 2018 11:38:35 +0530 Subject: [PATCH 1178/2059] Remove unused "use" statements. --- ResultSet.php | 1 - 1 file changed, 1 deletion(-) diff --git a/ResultSet.php b/ResultSet.php index 8c2fdeaa..2c23228d 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -17,7 +17,6 @@ use Cake\Collection\Collection; use Cake\Collection\CollectionTrait; use Cake\Database\Exception; -use Cake\Database\TypeFactory; use Cake\Datasource\EntityInterface; use Cake\Datasource\ResultSetInterface; use SplFixedArray; From 87a99f2ea711f491a313edb5e5aaf5f4d2ce0873 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 21 May 2018 13:46:54 +0530 Subject: [PATCH 1179/2059] Remove defunct method from "implementedMethods". --- Behavior/TranslateBehavior.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 8badf160..f2ccbb6b 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -79,7 +79,6 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface 'implementedMethods' => [ 'setLocale' => 'setLocale', 'getLocale' => 'getLocale', - 'locale' => 'locale', 'translationField' => 'translationField' ], 'fields' => [], From 1bb7c89418d6f0f4d34d060f5fb4443ed184bc97 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 21 May 2018 19:10:08 +0530 Subject: [PATCH 1180/2059] Move the guts of TranslateBehavior into strategy class. --- Behavior/Translate/EavStrategy.php | 671 +++++++++++++++++++++++++++++ Behavior/TranslateBehavior.php | 571 +++--------------------- 2 files changed, 729 insertions(+), 513 deletions(-) create mode 100644 Behavior/Translate/EavStrategy.php diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php new file mode 100644 index 00000000..83c6b23e --- /dev/null +++ b/Behavior/Translate/EavStrategy.php @@ -0,0 +1,671 @@ + [], + 'translationTable' => 'I18n', + 'defaultLocale' => null, + 'referenceName' => null, + 'allowEmptyTranslations' => true, + 'onlyTranslated' => false, + 'strategy' => 'subquery', + 'tableLocator' => null, + 'validator' => false, + ]; + + /** + * Constructor + * + * @param \Cake\ORM\Table $table The table this behavior is attached to. + * @param array $config The config for this behavior. + */ + public function __construct(Table $table, array $config = []) + { + if (isset($config['tableLocator'])) { + $this->_tableLocator = $config['tableLocator']; + } + + $this->setConfig($config); + $this->table = $table; + $this->translationTable = $this->getTableLocator()->get($this->_config['translationTable']); + + $this->setupFieldAssociations( + $this->_config['fields'], + $this->_config['translationTable'], + $this->_config['referenceName'], + $this->_config['strategy'] + ); + } + + /** + * Return translation table instance. + * + * @return \Cake\ORM\Table + */ + public function getTranslationTable(): Table + { + return $this->translationTable; + } + + /** + * Creates the associations between the bound table and every field passed to + * this method. + * + * Additionally it creates a `i18n` HasMany association that will be + * used for fetching all translations for each record in the bound table + * + * @param array $fields list of fields to create associations for + * @param string $table the table name to use for storing each field translation + * @param string $model the model field value + * @param string $strategy the strategy used in the _i18n association + * + * @return void + */ + protected function setupFieldAssociations($fields, $table, $model, $strategy) + { + $targetAlias = $this->translationTable->getAlias(); + $alias = $this->table->getAlias(); + $filter = $this->_config['onlyTranslated']; + $tableLocator = $this->getTableLocator(); + + foreach ($fields as $field) { + $name = $alias . '_' . $field . '_translation'; + + if (!$tableLocator->exists($name)) { + $fieldTable = $tableLocator->get($name, [ + 'className' => $table, + 'alias' => $name, + 'table' => $this->translationTable->getTable(), + ]); + } else { + $fieldTable = $tableLocator->get($name); + } + + $conditions = [ + $name . '.model' => $model, + $name . '.field' => $field, + ]; + if (!$this->_config['allowEmptyTranslations']) { + $conditions[$name . '.content !='] = ''; + } + + $this->table->hasOne($name, [ + 'targetTable' => $fieldTable, + 'foreignKey' => 'foreign_key', + 'joinType' => $filter ? QueryInterface::JOIN_TYPE_INNER : QueryInterface::JOIN_TYPE_LEFT, + 'conditions' => $conditions, + 'propertyName' => $field . '_translation', + ]); + } + + $conditions = ["$targetAlias.model" => $model]; + if (!$this->_config['allowEmptyTranslations']) { + $conditions["$targetAlias.content !="] = ''; + } + + $this->table->hasMany($targetAlias, [ + 'className' => $table, + 'foreignKey' => 'foreign_key', + 'strategy' => $strategy, + 'conditions' => $conditions, + 'propertyName' => '_i18n', + 'dependent' => true, + ]); + } + + /** + * Callback method that listens to the `beforeFind` event in the bound + * table. It modifies the passed query by eager loading the translated fields + * and adding a formatter to copy the values into the main table records. + * + * @param \Cake\Event\Event $event The beforeFind event that was fired. + * @param \Cake\ORM\Query $query Query + * @param \ArrayObject $options The options for the query + * @return void + */ + public function beforeFind(Event $event, Query $query, ArrayObject $options): void + { + $locale = $this->getLocale(); + + if ($locale === $this->getConfig('defaultLocale')) { + return; + } + + $conditions = function ($field, $locale, $query, $select) { + return function ($q) use ($field, $locale, $query, $select) { + /* @var \Cake\Datasource\QueryInterface $q */ + $q->where([$q->getRepository()->aliasField('locale') => $locale]); + + /* @var \Cake\ORM\Query $query */ + if ($query->isAutoFieldsEnabled() || + in_array($field, $select, true) || + in_array($this->table->aliasField($field), $select, true) + ) { + $q->select(['id', 'content']); + } + + return $q; + }; + }; + + $contain = []; + $fields = $this->_config['fields']; + $alias = $this->table->getAlias(); + $select = $query->clause('select'); + + $changeFilter = isset($options['filterByCurrentLocale']) && + $options['filterByCurrentLocale'] !== $this->_config['onlyTranslated']; + + foreach ($fields as $field) { + $name = $alias . '_' . $field . '_translation'; + + $contain[$name]['queryBuilder'] = $conditions( + $field, + $locale, + $query, + $select + ); + + if ($changeFilter) { + $filter = $options['filterByCurrentLocale'] ? QueryInterface::JOIN_TYPE_INNER : QueryInterface::JOIN_TYPE_LEFT; + $contain[$name]['joinType'] = $filter; + } + } + + $query->contain($contain); + $query->formatResults(function ($results) use ($locale) { + return $this->rowMapper($results, $locale); + }, $query::PREPEND); + } + + /** + * Modifies the entity before it is saved so that translated fields are persisted + * in the database too. + * + * @param \Cake\Event\Event $event The beforeSave event that was fired + * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved + * @param \ArrayObject $options the options passed to the save method + * @return void + */ + public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options): void + { + $locale = $entity->get('_locale') ?: $this->getLocale(); + $newOptions = [$this->translationTable->getAlias() => ['validate' => false]]; + $options['associated'] = $newOptions + $options['associated']; + + // Check early if empty translations are present in the entity. + // If this is the case, unset them to prevent persistence. + // This only applies if $this->_config['allowEmptyTranslations'] is false + if ($this->_config['allowEmptyTranslations'] === false) { + $this->unsetEmptyFields($entity); + } + + $this->bundleTranslatedFields($entity); + $bundled = $entity->get('_i18n') ?: []; + $noBundled = count($bundled) === 0; + + // No additional translation records need to be saved, + // as the entity is in the default locale. + if ($noBundled && $locale === $this->getConfig('defaultLocale')) { + return; + } + + $values = $entity->extract($this->_config['fields'], true); + $fields = array_keys($values); + $noFields = empty($fields); + + // If there are no fields and no bundled translations, or both fields + // in the default locale and bundled translations we can + // skip the remaining logic as its not necessary. + if ($noFields && $noBundled || ($fields && $bundled)) { + return; + } + + $primaryKey = (array)$this->table->getPrimaryKey(); + $key = $entity->get(current($primaryKey)); + + // When we have no key and bundled translations, we + // need to mark the entity dirty so the root + // entity persists. + if ($noFields && $bundled && !$key) { + foreach ($this->_config['fields'] as $field) { + $entity->setDirty($field, true); + } + + return; + } + + if ($noFields) { + return; + } + + $model = $this->_config['referenceName']; + $preexistent = $this->translationTable->find() + ->select(['id', 'field']) + ->where([ + 'field IN' => $fields, + 'locale' => $locale, + 'foreign_key' => $key, + 'model' => $model, + ]) + ->enableBufferedResults(false) + ->all() + ->indexBy('field'); + + $modified = []; + foreach ($preexistent as $field => $translation) { + $translation->set('content', $values[$field]); + $modified[$field] = $translation; + } + + $new = array_diff_key($values, $modified); + foreach ($new as $field => $content) { + $new[$field] = new Entity(compact('locale', 'field', 'content', 'model'), [ + 'useSetters' => false, + 'markNew' => true, + ]); + } + + $entity->set('_i18n', array_merge($bundled, array_values($modified + $new))); + $entity->set('_locale', $locale, ['setter' => false]); + $entity->setDirty('_locale', false); + + foreach ($fields as $field) { + $entity->setDirty($field, false); + } + } + + /** + * Unsets the temporary `_i18n` property after the entity has been saved + * + * @param \Cake\Event\Event $event The beforeSave event that was fired + * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved + * @return void + */ + public function afterSave(Event $event, EntityInterface $entity): void + { + $entity->unsetProperty('_i18n'); + } + + /** + * Add in `_translations` marshalling handlers. You can disable marshalling + * of translations by setting `'translations' => false` in the options + * provided to `Table::newEntity()` or `Table::patchEntity()`. + * + * {@inheritDoc} + */ + public function buildMarshalMap($marshaller, $map, $options) + { + if (isset($options['translations']) && !$options['translations']) { + return []; + } + + return [ + '_translations' => function ($value, $entity) use ($marshaller, $options) { + /* @var \Cake\Datasource\EntityInterface $entity */ + $translations = $entity->get('_translations'); + foreach ($this->_config['fields'] as $field) { + $options['validate'] = $this->_config['validator']; + $errors = []; + if (!is_array($value)) { + return null; + } + foreach ($value as $language => $fields) { + if (!isset($translations[$language])) { + $translations[$language] = $this->table->newEntity(); + } + $marshaller->merge($translations[$language], $fields, $options); + if ((bool)$translations[$language]->getErrors()) { + $errors[$language] = $translations[$language]->getErrors(); + } + } + // Set errors into the root entity, so validation errors + // match the original form data position. + $entity->setErrors($errors); + } + + return $translations; + }, + ]; + } + + /** + * Sets the locale that should be used for all future find and save operations on + * the table where this behavior is attached to. + * + * When fetching records, the behavior will include the content for the locale set + * via this method, and likewise when saving data, it will save the data in that + * locale. + * + * Note that in case an entity has a `_locale` property set, that locale will win + * over the locale set via this method (and over the globally configured one for + * that matter)! + * + * @param string|null $locale The locale to use for fetching and saving records. Pass `null` + * in order to unset the current locale, and to make the behavior fall back to using the + * globally configured locale. + * @return $this + * @see \Cake\ORM\Behavior\TranslateBehavior::getLocale() + * @link https://book.cakephp.org/3.0/en/orm/behaviors/translate.html#retrieving-one-language-without-using-i18n-locale + * @link https://book.cakephp.org/3.0/en/orm/behaviors/translate.html#saving-in-another-language + */ + public function setLocale(?string $locale) + { + $this->locale = $locale; + + return $this; + } + + /** + * Returns the current locale. + * + * If no locale has been explicitly set via `setLocale()`, this method will return + * the currently configured global locale. + * + * @return string + * @see \Cake\I18n\I18n::getLocale() + * @see \Cake\ORM\Behavior\TranslateBehavior::setLocale() + */ + public function getLocale(): string + { + return $this->locale ?: I18n::getLocale(); + } + + /** + * Returns a fully aliased field name for translated fields. + * + * If the requested field is configured as a translation field, the `content` + * field with an alias of a corresponding association is returned. Table-aliased + * field name is returned for all other fields. + * + * @param string $field Field name to be aliased. + * @return string + */ + public function translationField(string $field): string + { + $table = $this->table; + if ($this->getLocale() === $this->getConfig('defaultLocale')) { + return $table->aliasField($field); + } + $associationName = $table->getAlias() . '_' . $field . '_translation'; + + if ($table->associations()->has($associationName)) { + return $associationName . '.content'; + } + + return $table->aliasField($field); + } + + /** + * Modifies the results from a table find in order to merge the translated fields + * into each entity for a given locale. + * + * @param \Cake\Datasource\ResultSetInterface $results Results to map. + * @param string $locale Locale string + * @return \Cake\Collection\CollectionInterface + */ + protected function rowMapper($results, $locale) + { + return $results->map(function ($row) use ($locale) { + if ($row === null) { + return $row; + } + $hydrated = !is_array($row); + + foreach ($this->_config['fields'] as $field) { + $name = $field . '_translation'; + $translation = isset($row[$name]) ? $row[$name] : null; + + if ($translation === null || $translation === false) { + unset($row[$name]); + continue; + } + + $content = isset($translation['content']) ? $translation['content'] : null; + if ($content !== null) { + $row[$field] = $content; + } + + unset($row[$name]); + } + + $row['_locale'] = $locale; + if ($hydrated) { + /* @var \Cake\Datasource\EntityInterface $row */ + $row->clean(); + } + + return $row; + }); + } + + /** + * Modifies the results from a table find in order to merge full translation records + * into each entity under the `_translations` key + * + * @param \Cake\Datasource\ResultSetInterface $results Results to modify. + * @return \Cake\Collection\CollectionInterface + */ + public function groupTranslations($results): CollectionInterface + { + return $results->map(function ($row) { + if (!$row instanceof EntityInterface) { + return $row; + } + $translations = (array)$row->get('_i18n'); + if (empty($translations) && $row->get('_translations')) { + return $row; + } + $grouped = new Collection($translations); + + $result = []; + foreach ($grouped->combine('field', 'content', 'locale') as $locale => $keys) { + $entityClass = $this->table->getEntityClass(); + $translation = new $entityClass($keys + ['locale' => $locale], [ + 'markNew' => false, + 'useSetters' => false, + 'markClean' => true, + ]); + $result[$locale] = $translation; + } + + $options = ['setter' => false, 'guard' => false]; + $row->set('_translations', $result, $options); + unset($row['_i18n']); + $row->clean(); + + return $row; + }); + } + + /** + * Helper method used to generated multiple translated field entities + * out of the data found in the `_translations` property in the passed + * entity. The result will be put into its `_i18n` property + * + * @param \Cake\Datasource\EntityInterface $entity Entity + * @return void + */ + protected function bundleTranslatedFields($entity) + { + $translations = (array)$entity->get('_translations'); + + if (empty($translations) && !$entity->isDirty('_translations')) { + return; + } + + $fields = $this->_config['fields']; + $primaryKey = (array)$this->table->getPrimaryKey(); + $key = $entity->get(current($primaryKey)); + $find = []; + $contents = []; + + foreach ($translations as $lang => $translation) { + foreach ($fields as $field) { + if (!$translation->isDirty($field)) { + continue; + } + $find[] = ['locale' => $lang, 'field' => $field, 'foreign_key' => $key]; + $contents[] = new Entity(['content' => $translation->get($field)], [ + 'useSetters' => false, + ]); + } + } + + if (empty($find)) { + return; + } + + $results = $this->findExistingTranslations($find); + + foreach ($find as $i => $translation) { + if (!empty($results[$i])) { + $contents[$i]->set('id', $results[$i], ['setter' => false]); + $contents[$i]->isNew(false); + } else { + $translation['model'] = $this->_config['referenceName']; + $contents[$i]->set($translation, ['setter' => false, 'guard' => false]); + $contents[$i]->isNew(true); + } + } + + $entity->set('_i18n', $contents); + } + + /** + * Unset empty translations to avoid persistence. + * + * Should only be called if $this->_config['allowEmptyTranslations'] is false. + * + * @param \Cake\Datasource\EntityInterface $entity The entity to check for empty translations fields inside. + * @return void + */ + protected function unsetEmptyFields($entity) + { + $translations = (array)$entity->get('_translations'); + foreach ($translations as $locale => $translation) { + $fields = $translation->extract($this->_config['fields'], false); + foreach ($fields as $field => $value) { + if (strlen($value) === 0) { + $translation->unsetProperty($field); + } + } + + $translation = $translation->extract($this->_config['fields']); + + // If now, the current locale property is empty, + // unset it completely. + if (empty(array_filter($translation))) { + unset($entity->get('_translations')[$locale]); + } + } + + // If now, the whole _translations property is empty, + // unset it completely and return + if (empty($entity->get('_translations'))) { + $entity->unsetProperty('_translations'); + } + } + + /** + * Returns the ids found for each of the condition arrays passed for the translations + * table. Each records is indexed by the corresponding position to the conditions array + * + * @param array $ruleSet an array of arary of conditions to be used for finding each + * @return array + */ + protected function findExistingTranslations($ruleSet) + { + $association = $this->table->getAssociation($this->translationTable->getAlias()); + + $query = $association->find() + ->select(['id', 'num' => 0]) + ->where(current($ruleSet)) + ->enableHydration(false) + ->enableBufferedResults(false); + + unset($ruleSet[0]); + foreach ($ruleSet as $i => $conditions) { + $q = $association->find() + ->select(['id', 'num' => $i]) + ->where($conditions); + $query->unionAll($q); + } + + return $query->all()->combine('num', 'id')->toArray(); + } +} diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index f2ccbb6b..e4dd9185 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -14,15 +14,9 @@ */ namespace Cake\ORM\Behavior; -use ArrayObject; -use Cake\Collection\Collection; -use Cake\Datasource\EntityInterface; -use Cake\Datasource\QueryInterface; -use Cake\Event\Event; use Cake\I18n\I18n; use Cake\ORM\Behavior; -use Cake\ORM\Entity; -use Cake\ORM\Locator\LocatorAwareTrait; +use Cake\ORM\Behavior\Translate\EavStrategy; use Cake\ORM\PropertyMarshalInterface; use Cake\ORM\Query; use Cake\ORM\Table; @@ -42,31 +36,6 @@ */ class TranslateBehavior extends Behavior implements PropertyMarshalInterface { - - use LocatorAwareTrait; - - /** - * Table instance - * - * @var \Cake\ORM\Table - */ - protected $_table; - - /** - * The locale name that will be used to override fields in the bound table - * from the translations table - * - * @var string - */ - protected $_locale; - - /** - * Instance of Table responsible for translating - * - * @var \Cake\ORM\Table - */ - protected $_translationTable; - /** * Default config * @@ -79,19 +48,26 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface 'implementedMethods' => [ 'setLocale' => 'setLocale', 'getLocale' => 'getLocale', - 'translationField' => 'translationField' + 'translationField' => 'translationField', ], + 'strategyClass' => EavStrategy::class, 'fields' => [], - 'translationTable' => 'I18n', 'defaultLocale' => '', 'referenceName' => '', 'allowEmptyTranslations' => true, 'onlyTranslated' => false, 'strategy' => 'subquery', 'tableLocator' => null, - 'validator' => false + 'validator' => false, ]; + /** + * Translation strategy instance. + * + * @var object|null + */ + protected $strategy; + /** * Constructor * @@ -102,13 +78,9 @@ public function __construct(Table $table, array $config = []) { $config += [ 'defaultLocale' => I18n::getDefaultLocale(), - 'referenceName' => $this->_referenceName($table) + 'referenceName' => $this->referenceName($table), ]; - if (isset($config['tableLocator'])) { - $this->_tableLocator = $config['tableLocator']; - } - parent::__construct($table, $config); } @@ -120,254 +92,54 @@ public function __construct(Table $table, array $config = []) */ public function initialize(array $config) { - $this->_translationTable = $this->getTableLocator()->get($this->_config['translationTable']); - - $this->setupFieldAssociations( - $this->_config['fields'], - $this->_config['translationTable'], - $this->_config['referenceName'], - $this->_config['strategy'] - ); - } - - /** - * Creates the associations between the bound table and every field passed to - * this method. - * - * Additionally it creates a `i18n` HasMany association that will be - * used for fetching all translations for each record in the bound table - * - * @param array $fields list of fields to create associations for - * @param string $table the table name to use for storing each field translation - * @param string $model the model field value - * @param string $strategy the strategy used in the _i18n association - * - * @return void - */ - public function setupFieldAssociations($fields, $table, $model, $strategy) - { - $targetAlias = $this->_translationTable->getAlias(); - $alias = $this->_table->getAlias(); - $filter = $this->_config['onlyTranslated']; - $tableLocator = $this->getTableLocator(); - - foreach ($fields as $field) { - $name = $alias . '_' . $field . '_translation'; - - if (!$tableLocator->exists($name)) { - $fieldTable = $tableLocator->get($name, [ - 'className' => $table, - 'alias' => $name, - 'table' => $this->_translationTable->getTable() - ]); - } else { - $fieldTable = $tableLocator->get($name); - } - - $conditions = [ - $name . '.model' => $model, - $name . '.field' => $field, - ]; - if (!$this->_config['allowEmptyTranslations']) { - $conditions[$name . '.content !='] = ''; - } - - $this->_table->hasOne($name, [ - 'targetTable' => $fieldTable, - 'foreignKey' => 'foreign_key', - 'joinType' => $filter ? QueryInterface::JOIN_TYPE_INNER : QueryInterface::JOIN_TYPE_LEFT, - 'conditions' => $conditions, - 'propertyName' => $field . '_translation' - ]); - } - - $conditions = ["$targetAlias.model" => $model]; - if (!$this->_config['allowEmptyTranslations']) { - $conditions["$targetAlias.content !="] = ''; - } - - $this->_table->hasMany($targetAlias, [ - 'className' => $table, - 'foreignKey' => 'foreign_key', - 'strategy' => $strategy, - 'conditions' => $conditions, - 'propertyName' => '_i18n', - 'dependent' => true - ]); + $this->getStrategy(); } /** - * Callback method that listens to the `beforeFind` event in the bound - * table. It modifies the passed query by eager loading the translated fields - * and adding a formatter to copy the values into the main table records. + * Get strategy class instance. * - * @param \Cake\Event\Event $event The beforeFind event that was fired. - * @param \Cake\ORM\Query $query Query - * @param \ArrayObject $options The options for the query - * @return void + * @return object */ - public function beforeFind(Event $event, Query $query, $options) + public function getStrategy() { - $locale = $this->getLocale(); - - if ($locale === $this->getConfig('defaultLocale')) { - return; + if ($this->strategy !== null) { + return $this->strategy; } - $conditions = function ($field, $locale, $query, $select) { - return function ($q) use ($field, $locale, $query, $select) { - /* @var \Cake\Datasource\QueryInterface $q */ - $q->where([$q->getRepository()->aliasField('locale') => $locale]); - - /* @var \Cake\ORM\Query $query */ - if ($query->isAutoFieldsEnabled() || - in_array($field, $select, true) || - in_array($this->_table->aliasField($field), $select, true) - ) { - $q->select(['id', 'content']); - } - - return $q; - }; - }; - - $contain = []; - $fields = $this->_config['fields']; - $alias = $this->_table->getAlias(); - $select = $query->clause('select'); - - $changeFilter = isset($options['filterByCurrentLocale']) && - $options['filterByCurrentLocale'] !== $this->_config['onlyTranslated']; - - foreach ($fields as $field) { - $name = $alias . '_' . $field . '_translation'; - - $contain[$name]['queryBuilder'] = $conditions( - $field, - $locale, - $query, - $select - ); - - if ($changeFilter) { - $filter = $options['filterByCurrentLocale'] ? QueryInterface::JOIN_TYPE_INNER : QueryInterface::JOIN_TYPE_LEFT; - $contain[$name]['joinType'] = $filter; - } - } + $config = array_diff_key( + $this->_config, + ['implementedFinders', 'implementedMethods', 'strategyClass'] + ); + $this->strategy = new $this->_config['strategyClass']($this->_table, $config); - $query->contain($contain); - $query->formatResults(function ($results) use ($locale) { - return $this->_rowMapper($results, $locale); - }, $query::PREPEND); + return $this->strategy; } /** - * Modifies the entity before it is saved so that translated fields are persisted - * in the database too. + * Set strategy class instance. * - * @param \Cake\Event\Event $event The beforeSave event that was fired - * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved - * @param \ArrayObject $options the options passed to the save method - * @return void + * @param object $strategy Strategy class instance. + * @return $this */ - public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options) + public function setStrategy($strategy) { - $locale = $entity->get('_locale') ?: $this->getLocale(); - $newOptions = [$this->_translationTable->getAlias() => ['validate' => false]]; - $options['associated'] = $newOptions + $options['associated']; - - // Check early if empty translations are present in the entity. - // If this is the case, unset them to prevent persistence. - // This only applies if $this->_config['allowEmptyTranslations'] is false - if ($this->_config['allowEmptyTranslations'] === false) { - $this->_unsetEmptyFields($entity); - } - - $this->_bundleTranslatedFields($entity); - $bundled = $entity->get('_i18n') ?: []; - $noBundled = count($bundled) === 0; - - // No additional translation records need to be saved, - // as the entity is in the default locale. - if ($noBundled && $locale === $this->getConfig('defaultLocale')) { - return; - } - - $values = $entity->extract($this->_config['fields'], true); - $fields = array_keys($values); - $noFields = empty($fields); - - // If there are no fields and no bundled translations, or both fields - // in the default locale and bundled translations we can - // skip the remaining logic as its not necessary. - if ($noFields && $noBundled || ($fields && $bundled)) { - return; - } + $this->strategy = $strategy; - $primaryKey = (array)$this->_table->getPrimaryKey(); - $key = $entity->get(current($primaryKey)); - - // When we have no key and bundled translations, we - // need to mark the entity dirty so the root - // entity persists. - if ($noFields && $bundled && !$key) { - foreach ($this->_config['fields'] as $field) { - $entity->setDirty($field, true); - } - - return; - } - - if ($noFields) { - return; - } - - $model = $this->_config['referenceName']; - $preexistent = $this->_translationTable->find() - ->select(['id', 'field']) - ->where([ - 'field IN' => $fields, - 'locale' => $locale, - 'foreign_key' => $key, - 'model' => $model - ]) - ->enableBufferedResults(false) - ->all() - ->indexBy('field'); - - $modified = []; - foreach ($preexistent as $field => $translation) { - $translation->set('content', $values[$field]); - $modified[$field] = $translation; - } - - $new = array_diff_key($values, $modified); - foreach ($new as $field => $content) { - $new[$field] = new Entity(compact('locale', 'field', 'content', 'model'), [ - 'useSetters' => false, - 'markNew' => true - ]); - } - - $entity->set('_i18n', array_merge($bundled, array_values($modified + $new))); - $entity->set('_locale', $locale, ['setter' => false]); - $entity->setDirty('_locale', false); - - foreach ($fields as $field) { - $entity->setDirty($field, false); - } + return $this; } /** - * Unsets the temporary `_i18n` property after the entity has been saved + * Gets the Model callbacks this behavior is interested in. * - * @param \Cake\Event\Event $event The beforeSave event that was fired - * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved - * @return void + * @return array */ - public function afterSave(Event $event, EntityInterface $entity) + public function implementedEvents() { - $entity->unsetProperty('_i18n'); + return [ + 'Model.beforeFind' => 'beforeFind', + 'Model.beforeSave' => 'beforeSave', + 'Model.afterSave' => 'afterSave', + ]; } /** @@ -379,37 +151,7 @@ public function afterSave(Event $event, EntityInterface $entity) */ public function buildMarshalMap($marshaller, $map, $options) { - if (isset($options['translations']) && !$options['translations']) { - return []; - } - - return [ - '_translations' => function ($value, $entity) use ($marshaller, $options) { - /* @var \Cake\Datasource\EntityInterface $entity */ - $translations = $entity->get('_translations'); - foreach ($this->_config['fields'] as $field) { - $options['validate'] = $this->_config['validator']; - $errors = []; - if (!is_array($value)) { - return null; - } - foreach ($value as $language => $fields) { - if (!isset($translations[$language])) { - $translations[$language] = $this->_table->newEntity(); - } - $marshaller->merge($translations[$language], $fields, $options); - if ((bool)$translations[$language]->getErrors()) { - $errors[$language] = $translations[$language]->getErrors(); - } - } - // Set errors into the root entity, so validation errors - // match the original form data position. - $entity->setErrors($errors); - } - - return $translations; - } - ]; + return $this->getStrategy()->buildMarshalMap($marshaller, $map, $options); } /** @@ -434,7 +176,7 @@ public function buildMarshalMap($marshaller, $map, $options) */ public function setLocale($locale) { - $this->_locale = $locale; + $this->getStrategy()->setLocale($locale); return $this; } @@ -451,7 +193,7 @@ public function setLocale($locale) */ public function getLocale() { - return $this->_locale ?: I18n::getLocale(); + return $this->getStrategy()->getLocale(); } /** @@ -466,17 +208,7 @@ public function getLocale() */ public function translationField($field) { - $table = $this->_table; - if ($this->getLocale() === $this->getConfig('defaultLocale')) { - return $table->aliasField($field); - } - $associationName = $table->getAlias() . '_' . $field . '_translation'; - - if ($table->associations()->has($associationName)) { - return $associationName . '.content'; - } - - return $table->aliasField($field); + return $this->getStrategy()->translationField($field); } /** @@ -503,8 +235,8 @@ public function translationField($field) */ public function findTranslations(Query $query, array $options) { - $locales = isset($options['locales']) ? $options['locales'] : []; - $targetAlias = $this->_translationTable->getAlias(); + $locales = $options['locales'] ?? []; + $targetAlias = $this->getStrategy()->getTranslationTable()->getAlias(); return $query ->contain([$targetAlias => function ($query) use ($locales, $targetAlias) { @@ -515,7 +247,19 @@ public function findTranslations(Query $query, array $options) return $query; }]) - ->formatResults([$this, 'groupTranslations'], $query::PREPEND); + ->formatResults([$this->getStrategy(), 'groupTranslations'], $query::PREPEND); + } + + /** + * Proxy method calls to strategy class instance. + * + * @param string $method Method name. + * @param array $args Method arguments. + * @return mixed + */ + public function __call($method, $args) + { + return call_user_func_array([$this->strategy, $method], $args); } /** @@ -529,7 +273,7 @@ public function findTranslations(Query $query, array $options) * @param \Cake\ORM\Table $table The table class to get a reference name for. * @return string */ - protected function _referenceName(Table $table) + protected function referenceName(Table $table) { $name = namespaceSplit(get_class($table)); $name = substr(end($name), 0, -5); @@ -540,203 +284,4 @@ protected function _referenceName(Table $table) return $name; } - - /** - * Modifies the results from a table find in order to merge the translated fields - * into each entity for a given locale. - * - * @param \Cake\Datasource\ResultSetInterface $results Results to map. - * @param string $locale Locale string - * @return \Cake\Collection\CollectionInterface - */ - protected function _rowMapper($results, $locale) - { - return $results->map(function ($row) use ($locale) { - if ($row === null) { - return $row; - } - $hydrated = !is_array($row); - - foreach ($this->_config['fields'] as $field) { - $name = $field . '_translation'; - $translation = isset($row[$name]) ? $row[$name] : null; - - if ($translation === null || $translation === false) { - unset($row[$name]); - continue; - } - - $content = isset($translation['content']) ? $translation['content'] : null; - if ($content !== null) { - $row[$field] = $content; - } - - unset($row[$name]); - } - - $row['_locale'] = $locale; - if ($hydrated) { - /* @var \Cake\Datasource\EntityInterface $row */ - $row->clean(); - } - - return $row; - }); - } - - /** - * Modifies the results from a table find in order to merge full translation records - * into each entity under the `_translations` key - * - * @param \Cake\Datasource\ResultSetInterface $results Results to modify. - * @return \Cake\Collection\CollectionInterface - */ - public function groupTranslations($results) - { - return $results->map(function ($row) { - if (!$row instanceof EntityInterface) { - return $row; - } - $translations = (array)$row->get('_i18n'); - if (empty($translations) && $row->get('_translations')) { - return $row; - } - $grouped = new Collection($translations); - - $result = []; - foreach ($grouped->combine('field', 'content', 'locale') as $locale => $keys) { - $entityClass = $this->_table->getEntityClass(); - $translation = new $entityClass($keys + ['locale' => $locale], [ - 'markNew' => false, - 'useSetters' => false, - 'markClean' => true - ]); - $result[$locale] = $translation; - } - - $options = ['setter' => false, 'guard' => false]; - $row->set('_translations', $result, $options); - unset($row['_i18n']); - $row->clean(); - - return $row; - }); - } - - /** - * Helper method used to generated multiple translated field entities - * out of the data found in the `_translations` property in the passed - * entity. The result will be put into its `_i18n` property - * - * @param \Cake\Datasource\EntityInterface $entity Entity - * @return void - */ - protected function _bundleTranslatedFields($entity) - { - $translations = (array)$entity->get('_translations'); - - if (empty($translations) && !$entity->isDirty('_translations')) { - return; - } - - $fields = $this->_config['fields']; - $primaryKey = (array)$this->_table->getPrimaryKey(); - $key = $entity->get(current($primaryKey)); - $find = []; - $contents = []; - - foreach ($translations as $lang => $translation) { - foreach ($fields as $field) { - if (!$translation->isDirty($field)) { - continue; - } - $find[] = ['locale' => $lang, 'field' => $field, 'foreign_key' => $key]; - $contents[] = new Entity(['content' => $translation->get($field)], [ - 'useSetters' => false - ]); - } - } - - if (empty($find)) { - return; - } - - $results = $this->_findExistingTranslations($find); - - foreach ($find as $i => $translation) { - if (!empty($results[$i])) { - $contents[$i]->set('id', $results[$i], ['setter' => false]); - $contents[$i]->isNew(false); - } else { - $translation['model'] = $this->_config['referenceName']; - $contents[$i]->set($translation, ['setter' => false, 'guard' => false]); - $contents[$i]->isNew(true); - } - } - - $entity->set('_i18n', $contents); - } - - /** - * Unset empty translations to avoid persistence. - * - * Should only be called if $this->_config['allowEmptyTranslations'] is false. - * - * @param \Cake\Datasource\EntityInterface $entity The entity to check for empty translations fields inside. - * @return void - */ - protected function _unsetEmptyFields(EntityInterface $entity) - { - $translations = (array)$entity->get('_translations'); - foreach ($translations as $locale => $translation) { - $fields = $translation->extract($this->_config['fields'], false); - foreach ($fields as $field => $value) { - if (strlen($value) === 0) { - $translation->unsetProperty($field); - } - } - - $translation = $translation->extract($this->_config['fields']); - - // If now, the current locale property is empty, - // unset it completely. - if (empty(array_filter($translation))) { - unset($entity->get('_translations')[$locale]); - } - } - - // If now, the whole _translations property is empty, - // unset it completely and return - if (empty($entity->get('_translations'))) { - $entity->unsetProperty('_translations'); - } - } - - /** - * Returns the ids found for each of the condition arrays passed for the translations - * table. Each records is indexed by the corresponding position to the conditions array - * - * @param array $ruleSet an array of arary of conditions to be used for finding each - * @return array - */ - protected function _findExistingTranslations($ruleSet) - { - $association = $this->_table->getAssociation($this->_translationTable->getAlias()); - - $query = $association->find() - ->select(['id', 'num' => 0]) - ->where(current($ruleSet)) - ->enableHydration(false) - ->enableBufferedResults(false); - - unset($ruleSet[0]); - foreach ($ruleSet as $i => $conditions) { - $q = $association->find() - ->select(['id', 'num' => $i]) - ->where($conditions); - $query->unionAll($q); - } - - return $query->all()->combine('num', 'id')->toArray(); - } } From 5ad22d836a62f1fdb22972e67107f8e995767da4 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 22 May 2018 23:42:58 +0530 Subject: [PATCH 1181/2059] Add ShadowTableStrategy for TranslateBehavior. --- Behavior/Translate/ShadowTableStrategy.php | 739 +++++++++++++++++++++ 1 file changed, 739 insertions(+) create mode 100644 Behavior/Translate/ShadowTableStrategy.php diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php new file mode 100644 index 00000000..188e6394 --- /dev/null +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -0,0 +1,739 @@ + [], + 'defaultLocale' => null, + 'referenceName' => null, + 'allowEmptyTranslations' => true, + 'onlyTranslated' => false, + 'strategy' => 'subquery', + 'tableLocator' => null, + 'validator' => false, + ]; + + /** + * Constructor + * + * @param \Cake\ORM\Table $table Table instance + * @param array $config Configuration + */ + public function __construct(Table $table, array $config = []) + { + $tableAlias = $table->getAlias(); + list($plugin) = pluginSplit($table->getRegistryAlias(), true); + $tableReferenceName = $config['referenceName']; + + $config += [ + 'mainTableAlias' => $tableAlias, + 'translationTable' => $plugin . $tableReferenceName . 'Translations', + 'hasOneAlias' => $tableAlias . 'Translation', + ]; + + if (isset($config['tableLocator'])) { + $this->_tableLocator = $config['tableLocator']; + } + + $this->setConfig($config); + $this->table = $table; + $this->translationTable = $this->getTableLocator()->get($this->_config['translationTable']); + + $this->setupFieldAssociations( + $this->_config['fields'], + $this->_config['translationTable'], + $this->_config['referenceName'], + $this->_config['strategy'] + ); + } + + /** + * Return translation table instance. + * + * @return \Cake\ORM\Table + */ + public function getTranslationTable(): Table + { + return $this->translationTable; + } + + /** + * Sets the locale that should be used for all future find and save operations on + * the table where this behavior is attached to. + * + * When fetching records, the behavior will include the content for the locale set + * via this method, and likewise when saving data, it will save the data in that + * locale. + * + * Note that in case an entity has a `_locale` property set, that locale will win + * over the locale set via this method (and over the globally configured one for + * that matter)! + * + * @param string|null $locale The locale to use for fetching and saving records. Pass `null` + * in order to unset the current locale, and to make the behavior fall back to using the + * globally configured locale. + * @return $this + * @see \Cake\ORM\Behavior\TranslateBehavior::getLocale() + * @link https://book.cakephp.org/3.0/en/orm/behaviors/translate.html#retrieving-one-language-without-using-i18n-locale + * @link https://book.cakephp.org/3.0/en/orm/behaviors/translate.html#saving-in-another-language + */ + public function setLocale(?string $locale) + { + $this->locale = $locale; + + return $this; + } + + /** + * Returns the current locale. + * + * If no locale has been explicitly set via `setLocale()`, this method will return + * the currently configured global locale. + * + * @return string + * @see \Cake\I18n\I18n::getLocale() + * @see \Cake\ORM\Behavior\TranslateBehavior::setLocale() + */ + public function getLocale(): string + { + return $this->locale ?: I18n::getLocale(); + } + + /** + * Create a hasMany association for all records + * + * Don't create a hasOne association here as the join conditions are modified + * in before find - so create/modify it there + * + * @param array $fields - ignored + * @param string $table - ignored + * @param string $fieldConditions - ignored + * @param string $strategy the strategy used in the _i18n association + * + * @return void + */ + public function setupFieldAssociations($fields, $table, $fieldConditions, $strategy) + { + $config = $this->getConfig(); + + $this->table->hasMany($config['translationTable'], [ + 'className' => $config['translationTable'], + 'foreignKey' => 'id', + 'strategy' => $strategy, + 'propertyName' => '_i18n', + 'dependent' => true, + ]); + } + + /** + * Callback method that listens to the `beforeFind` event in the bound + * table. It modifies the passed query by eager loading the translated fields + * and adding a formatter to copy the values into the main table records. + * + * @param \Cake\Event\Event $event The beforeFind event that was fired. + * @param \Cake\ORM\Query $query Query + * @param \ArrayObject $options The options for the query + * @return void + */ + public function beforeFind(Event $event, Query $query, ArrayObject $options) + { + $locale = $this->getLocale(); + + if ($locale === $this->getConfig('defaultLocale')) { + return; + } + + $config = $this->getConfig(); + + if (isset($options['filterByCurrentLocale'])) { + $joinType = $options['filterByCurrentLocale'] ? 'INNER' : 'LEFT'; + } else { + $joinType = $config['onlyTranslated'] ? 'INNER' : 'LEFT'; + } + + $this->table->hasOne($config['hasOneAlias'], [ + 'foreignKey' => ['id'], + 'joinType' => $joinType, + 'propertyName' => 'translation', + 'className' => $config['translationTable'], + 'conditions' => [ + $config['hasOneAlias'] . '.locale' => $locale, + ], + ]); + + $fieldsAdded = $this->addFieldsToQuery($query, $config); + $orderByTranslatedField = $this->iterateClause($query, 'order', $config); + $filteredByTranslatedField = $this->traverseClause($query, 'where', $config); + + if (!$fieldsAdded && !$orderByTranslatedField && !$filteredByTranslatedField) { + return; + } + + $query->contain([$config['hasOneAlias']]); + + $query->formatResults(function ($results) use ($locale) { + return $this->rowMapper($results, $locale); + }, $query::PREPEND); + } + + /** + * Add translation fields to query + * + * If the query is using autofields (directly or implicitly) add the + * main table's fields to the query first. + * + * Only add translations for fields that are in the main table, always + * add the locale field though. + * + * @param \Cake\ORM\Query $query the query to check + * @param array $config the config to use for adding fields + * @return bool Whether a join to the translation table is required + */ + protected function addFieldsToQuery(Query $query, array $config) + { + if ($query->isAutoFieldsEnabled()) { + return true; + } + + $select = array_filter($query->clause('select'), function ($field) { + return is_string($field); + }); + + if (!$select) { + return true; + } + + $alias = $config['mainTableAlias']; + $joinRequired = false; + foreach ($this->translatedFields() as $field) { + if (array_intersect($select, [$field, "$alias.$field"])) { + $joinRequired = true; + $query->select($query->aliasField($field, $config['hasOneAlias'])); + } + } + + if ($joinRequired) { + $query->select($query->aliasField('locale', $config['hasOneAlias'])); + } + + return $joinRequired; + } + + /** + * Iterate over a clause to alias fields + * + * The objective here is to transparently prevent ambiguous field errors by + * prefixing fields with the appropriate table alias. This method currently + * expects to receive an order clause only. + * + * @param \Cake\ORM\Query $query the query to check + * @param string $name The clause name + * @param array $config the config to use for adding fields + * @return bool Whether a join to the translation table is required + */ + protected function iterateClause(Query $query, $name = '', $config = []) + { + $clause = $query->clause($name); + if (!$clause || !$clause->count()) { + return false; + } + + $alias = $config['hasOneAlias']; + $fields = $this->translatedFields(); + $mainTableAlias = $config['mainTableAlias']; + $mainTableFields = $this->mainFields(); + $joinRequired = false; + + $clause->iterateParts(function ($c, &$field) use ($fields, $alias, $mainTableAlias, $mainTableFields, &$joinRequired) { + if (!is_string($field) || strpos($field, '.')) { + return $c; + } + + if (in_array($field, $fields)) { + $joinRequired = true; + $field = "$alias.$field"; + } elseif (in_array($field, $mainTableFields)) { + $field = "$mainTableAlias.$field"; + } + + return $c; + }); + + return $joinRequired; + } + + /** + * Traverse over a clause to alias fields + * + * The objective here is to transparently prevent ambiguous field errors by + * prefixing fields with the appropriate table alias. This method currently + * expects to receive a where clause only. + * + * @param \Cake\ORM\Query $query the query to check + * @param string $name The clause name + * @param array $config the config to use for adding fields + * @return bool Whether a join to the translation table is required + */ + protected function traverseClause(Query $query, $name = '', $config = []) + { + $clause = $query->clause($name); + if (!$clause || !$clause->count()) { + return false; + } + + $alias = $config['hasOneAlias']; + $fields = $this->translatedFields(); + $mainTableAlias = $config['mainTableAlias']; + $mainTableFields = $this->mainFields(); + $joinRequired = false; + + $clause->traverse(function ($expression) use ($fields, $alias, $mainTableAlias, $mainTableFields, &$joinRequired) { + if (!($expression instanceof FieldInterface)) { + return; + } + $field = $expression->getField(); + if (!is_string($field) || strpos($field, '.')) { + return; + } + + if (in_array($field, $fields)) { + $joinRequired = true; + $expression->setField("$alias.$field"); + + return; + } + + if (in_array($field, $mainTableFields)) { + $expression->setField("$mainTableAlias.$field"); + } + }); + + return $joinRequired; + } + + /** + * Modifies the entity before it is saved so that translated fields are persisted + * in the database too. + * + * @param \Cake\Event\Event $event The beforeSave event that was fired + * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved + * @param \ArrayObject $options the options passed to the save method + * @return void + */ + public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options) + { + $locale = $entity->get('_locale') ?: $this->getLocale(); + $newOptions = [$this->translationTable->getAlias() => ['validate' => false]]; + $options['associated'] = $newOptions + $options['associated']; + + // Check early if empty translations are present in the entity. + // If this is the case, unset them to prevent persistence. + // This only applies if $this->_config['allowEmptyTranslations'] is false + if ($this->_config['allowEmptyTranslations'] === false) { + $this->unsetEmptyFields($entity); + } + + $this->bundleTranslatedFields($entity); + $bundled = $entity->get('_i18n') ?: []; + $noBundled = count($bundled) === 0; + + // No additional translation records need to be saved, + // as the entity is in the default locale. + if ($noBundled && $locale === $this->getConfig('defaultLocale')) { + return; + } + + $values = $entity->extract($this->translatedFields(), true); + $fields = array_keys($values); + $noFields = empty($fields); + + // If there are no fields and no bundled translations, or both fields + // in the default locale and bundled translations we can + // skip the remaining logic as its not necessary. + if ($noFields && $noBundled || ($fields && $bundled)) { + return; + } + + $primaryKey = (array)$this->table->getPrimaryKey(); + $id = $entity->get(current($primaryKey)); + + // When we have no key and bundled translations, we + // need to mark the entity dirty so the root + // entity persists. + if ($noFields && $bundled && !$id) { + foreach ($this->translatedFields() as $field) { + $entity->setDirty($field, true); + } + + return; + } + + if ($noFields) { + return; + } + + $where = compact('id', 'locale'); + + $translation = $this->translationTable->find() + ->select(array_merge(['id', 'locale'], $fields)) + ->where($where) + ->enableBufferedResults(false) + ->first(); + + if ($translation) { + foreach ($fields as $field) { + $translation->set($field, $values[$field]); + } + } else { + $translation = $this->translationTable->newEntity( + $where + $values, + [ + 'useSetters' => false, + 'markNew' => true, + ] + ); + } + + $entity->set('_i18n', array_merge($bundled, [$translation])); + $entity->set('_locale', $locale, ['setter' => false]); + $entity->setDirty('_locale', false); + + foreach ($fields as $field) { + $entity->setDirty($field, false); + } + } + + /** + * Unsets the temporary `_i18n` property after the entity has been saved + * + * @param \Cake\Event\Event $event The beforeSave event that was fired + * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved + * @return void + */ + public function afterSave(Event $event, EntityInterface $entity): void + { + $entity->unsetProperty('_i18n'); + } + + /** + * {@inheritDoc} + */ + public function buildMarshalMap($marshaller, $map, $options) + { + $this->translatedFields(); + + if (isset($options['translations']) && !$options['translations']) { + return []; + } + + return [ + '_translations' => function ($value, $entity) use ($marshaller, $options) { + /* @var \Cake\Datasource\EntityInterface $entity */ + $translations = $entity->get('_translations'); + foreach ($this->_config['fields'] as $field) { + $options['validate'] = $this->_config['validator']; + $errors = []; + if (!is_array($value)) { + return null; + } + foreach ($value as $language => $fields) { + if (!isset($translations[$language])) { + $translations[$language] = $this->table->newEntity(); + } + $marshaller->merge($translations[$language], $fields, $options); + if ((bool)$translations[$language]->getErrors()) { + $errors[$language] = $translations[$language]->getErrors(); + } + } + // Set errors into the root entity, so validation errors + // match the original form data position. + $entity->setErrors($errors); + } + + return $translations; + }, + ]; + } + + /** + * Returns a fully aliased field name for translated fields. + * + * If the requested field is configured as a translation field, field with + * an alias of a corresponding association is returned. Table-aliased + * field name is returned for all other fields. + * + * @param string $field Field name to be aliased. + * @return string + */ + public function translationField($field) + { + if ($this->getLocale() === $this->getConfig('defaultLocale')) { + return $this->table->aliasField($field); + } + + $translatedFields = $this->translatedFields(); + if (in_array($field, $translatedFields)) { + return $this->getConfig('hasOneAlias') . '.' . $field; + } + + return $this->table->aliasField($field); + } + + /** + * Modifies the results from a table find in order to merge the translated fields + * into each entity for a given locale. + * + * @param \Cake\Datasource\ResultSetInterface $results Results to map. + * @param string $locale Locale string + * @return \Cake\Collection\CollectionInterface + */ + protected function rowMapper($results, $locale) + { + $allowEmpty = $this->_config['allowEmptyTranslations']; + + return $results->map(function ($row) use ($allowEmpty) { + if ($row === null) { + return $row; + } + + $hydrated = !is_array($row); + + if (empty($row['translation'])) { + $row['_locale'] = $this->getLocale(); + unset($row['translation']); + + if ($hydrated) { + $row->clean(); + } + + return $row; + } + + $translation = $row['translation']; + + $keys = $hydrated ? $translation->visibleProperties() : array_keys($translation); + + foreach ($keys as $field) { + if ($field === 'locale') { + $row['_locale'] = $translation[$field]; + continue; + } + + if ($translation[$field] !== null) { + if ($allowEmpty || $translation[$field] !== '') { + $row[$field] = $translation[$field]; + } + } + } + + unset($row['translation']); + + if ($hydrated) { + $row->clean(); + } + + return $row; + }); + } + + /** + * Modifies the results from a table find in order to merge full translation records + * into each entity under the `_translations` key + * + * @param \Cake\Datasource\ResultSetInterface $results Results to modify. + * @return \Cake\Collection\CollectionInterface + */ + public function groupTranslations($results) + { + return $results->map(function ($row) { + $translations = (array)$row['_i18n']; + if (count($translations) === 0 && $row->get('_translations')) { + return $row; + } + + $result = []; + foreach ($translations as $translation) { + unset($translation['id']); + $result[$translation['locale']] = $translation; + } + + $row['_translations'] = $result; + unset($row['_i18n']); + if ($row instanceof EntityInterface) { + $row->clean(); + } + + return $row; + }); + } + + /** + * Helper method used to generated multiple translated field entities + * out of the data found in the `_translations` property in the passed + * entity. The result will be put into its `_i18n` property + * + * @param \Cake\Datasource\EntityInterface $entity Entity + * @return void + */ + protected function bundleTranslatedFields($entity) + { + $translations = (array)$entity->get('_translations'); + + if (empty($translations) && !$entity->isDirty('_translations')) { + return; + } + + $primaryKey = (array)$this->table->getPrimaryKey(); + $key = $entity->get(current($primaryKey)); + + foreach ($translations as $lang => $translation) { + if (!$translation->id) { + $update = [ + 'id' => $key, + 'locale' => $lang, + ]; + $translation->set($update, ['guard' => false]); + } + } + + $entity->set('_i18n', $translations); + } + + /** + * Unset empty translations to avoid persistence. + * + * Should only be called if $this->_config['allowEmptyTranslations'] is false. + * + * @param \Cake\Datasource\EntityInterface $entity The entity to check for empty translations fields inside. + * @return void + */ + protected function unsetEmptyFields($entity) + { + $translations = (array)$entity->get('_translations'); + foreach ($translations as $locale => $translation) { + $fields = $translation->extract($this->_config['fields'], false); + foreach ($fields as $field => $value) { + if (strlen($value) === 0) { + $translation->unsetProperty($field); + } + } + + $translation = $translation->extract($this->_config['fields']); + + // If now, the current locale property is empty, + // unset it completely. + if (empty(array_filter($translation))) { + unset($entity->get('_translations')[$locale]); + } + } + + // If now, the whole _translations property is empty, + // unset it completely and return + if (empty($entity->get('_translations'))) { + $entity->unsetProperty('_translations'); + } + } + + /** + * Lazy define and return the main table fields + * + * @return array + */ + protected function mainFields() + { + $fields = $this->getConfig('mainTableFields'); + + if ($fields) { + return $fields; + } + + $fields = $this->table->getSchema()->columns(); + + $this->setConfig('mainTableFields', $fields); + + return $fields; + } + + /** + * Lazy define and return the translation table fields + * + * @return array + */ + protected function translatedFields() + { + $fields = $this->getConfig('fields'); + + if ($fields) { + return $fields; + } + + $table = $this->translationTable; + $fields = $table->getSchema()->columns(); + $fields = array_values(array_diff($fields, ['id', 'locale'])); + + $this->setConfig('fields', $fields); + + return $fields; + } +} From b8952f4ec4c9e89705c99854b398aa3261d428d0 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 23 May 2018 00:15:08 +0530 Subject: [PATCH 1182/2059] Move common code into TranslateStrategyTrait. --- Behavior/Translate/EavStrategy.php | 165 +-------------- Behavior/Translate/ShadowTableStrategy.php | 157 +------------- Behavior/Translate/TranslateStrategyTrait.php | 195 ++++++++++++++++++ 3 files changed, 200 insertions(+), 317 deletions(-) create mode 100644 Behavior/Translate/TranslateStrategyTrait.php diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index 83c6b23e..88d8fb42 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -21,7 +21,6 @@ use Cake\Datasource\EntityInterface; use Cake\Datasource\QueryInterface; use Cake\Event\Event; -use Cake\I18n\I18n; use Cake\ORM\Entity; use Cake\ORM\Locator\LocatorAwareTrait; use Cake\ORM\PropertyMarshalInterface; @@ -45,28 +44,7 @@ class EavStrategy implements PropertyMarshalInterface use InstanceConfigTrait; use LocatorAwareTrait; - - /** - * Table instance - * - * @var \Cake\ORM\Table - */ - protected $table; - - /** - * The locale name that will be used to override fields in the bound table - * from the translations table - * - * @var string - */ - protected $locale; - - /** - * Instance of Table responsible for translating - * - * @var \Cake\ORM\Table - */ - protected $translationTable; + use TranslateStrategyTrait; /** * Default config @@ -111,16 +89,6 @@ public function __construct(Table $table, array $config = []) ); } - /** - * Return translation table instance. - * - * @return \Cake\ORM\Table - */ - public function getTranslationTable(): Table - { - return $this->translationTable; - } - /** * Creates the associations between the bound table and every field passed to * this method. @@ -349,102 +317,6 @@ public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $o } } - /** - * Unsets the temporary `_i18n` property after the entity has been saved - * - * @param \Cake\Event\Event $event The beforeSave event that was fired - * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved - * @return void - */ - public function afterSave(Event $event, EntityInterface $entity): void - { - $entity->unsetProperty('_i18n'); - } - - /** - * Add in `_translations` marshalling handlers. You can disable marshalling - * of translations by setting `'translations' => false` in the options - * provided to `Table::newEntity()` or `Table::patchEntity()`. - * - * {@inheritDoc} - */ - public function buildMarshalMap($marshaller, $map, $options) - { - if (isset($options['translations']) && !$options['translations']) { - return []; - } - - return [ - '_translations' => function ($value, $entity) use ($marshaller, $options) { - /* @var \Cake\Datasource\EntityInterface $entity */ - $translations = $entity->get('_translations'); - foreach ($this->_config['fields'] as $field) { - $options['validate'] = $this->_config['validator']; - $errors = []; - if (!is_array($value)) { - return null; - } - foreach ($value as $language => $fields) { - if (!isset($translations[$language])) { - $translations[$language] = $this->table->newEntity(); - } - $marshaller->merge($translations[$language], $fields, $options); - if ((bool)$translations[$language]->getErrors()) { - $errors[$language] = $translations[$language]->getErrors(); - } - } - // Set errors into the root entity, so validation errors - // match the original form data position. - $entity->setErrors($errors); - } - - return $translations; - }, - ]; - } - - /** - * Sets the locale that should be used for all future find and save operations on - * the table where this behavior is attached to. - * - * When fetching records, the behavior will include the content for the locale set - * via this method, and likewise when saving data, it will save the data in that - * locale. - * - * Note that in case an entity has a `_locale` property set, that locale will win - * over the locale set via this method (and over the globally configured one for - * that matter)! - * - * @param string|null $locale The locale to use for fetching and saving records. Pass `null` - * in order to unset the current locale, and to make the behavior fall back to using the - * globally configured locale. - * @return $this - * @see \Cake\ORM\Behavior\TranslateBehavior::getLocale() - * @link https://book.cakephp.org/3.0/en/orm/behaviors/translate.html#retrieving-one-language-without-using-i18n-locale - * @link https://book.cakephp.org/3.0/en/orm/behaviors/translate.html#saving-in-another-language - */ - public function setLocale(?string $locale) - { - $this->locale = $locale; - - return $this; - } - - /** - * Returns the current locale. - * - * If no locale has been explicitly set via `setLocale()`, this method will return - * the currently configured global locale. - * - * @return string - * @see \Cake\I18n\I18n::getLocale() - * @see \Cake\ORM\Behavior\TranslateBehavior::setLocale() - */ - public function getLocale(): string - { - return $this->locale ?: I18n::getLocale(); - } - /** * Returns a fully aliased field name for translated fields. * @@ -606,41 +478,6 @@ protected function bundleTranslatedFields($entity) $entity->set('_i18n', $contents); } - /** - * Unset empty translations to avoid persistence. - * - * Should only be called if $this->_config['allowEmptyTranslations'] is false. - * - * @param \Cake\Datasource\EntityInterface $entity The entity to check for empty translations fields inside. - * @return void - */ - protected function unsetEmptyFields($entity) - { - $translations = (array)$entity->get('_translations'); - foreach ($translations as $locale => $translation) { - $fields = $translation->extract($this->_config['fields'], false); - foreach ($fields as $field => $value) { - if (strlen($value) === 0) { - $translation->unsetProperty($field); - } - } - - $translation = $translation->extract($this->_config['fields']); - - // If now, the current locale property is empty, - // unset it completely. - if (empty(array_filter($translation))) { - unset($entity->get('_translations')[$locale]); - } - } - - // If now, the whole _translations property is empty, - // unset it completely and return - if (empty($entity->get('_translations'))) { - $entity->unsetProperty('_translations'); - } - } - /** * Returns the ids found for each of the condition arrays passed for the translations * table. Each records is indexed by the corresponding position to the conditions array diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 188e6394..2a794e3c 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -19,7 +19,6 @@ use Cake\Database\Expression\FieldInterface; use Cake\Datasource\EntityInterface; use Cake\Event\Event; -use Cake\I18n\I18n; use Cake\ORM\Locator\LocatorAwareTrait; use Cake\ORM\PropertyMarshalInterface; use Cake\ORM\Query; @@ -33,28 +32,9 @@ class ShadowTableStrategy implements PropertyMarshalInterface use InstanceConfigTrait; use LocatorAwareTrait; - - /** - * Table instance - * - * @var \Cake\ORM\Table - */ - protected $table; - - /** - * The locale name that will be used to override fields in the bound table - * from the translations table - * - * @var string - */ - protected $locale; - - /** - * Instance of Table responsible for translating - * - * @var \Cake\ORM\Table - */ - protected $translationTable; + use TranslateStrategyTrait { + buildMarshalMap as private _buildMarshalMap; + } /** * Default config @@ -108,58 +88,6 @@ public function __construct(Table $table, array $config = []) ); } - /** - * Return translation table instance. - * - * @return \Cake\ORM\Table - */ - public function getTranslationTable(): Table - { - return $this->translationTable; - } - - /** - * Sets the locale that should be used for all future find and save operations on - * the table where this behavior is attached to. - * - * When fetching records, the behavior will include the content for the locale set - * via this method, and likewise when saving data, it will save the data in that - * locale. - * - * Note that in case an entity has a `_locale` property set, that locale will win - * over the locale set via this method (and over the globally configured one for - * that matter)! - * - * @param string|null $locale The locale to use for fetching and saving records. Pass `null` - * in order to unset the current locale, and to make the behavior fall back to using the - * globally configured locale. - * @return $this - * @see \Cake\ORM\Behavior\TranslateBehavior::getLocale() - * @link https://book.cakephp.org/3.0/en/orm/behaviors/translate.html#retrieving-one-language-without-using-i18n-locale - * @link https://book.cakephp.org/3.0/en/orm/behaviors/translate.html#saving-in-another-language - */ - public function setLocale(?string $locale) - { - $this->locale = $locale; - - return $this; - } - - /** - * Returns the current locale. - * - * If no locale has been explicitly set via `setLocale()`, this method will return - * the currently configured global locale. - * - * @return string - * @see \Cake\I18n\I18n::getLocale() - * @see \Cake\ORM\Behavior\TranslateBehavior::setLocale() - */ - public function getLocale(): string - { - return $this->locale ?: I18n::getLocale(); - } - /** * Create a hasMany association for all records * @@ -464,18 +392,6 @@ public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $o } } - /** - * Unsets the temporary `_i18n` property after the entity has been saved - * - * @param \Cake\Event\Event $event The beforeSave event that was fired - * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved - * @return void - */ - public function afterSave(Event $event, EntityInterface $entity): void - { - $entity->unsetProperty('_i18n'); - } - /** * {@inheritDoc} */ @@ -483,37 +399,7 @@ public function buildMarshalMap($marshaller, $map, $options) { $this->translatedFields(); - if (isset($options['translations']) && !$options['translations']) { - return []; - } - - return [ - '_translations' => function ($value, $entity) use ($marshaller, $options) { - /* @var \Cake\Datasource\EntityInterface $entity */ - $translations = $entity->get('_translations'); - foreach ($this->_config['fields'] as $field) { - $options['validate'] = $this->_config['validator']; - $errors = []; - if (!is_array($value)) { - return null; - } - foreach ($value as $language => $fields) { - if (!isset($translations[$language])) { - $translations[$language] = $this->table->newEntity(); - } - $marshaller->merge($translations[$language], $fields, $options); - if ((bool)$translations[$language]->getErrors()) { - $errors[$language] = $translations[$language]->getErrors(); - } - } - // Set errors into the root entity, so validation errors - // match the original form data position. - $entity->setErrors($errors); - } - - return $translations; - }, - ]; + return $this->_buildMarshalMap($marshaller, $map, $options); } /** @@ -660,41 +546,6 @@ protected function bundleTranslatedFields($entity) $entity->set('_i18n', $translations); } - /** - * Unset empty translations to avoid persistence. - * - * Should only be called if $this->_config['allowEmptyTranslations'] is false. - * - * @param \Cake\Datasource\EntityInterface $entity The entity to check for empty translations fields inside. - * @return void - */ - protected function unsetEmptyFields($entity) - { - $translations = (array)$entity->get('_translations'); - foreach ($translations as $locale => $translation) { - $fields = $translation->extract($this->_config['fields'], false); - foreach ($fields as $field => $value) { - if (strlen($value) === 0) { - $translation->unsetProperty($field); - } - } - - $translation = $translation->extract($this->_config['fields']); - - // If now, the current locale property is empty, - // unset it completely. - if (empty(array_filter($translation))) { - unset($entity->get('_translations')[$locale]); - } - } - - // If now, the whole _translations property is empty, - // unset it completely and return - if (empty($entity->get('_translations'))) { - $entity->unsetProperty('_translations'); - } - } - /** * Lazy define and return the main table fields * diff --git a/Behavior/Translate/TranslateStrategyTrait.php b/Behavior/Translate/TranslateStrategyTrait.php new file mode 100644 index 00000000..a1ab6ba6 --- /dev/null +++ b/Behavior/Translate/TranslateStrategyTrait.php @@ -0,0 +1,195 @@ +translationTable; + } + + /** + * Sets the locale that should be used for all future find and save operations on + * the table where this behavior is attached to. + * + * When fetching records, the behavior will include the content for the locale set + * via this method, and likewise when saving data, it will save the data in that + * locale. + * + * Note that in case an entity has a `_locale` property set, that locale will win + * over the locale set via this method (and over the globally configured one for + * that matter)! + * + * @param string|null $locale The locale to use for fetching and saving records. Pass `null` + * in order to unset the current locale, and to make the behavior fall back to using the + * globally configured locale. + * @return $this + * @see \Cake\ORM\Behavior\TranslateBehavior::getLocale() + * @link https://book.cakephp.org/3.0/en/orm/behaviors/translate.html#retrieving-one-language-without-using-i18n-locale + * @link https://book.cakephp.org/3.0/en/orm/behaviors/translate.html#saving-in-another-language + */ + public function setLocale(?string $locale) + { + $this->locale = $locale; + + return $this; + } + + /** + * Returns the current locale. + * + * If no locale has been explicitly set via `setLocale()`, this method will return + * the currently configured global locale. + * + * @return string + * @see \Cake\I18n\I18n::getLocale() + * @see \Cake\ORM\Behavior\TranslateBehavior::setLocale() + */ + public function getLocale(): string + { + return $this->locale ?: I18n::getLocale(); + } + + /** + * Unset empty translations to avoid persistence. + * + * Should only be called if $this->_config['allowEmptyTranslations'] is false. + * + * @param \Cake\Datasource\EntityInterface $entity The entity to check for empty translations fields inside. + * @return void + */ + protected function unsetEmptyFields($entity) + { + $translations = (array)$entity->get('_translations'); + foreach ($translations as $locale => $translation) { + $fields = $translation->extract($this->_config['fields'], false); + foreach ($fields as $field => $value) { + if (strlen($value) === 0) { + $translation->unsetProperty($field); + } + } + + $translation = $translation->extract($this->_config['fields']); + + // If now, the current locale property is empty, + // unset it completely. + if (empty(array_filter($translation))) { + unset($entity->get('_translations')[$locale]); + } + } + + // If now, the whole _translations property is empty, + // unset it completely and return + if (empty($entity->get('_translations'))) { + $entity->unsetProperty('_translations'); + } + } + + /** + * Build a set of properties that should be included in the marshalling process. + + * Add in `_translations` marshalling handlers. You can disable marshalling + * of translations by setting `'translations' => false` in the options + * provided to `Table::newEntity()` or `Table::patchEntity()`. + * + * @param \Cake\ORM\Marshaller $marshaller The marhshaller of the table the behavior is attached to. + * @param array $map The property map being built. + * @param array $options The options array used in the marshalling call. + * @return array A map of `[property => callable]` of additional properties to marshal. + */ + public function buildMarshalMap($marshaller, $map, $options) + { + if (isset($options['translations']) && !$options['translations']) { + return []; + } + + return [ + '_translations' => function ($value, $entity) use ($marshaller, $options) { + /* @var \Cake\Datasource\EntityInterface $entity */ + $translations = $entity->get('_translations'); + foreach ($this->_config['fields'] as $field) { + $options['validate'] = $this->_config['validator']; + $errors = []; + if (!is_array($value)) { + return null; + } + foreach ($value as $language => $fields) { + if (!isset($translations[$language])) { + $translations[$language] = $this->table->newEntity(); + } + $marshaller->merge($translations[$language], $fields, $options); + if ((bool)$translations[$language]->getErrors()) { + $errors[$language] = $translations[$language]->getErrors(); + } + } + // Set errors into the root entity, so validation errors + // match the original form data position. + $entity->setErrors($errors); + } + + return $translations; + }, + ]; + } + + /** + * Unsets the temporary `_i18n` property after the entity has been saved + * + * @param \Cake\Event\Event $event The beforeSave event that was fired + * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved + * @return void + */ + public function afterSave(Event $event, EntityInterface $entity): void + { + $entity->unsetProperty('_i18n'); + } +} From 9712d093e113a31b9c1698757e9a8a8502fa99c8 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 23 May 2018 00:54:41 +0530 Subject: [PATCH 1183/2059] Add TranslateStrategyInterface. --- Behavior/Translate/EavStrategy.php | 4 +- Behavior/Translate/ShadowTableStrategy.php | 13 +- .../Translate/TranslateStrategyInterface.php | 116 ++++++++++++++++++ Behavior/Translate/TranslateStrategyTrait.php | 23 ++-- Behavior/TranslateBehavior.php | 11 +- 5 files changed, 140 insertions(+), 27 deletions(-) create mode 100644 Behavior/Translate/TranslateStrategyInterface.php diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index 88d8fb42..badf93ba 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -21,9 +21,9 @@ use Cake\Datasource\EntityInterface; use Cake\Datasource\QueryInterface; use Cake\Event\Event; +use Cake\ORM\Behavior\Translate\TranslateStrategyInterface; use Cake\ORM\Entity; use Cake\ORM\Locator\LocatorAwareTrait; -use Cake\ORM\PropertyMarshalInterface; use Cake\ORM\Query; use Cake\ORM\Table; @@ -39,7 +39,7 @@ * If you want to bring all or certain languages for each of the fetched records, * you can use the custom `translations` finders that is exposed to the table. */ -class EavStrategy implements PropertyMarshalInterface +class EavStrategy implements TranslateStrategyInterface { use InstanceConfigTrait; diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 2a794e3c..3394863b 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -15,19 +15,20 @@ namespace Cake\ORM\Behavior\Translate; use ArrayObject; +use Cake\Collection\CollectionInterface; use Cake\Core\InstanceConfigTrait; use Cake\Database\Expression\FieldInterface; use Cake\Datasource\EntityInterface; use Cake\Event\Event; +use Cake\ORM\Behavior\Translate\TranslateStrategyInterface; use Cake\ORM\Locator\LocatorAwareTrait; -use Cake\ORM\PropertyMarshalInterface; use Cake\ORM\Query; use Cake\ORM\Table; /** * ShadowTable strategy */ -class ShadowTableStrategy implements PropertyMarshalInterface +class ShadowTableStrategy implements TranslateStrategyInterface { use InstanceConfigTrait; @@ -124,7 +125,7 @@ public function setupFieldAssociations($fields, $table, $fieldConditions, $strat * @param \ArrayObject $options The options for the query * @return void */ - public function beforeFind(Event $event, Query $query, ArrayObject $options) + public function beforeFind(Event $event, Query $query, ArrayObject $options): void { $locale = $this->getLocale(); @@ -309,7 +310,7 @@ protected function traverseClause(Query $query, $name = '', $config = []) * @param \ArrayObject $options the options passed to the save method * @return void */ - public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options) + public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options): void { $locale = $entity->get('_locale') ?: $this->getLocale(); $newOptions = [$this->translationTable->getAlias() => ['validate' => false]]; @@ -412,7 +413,7 @@ public function buildMarshalMap($marshaller, $map, $options) * @param string $field Field name to be aliased. * @return string */ - public function translationField($field) + public function translationField(string $field): string { if ($this->getLocale() === $this->getConfig('defaultLocale')) { return $this->table->aliasField($field); @@ -490,7 +491,7 @@ protected function rowMapper($results, $locale) * @param \Cake\Datasource\ResultSetInterface $results Results to modify. * @return \Cake\Collection\CollectionInterface */ - public function groupTranslations($results) + public function groupTranslations($results): CollectionInterface { return $results->map(function ($row) { $translations = (array)$row['_i18n']; diff --git a/Behavior/Translate/TranslateStrategyInterface.php b/Behavior/Translate/TranslateStrategyInterface.php new file mode 100644 index 00000000..8ab03ffc --- /dev/null +++ b/Behavior/Translate/TranslateStrategyInterface.php @@ -0,0 +1,116 @@ +strategy !== null) { return $this->strategy; @@ -118,10 +119,10 @@ public function getStrategy() /** * Set strategy class instance. * - * @param object $strategy Strategy class instance. + * @param \Cake\ORM\Behavior\Translate\TranslateStrategyInterface $strategy Strategy class instance. * @return $this */ - public function setStrategy($strategy) + public function setStrategy(TranslateStrategyInterface $strategy) { $this->strategy = $strategy; From 9ef55a3a202652e725399dbe5fa5c668e40f70d9 Mon Sep 17 00:00:00 2001 From: Hideki Kinjyo Date: Wed, 23 May 2018 15:53:22 +0900 Subject: [PATCH 1184/2059] Fix a typo --- LazyEagerLoader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 574e48fa..092a6534 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -36,7 +36,7 @@ class LazyEagerLoader * * @param \Cake\Datasource\EntityInterface|array $entities a single entity or list of entities * @param array $contain A `contain()` compatible array. - * @see \Cake\ORM\Query\contain() + * @see \Cake\ORM\Query::contain() * @param \Cake\ORM\Table $source The table to use for fetching the top level entities * @return \Cake\Datasource\EntityInterface|array */ From accdfd92fd57997073b2cc51b60bdf122de92c03 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 26 May 2018 00:22:03 +0530 Subject: [PATCH 1185/2059] Add methods to get/set default strategy class. --- Behavior/TranslateBehavior.php | 43 +++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 606fb7d1..04cad1a8 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -51,7 +51,6 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface 'getLocale' => 'getLocale', 'translationField' => 'translationField', ], - 'strategyClass' => EavStrategy::class, 'fields' => [], 'defaultLocale' => '', 'referenceName' => '', @@ -62,6 +61,13 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface 'validator' => false, ]; + /** + * Default strategy class name. + * + * @var string + */ + protected static $defaultStrategyClass = EavStrategy::class; + /** * Translation strategy instance. * @@ -96,6 +102,27 @@ public function initialize(array $config) $this->getStrategy(); } + /** + * Set default strategy class name. + * + * @param string $class Class name. + * @return void + */ + public static function setDefaultStrategyClass(string $class) + { + static::$defaultStrategyClass = $class; + } + + /** + * Get default strategy class name. + * + * @return string + */ + public static function getDefaultStrategyClass(): string + { + return static::$defaultStrategyClass; + } + /** * Get strategy class instance. * @@ -107,13 +134,23 @@ public function getStrategy(): TranslateStrategyInterface return $this->strategy; } + return $this->strategy = $this->createStrategy(); + } + + /** + * Create strategy instance. + * + * @return \Cake\ORM\Behavior\Translate\TranslateStrategyInterface + */ + protected function createStrategy() + { $config = array_diff_key( $this->_config, ['implementedFinders', 'implementedMethods', 'strategyClass'] ); - $this->strategy = new $this->_config['strategyClass']($this->_table, $config); + $className = $this->getConfig('strategyClass', static::$defaultStrategyClass); - return $this->strategy; + return new $className($this->_table, $config); } /** From d7244c4b580abb057e054993abfd06f5e3195527 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 26 May 2018 17:34:38 +0530 Subject: [PATCH 1186/2059] Update docblock --- Behavior/Translate/TranslateStrategyTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/Translate/TranslateStrategyTrait.php b/Behavior/Translate/TranslateStrategyTrait.php index 9aee7286..85050eea 100644 --- a/Behavior/Translate/TranslateStrategyTrait.php +++ b/Behavior/Translate/TranslateStrategyTrait.php @@ -36,7 +36,7 @@ trait TranslateStrategyTrait * The locale name that will be used to override fields in the bound table * from the translations table * - * @var string + * @var string|null */ protected $locale; From 653f43a6fd1336f6715ab79f5ef21a5417353083 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 26 May 2018 17:35:49 +0530 Subject: [PATCH 1187/2059] Avoid use of compact(). --- Behavior/Translate/ShadowTableStrategy.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 3394863b..a8d6fbd9 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -362,7 +362,7 @@ public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $o return; } - $where = compact('id', 'locale'); + $where = ['id' => $id, 'locale' => $locale]; $translation = $this->translationTable->find() ->select(array_merge(['id', 'locale'], $fields)) From 79a02d76c3696a0ad530f56097c506a921046912 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 26 May 2018 17:43:26 +0530 Subject: [PATCH 1188/2059] Code cleanup. --- Behavior/Translate/ShadowTableStrategy.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index a8d6fbd9..1ba76e87 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -371,9 +371,7 @@ public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $o ->first(); if ($translation) { - foreach ($fields as $field) { - $translation->set($field, $values[$field]); - } + $translation->set($values); } else { $translation = $this->translationTable->newEntity( $where + $values, @@ -495,7 +493,7 @@ public function groupTranslations($results): CollectionInterface { return $results->map(function ($row) { $translations = (array)$row['_i18n']; - if (count($translations) === 0 && $row->get('_translations')) { + if (empty($translations) && $row->get('_translations')) { return $row; } From fa2ba11a127081ad43214e0e167e35d138d08163 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 26 May 2018 18:12:44 +0530 Subject: [PATCH 1189/2059] Update docblocks. --- Behavior/Translate/EavStrategy.php | 35 +++++----- Behavior/Translate/ShadowTableStrategy.php | 76 +++++++++++----------- 2 files changed, 56 insertions(+), 55 deletions(-) diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index badf93ba..c0671c9c 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -28,7 +28,7 @@ use Cake\ORM\Table; /** - * This behavior provides a way to translate dynamic data by keeping translations + * This class provides a way to translate dynamic data by keeping translations * in a separate table linked to the original record from another one. Translated * fields can be configured to override those in the main table when fetched or * put aside into another property for the same entity. @@ -37,11 +37,11 @@ * behavior for setting the language you want to fetch from the translations table. * * If you want to bring all or certain languages for each of the fetched records, - * you can use the custom `translations` finders that is exposed to the table. + * you can use the custom `translations` finder of `TranslateBehavior` that is + * exposed to the table. */ class EavStrategy implements TranslateStrategyInterface { - use InstanceConfigTrait; use LocatorAwareTrait; use TranslateStrategyTrait; @@ -49,7 +49,7 @@ class EavStrategy implements TranslateStrategyInterface /** * Default config * - * These are merged with user-provided configuration when the behavior is used. + * These are merged with user-provided configuration. * * @var array */ @@ -68,8 +68,8 @@ class EavStrategy implements TranslateStrategyInterface /** * Constructor * - * @param \Cake\ORM\Table $table The table this behavior is attached to. - * @param array $config The config for this behavior. + * @param \Cake\ORM\Table $table The table this strategy is attached to. + * @param array $config The config for this strategy. */ public function __construct(Table $table, array $config = []) { @@ -94,12 +94,12 @@ public function __construct(Table $table, array $config = []) * this method. * * Additionally it creates a `i18n` HasMany association that will be - * used for fetching all translations for each record in the bound table + * used for fetching all translations for each record in the bound table. * - * @param array $fields list of fields to create associations for - * @param string $table the table name to use for storing each field translation - * @param string $model the model field value - * @param string $strategy the strategy used in the _i18n association + * @param array $fields List of fields to create associations for. + * @param string $table The table name to use for storing each field translation. + * @param string $model The model field value. + * @param string $strategy The strategy used in the _i18n association. * * @return void */ @@ -386,8 +386,8 @@ protected function rowMapper($results, $locale) } /** - * Modifies the results from a table find in order to merge full translation records - * into each entity under the `_translations` key + * Modifies the results from a table find in order to merge full translation + * records into each entity under the `_translations` key. * * @param \Cake\Datasource\ResultSetInterface $results Results to modify. * @return \Cake\Collection\CollectionInterface @@ -427,7 +427,7 @@ public function groupTranslations($results): CollectionInterface /** * Helper method used to generated multiple translated field entities * out of the data found in the `_translations` property in the passed - * entity. The result will be put into its `_i18n` property + * entity. The result will be put into its `_i18n` property. * * @param \Cake\Datasource\EntityInterface $entity Entity * @return void @@ -479,10 +479,11 @@ protected function bundleTranslatedFields($entity) } /** - * Returns the ids found for each of the condition arrays passed for the translations - * table. Each records is indexed by the corresponding position to the conditions array + * Returns the ids found for each of the condition arrays passed for the + * translations table. Each records is indexed by the corresponding position + * to the conditions array. * - * @param array $ruleSet an array of arary of conditions to be used for finding each + * @param array $ruleSet An array of array of conditions to be used for finding each * @return array */ protected function findExistingTranslations($ruleSet) diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 1ba76e87..ecffc528 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -26,11 +26,11 @@ use Cake\ORM\Table; /** - * ShadowTable strategy + * This class provides a way to translate dynamic data by keeping translations + * in a separate shadow table where each row corresponds to a row of primary table. */ class ShadowTableStrategy implements TranslateStrategyInterface { - use InstanceConfigTrait; use LocatorAwareTrait; use TranslateStrategyTrait { @@ -40,7 +40,7 @@ class ShadowTableStrategy implements TranslateStrategyInterface /** * Default config * - * These are merged with user-provided configuration when the behavior is used. + * These are merged with user-provided configuration. * * @var array */ @@ -58,8 +58,8 @@ class ShadowTableStrategy implements TranslateStrategyInterface /** * Constructor * - * @param \Cake\ORM\Table $table Table instance - * @param array $config Configuration + * @param \Cake\ORM\Table $table Table instance. + * @param array $config Configuration. */ public function __construct(Table $table, array $config = []) { @@ -90,15 +90,15 @@ public function __construct(Table $table, array $config = []) } /** - * Create a hasMany association for all records + * Create a hasMany association for all records. * * Don't create a hasOne association here as the join conditions are modified - * in before find - so create/modify it there + * in before find - so create/modify it there. * - * @param array $fields - ignored - * @param string $table - ignored - * @param string $fieldConditions - ignored - * @param string $strategy the strategy used in the _i18n association + * @param array $fields Unused. + * @param string $table Unused. + * @param string $fieldConditions Unused. + * @param string $strategy The strategy used in the shadow table association. * * @return void */ @@ -121,8 +121,8 @@ public function setupFieldAssociations($fields, $table, $fieldConditions, $strat * and adding a formatter to copy the values into the main table records. * * @param \Cake\Event\Event $event The beforeFind event that was fired. - * @param \Cake\ORM\Query $query Query - * @param \ArrayObject $options The options for the query + * @param \Cake\ORM\Query $query Query. + * @param \ArrayObject $options The options for the query. * @return void */ public function beforeFind(Event $event, Query $query, ArrayObject $options): void @@ -167,7 +167,7 @@ public function beforeFind(Event $event, Query $query, ArrayObject $options): vo } /** - * Add translation fields to query + * Add translation fields to query. * * If the query is using autofields (directly or implicitly) add the * main table's fields to the query first. @@ -175,9 +175,9 @@ public function beforeFind(Event $event, Query $query, ArrayObject $options): vo * Only add translations for fields that are in the main table, always * add the locale field though. * - * @param \Cake\ORM\Query $query the query to check - * @param array $config the config to use for adding fields - * @return bool Whether a join to the translation table is required + * @param \Cake\ORM\Query $query The query to check. + * @param array $config The config to use for adding fields. + * @return bool Whether a join to the translation table is required. */ protected function addFieldsToQuery(Query $query, array $config) { @@ -210,16 +210,16 @@ protected function addFieldsToQuery(Query $query, array $config) } /** - * Iterate over a clause to alias fields + * Iterate over a clause to alias fields. * * The objective here is to transparently prevent ambiguous field errors by * prefixing fields with the appropriate table alias. This method currently * expects to receive an order clause only. * - * @param \Cake\ORM\Query $query the query to check - * @param string $name The clause name - * @param array $config the config to use for adding fields - * @return bool Whether a join to the translation table is required + * @param \Cake\ORM\Query $query the query to check. + * @param string $name The clause name. + * @param array $config The config to use for adding fields. + * @return bool Whether a join to the translation table is required. */ protected function iterateClause(Query $query, $name = '', $config = []) { @@ -253,16 +253,16 @@ protected function iterateClause(Query $query, $name = '', $config = []) } /** - * Traverse over a clause to alias fields + * Traverse over a clause to alias fields. * * The objective here is to transparently prevent ambiguous field errors by * prefixing fields with the appropriate table alias. This method currently * expects to receive a where clause only. * - * @param \Cake\ORM\Query $query the query to check - * @param string $name The clause name - * @param array $config the config to use for adding fields - * @return bool Whether a join to the translation table is required + * @param \Cake\ORM\Query $query the query to check. + * @param string $name The clause name. + * @param array $config The config to use for adding fields. + * @return bool Whether a join to the translation table is required. */ protected function traverseClause(Query $query, $name = '', $config = []) { @@ -305,9 +305,9 @@ protected function traverseClause(Query $query, $name = '', $config = []) * Modifies the entity before it is saved so that translated fields are persisted * in the database too. * - * @param \Cake\Event\Event $event The beforeSave event that was fired - * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved - * @param \ArrayObject $options the options passed to the save method + * @param \Cake\Event\Event $event The beforeSave event that was fired. + * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved. + * @param \ArrayObject $options the options passed to the save method. * @return void */ public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options): void @@ -426,8 +426,8 @@ public function translationField(string $field): string } /** - * Modifies the results from a table find in order to merge the translated fields - * into each entity for a given locale. + * Modifies the results from a table find in order to merge the translated + * fields into each entity for a given locale. * * @param \Cake\Datasource\ResultSetInterface $results Results to map. * @param string $locale Locale string @@ -483,8 +483,8 @@ protected function rowMapper($results, $locale) } /** - * Modifies the results from a table find in order to merge full translation records - * into each entity under the `_translations` key + * Modifies the results from a table find in order to merge full translation + * records into each entity under the `_translations` key. * * @param \Cake\Datasource\ResultSetInterface $results Results to modify. * @return \Cake\Collection\CollectionInterface @@ -516,9 +516,9 @@ public function groupTranslations($results): CollectionInterface /** * Helper method used to generated multiple translated field entities * out of the data found in the `_translations` property in the passed - * entity. The result will be put into its `_i18n` property + * entity. The result will be put into its `_i18n` property. * - * @param \Cake\Datasource\EntityInterface $entity Entity + * @param \Cake\Datasource\EntityInterface $entity Entity. * @return void */ protected function bundleTranslatedFields($entity) @@ -546,7 +546,7 @@ protected function bundleTranslatedFields($entity) } /** - * Lazy define and return the main table fields + * Lazy define and return the main table fields. * * @return array */ @@ -566,7 +566,7 @@ protected function mainFields() } /** - * Lazy define and return the translation table fields + * Lazy define and return the translation table fields. * * @return array */ From ab3fbbb47240b514130a4466c9f3cbea1cdcf30a Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 26 May 2018 18:17:46 +0530 Subject: [PATCH 1190/2059] Remove unneeded method arguments. --- Behavior/Translate/EavStrategy.php | 21 ++++++++------------- Behavior/Translate/ShadowTableStrategy.php | 16 +++------------- 2 files changed, 11 insertions(+), 26 deletions(-) diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index c0671c9c..c742b760 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -81,12 +81,7 @@ public function __construct(Table $table, array $config = []) $this->table = $table; $this->translationTable = $this->getTableLocator()->get($this->_config['translationTable']); - $this->setupFieldAssociations( - $this->_config['fields'], - $this->_config['translationTable'], - $this->_config['referenceName'], - $this->_config['strategy'] - ); + $this->setupAssociations(); } /** @@ -96,18 +91,18 @@ public function __construct(Table $table, array $config = []) * Additionally it creates a `i18n` HasMany association that will be * used for fetching all translations for each record in the bound table. * - * @param array $fields List of fields to create associations for. - * @param string $table The table name to use for storing each field translation. - * @param string $model The model field value. - * @param string $strategy The strategy used in the _i18n association. - * * @return void */ - protected function setupFieldAssociations($fields, $table, $model, $strategy) + protected function setupAssociations() { + $fields = $this->_config['fields']; + $table = $this->_config['translationTable']; + $model = $this->_config['referenceName']; + $strategy = $this->_config['strategy']; + $filter = $this->_config['onlyTranslated']; + $targetAlias = $this->translationTable->getAlias(); $alias = $this->table->getAlias(); - $filter = $this->_config['onlyTranslated']; $tableLocator = $this->getTableLocator(); foreach ($fields as $field) { diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index ecffc528..a9a36930 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -81,12 +81,7 @@ public function __construct(Table $table, array $config = []) $this->table = $table; $this->translationTable = $this->getTableLocator()->get($this->_config['translationTable']); - $this->setupFieldAssociations( - $this->_config['fields'], - $this->_config['translationTable'], - $this->_config['referenceName'], - $this->_config['strategy'] - ); + $this->setupAssociations(); } /** @@ -95,21 +90,16 @@ public function __construct(Table $table, array $config = []) * Don't create a hasOne association here as the join conditions are modified * in before find - so create/modify it there. * - * @param array $fields Unused. - * @param string $table Unused. - * @param string $fieldConditions Unused. - * @param string $strategy The strategy used in the shadow table association. - * * @return void */ - public function setupFieldAssociations($fields, $table, $fieldConditions, $strategy) + protected function setupAssociations() { $config = $this->getConfig(); $this->table->hasMany($config['translationTable'], [ 'className' => $config['translationTable'], 'foreignKey' => 'id', - 'strategy' => $strategy, + 'strategy' => $config['strategy'], 'propertyName' => '_i18n', 'dependent' => true, ]); From a3ca885242705a8a59253c8e43c7800ed8b19aa3 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 28 May 2018 00:56:15 +0530 Subject: [PATCH 1191/2059] Add missing methods to LocatorInterface. --- Locator/LocatorInterface.php | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/Locator/LocatorInterface.php b/Locator/LocatorInterface.php index 124ca09a..4bc4ef70 100644 --- a/Locator/LocatorInterface.php +++ b/Locator/LocatorInterface.php @@ -18,12 +18,31 @@ /** * Registries for Table objects should implement this interface. - * - * @method array getConfig($alias) - * @method $this setConfig($alias, $options = null) */ interface LocatorInterface { + /** + * Returns configuration for an alias or the full configuration array for + * all aliases. + * + * @param string|null $alias Alias to get config for, null for complete config. + * @return array The config data. + */ + public function getConfig($alias = null); + + /** + * Stores a list of options to be used when instantiating an object + * with a matching alias. + * + * @param string|array $alias Name of the alias or array to completely + * overwrite current config. + * @param array|null $options list of options for the alias + * @return $this + * @throws \RuntimeException When you attempt to configure an existing + * table instance. + */ + public function setConfig($alias, $options = null); + /** * Get a table instance from the registry. * From c7885267f73a5c68557c59d23ba5b757366f8bf6 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 28 May 2018 19:32:41 +0530 Subject: [PATCH 1192/2059] Use interfaces instead of classes for typehinting. --- Behavior/Translate/EavStrategy.php | 13 +++++----- Behavior/Translate/ShadowTableStrategy.php | 26 +++++++++---------- .../Translate/TranslateStrategyInterface.php | 16 ++++++------ Behavior/Translate/TranslateStrategyTrait.php | 6 ++--- 4 files changed, 30 insertions(+), 31 deletions(-) diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index c742b760..dc335a8c 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -20,11 +20,10 @@ use Cake\Core\InstanceConfigTrait; use Cake\Datasource\EntityInterface; use Cake\Datasource\QueryInterface; -use Cake\Event\Event; +use Cake\Event\EventInterface; use Cake\ORM\Behavior\Translate\TranslateStrategyInterface; use Cake\ORM\Entity; use Cake\ORM\Locator\LocatorAwareTrait; -use Cake\ORM\Query; use Cake\ORM\Table; /** @@ -155,12 +154,12 @@ protected function setupAssociations() * table. It modifies the passed query by eager loading the translated fields * and adding a formatter to copy the values into the main table records. * - * @param \Cake\Event\Event $event The beforeFind event that was fired. - * @param \Cake\ORM\Query $query Query + * @param \Cake\Event\EventInterface $event The beforeFind event that was fired. + * @param \Cake\Datasource\QueryInterface $query Query * @param \ArrayObject $options The options for the query * @return void */ - public function beforeFind(Event $event, Query $query, ArrayObject $options): void + public function beforeFind(EventInterface $event, QueryInterface $query, ArrayObject $options): void { $locale = $this->getLocale(); @@ -219,12 +218,12 @@ public function beforeFind(Event $event, Query $query, ArrayObject $options): vo * Modifies the entity before it is saved so that translated fields are persisted * in the database too. * - * @param \Cake\Event\Event $event The beforeSave event that was fired + * @param \Cake\Event\EventInterface $event The beforeSave event that was fired * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved * @param \ArrayObject $options the options passed to the save method * @return void */ - public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options): void + public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void { $locale = $entity->get('_locale') ?: $this->getLocale(); $newOptions = [$this->translationTable->getAlias() => ['validate' => false]]; diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index a9a36930..8ad0a2bd 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -19,10 +19,10 @@ use Cake\Core\InstanceConfigTrait; use Cake\Database\Expression\FieldInterface; use Cake\Datasource\EntityInterface; -use Cake\Event\Event; +use Cake\Datasource\QueryInterface; +use Cake\Event\EventInterface; use Cake\ORM\Behavior\Translate\TranslateStrategyInterface; use Cake\ORM\Locator\LocatorAwareTrait; -use Cake\ORM\Query; use Cake\ORM\Table; /** @@ -110,12 +110,12 @@ protected function setupAssociations() * table. It modifies the passed query by eager loading the translated fields * and adding a formatter to copy the values into the main table records. * - * @param \Cake\Event\Event $event The beforeFind event that was fired. - * @param \Cake\ORM\Query $query Query. + * @param \Cake\Event\EventInterface $event The beforeFind event that was fired. + * @param \Cake\Datasource\QueryInterface $query Query. * @param \ArrayObject $options The options for the query. * @return void */ - public function beforeFind(Event $event, Query $query, ArrayObject $options): void + public function beforeFind(EventInterface $event, QueryInterface $query, ArrayObject $options): void { $locale = $this->getLocale(); @@ -165,11 +165,11 @@ public function beforeFind(Event $event, Query $query, ArrayObject $options): vo * Only add translations for fields that are in the main table, always * add the locale field though. * - * @param \Cake\ORM\Query $query The query to check. + * @param \Cake\Datasource\QueryInterface $query The query to check. * @param array $config The config to use for adding fields. * @return bool Whether a join to the translation table is required. */ - protected function addFieldsToQuery(Query $query, array $config) + protected function addFieldsToQuery(QueryInterface $query, array $config) { if ($query->isAutoFieldsEnabled()) { return true; @@ -206,12 +206,12 @@ protected function addFieldsToQuery(Query $query, array $config) * prefixing fields with the appropriate table alias. This method currently * expects to receive an order clause only. * - * @param \Cake\ORM\Query $query the query to check. + * @param \Cake\Datasource\QueryInterface $query the query to check. * @param string $name The clause name. * @param array $config The config to use for adding fields. * @return bool Whether a join to the translation table is required. */ - protected function iterateClause(Query $query, $name = '', $config = []) + protected function iterateClause(QueryInterface $query, $name = '', $config = []) { $clause = $query->clause($name); if (!$clause || !$clause->count()) { @@ -249,12 +249,12 @@ protected function iterateClause(Query $query, $name = '', $config = []) * prefixing fields with the appropriate table alias. This method currently * expects to receive a where clause only. * - * @param \Cake\ORM\Query $query the query to check. + * @param \Cake\Datasource\QueryInterface $query the query to check. * @param string $name The clause name. * @param array $config The config to use for adding fields. * @return bool Whether a join to the translation table is required. */ - protected function traverseClause(Query $query, $name = '', $config = []) + protected function traverseClause(QueryInterface $query, $name = '', $config = []) { $clause = $query->clause($name); if (!$clause || !$clause->count()) { @@ -295,12 +295,12 @@ protected function traverseClause(Query $query, $name = '', $config = []) * Modifies the entity before it is saved so that translated fields are persisted * in the database too. * - * @param \Cake\Event\Event $event The beforeSave event that was fired. + * @param \Cake\Event\EventInterface $event The beforeSave event that was fired. * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved. * @param \ArrayObject $options the options passed to the save method. * @return void */ - public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options): void + public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void { $locale = $entity->get('_locale') ?: $this->getLocale(); $newOptions = [$this->translationTable->getAlias() => ['validate' => false]]; diff --git a/Behavior/Translate/TranslateStrategyInterface.php b/Behavior/Translate/TranslateStrategyInterface.php index 8ab03ffc..c4baa29e 100644 --- a/Behavior/Translate/TranslateStrategyInterface.php +++ b/Behavior/Translate/TranslateStrategyInterface.php @@ -17,9 +17,9 @@ use ArrayObject; use Cake\Collection\CollectionInterface; use Cake\Datasource\EntityInterface; -use Cake\Event\Event; +use Cake\Datasource\QueryInterface; +use Cake\Event\EventInterface; use Cake\ORM\PropertyMarshalInterface; -use Cake\ORM\Query; use Cake\ORM\Table; /** @@ -87,30 +87,30 @@ public function groupTranslations($results): CollectionInterface; * table. It modifies the passed query by eager loading the translated fields * and adding a formatter to copy the values into the main table records. * - * @param \Cake\Event\Event $event The beforeFind event that was fired. + * @param \Cake\Event\EventInterface $event The beforeFind event that was fired. * @param \Cake\ORM\Query $query Query * @param \ArrayObject $options The options for the query * @return void */ - public function beforeFind(Event $event, Query $query, ArrayObject $options): void; + public function beforeFind(EventInterface $event, QueryInterface $query, ArrayObject $options): void; /** * Modifies the entity before it is saved so that translated fields are persisted * in the database too. * - * @param \Cake\Event\Event $event The beforeSave event that was fired + * @param \Cake\Event\EntityInterface $event The beforeSave event that was fired * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved * @param \ArrayObject $options the options passed to the save method * @return void */ - public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options): void; + public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void; /** * Unsets the temporary `_i18n` property after the entity has been saved * - * @param \Cake\Event\Event $event The beforeSave event that was fired + * @param \Cake\Event\EventInterface $event The beforeSave event that was fired * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved * @return void */ - public function afterSave(Event $event, EntityInterface $entity): void; + public function afterSave(EventInterface $event, EntityInterface $entity): void; } diff --git a/Behavior/Translate/TranslateStrategyTrait.php b/Behavior/Translate/TranslateStrategyTrait.php index 85050eea..22142550 100644 --- a/Behavior/Translate/TranslateStrategyTrait.php +++ b/Behavior/Translate/TranslateStrategyTrait.php @@ -15,7 +15,7 @@ namespace Cake\ORM\Behavior\Translate; use Cake\Datasource\EntityInterface; -use Cake\Event\Event; +use Cake\Event\EventInterface; use Cake\I18n\I18n; use Cake\ORM\Table; @@ -179,11 +179,11 @@ public function buildMarshalMap($marshaller, $map, $options) /** * Unsets the temporary `_i18n` property after the entity has been saved * - * @param \Cake\Event\Event $event The beforeSave event that was fired + * @param \Cake\Event\EventInterface $event The beforeSave event that was fired * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved * @return void */ - public function afterSave(Event $event, EntityInterface $entity): void + public function afterSave(EventInterface $event, EntityInterface $entity): void { $entity->unsetProperty('_i18n'); } From 32a29bd53b8b34d65d39116d99bf258a3e0c79b1 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 30 May 2018 19:25:26 +0530 Subject: [PATCH 1193/2059] Revert to using Query instead of QueryInterface for typehint. --- Behavior/Translate/EavStrategy.php | 7 +++---- Behavior/Translate/ShadowTableStrategy.php | 18 +++++++++--------- .../Translate/TranslateStrategyInterface.php | 6 +++--- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index dc335a8c..2239ec23 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -24,6 +24,7 @@ use Cake\ORM\Behavior\Translate\TranslateStrategyInterface; use Cake\ORM\Entity; use Cake\ORM\Locator\LocatorAwareTrait; +use Cake\ORM\Query; use Cake\ORM\Table; /** @@ -155,11 +156,11 @@ protected function setupAssociations() * and adding a formatter to copy the values into the main table records. * * @param \Cake\Event\EventInterface $event The beforeFind event that was fired. - * @param \Cake\Datasource\QueryInterface $query Query + * @param \Cake\ORM\Query $query Query * @param \ArrayObject $options The options for the query * @return void */ - public function beforeFind(EventInterface $event, QueryInterface $query, ArrayObject $options): void + public function beforeFind(EventInterface $event, Query $query, ArrayObject $options): void { $locale = $this->getLocale(); @@ -169,10 +170,8 @@ public function beforeFind(EventInterface $event, QueryInterface $query, ArrayOb $conditions = function ($field, $locale, $query, $select) { return function ($q) use ($field, $locale, $query, $select) { - /* @var \Cake\Datasource\QueryInterface $q */ $q->where([$q->getRepository()->aliasField('locale') => $locale]); - /* @var \Cake\ORM\Query $query */ if ($query->isAutoFieldsEnabled() || in_array($field, $select, true) || in_array($this->table->aliasField($field), $select, true) diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 8ad0a2bd..5329bb0b 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -19,10 +19,10 @@ use Cake\Core\InstanceConfigTrait; use Cake\Database\Expression\FieldInterface; use Cake\Datasource\EntityInterface; -use Cake\Datasource\QueryInterface; use Cake\Event\EventInterface; use Cake\ORM\Behavior\Translate\TranslateStrategyInterface; use Cake\ORM\Locator\LocatorAwareTrait; +use Cake\ORM\Query; use Cake\ORM\Table; /** @@ -111,11 +111,11 @@ protected function setupAssociations() * and adding a formatter to copy the values into the main table records. * * @param \Cake\Event\EventInterface $event The beforeFind event that was fired. - * @param \Cake\Datasource\QueryInterface $query Query. + * @param \Cake\ORM\Query $query Query. * @param \ArrayObject $options The options for the query. * @return void */ - public function beforeFind(EventInterface $event, QueryInterface $query, ArrayObject $options): void + public function beforeFind(EventInterface $event, Query $query, ArrayObject $options): void { $locale = $this->getLocale(); @@ -165,11 +165,11 @@ public function beforeFind(EventInterface $event, QueryInterface $query, ArrayOb * Only add translations for fields that are in the main table, always * add the locale field though. * - * @param \Cake\Datasource\QueryInterface $query The query to check. + * @param \Cake\ORM\Query $query The query to check. * @param array $config The config to use for adding fields. * @return bool Whether a join to the translation table is required. */ - protected function addFieldsToQuery(QueryInterface $query, array $config) + protected function addFieldsToQuery($query, array $config) { if ($query->isAutoFieldsEnabled()) { return true; @@ -206,12 +206,12 @@ protected function addFieldsToQuery(QueryInterface $query, array $config) * prefixing fields with the appropriate table alias. This method currently * expects to receive an order clause only. * - * @param \Cake\Datasource\QueryInterface $query the query to check. + * @param \Cake\ORM\Query $query the query to check. * @param string $name The clause name. * @param array $config The config to use for adding fields. * @return bool Whether a join to the translation table is required. */ - protected function iterateClause(QueryInterface $query, $name = '', $config = []) + protected function iterateClause($query, $name = '', $config = []) { $clause = $query->clause($name); if (!$clause || !$clause->count()) { @@ -249,12 +249,12 @@ protected function iterateClause(QueryInterface $query, $name = '', $config = [] * prefixing fields with the appropriate table alias. This method currently * expects to receive a where clause only. * - * @param \Cake\Datasource\QueryInterface $query the query to check. + * @param \Cake\ORM\Query $query the query to check. * @param string $name The clause name. * @param array $config The config to use for adding fields. * @return bool Whether a join to the translation table is required. */ - protected function traverseClause(QueryInterface $query, $name = '', $config = []) + protected function traverseClause($query, $name = '', $config = []) { $clause = $query->clause($name); if (!$clause || !$clause->count()) { diff --git a/Behavior/Translate/TranslateStrategyInterface.php b/Behavior/Translate/TranslateStrategyInterface.php index c4baa29e..786cd140 100644 --- a/Behavior/Translate/TranslateStrategyInterface.php +++ b/Behavior/Translate/TranslateStrategyInterface.php @@ -17,9 +17,9 @@ use ArrayObject; use Cake\Collection\CollectionInterface; use Cake\Datasource\EntityInterface; -use Cake\Datasource\QueryInterface; use Cake\Event\EventInterface; use Cake\ORM\PropertyMarshalInterface; +use Cake\ORM\Query; use Cake\ORM\Table; /** @@ -92,13 +92,13 @@ public function groupTranslations($results): CollectionInterface; * @param \ArrayObject $options The options for the query * @return void */ - public function beforeFind(EventInterface $event, QueryInterface $query, ArrayObject $options): void; + public function beforeFind(EventInterface $event, Query $query, ArrayObject $options): void; /** * Modifies the entity before it is saved so that translated fields are persisted * in the database too. * - * @param \Cake\Event\EntityInterface $event The beforeSave event that was fired + * @param \Cake\Event\EventInterface $event The beforeSave event that was fired * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved * @param \ArrayObject $options the options passed to the save method * @return void From 34e6801273d173c227847c7562351da61e8dd0b0 Mon Sep 17 00:00:00 2001 From: Joep Roebroek Date: Thu, 31 May 2018 08:25:11 +0200 Subject: [PATCH 1194/2059] Fix #12163 --- Table.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Table.php b/Table.php index a4979a8c..856850d0 100644 --- a/Table.php +++ b/Table.php @@ -1675,13 +1675,13 @@ protected function _transactionCommitted($atomic, $primary) */ public function findOrCreate($search, callable $callback = null, $options = []) { - $options += [ + $options = new ArrayObject($options + [ 'atomic' => true, 'defaults' => true, - ]; + ]); $entity = $this->_executeTransaction(function () use ($search, $callback, $options) { - return $this->_processFindOrCreate($search, $callback, $options); + return $this->_processFindOrCreate($search, $callback, $options->getArrayCopy()); }, $options['atomic']); if ($entity && $this->_transactionCommitted($options['atomic'], true)) { From 6a358a0a43e8c3e271e346e874ef607bd38c7d47 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 3 Jun 2018 02:06:53 +0530 Subject: [PATCH 1195/2059] Use EventInterface instead of Event for typehints. --- Behavior.php | 18 +++++++++--------- Behavior/CounterCacheBehavior.php | 24 ++++++++++++------------ Behavior/TimestampBehavior.php | 6 +++--- Behavior/TranslateBehavior.php | 14 +++++++------- Behavior/TreeBehavior.php | 14 +++++++------- Table.php | 20 ++++++++++---------- 6 files changed, 48 insertions(+), 48 deletions(-) diff --git a/Behavior.php b/Behavior.php index ab11b674..a3452c4e 100644 --- a/Behavior.php +++ b/Behavior.php @@ -48,41 +48,41 @@ * CakePHP provides a number of lifecycle events your behaviors can * listen to: * - * - `beforeFind(Event $event, Query $query, ArrayObject $options, boolean $primary)` + * - `beforeFind(EventInterface $event, Query $query, ArrayObject $options, boolean $primary)` * Fired before each find operation. By stopping the event and supplying a * return value you can bypass the find operation entirely. Any changes done * to the $query instance will be retained for the rest of the find. The * $primary parameter indicates whether or not this is the root query, * or an associated query. * - * - `buildValidator(Event $event, Validator $validator, string $name)` + * - `buildValidator(EventInterface $event, Validator $validator, string $name)` * Fired when the validator object identified by $name is being built. You can use this * callback to add validation rules or add validation providers. * - * - `buildRules(Event $event, RulesChecker $rules)` + * - `buildRules(EventInterface $event, RulesChecker $rules)` * Fired when the rules checking object for the table is being built. You can use this * callback to add more rules to the set. * - * - `beforeRules(Event $event, EntityInterface $entity, ArrayObject $options, $operation)` + * - `beforeRules(EventInterface $event, EntityInterface $entity, ArrayObject $options, $operation)` * Fired before an entity is validated using by a rules checker. By stopping this event, * you can return the final value of the rules checking operation. * - * - `afterRules(Event $event, EntityInterface $entity, ArrayObject $options, bool $result, $operation)` + * - `afterRules(EventInterface $event, EntityInterface $entity, ArrayObject $options, bool $result, $operation)` * Fired after the rules have been checked on the entity. By stopping this event, * you can return the final value of the rules checking operation. * - * - `beforeSave(Event $event, EntityInterface $entity, ArrayObject $options)` + * - `beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options)` * Fired before each entity is saved. Stopping this event will abort the save * operation. When the event is stopped the result of the event will be returned. * - * - `afterSave(Event $event, EntityInterface $entity, ArrayObject $options)` + * - `afterSave(EventInterface $event, EntityInterface $entity, ArrayObject $options)` * Fired after an entity is saved. * - * - `beforeDelete(Event $event, EntityInterface $entity, ArrayObject $options)` + * - `beforeDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options)` * Fired before an entity is deleted. By stopping this event you will abort * the delete operation. * - * - `afterDelete(Event $event, EntityInterface $entity, ArrayObject $options)` + * - `afterDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options)` * Fired after an entity has been deleted. * * In addition to the core events, behaviors can respond to any diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index b89a42d3..d7e2191b 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -15,7 +15,7 @@ namespace Cake\ORM\Behavior; use Cake\Datasource\EntityInterface; -use Cake\Event\Event; +use Cake\Event\EventInterface; use Cake\ORM\Association; use Cake\ORM\Behavior; use RuntimeException; @@ -66,7 +66,7 @@ * ``` * [ * 'Users' => [ - * 'posts_published' => function (Event $event, EntityInterface $entity, Table $table) { + * 'posts_published' => function (EventInterface $event, EntityInterface $entity, Table $table) { * $query = $table->find('all')->where([ * 'published' => true, * 'user_id' => $entity->get('user_id') @@ -113,12 +113,12 @@ class CounterCacheBehavior extends Behavior * * Check if a field, which should be ignored, is dirty * - * @param \Cake\Event\Event $event The beforeSave event that was fired + * @param \Cake\Event\EventInterface $event The beforeSave event that was fired * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved * @param \ArrayObject $options The options for the query * @return void */ - public function beforeSave(Event $event, EntityInterface $entity, $options) + public function beforeSave(EventInterface $event, EntityInterface $entity, $options) { if (isset($options['ignoreCounterCache']) && $options['ignoreCounterCache'] === true) { return; @@ -150,12 +150,12 @@ public function beforeSave(Event $event, EntityInterface $entity, $options) * * Makes sure to update counter cache when a new record is created or updated. * - * @param \Cake\Event\Event $event The afterSave event that was fired. + * @param \Cake\Event\EventInterface $event The afterSave event that was fired. * @param \Cake\Datasource\EntityInterface $entity The entity that was saved. * @param \ArrayObject $options The options for the query * @return void */ - public function afterSave(Event $event, EntityInterface $entity, $options) + public function afterSave(EventInterface $event, EntityInterface $entity, $options) { if (isset($options['ignoreCounterCache']) && $options['ignoreCounterCache'] === true) { return; @@ -170,12 +170,12 @@ public function afterSave(Event $event, EntityInterface $entity, $options) * * Makes sure to update counter cache when a record is deleted. * - * @param \Cake\Event\Event $event The afterDelete event that was fired. + * @param \Cake\Event\EventInterface $event The afterDelete event that was fired. * @param \Cake\Datasource\EntityInterface $entity The entity that was deleted. * @param \ArrayObject $options The options for the query * @return void */ - public function afterDelete(Event $event, EntityInterface $entity, $options) + public function afterDelete(EventInterface $event, EntityInterface $entity, $options) { if (isset($options['ignoreCounterCache']) && $options['ignoreCounterCache'] === true) { return; @@ -187,11 +187,11 @@ public function afterDelete(Event $event, EntityInterface $entity, $options) /** * Iterate all associations and update counter caches. * - * @param \Cake\Event\Event $event Event instance. + * @param \Cake\Event\EventInterface $event Event instance. * @param \Cake\Datasource\EntityInterface $entity Entity. * @return void */ - protected function _processAssociations(Event $event, EntityInterface $entity) + protected function _processAssociations(EventInterface $event, EntityInterface $entity) { foreach ($this->_config as $assoc => $settings) { $assoc = $this->_table->getAssociation($assoc); @@ -202,14 +202,14 @@ protected function _processAssociations(Event $event, EntityInterface $entity) /** * Updates counter cache for a single association * - * @param \Cake\Event\Event $event Event instance. + * @param \Cake\Event\EventInterface $event Event instance. * @param \Cake\Datasource\EntityInterface $entity Entity * @param \Cake\ORM\Association $assoc The association object * @param array $settings The settings for for counter cache for this association * @return void * @throws \RuntimeException If invalid callable is passed. */ - protected function _processAssociation(Event $event, EntityInterface $entity, Association $assoc, array $settings) + protected function _processAssociation(EventInterface $event, EntityInterface $entity, Association $assoc, array $settings) { $foreignKeys = (array)$assoc->getForeignKey(); $primaryKeys = (array)$assoc->getBindingKey(); diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 1e01520b..95b3283f 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -17,7 +17,7 @@ use Cake\Database\TypeFactory; use Cake\Database\Type\DateTimeType; use Cake\Datasource\EntityInterface; -use Cake\Event\Event; +use Cake\Event\EventInterface; use Cake\I18n\Time; use Cake\ORM\Behavior; use DateTime; @@ -86,13 +86,13 @@ public function initialize(array $config) /** * There is only one event handler, it can be configured to be called for any event * - * @param \Cake\Event\Event $event Event instance. + * @param \Cake\Event\EventInterface $event Event instance. * @param \Cake\Datasource\EntityInterface $entity Entity instance. * @throws \UnexpectedValueException if a field's when value is misdefined * @return bool Returns true irrespective of the behavior logic, the save will not be prevented. * @throws \UnexpectedValueException When the value for an event is not 'always', 'new' or 'existing' */ - public function handleEvent(Event $event, EntityInterface $entity) + public function handleEvent(EventInterface $event, EntityInterface $entity) { $eventName = $event->getName(); $events = $this->_config['events']; diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index f2ccbb6b..aded9be9 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -18,7 +18,7 @@ use Cake\Collection\Collection; use Cake\Datasource\EntityInterface; use Cake\Datasource\QueryInterface; -use Cake\Event\Event; +use Cake\Event\EventInterface; use Cake\I18n\I18n; use Cake\ORM\Behavior; use Cake\ORM\Entity; @@ -201,12 +201,12 @@ public function setupFieldAssociations($fields, $table, $model, $strategy) * table. It modifies the passed query by eager loading the translated fields * and adding a formatter to copy the values into the main table records. * - * @param \Cake\Event\Event $event The beforeFind event that was fired. + * @param \Cake\Event\EventInterface $event The beforeFind event that was fired. * @param \Cake\ORM\Query $query Query * @param \ArrayObject $options The options for the query * @return void */ - public function beforeFind(Event $event, Query $query, $options) + public function beforeFind(EventInterface $event, Query $query, $options) { $locale = $this->getLocale(); @@ -265,12 +265,12 @@ public function beforeFind(Event $event, Query $query, $options) * Modifies the entity before it is saved so that translated fields are persisted * in the database too. * - * @param \Cake\Event\Event $event The beforeSave event that was fired + * @param \Cake\Event\EventInterface $event The beforeSave event that was fired * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved * @param \ArrayObject $options the options passed to the save method * @return void */ - public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options) + public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options) { $locale = $entity->get('_locale') ?: $this->getLocale(); $newOptions = [$this->_translationTable->getAlias() => ['validate' => false]]; @@ -361,11 +361,11 @@ public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $o /** * Unsets the temporary `_i18n` property after the entity has been saved * - * @param \Cake\Event\Event $event The beforeSave event that was fired + * @param \Cake\Event\EventInterface $event The beforeSave event that was fired * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved * @return void */ - public function afterSave(Event $event, EntityInterface $entity) + public function afterSave(EventInterface $event, EntityInterface $entity) { $entity->unsetProperty('_i18n'); } diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 70714be2..8d8b2e8a 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -17,7 +17,7 @@ use Cake\Database\Expression\IdentifierExpression; use Cake\Datasource\EntityInterface; use Cake\Datasource\Exception\RecordNotFoundException; -use Cake\Event\Event; +use Cake\Event\EventInterface; use Cake\ORM\Behavior; use Cake\ORM\Query; use InvalidArgumentException; @@ -89,12 +89,12 @@ public function initialize(array $config) * Transparently manages setting the lft and rght fields if the parent field is * included in the parameters to be saved. * - * @param \Cake\Event\Event $event The beforeSave event that was fired + * @param \Cake\Event\EventInterface $event The beforeSave event that was fired * @param \Cake\Datasource\EntityInterface $entity the entity that is going to be saved * @return void * @throws \RuntimeException if the parent to set for the node is invalid */ - public function beforeSave(Event $event, EntityInterface $entity) + public function beforeSave(EventInterface $event, EntityInterface $entity) { $isNew = $entity->isNew(); $config = $this->getConfig(); @@ -158,11 +158,11 @@ public function beforeSave(Event $event, EntityInterface $entity) * * Manages updating level of descendants of currently saved entity. * - * @param \Cake\Event\Event $event The afterSave event that was fired + * @param \Cake\Event\EventInterface $event The afterSave event that was fired * @param \Cake\Datasource\EntityInterface $entity the entity that is going to be saved * @return void */ - public function afterSave(Event $event, EntityInterface $entity) + public function afterSave(EventInterface $event, EntityInterface $entity) { if (!$this->_config['level'] || $entity->isNew()) { return; @@ -211,11 +211,11 @@ protected function _setChildrenLevel($entity) /** * Also deletes the nodes in the subtree of the entity to be delete * - * @param \Cake\Event\Event $event The beforeDelete event that was fired + * @param \Cake\Event\EventInterface $event The beforeDelete event that was fired * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved * @return void */ - public function beforeDelete(Event $event, EntityInterface $entity) + public function beforeDelete(EventInterface $event, EntityInterface $entity) { $config = $this->getConfig(); $this->_ensureFields($entity); diff --git a/Table.php b/Table.php index e94ea352..c32ff898 100644 --- a/Table.php +++ b/Table.php @@ -81,45 +81,45 @@ * Table objects provide a few callbacks/events you can hook into to augment/replace * find operations. Each event uses the standard event subsystem in CakePHP * - * - `beforeFind(Event $event, Query $query, ArrayObject $options, boolean $primary)` + * - `beforeFind(EventInterface $event, Query $query, ArrayObject $options, boolean $primary)` * Fired before each find operation. By stopping the event and supplying a * return value you can bypass the find operation entirely. Any changes done * to the $query instance will be retained for the rest of the find. The * $primary parameter indicates whether or not this is the root query, * or an associated query. * - * - `buildValidator(Event $event, Validator $validator, string $name)` + * - `buildValidator(EventInterface $event, Validator $validator, string $name)` * Allows listeners to modify validation rules for the provided named validator. * - * - `buildRules(Event $event, RulesChecker $rules)` + * - `buildRules(EventInterface $event, RulesChecker $rules)` * Allows listeners to modify the rules checker by adding more rules. * - * - `beforeRules(Event $event, EntityInterface $entity, ArrayObject $options, string $operation)` + * - `beforeRules(EventInterface $event, EntityInterface $entity, ArrayObject $options, string $operation)` * Fired before an entity is validated using the rules checker. By stopping this event, * you can return the final value of the rules checking operation. * - * - `afterRules(Event $event, EntityInterface $entity, ArrayObject $options, bool $result, string $operation)` + * - `afterRules(EventInterface $event, EntityInterface $entity, ArrayObject $options, bool $result, string $operation)` * Fired after the rules have been checked on the entity. By stopping this event, * you can return the final value of the rules checking operation. * - * - `beforeSave(Event $event, EntityInterface $entity, ArrayObject $options)` + * - `beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options)` * Fired before each entity is saved. Stopping this event will abort the save * operation. When the event is stopped the result of the event will be returned. * - * - `afterSave(Event $event, EntityInterface $entity, ArrayObject $options)` + * - `afterSave(EventInterface $event, EntityInterface $entity, ArrayObject $options)` * Fired after an entity is saved. * - * - `afterSaveCommit(Event $event, EntityInterface $entity, ArrayObject $options)` + * - `afterSaveCommit(EventInterface $event, EntityInterface $entity, ArrayObject $options)` * Fired after the transaction in which the save operation is wrapped has been committed. * It’s also triggered for non atomic saves where database operations are implicitly committed. * The event is triggered only for the primary table on which save() is directly called. * The event is not triggered if a transaction is started before calling save. * - * - `beforeDelete(Event $event, EntityInterface $entity, ArrayObject $options)` + * - `beforeDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options)` * Fired before an entity is deleted. By stopping this event you will abort * the delete operation. * - * - `afterDelete(Event $event, EntityInterface $entity, ArrayObject $options)` + * - `afterDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options)` * Fired after an entity has been deleted. * * @see \Cake\Event\EventManager for reference on the events system. From c9d4033c6f802db6adad440dde2f068fee13f259 Mon Sep 17 00:00:00 2001 From: mscherer Date: Wed, 6 Jun 2018 10:38:24 +0200 Subject: [PATCH 1196/2059] Fix docblock to what its interface contracts says. --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index a4979a8c..afed8df2 100644 --- a/Table.php +++ b/Table.php @@ -1880,7 +1880,7 @@ public function exists($conditions) * * @param \Cake\Datasource\EntityInterface $entity * @param array $options - * @return bool|\Cake\Datasource\EntityInterface|mixed + * @return \Cake\Datasource\EntityInterface|false * @throws \Cake\ORM\Exception\RolledbackTransactionException If the transaction is aborted in the afterSave event. */ public function save(EntityInterface $entity, $options = []) From 5f407c3915e8e5f8f4b794e9fd86894aa01e974b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Tue, 12 Jun 2018 09:57:52 +0200 Subject: [PATCH 1197/2059] Remove ORM return types from RepositoryInterface. --- Table.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index 831b9aeb..4dced40b 100644 --- a/Table.php +++ b/Table.php @@ -1288,7 +1288,8 @@ public function belongsToMany($associated, array $options = []) } /** - * {@inheritDoc} + * Creates a new Query for this repository and applies some defaults based on the + * type of search that was selected. * * ### Model.beforeFind event * @@ -1340,6 +1341,8 @@ public function belongsToMany($associated, array $options = []) * * Would invoke the `findPublished` method. * + * @param string $type the type of query to perform + * @param array|\ArrayAccess $options An array that will be passed to Query::applyOptions() * @return \Cake\ORM\Query The query builder */ public function find($type = 'all', $options = []) @@ -1746,7 +1749,9 @@ protected function _getFindOrCreateQuery($search) } /** - * {@inheritDoc} + * Creates a new Query instance for a table. + * + * @return \Cake\ORM\Query */ public function query() { From 201fde18c8a6b57bae2a09a572452486cf3c0cb8 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 13 Jun 2018 11:32:58 +0530 Subject: [PATCH 1198/2059] Remove unneeded "use" statements. --- Query.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Query.php b/Query.php index f061f4a7..be15bed1 100644 --- a/Query.php +++ b/Query.php @@ -22,7 +22,6 @@ use Cake\Database\ValueBinder; use Cake\Datasource\QueryInterface; use Cake\Datasource\QueryTrait; -use InvalidArgumentException; use JsonSerializable; use RuntimeException; From ae7ccc194aa41a6bfaec8119d933504591bf86bc Mon Sep 17 00:00:00 2001 From: Mark Story Date: Wed, 13 Jun 2018 09:12:56 -0700 Subject: [PATCH 1199/2059] Deprecate the read side of join() and from() These methods were missed in previous get/set sweeps. Having mixed return types makes life harder as we add more typing. Refs #12225 --- Association/BelongsToMany.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 506801ab..5a5cc9a7 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1129,7 +1129,7 @@ public function find($type = null, array $options = []) protected function _appendJunctionJoin($query, $conditions) { $name = $this->_junctionAssociationName(); - $joins = $query->join(); + $joins = $query->clause('join'); $matching = [ $name => [ 'table' => $this->junction()->getTable(), From ac9c86027c7fb3cac8d97cc6947b38e4de14a4d5 Mon Sep 17 00:00:00 2001 From: Walther Lalk Date: Thu, 14 Jun 2018 09:37:45 +0200 Subject: [PATCH 1200/2059] Fixes issue with saveMany and exceptions --- Table.php | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/Table.php b/Table.php index 4dced40b..52d97e32 100644 --- a/Table.php +++ b/Table.php @@ -2209,25 +2209,33 @@ protected function _update($entity, $data) public function saveMany($entities, $options = []) { $isNew = []; - - $return = $this->getConnection()->transactional( - function () use ($entities, $options, &$isNew) { - foreach ($entities as $key => $entity) { - $isNew[$key] = $entity->isNew(); - if ($this->save($entity, $options) === false) { - return false; - } - } - } - ); - - if ($return === false) { + $cleanup = function ($entities) use (&$isNew) { foreach ($entities as $key => $entity) { if (isset($isNew[$key]) && $isNew[$key]) { $entity->unsetProperty($this->getPrimaryKey()); $entity->isNew(true); } } + }; + + try { + $return = $this->getConnection() + ->transactional(function () use ($entities, $options, &$isNew) { + foreach ($entities as $key => $entity) { + $isNew[$key] = $entity->isNew(); + if ($this->save($entity, $options) === false) { + return false; + } + } + }); + } catch (\Exception $e) { + $cleanup($entities); + + throw $e; + } + + if ($return === false) { + $cleanup($entities); return false; } From 731307f3bccfb0b51067baa832767c673b59ec3d Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 14 Jun 2018 15:10:13 -0700 Subject: [PATCH 1201/2059] Add type to fix conflict with interface. CollectionInterface adds a return type to count() --- ResultSet.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ResultSet.php b/ResultSet.php index f166edda..ad9ef097 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -342,7 +342,7 @@ public function unserialize($serialized) * * @return int */ - public function count() + public function count(): int { if ($this->_count !== null) { return $this->_count; From 0e2c2c1bc6dd60c9ae4616894178bb12aeba9f8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Fri, 15 Jun 2018 13:26:35 +0200 Subject: [PATCH 1202/2059] Make TranslateBehavior use the same locator as AssociationCollection. --- Behavior/TranslateBehavior.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 55244e69..77f9dcce 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -108,6 +108,8 @@ public function __construct(Table $table, array $config = []) if (isset($config['tableLocator'])) { $this->_tableLocator = $config['tableLocator']; + } else { + $this->_tableLocator = $table->associations()->getTableLocator(); } parent::__construct($table, $config); From cde5c27586bb877e928ba6abed351a86c94928ba Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 15 Jun 2018 18:44:55 +0530 Subject: [PATCH 1203/2059] Fix CS errors. --- Association.php | 27 +++++++------- Association/BelongsTo.php | 5 ++- Association/BelongsToMany.php | 27 +++++++------- Association/DependentDeleteHelper.php | 1 - Association/HasMany.php | 18 +++++----- Association/HasOne.php | 5 ++- Association/Loader/SelectLoader.php | 7 ++-- Association/Loader/SelectWithPivotLoader.php | 1 - AssociationCollection.php | 5 ++- AssociationsNormalizerTrait.php | 3 +- Behavior.php | 7 ++-- Behavior/CounterCacheBehavior.php | 1 - Behavior/TimestampBehavior.php | 13 ++++--- Behavior/Translate/TranslateTrait.php | 1 - Behavior/TranslateBehavior.php | 29 ++++++++------- Behavior/TreeBehavior.php | 5 ++- BehaviorRegistry.php | 5 ++- EagerLoadable.php | 7 ++-- EagerLoader.php | 26 +++++++------- Entity.php | 4 +-- Exception/MissingBehaviorException.php | 1 - Exception/MissingEntityException.php | 1 - Exception/MissingTableClassException.php | 1 - Exception/PersistenceFailedException.php | 1 - Exception/RolledbackTransactionException.php | 1 - LazyEagerLoader.php | 1 - Locator/LocatorAwareTrait.php | 1 - Locator/TableLocator.php | 3 +- Marshaller.php | 15 ++++---- Query.php | 28 +++++++-------- ResultSet.php | 5 ++- Rule/ExistsIn.php | 1 - Rule/IsUnique.php | 1 - Rule/ValidCount.php | 1 - RulesChecker.php | 1 - SaveOptionsBuilder.php | 1 - Table.php | 37 ++++++++++---------- TableRegistry.php | 3 +- 38 files changed, 130 insertions(+), 170 deletions(-) diff --git a/Association.php b/Association.php index d81dab9b..4e77887f 100644 --- a/Association.php +++ b/Association.php @@ -34,7 +34,6 @@ */ abstract class Association { - use ConventionsTrait; use LocatorAwareTrait; @@ -43,49 +42,49 @@ abstract class Association * * @var string */ - const STRATEGY_JOIN = 'join'; + public const STRATEGY_JOIN = 'join'; /** * Strategy name to use a subquery for fetching associated records * * @var string */ - const STRATEGY_SUBQUERY = 'subquery'; + public const STRATEGY_SUBQUERY = 'subquery'; /** * Strategy name to use a select for fetching associated records * * @var string */ - const STRATEGY_SELECT = 'select'; + public const STRATEGY_SELECT = 'select'; /** * Association type for one to one associations. * * @var string */ - const ONE_TO_ONE = 'oneToOne'; + public const ONE_TO_ONE = 'oneToOne'; /** * Association type for one to many associations. * * @var string */ - const ONE_TO_MANY = 'oneToMany'; + public const ONE_TO_MANY = 'oneToMany'; /** * Association type for many to many associations. * * @var string */ - const MANY_TO_MANY = 'manyToMany'; + public const MANY_TO_MANY = 'manyToMany'; /** * Association type for many to one associations. * * @var string */ - const MANY_TO_ONE = 'manyToOne'; + public const MANY_TO_ONE = 'manyToOne'; /** * Name given to the association, it usually represents the alias @@ -192,7 +191,7 @@ abstract class Association protected $_validStrategies = [ self::STRATEGY_JOIN, self::STRATEGY_SELECT, - self::STRATEGY_SUBQUERY + self::STRATEGY_SUBQUERY, ]; /** @@ -216,7 +215,7 @@ public function __construct($alias, array $options = []) 'tableLocator', 'propertyName', 'sourceTable', - 'targetTable' + 'targetTable', ]; foreach ($defaults as $property) { if (isset($options[$property])) { @@ -505,9 +504,9 @@ public function getDependent() */ public function canBeJoined(array $options = []) { - $strategy = isset($options['strategy']) ? $options['strategy'] : $this->getStrategy(); + $strategy = $options['strategy'] ?? $this->getStrategy(); - return $strategy == $this::STRATEGY_JOIN; + return $strategy === $this::STRATEGY_JOIN; } /** @@ -691,7 +690,7 @@ public function attachTo(Query $query, array $options = []) 'fields' => [], 'type' => $joinType, 'table' => $table, - 'finder' => $this->getFinder() + 'finder' => $this->getFinder(), ]; if (!empty($options['foreignKey'])) { @@ -885,7 +884,7 @@ public function deleteAll($conditions) */ public function requiresKeys(array $options = []) { - $strategy = isset($options['strategy']) ? $options['strategy'] : $this->getStrategy(); + $strategy = $options['strategy'] ?? $this->getStrategy(); return $strategy === static::STRATEGY_SELECT; } diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 651f81e3..9c8284b9 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -30,7 +30,6 @@ */ class BelongsTo extends Association { - /** * Valid strategies for this type of association * @@ -38,7 +37,7 @@ class BelongsTo extends Association */ protected $_validStrategies = [ self::STRATEGY_JOIN, - self::STRATEGY_SELECT + self::STRATEGY_SELECT, ]; /** @@ -194,7 +193,7 @@ public function eagerLoader(array $options) 'bindingKey' => $this->getBindingKey(), 'strategy' => $this->getStrategy(), 'associationType' => $this->type(), - 'finder' => [$this, 'find'] + 'finder' => [$this, 'find'], ]); return $loader->buildEagerLoader($options); diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index e453d918..e0a349de 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -15,8 +15,8 @@ namespace Cake\ORM\Association; use Cake\Core\App; -use Cake\Database\ExpressionInterface; use Cake\Database\Expression\IdentifierExpression; +use Cake\Database\ExpressionInterface; use Cake\Datasource\EntityInterface; use Cake\Datasource\QueryInterface; use Cake\ORM\Association; @@ -36,20 +36,19 @@ */ class BelongsToMany extends Association { - /** * Saving strategy that will only append to the links set * * @var string */ - const SAVE_APPEND = 'append'; + public const SAVE_APPEND = 'append'; /** * Saving strategy that will replace the links with the provided set * * @var string */ - const SAVE_REPLACE = 'replace'; + public const SAVE_REPLACE = 'replace'; /** * The type of join to be used when adding the association to a query @@ -123,7 +122,7 @@ class BelongsToMany extends Association */ protected $_validStrategies = [ self::STRATEGY_SELECT, - self::STRATEGY_SUBQUERY + self::STRATEGY_SUBQUERY, ]; /** @@ -382,13 +381,13 @@ protected function _generateJunctionAssociations($junction, $source, $target) if (!$junction->hasAssociation($tAlias)) { $junction->belongsTo($tAlias, [ 'foreignKey' => $this->getTargetForeignKey(), - 'targetTable' => $target + 'targetTable' => $target, ]); } if (!$junction->hasAssociation($sAlias)) { $junction->belongsTo($sAlias, [ 'foreignKey' => $this->getForeignKey(), - 'targetTable' => $source + 'targetTable' => $source, ]); } } @@ -472,7 +471,7 @@ protected function _appendNotMatching($query, $options) $assoc = $junction->getAssociation($this->getTarget()->getAlias()); $conditions = $assoc->_joinCondition([ - 'foreignKey' => $this->getTargetForeignKey() + 'foreignKey' => $this->getTargetForeignKey(), ]); $subquery = $this->_appendJunctionJoin($subquery, $conditions); @@ -537,7 +536,7 @@ public function eagerLoader(array $options) 'junctionConditions' => $this->junctionConditions(), 'finder' => function () { return $this->_appendJunctionJoin($this->find(), []); - } + }, ]); return $loader->buildEagerLoader($options); @@ -881,7 +880,7 @@ public function unlink(EntityInterface $sourceEntity, array $targetEntities, $op { if (is_bool($options)) { $options = [ - 'cleanProperty' => $options + 'cleanProperty' => $options, ]; } else { $options += ['cleanProperty' => true]; @@ -1048,7 +1047,7 @@ public function find($type = null, array $options = []) $belongsTo = $this->junction()->getAssociation($this->getTarget()->getAlias()); $conditions = $belongsTo->_joinCondition([ - 'foreignKey' => $this->getTargetForeignKey() + 'foreignKey' => $this->getTargetForeignKey(), ]); $conditions += $this->junctionConditions(); @@ -1070,8 +1069,8 @@ protected function _appendJunctionJoin($query, $conditions) $name => [ 'table' => $this->junction()->getTable(), 'conditions' => $conditions, - 'type' => QueryInterface::JOIN_TYPE_INNER - ] + 'type' => QueryInterface::JOIN_TYPE_INNER, + ], ]; $assoc = $this->getTarget()->getAssociation($name); @@ -1368,7 +1367,7 @@ protected function _junctionTableName($name = null) if (empty($this->_junctionTableName)) { $tablesNames = array_map('Cake\Utility\Inflector::underscore', [ $this->getSource()->getTable(), - $this->getTarget()->getTable() + $this->getTarget()->getTable(), ]); sort($tablesNames); $this->_junctionTableName = implode('_', $tablesNames); diff --git a/Association/DependentDeleteHelper.php b/Association/DependentDeleteHelper.php index 5bd74aff..7e395c56 100644 --- a/Association/DependentDeleteHelper.php +++ b/Association/DependentDeleteHelper.php @@ -24,7 +24,6 @@ */ class DependentDeleteHelper { - /** * Cascade a delete to remove dependent records. * diff --git a/Association/HasMany.php b/Association/HasMany.php index 89730efc..6dbe2872 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -21,7 +21,6 @@ use Cake\Datasource\EntityInterface; use Cake\Datasource\QueryInterface; use Cake\ORM\Association; -use Cake\ORM\Association\DependentDeleteHelper; use Cake\ORM\Association\Loader\SelectLoader; use Cake\ORM\Table; use InvalidArgumentException; @@ -35,7 +34,6 @@ */ class HasMany extends Association { - /** * Order in which target records should be returned * @@ -64,7 +62,7 @@ class HasMany extends Association */ protected $_validStrategies = [ self::STRATEGY_SELECT, - self::STRATEGY_SUBQUERY + self::STRATEGY_SUBQUERY, ]; /** @@ -72,14 +70,14 @@ class HasMany extends Association * * @var string */ - const SAVE_APPEND = 'append'; + public const SAVE_APPEND = 'append'; /** * Saving strategy that will replace the links with the provided set * * @var string */ - const SAVE_REPLACE = 'replace'; + public const SAVE_REPLACE = 'replace'; /** * Saving strategy to be used by this association @@ -335,7 +333,7 @@ public function unlink(EntityInterface $sourceEntity, array $targetEntities, $op { if (is_bool($options)) { $options = [ - 'cleanProperty' => $options + 'cleanProperty' => $options, ]; } else { $options += ['cleanProperty' => true]; @@ -354,7 +352,7 @@ public function unlink(EntityInterface $sourceEntity, array $targetEntities, $op ->map(function ($entity) use ($targetPrimaryKey) { return $entity->extract($targetPrimaryKey); }) - ->toList() + ->toList(), ]; $this->_unlink($foreignKey, $target, $conditions, $options); @@ -469,9 +467,9 @@ function ($v) { if (count($exclusions) > 0) { $conditions = [ 'NOT' => [ - 'OR' => $exclusions + 'OR' => $exclusions, ], - $foreignKeyReference + $foreignKeyReference, ]; } @@ -646,7 +644,7 @@ public function eagerLoader(array $options) 'strategy' => $this->getStrategy(), 'associationType' => $this->type(), 'sort' => $this->getSort(), - 'finder' => [$this, 'find'] + 'finder' => [$this, 'find'], ]); return $loader->buildEagerLoader($options); diff --git a/Association/HasOne.php b/Association/HasOne.php index 88e6b49f..ce680c55 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -16,7 +16,6 @@ use Cake\Datasource\EntityInterface; use Cake\ORM\Association; -use Cake\ORM\Association\DependentDeleteHelper; use Cake\ORM\Association\Loader\SelectLoader; use Cake\ORM\Table; use Cake\Utility\Inflector; @@ -36,7 +35,7 @@ class HasOne extends Association */ protected $_validStrategies = [ self::STRATEGY_JOIN, - self::STRATEGY_SELECT + self::STRATEGY_SELECT, ]; /** @@ -137,7 +136,7 @@ public function eagerLoader(array $options) 'bindingKey' => $this->getBindingKey(), 'strategy' => $this->getStrategy(), 'associationType' => $this->type(), - 'finder' => [$this, 'find'] + 'finder' => [$this, 'find'], ]); return $loader->buildEagerLoader($options); diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index e196e0fb..2efd9b55 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -28,7 +28,6 @@ */ class SelectLoader { - /** * The alias of the association loading the results * @@ -108,7 +107,7 @@ public function __construct(array $options) $this->bindingKey = $options['bindingKey']; $this->finder = $options['finder']; $this->associationType = $options['associationType']; - $this->sort = isset($options['sort']) ? $options['sort'] : null; + $this->sort = $options['sort'] ?? null; } /** @@ -139,7 +138,7 @@ protected function _defaultOptions() 'conditions' => [], 'strategy' => $this->strategy, 'nestKey' => $this->alias, - 'sort' => $this->sort + 'sort' => $this->sort, ]; } @@ -324,7 +323,7 @@ protected function _addFilteringCondition($query, $key, $filter) $conditions = $this->_createTupleCondition($query, $key, $filter, 'IN'); } - $conditions = isset($conditions) ? $conditions : [$key . ' IN' => $filter]; + $conditions = $conditions ?? [$key . ' IN' => $filter]; return $query->andWhere($conditions); } diff --git a/Association/Loader/SelectWithPivotLoader.php b/Association/Loader/SelectWithPivotLoader.php index 733a8c7f..9564bc09 100644 --- a/Association/Loader/SelectWithPivotLoader.php +++ b/Association/Loader/SelectWithPivotLoader.php @@ -23,7 +23,6 @@ */ class SelectWithPivotLoader extends SelectLoader { - /** * The name of the junction association * diff --git a/AssociationCollection.php b/AssociationCollection.php index c3ea52a3..c95f7613 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -29,7 +29,6 @@ */ class AssociationCollection implements IteratorAggregate { - use AssociationsNormalizerTrait; use LocatorAwareTrait; @@ -48,7 +47,7 @@ class AssociationCollection implements IteratorAggregate * * @param \Cake\ORM\Locator\LocatorInterface|null $tableLocator Table locator instance. */ - public function __construct(LocatorInterface $tableLocator = null) + public function __construct(?LocatorInterface $tableLocator = null) { if ($tableLocator !== null) { $this->_tableLocator = $tableLocator; @@ -84,7 +83,7 @@ public function add($alias, Association $association) public function load($className, $associated, array $options = []) { $options += [ - 'tableLocator' => $this->getTableLocator() + 'tableLocator' => $this->getTableLocator(), ]; $association = new $className($associated, $options); diff --git a/AssociationsNormalizerTrait.php b/AssociationsNormalizerTrait.php index 335804b4..dc262f11 100644 --- a/AssociationsNormalizerTrait.php +++ b/AssociationsNormalizerTrait.php @@ -20,7 +20,6 @@ */ trait AssociationsNormalizerTrait { - /** * Returns an array out of the original passed associations list where dot notation * is transformed into nested arrays so that they can be parsed by other routines @@ -62,6 +61,6 @@ protected function _normalizeAssociations($associations) $pointer['associated'][$table] = $options + $pointer['associated'][$table]; } - return isset($result['associated']) ? $result['associated'] : $result; + return $result['associated'] ?? $result; } } diff --git a/Behavior.php b/Behavior.php index a3452c4e..98c2b8a1 100644 --- a/Behavior.php +++ b/Behavior.php @@ -113,7 +113,6 @@ */ class Behavior implements EventListenerInterface { - use InstanceConfigTrait; /** @@ -275,7 +274,7 @@ public function implementedEvents() 'Model.afterRules' => 'afterRules', ]; $config = $this->getConfig(); - $priority = isset($config['priority']) ? $config['priority'] : null; + $priority = $config['priority'] ?? null; $events = []; foreach ($eventMap as $event => $method) { @@ -287,7 +286,7 @@ public function implementedEvents() } else { $events[$event] = [ 'callable' => $method, - 'priority' => $priority + 'priority' => $priority, ]; } } @@ -397,7 +396,7 @@ protected function _reflectionCache() $return = [ 'finders' => [], - 'methods' => [] + 'methods' => [], ]; $reflection = new ReflectionClass($class); diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index d7e2191b..2271cc98 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -100,7 +100,6 @@ */ class CounterCacheBehavior extends Behavior { - /** * Store the fields which should be ignored * diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 95b3283f..955134d3 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -14,8 +14,8 @@ */ namespace Cake\ORM\Behavior; -use Cake\Database\TypeFactory; use Cake\Database\Type\DateTimeType; +use Cake\Database\TypeFactory; use Cake\Datasource\EntityInterface; use Cake\Event\EventInterface; use Cake\I18n\Time; @@ -29,7 +29,6 @@ */ class TimestampBehavior extends Behavior { - /** * Default config * @@ -49,15 +48,15 @@ class TimestampBehavior extends Behavior 'implementedFinders' => [], 'implementedMethods' => [ 'timestamp' => 'timestamp', - 'touch' => 'touch' + 'touch' => 'touch', ], 'events' => [ 'Model.beforeSave' => [ 'created' => 'new', - 'modified' => 'always' - ] + 'modified' => 'always', + ], ], - 'refreshTimestamp' => true + 'refreshTimestamp' => true, ]; /** @@ -140,7 +139,7 @@ public function implementedEvents() * @param bool $refreshTimestamp If true timestamp is refreshed. * @return \DateTime */ - public function timestamp(DateTime $ts = null, $refreshTimestamp = false) + public function timestamp(?DateTime $ts = null, $refreshTimestamp = false) { if ($ts) { if ($this->_config['refreshTimestamp']) { diff --git a/Behavior/Translate/TranslateTrait.php b/Behavior/Translate/TranslateTrait.php index 343740e2..9ce6126e 100644 --- a/Behavior/Translate/TranslateTrait.php +++ b/Behavior/Translate/TranslateTrait.php @@ -22,7 +22,6 @@ */ trait TranslateTrait { - /** * Returns the entity containing the translated fields for this object and for * the specified language. If the translation for the passed language is not diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index aded9be9..5eab5637 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -42,7 +42,6 @@ */ class TranslateBehavior extends Behavior implements PropertyMarshalInterface { - use LocatorAwareTrait; /** @@ -79,7 +78,7 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface 'implementedMethods' => [ 'setLocale' => 'setLocale', 'getLocale' => 'getLocale', - 'translationField' => 'translationField' + 'translationField' => 'translationField', ], 'fields' => [], 'translationTable' => 'I18n', @@ -89,7 +88,7 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface 'onlyTranslated' => false, 'strategy' => 'subquery', 'tableLocator' => null, - 'validator' => false + 'validator' => false, ]; /** @@ -102,7 +101,7 @@ public function __construct(Table $table, array $config = []) { $config += [ 'defaultLocale' => I18n::getDefaultLocale(), - 'referenceName' => $this->_referenceName($table) + 'referenceName' => $this->_referenceName($table), ]; if (isset($config['tableLocator'])) { @@ -158,7 +157,7 @@ public function setupFieldAssociations($fields, $table, $model, $strategy) $fieldTable = $tableLocator->get($name, [ 'className' => $table, 'alias' => $name, - 'table' => $this->_translationTable->getTable() + 'table' => $this->_translationTable->getTable(), ]); } else { $fieldTable = $tableLocator->get($name); @@ -177,7 +176,7 @@ public function setupFieldAssociations($fields, $table, $model, $strategy) 'foreignKey' => 'foreign_key', 'joinType' => $filter ? QueryInterface::JOIN_TYPE_INNER : QueryInterface::JOIN_TYPE_LEFT, 'conditions' => $conditions, - 'propertyName' => $field . '_translation' + 'propertyName' => $field . '_translation', ]); } @@ -192,7 +191,7 @@ public function setupFieldAssociations($fields, $table, $model, $strategy) 'strategy' => $strategy, 'conditions' => $conditions, 'propertyName' => '_i18n', - 'dependent' => true + 'dependent' => true, ]); } @@ -329,7 +328,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array 'field IN' => $fields, 'locale' => $locale, 'foreign_key' => $key, - 'model' => $model + 'model' => $model, ]) ->enableBufferedResults(false) ->all() @@ -345,7 +344,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array foreach ($new as $field => $content) { $new[$field] = new Entity(compact('locale', 'field', 'content', 'model'), [ 'useSetters' => false, - 'markNew' => true + 'markNew' => true, ]); } @@ -408,7 +407,7 @@ public function buildMarshalMap($marshaller, $map, $options) } return $translations; - } + }, ]; } @@ -503,7 +502,7 @@ public function translationField($field) */ public function findTranslations(Query $query, array $options) { - $locales = isset($options['locales']) ? $options['locales'] : []; + $locales = $options['locales'] ?? []; $targetAlias = $this->_translationTable->getAlias(); return $query @@ -559,14 +558,14 @@ protected function _rowMapper($results, $locale) foreach ($this->_config['fields'] as $field) { $name = $field . '_translation'; - $translation = isset($row[$name]) ? $row[$name] : null; + $translation = $row[$name] ?? null; if ($translation === null || $translation === false) { unset($row[$name]); continue; } - $content = isset($translation['content']) ? $translation['content'] : null; + $content = $translation['content'] ?? null; if ($content !== null) { $row[$field] = $content; } @@ -609,7 +608,7 @@ public function groupTranslations($results) $translation = new $entityClass($keys + ['locale' => $locale], [ 'markNew' => false, 'useSetters' => false, - 'markClean' => true + 'markClean' => true, ]); $result[$locale] = $translation; } @@ -652,7 +651,7 @@ protected function _bundleTranslatedFields($entity) } $find[] = ['locale' => $lang, 'field' => $field, 'foreign_key' => $key]; $contents[] = new Entity(['content' => $translation->get($field)], [ - 'useSetters' => false + 'useSetters' => false, ]); } } diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 8d8b2e8a..5f0a4f7f 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -37,7 +37,6 @@ */ class TreeBehavior extends Behavior { - /** * Cached copy of the first column in a table's primary key. * @@ -103,7 +102,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity) $dirty = $entity->isDirty($config['parent']); $level = $config['level']; - if ($parent && $entity->get($primaryKey) == $parent) { + if ($parent && $entity->get($primaryKey) === $parent) { throw new RuntimeException("Cannot set a node's parent as itself"); } @@ -570,7 +569,7 @@ protected function _removeFromTree($node) $node->set($config['parent'], null); - if ($right - $left == 1) { + if ($right - $left === 1) { return $this->_table->save($node); } diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index a72389e6..79e62e6c 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -30,7 +30,6 @@ */ class BehaviorRegistry extends ObjectRegistry implements EventDispatcherInterface { - use EventDispatcherTrait; /** @@ -126,7 +125,7 @@ protected function _throwMissingClassError($class, $plugin) { throw new MissingBehaviorException([ 'class' => $class . 'Behavior', - 'plugin' => $plugin + 'plugin' => $plugin, ]); } @@ -144,7 +143,7 @@ protected function _throwMissingClassError($class, $plugin) protected function _create($class, $alias, $config) { $instance = new $class($this->_table, $config); - $enable = isset($config['enabled']) ? $config['enabled'] : true; + $enable = $config['enabled'] ?? true; if ($enable) { $this->getEventManager()->on($instance); } diff --git a/EagerLoadable.php b/EagerLoadable.php index aa3aab96..5cc7e7f5 100644 --- a/EagerLoadable.php +++ b/EagerLoadable.php @@ -24,7 +24,6 @@ */ class EagerLoadable { - /** * The name of the association to load. * @@ -132,7 +131,7 @@ public function __construct($name, array $config = []) $this->_name = $name; $allowed = [ 'associations', 'instance', 'config', 'canBeJoined', - 'aliasPath', 'propertyPath', 'forMatching', 'targetProperty' + 'aliasPath', 'propertyPath', 'forMatching', 'targetProperty', ]; foreach ($allowed as $property) { if (isset($config[$property])) { @@ -301,8 +300,8 @@ public function asContainArray() return [ $this->_name => [ 'associations' => $associations, - 'config' => $config - ] + 'config' => $config, + ], ]; } } diff --git a/EagerLoader.php b/EagerLoader.php index ae4cb8d2..f5564481 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -28,7 +28,6 @@ */ class EagerLoader { - /** * Nested array describing the association to be fetched * and the options to apply for each of them, if any @@ -62,7 +61,7 @@ class EagerLoader 'finder' => 1, 'joinType' => 1, 'strategy' => 1, - 'negateMatch' => 1 + 'negateMatch' => 1, ]; /** @@ -131,7 +130,7 @@ class EagerLoader * @return array Containments. * @throws \InvalidArgumentException When using $queryBuilder with an array of $associations */ - public function contain($associations, callable $queryBuilder = null) + public function contain($associations, ?callable $queryBuilder = null) { if ($queryBuilder) { if (!is_string($associations)) { @@ -142,8 +141,8 @@ public function contain($associations, callable $queryBuilder = null) $associations = [ $associations => [ - 'queryBuilder' => $queryBuilder - ] + 'queryBuilder' => $queryBuilder, + ], ]; } @@ -225,7 +224,7 @@ public function isAutoFieldsEnabled() * @param array $options Extra options for the association matching. * @return $this */ - public function setMatching($assoc, callable $builder = null, $options = []) + public function setMatching($assoc, ?callable $builder = null, $options = []) { if ($this->_matching === null) { $this->_matching = new static(); @@ -354,7 +353,7 @@ protected function _reformatContain($associations, $original) $options; $options = $this->_reformatContain( $options, - isset($pointer[$table]) ? $pointer[$table] : [] + $pointer[$table] ?? [] ); } @@ -494,7 +493,7 @@ protected function _normalizeContain(Table $parent, $alias, $options, $paths) 'config' => array_diff_key($options, $extra), 'aliasPath' => trim($paths['aliasPath'], '.'), 'propertyPath' => trim($paths['propertyPath'], '.'), - 'targetProperty' => $instance->getProperty() + 'targetProperty' => $instance->getProperty(), ]; $config['canBeJoined'] = $instance->canBeJoined($config['config']); $eagerLoadable = new EagerLoadable($alias, $config); @@ -551,8 +550,7 @@ protected function _fixStrategies() protected function _correctStrategy($loadable) { $config = $loadable->getConfig(); - $currentStrategy = isset($config['strategy']) ? - $config['strategy'] : + $currentStrategy = $config['strategy'] ?? 'join'; if (!$loadable->canBeJoined() || $currentStrategy !== 'join') { @@ -629,13 +627,13 @@ public function loadExternal($query, $statement) continue; } - $keys = isset($collected[$path][$alias]) ? $collected[$path][$alias] : null; + $keys = $collected[$path][$alias] ?? null; $f = $instance->eagerLoader( $config + [ 'query' => $query, 'contain' => $contain, 'keys' => $keys, - 'nestKey' => $meta->aliasPath() + 'nestKey' => $meta->aliasPath(), ] ); $statement = new CallbackStatement($statement, $driver, $f); @@ -698,7 +696,7 @@ protected function _buildAssociationsMap($map, $level, $matching = false) 'entityClass' => $instance->getTarget()->getEntityClass(), 'nestKey' => $canBeJoined ? $assoc : $meta->aliasPath(), 'matching' => $forMatching !== null ? $forMatching : $matching, - 'targetProperty' => $meta->targetProperty() + 'targetProperty' => $meta->targetProperty(), ]; if ($canBeJoined && $associations) { $map = $this->_buildAssociationsMap($map, $associations, $matching); @@ -729,7 +727,7 @@ public function addToJoinsMap($alias, Association $assoc, $asMatching = false, $ 'instance' => $assoc, 'canBeJoined' => true, 'forMatching' => $asMatching, - 'targetProperty' => $targetProperty ?: $assoc->getProperty() + 'targetProperty' => $targetProperty ?: $assoc->getProperty(), ]); } diff --git a/Entity.php b/Entity.php index a3771491..add57bc7 100644 --- a/Entity.php +++ b/Entity.php @@ -52,7 +52,7 @@ public function __construct(array $properties = [], array $options = []) 'markClean' => false, 'markNew' => null, 'guard' => false, - 'source' => null + 'source' => null, ]; if (!empty($options['source'])) { @@ -72,7 +72,7 @@ public function __construct(array $properties = [], array $options = []) if (!empty($properties)) { $this->set($properties, [ 'setter' => $options['useSetters'], - 'guard' => $options['guard'] + 'guard' => $options['guard'], ]); } diff --git a/Exception/MissingBehaviorException.php b/Exception/MissingBehaviorException.php index 725e5e8a..c6c34cf6 100644 --- a/Exception/MissingBehaviorException.php +++ b/Exception/MissingBehaviorException.php @@ -19,6 +19,5 @@ */ class MissingBehaviorException extends Exception { - protected $_messageTemplate = 'Behavior class %s could not be found.'; } diff --git a/Exception/MissingEntityException.php b/Exception/MissingEntityException.php index 6f355279..d4771e3c 100644 --- a/Exception/MissingEntityException.php +++ b/Exception/MissingEntityException.php @@ -23,6 +23,5 @@ */ class MissingEntityException extends Exception { - protected $_messageTemplate = 'Entity class %s could not be found.'; } diff --git a/Exception/MissingTableClassException.php b/Exception/MissingTableClassException.php index 544a968d..83c96059 100644 --- a/Exception/MissingTableClassException.php +++ b/Exception/MissingTableClassException.php @@ -21,6 +21,5 @@ */ class MissingTableClassException extends Exception { - protected $_messageTemplate = 'Table class %s could not be found.'; } diff --git a/Exception/PersistenceFailedException.php b/Exception/PersistenceFailedException.php index 4bfb1db0..d5814d81 100644 --- a/Exception/PersistenceFailedException.php +++ b/Exception/PersistenceFailedException.php @@ -20,7 +20,6 @@ */ class PersistenceFailedException extends Exception { - /** * The entity on which the persistence operation failed * diff --git a/Exception/RolledbackTransactionException.php b/Exception/RolledbackTransactionException.php index ac31a9d0..7cdd2fe5 100644 --- a/Exception/RolledbackTransactionException.php +++ b/Exception/RolledbackTransactionException.php @@ -19,6 +19,5 @@ */ class RolledbackTransactionException extends Exception { - protected $_messageTemplate = 'The afterSave event in "%s" is aborting the transaction before the save process is done.'; } diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 092a6534..04b18088 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -26,7 +26,6 @@ */ class LazyEagerLoader { - /** * Loads the specified associations in the passed entity or list of entities * by executing extra queries in the database and merging the results in the diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index 3924e553..f79ac115 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -21,7 +21,6 @@ */ trait LocatorAwareTrait { - /** * Table locator instance * diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 55b19a59..33d44c7f 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -26,7 +26,6 @@ */ class TableLocator implements LocatorInterface { - /** * Configuration for aliases. * @@ -97,7 +96,7 @@ public function getConfig($alias = null) return $this->_config; } - return isset($this->_config[$alias]) ? $this->_config[$alias] : []; + return $this->_config[$alias] ?? []; } /** diff --git a/Marshaller.php b/Marshaller.php index 94418ff2..02ca5c92 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -35,7 +35,6 @@ */ class Marshaller { - use AssociationsNormalizerTrait; /** @@ -370,8 +369,8 @@ public function many(array $data, array $options = []) */ protected function _belongsToMany(BelongsToMany $assoc, array $data, $options = []) { - $associated = isset($options['associated']) ? $options['associated'] : []; - $forceNew = isset($options['forceNew']) ? $options['forceNew'] : false; + $associated = $options['associated'] ?? []; + $forceNew = $options['forceNew'] ?? false; $data = array_values($data); @@ -560,7 +559,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) // same objects, even though those objects may have changed internally. if ((is_scalar($value) && $original === $value) || ($value === null && $original === $value) || - (is_object($value) && !($value instanceof EntityInterface) && $original == $value) + (is_object($value) && !($value instanceof EntityInterface) && $original === $value) ) { continue; } @@ -633,7 +632,7 @@ public function mergeMany($entities, array $data, array $options = []) ->groupBy(function ($el) use ($primary) { $keys = []; foreach ($primary as $key) { - $keys[] = isset($el[$key]) ? $el[$key] : ''; + $keys[] = $el[$key] ?? ''; } return implode(';', $keys); @@ -643,7 +642,7 @@ public function mergeMany($entities, array $data, array $options = []) }) ->toArray(); - $new = isset($indexed[null]) ? $indexed[null] : []; + $new = $indexed[null] ?? []; unset($indexed[null]); $output = []; @@ -739,7 +738,7 @@ protected function _mergeAssociation($original, $assoc, $value, $options) */ protected function _mergeBelongsToMany($original, $assoc, $value, $options) { - $associated = isset($options['associated']) ? $options['associated'] : []; + $associated = $options['associated'] ?? []; $hasIds = array_key_exists('_ids', $value); $onlyIds = array_key_exists('onlyIds', $options) && $options['onlyIds']; @@ -769,7 +768,7 @@ protected function _mergeBelongsToMany($original, $assoc, $value, $options) */ protected function _mergeJoinData($original, $assoc, $value, $options) { - $associated = isset($options['associated']) ? $options['associated'] : []; + $associated = $options['associated'] ?? []; $extra = []; foreach ($original as $entity) { // Mark joinData as accessible so we can marshal it properly. diff --git a/Query.php b/Query.php index f061f4a7..ef54aaf8 100644 --- a/Query.php +++ b/Query.php @@ -22,7 +22,6 @@ use Cake\Database\ValueBinder; use Cake\Datasource\QueryInterface; use Cake\Datasource\QueryTrait; -use InvalidArgumentException; use JsonSerializable; use RuntimeException; @@ -70,7 +69,6 @@ */ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface { - use QueryTrait { cache as private _cache; all as private _all; @@ -83,21 +81,21 @@ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface * * @var int */ - const APPEND = 0; + public const APPEND = 0; /** * Indicates that the operation should prepend to the list * * @var int */ - const PREPEND = 1; + public const PREPEND = 1; /** * Indicates that the operation should overwrite the list * * @var bool */ - const OVERWRITE = true; + public const OVERWRITE = true; /** * Whether the user select any fields before being executed, this is used @@ -545,7 +543,7 @@ protected function _addAssociationsToTypeMap($table, $typeMap, $associations) * that can be used to add custom conditions or selecting some fields * @return $this */ - public function matching($assoc, callable $builder = null) + public function matching($assoc, ?callable $builder = null) { $result = $this->getEagerLoader()->setMatching($assoc, $builder)->getMatching(); $this->_addAssociationsToTypeMap($this->getRepository(), $this->getTypeMap(), $result); @@ -617,12 +615,12 @@ public function matching($assoc, callable $builder = null) * that can be used to add custom conditions or selecting some fields * @return $this */ - public function leftJoinWith($assoc, callable $builder = null) + public function leftJoinWith($assoc, ?callable $builder = null) { $result = $this->getEagerLoader() ->setMatching($assoc, $builder, [ 'joinType' => QueryInterface::JOIN_TYPE_LEFT, - 'fields' => false + 'fields' => false, ]) ->getMatching(); $this->_addAssociationsToTypeMap($this->getRepository(), $this->getTypeMap(), $result); @@ -666,12 +664,12 @@ public function leftJoinWith($assoc, callable $builder = null) * @return $this * @see \Cake\ORM\Query::matching() */ - public function innerJoinWith($assoc, callable $builder = null) + public function innerJoinWith($assoc, ?callable $builder = null) { $result = $this->getEagerLoader() ->setMatching($assoc, $builder, [ 'joinType' => QueryInterface::JOIN_TYPE_INNER, - 'fields' => false + 'fields' => false, ]) ->getMatching(); $this->_addAssociationsToTypeMap($this->getRepository(), $this->getTypeMap(), $result); @@ -730,13 +728,13 @@ public function innerJoinWith($assoc, callable $builder = null) * that can be used to add custom conditions or selecting some fields * @return $this */ - public function notMatching($assoc, callable $builder = null) + public function notMatching($assoc, ?callable $builder = null) { $result = $this->getEagerLoader() ->setMatching($assoc, $builder, [ 'joinType' => QueryInterface::JOIN_TYPE_LEFT, 'fields' => false, - 'negateMatch' => true + 'negateMatch' => true, ]) ->getMatching(); $this->_addAssociationsToTypeMap($this->getRepository(), $this->getTypeMap(), $result); @@ -1031,7 +1029,7 @@ public function triggerBeforeFind() $table->dispatchEvent('Model.beforeFind', [ $this, new ArrayObject($this->_options), - !$this->isEagerLoaded() + !$this->isEagerLoaded(), ]); } } @@ -1039,7 +1037,7 @@ public function triggerBeforeFind() /** * {@inheritDoc} */ - public function sql(ValueBinder $binder = null) + public function sql(?ValueBinder $binder = null) { $this->triggerBeforeFind(); @@ -1250,7 +1248,7 @@ public function __debugInfo() 'contain' => $eagerLoader ? $eagerLoader->getContain() : [], 'matching' => $eagerLoader ? $eagerLoader->getMatching() : [], 'extraOptions' => $this->_options, - 'repository' => $this->_repository + 'repository' => $this->_repository, ]; } diff --git a/ResultSet.php b/ResultSet.php index ad9ef097..151873b3 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -29,7 +29,6 @@ */ class ResultSet implements ResultSetInterface { - use CollectionTrait; /** @@ -230,7 +229,7 @@ public function next() */ public function rewind() { - if ($this->_index == 0) { + if ($this->_index === 0) { return; } @@ -448,7 +447,7 @@ protected function _groupResult($row) 'useSetters' => false, 'markClean' => true, 'markNew' => false, - 'guard' => false + 'guard' => false, ]; foreach ($this->_matchingMapColumns as $alias => $keys) { diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index d583120b..104f9a86 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -24,7 +24,6 @@ */ class ExistsIn { - /** * The list of fields to check * diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index 2d024ed1..d8ade414 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -21,7 +21,6 @@ */ class IsUnique { - /** * The list of fields to check * diff --git a/Rule/ValidCount.php b/Rule/ValidCount.php index 33a98081..33696ad7 100644 --- a/Rule/ValidCount.php +++ b/Rule/ValidCount.php @@ -23,7 +23,6 @@ */ class ValidCount { - /** * The field to check * diff --git a/RulesChecker.php b/RulesChecker.php index feea8c89..34305b44 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -28,7 +28,6 @@ */ class RulesChecker extends BaseRulesChecker { - /** * Returns a callable that can be used as a rule for checking the uniqueness of a value * in the table. diff --git a/SaveOptionsBuilder.php b/SaveOptionsBuilder.php index f3ea0b95..470ed502 100644 --- a/SaveOptionsBuilder.php +++ b/SaveOptionsBuilder.php @@ -27,7 +27,6 @@ */ class SaveOptionsBuilder extends ArrayObject { - use AssociationsNormalizerTrait; /** diff --git a/Table.php b/Table.php index 397b4d57..9aa10242 100644 --- a/Table.php +++ b/Table.php @@ -126,7 +126,6 @@ */ class Table implements RepositoryInterface, EventListenerInterface, EventDispatcherInterface, ValidatorAwareInterface { - use EventDispatcherTrait; use RulesAwareTrait; use ValidatorAwareTrait; @@ -136,21 +135,21 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc * * @var string */ - const VALIDATOR_PROVIDER_NAME = 'table'; + public const VALIDATOR_PROVIDER_NAME = 'table'; /** * The name of the event dispatched when a validator has been built. * * @var string */ - const BUILD_VALIDATOR_EVENT = 'Model.buildValidator'; + public const BUILD_VALIDATOR_EVENT = 'Model.buildValidator'; /** * The rules class name that is used. * * @var string */ - const RULES_CLASS = 'Cake\ORM\RulesChecker'; + public const RULES_CLASS = 'Cake\ORM\RulesChecker'; /** * Name of the table as it can be found in the database @@ -1249,7 +1248,7 @@ public function findList(Query $query, array $options) $options += [ 'keyField' => $this->getPrimaryKey(), 'valueField' => $this->getDisplayField(), - 'groupField' => null + 'groupField' => null, ]; if (!$query->clause('select') && @@ -1312,7 +1311,7 @@ public function findThreaded(Query $query, array $options) $options += [ 'keyField' => $this->getPrimaryKey(), 'parentField' => 'parent_id', - 'nestingKey' => 'children' + 'nestingKey' => 'children', ]; $options = $this->_setFieldMatchers($options, ['keyField', 'parentField']); @@ -1398,9 +1397,9 @@ public function get($primaryKey, $options = []) } $conditions = array_combine($key, $primaryKey); - $cacheConfig = isset($options['cache']) ? $options['cache'] : false; - $cacheKey = isset($options['key']) ? $options['key'] : false; - $finder = isset($options['finder']) ? $options['finder'] : 'all'; + $cacheConfig = $options['cache'] ?? false; + $cacheKey = $options['key'] ?? false; + $finder = $options['finder'] ?? 'all'; unset($options['key'], $options['cache'], $options['finder']); $query = $this->find($finder, $options)->where($conditions); @@ -1483,7 +1482,7 @@ protected function _transactionCommitted($atomic, $primary) * @param array $options The options to use when saving. * @return \Cake\Datasource\EntityInterface An entity. */ - public function findOrCreate($search, callable $callback = null, $options = []) + public function findOrCreate($search, ?callable $callback = null, $options = []) { $options = new ArrayObject($options + [ 'atomic' => true, @@ -1512,7 +1511,7 @@ public function findOrCreate($search, callable $callback = null, $options = []) * @param array $options The options to use when saving. * @return \Cake\Datasource\EntityInterface An entity. */ - protected function _processFindOrCreate($search, callable $callback = null, $options = []) + protected function _processFindOrCreate($search, ?callable $callback = null, $options = []) { if (is_callable($search)) { $query = $this->find(); @@ -1706,7 +1705,7 @@ public function save(EntityInterface $entity, $options = []) 'associated' => true, 'checkRules' => true, 'checkExisting' => true, - '_primary' => true + '_primary' => true, ]); if ($entity->getErrors()) { @@ -2084,7 +2083,7 @@ public function delete(EntityInterface $entity, $options = []) if ($success && $this->_transactionCommitted($options['atomic'], $options['_primary'])) { $this->dispatchEvent('Model.afterDeleteCommit', [ 'entity' => $entity, - 'options' => $options + 'options' => $options, ]); } @@ -2141,7 +2140,7 @@ protected function _processDelete($entity, $options) $event = $this->dispatchEvent('Model.beforeDelete', [ 'entity' => $entity, - 'options' => $options + 'options' => $options, ]); if ($event->isStopped()) { @@ -2166,7 +2165,7 @@ protected function _processDelete($entity, $options) $this->dispatchEvent('Model.afterDelete', [ 'entity' => $entity, - 'options' => $options + 'options' => $options, ]); return $success; @@ -2266,7 +2265,7 @@ protected function _dynamicFinder($method, $args) } elseif ($hasOr !== false) { $fields = explode('_or_', $fields); $conditions = [ - 'OR' => $makeConditions($fields, $args) + 'OR' => $makeConditions($fields, $args), ]; } elseif ($hasAnd !== false) { $fields = explode('_and_', $fields); @@ -2573,7 +2572,7 @@ public function patchEntities($entities, array $data, array $options = []) * @param array|null $context Either the validation context or null. * @return bool True if the value is unique, or false if a non-scalar, non-unique value was given. */ - public function validateUnique($value, array $options, array $context = null) + public function validateUnique($value, array $options, ?array $context = null) { if ($context === null) { $context = $options; @@ -2583,7 +2582,7 @@ public function validateUnique($value, array $options, array $context = null) [ 'useSetters' => false, 'markNew' => $context['newRecord'], - 'source' => $this->getRegistryAlias() + 'source' => $this->getRegistryAlias(), ] ); $fields = array_merge( @@ -2738,7 +2737,7 @@ public function __debugInfo() 'associations' => $associations ? $associations->keys() : false, 'behaviors' => $behaviors ? $behaviors->loaded() : false, 'defaultConnection' => static::defaultConnectionName(), - 'connectionName' => $conn ? $conn->configName() : null + 'connectionName' => $conn ? $conn->configName() : null, ]; } } diff --git a/TableRegistry.php b/TableRegistry.php index 3270d003..25bfb737 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -49,7 +49,6 @@ */ class TableRegistry { - /** * LocatorInterface implementation instance. * @@ -71,7 +70,7 @@ class TableRegistry * @return \Cake\ORM\Locator\LocatorInterface * @deprecated 3.5.0 Use getTableLocator()/setTableLocator() instead. */ - public static function locator(LocatorInterface $locator = null) + public static function locator(?LocatorInterface $locator = null) { deprecationWarning( 'TableRegistry::locator() is deprecated. ' . From 5489bde915fa920b7b474e0b5ea4259d8ff2758a Mon Sep 17 00:00:00 2001 From: Tomas Saghy Date: Mon, 18 Jun 2018 13:52:38 +0200 Subject: [PATCH 1204/2059] fix entity class for irregular table names --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 52d97e32..d6ebdce3 100644 --- a/Table.php +++ b/Table.php @@ -773,7 +773,7 @@ public function getEntityClass() return $this->_entityClass = $default; } - $alias = Inflector::singularize(substr(array_pop($parts), 0, -5)); + $alias = Inflector::classify(Inflector::singularize(Inflector::underscore(substr(array_pop($parts), 0, -5)))); $name = implode('\\', array_slice($parts, 0, -1)) . '\\Entity\\' . $alias; if (!class_exists($name)) { return $this->_entityClass = $default; From e5f8c8d89d2c6961cbb294b241cf37dd4431f3b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgaras=20Janu=C5=A1auskas?= Date: Sat, 23 Jun 2018 13:04:24 +0300 Subject: [PATCH 1205/2059] Replace deprecated 'fieldList' with 'fields' in phpdoc --- Table.php | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Table.php b/Table.php index 52d97e32..d2646d43 100644 --- a/Table.php +++ b/Table.php @@ -2567,17 +2567,17 @@ public function marshaller() * ``` * * You can limit fields that will be present in the constructed entity by - * passing the `fieldList` option, which is also accepted for associations: + * passing the `fields` option, which is also accepted for associations: * * ``` * $article = $this->Articles->newEntity($this->request->getData(), [ - * 'fieldList' => ['title', 'body', 'tags', 'comments'], - * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']] + * 'fields' => ['title', 'body', 'tags', 'comments'], + * 'associated' => ['Tags', 'Comments.Users' => ['fields' => 'username']] * ] * ); * ``` * - * The `fieldList` option lets remove or restrict input data from ending up in + * The `fields` option lets remove or restrict input data from ending up in * the entity. If you'd like to relax the entity's default accessible fields, * you can use the `accessibleFields` option: * @@ -2636,12 +2636,12 @@ public function newEntity($data = null, array $options = []) * ``` * * You can limit fields that will be present in the constructed entities by - * passing the `fieldList` option, which is also accepted for associations: + * passing the `fields` option, which is also accepted for associations: * * ``` * $articles = $this->Articles->newEntities($this->request->getData(), [ - * 'fieldList' => ['title', 'body', 'tags', 'comments'], - * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']] + * 'fields' => ['title', 'body', 'tags', 'comments'], + * 'associated' => ['Tags', 'Comments.Users' => ['fields' => 'username']] * ] * ); * ``` @@ -2667,12 +2667,12 @@ public function newEntities(array $data, array $options = []) * the data merged, but those that cannot, will be discarded. * * You can limit fields that will be present in the merged entity by - * passing the `fieldList` option, which is also accepted for associations: + * passing the `fields` option, which is also accepted for associations: * * ``` * $article = $this->Articles->patchEntity($article, $this->request->getData(), [ - * 'fieldList' => ['title', 'body', 'tags', 'comments'], - * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']] + * 'fields' => ['title', 'body', 'tags', 'comments'], + * 'associated' => ['Tags', 'Comments.Users' => ['fields' => 'username']] * ] * ); * ``` @@ -2717,12 +2717,12 @@ public function patchEntity(EntityInterface $entity, array $data, array $options * the data merged, but those that cannot, will be discarded. * * You can limit fields that will be present in the merged entities by - * passing the `fieldList` option, which is also accepted for associations: + * passing the `fields` option, which is also accepted for associations: * * ``` * $articles = $this->Articles->patchEntities($articles, $this->request->getData(), [ - * 'fieldList' => ['title', 'body', 'tags', 'comments'], - * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']] + * 'fields' => ['title', 'body', 'tags', 'comments'], + * 'associated' => ['Tags', 'Comments.Users' => ['fields' => 'username']] * ] * ); * ``` From 012835db6630d46ebe2ea485904bd9ee066d55eb Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 23 Jun 2018 13:33:50 -0400 Subject: [PATCH 1206/2059] Add typehints to event classes. A few implementations of events were missing the required interfaces. I've shored that up too. --- Behavior.php | 2 +- Behavior/TimestampBehavior.php | 2 +- Table.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Behavior.php b/Behavior.php index a3452c4e..f4c219cb 100644 --- a/Behavior.php +++ b/Behavior.php @@ -258,7 +258,7 @@ public function verifyConfig() * * @return array */ - public function implementedEvents() + public function implementedEvents(): array { $eventMap = [ 'Model.beforeMarshal' => 'beforeMarshal', diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 95b3283f..963e3f9d 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -124,7 +124,7 @@ public function handleEvent(EventInterface $event, EntityInterface $entity) * * @return array */ - public function implementedEvents() + public function implementedEvents(): array { return array_fill_keys(array_keys($this->_config['events']), 'handleEvent'); } diff --git a/Table.php b/Table.php index c2619364..ee5a879a 100644 --- a/Table.php +++ b/Table.php @@ -2634,7 +2634,7 @@ public function validateUnique($value, array $options, array $context = null) * * @return array */ - public function implementedEvents() + public function implementedEvents(): array { $eventMap = [ 'Model.beforeMarshal' => 'beforeMarshal', From 0c470ddcb35f533a80ec610a6811b30bbf19dd04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgaras=20Janu=C5=A1auskas?= Date: Sat, 23 Jun 2018 13:04:24 +0300 Subject: [PATCH 1207/2059] Replace deprecated 'fieldList' with 'fields' in phpdoc --- Table.php | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Table.php b/Table.php index 52d97e32..d2646d43 100644 --- a/Table.php +++ b/Table.php @@ -2567,17 +2567,17 @@ public function marshaller() * ``` * * You can limit fields that will be present in the constructed entity by - * passing the `fieldList` option, which is also accepted for associations: + * passing the `fields` option, which is also accepted for associations: * * ``` * $article = $this->Articles->newEntity($this->request->getData(), [ - * 'fieldList' => ['title', 'body', 'tags', 'comments'], - * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']] + * 'fields' => ['title', 'body', 'tags', 'comments'], + * 'associated' => ['Tags', 'Comments.Users' => ['fields' => 'username']] * ] * ); * ``` * - * The `fieldList` option lets remove or restrict input data from ending up in + * The `fields` option lets remove or restrict input data from ending up in * the entity. If you'd like to relax the entity's default accessible fields, * you can use the `accessibleFields` option: * @@ -2636,12 +2636,12 @@ public function newEntity($data = null, array $options = []) * ``` * * You can limit fields that will be present in the constructed entities by - * passing the `fieldList` option, which is also accepted for associations: + * passing the `fields` option, which is also accepted for associations: * * ``` * $articles = $this->Articles->newEntities($this->request->getData(), [ - * 'fieldList' => ['title', 'body', 'tags', 'comments'], - * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']] + * 'fields' => ['title', 'body', 'tags', 'comments'], + * 'associated' => ['Tags', 'Comments.Users' => ['fields' => 'username']] * ] * ); * ``` @@ -2667,12 +2667,12 @@ public function newEntities(array $data, array $options = []) * the data merged, but those that cannot, will be discarded. * * You can limit fields that will be present in the merged entity by - * passing the `fieldList` option, which is also accepted for associations: + * passing the `fields` option, which is also accepted for associations: * * ``` * $article = $this->Articles->patchEntity($article, $this->request->getData(), [ - * 'fieldList' => ['title', 'body', 'tags', 'comments'], - * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']] + * 'fields' => ['title', 'body', 'tags', 'comments'], + * 'associated' => ['Tags', 'Comments.Users' => ['fields' => 'username']] * ] * ); * ``` @@ -2717,12 +2717,12 @@ public function patchEntity(EntityInterface $entity, array $data, array $options * the data merged, but those that cannot, will be discarded. * * You can limit fields that will be present in the merged entities by - * passing the `fieldList` option, which is also accepted for associations: + * passing the `fields` option, which is also accepted for associations: * * ``` * $articles = $this->Articles->patchEntities($articles, $this->request->getData(), [ - * 'fieldList' => ['title', 'body', 'tags', 'comments'], - * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']] + * 'fields' => ['title', 'body', 'tags', 'comments'], + * 'associated' => ['Tags', 'Comments.Users' => ['fields' => 'username']] * ] * ); * ``` From f1374009dae9e2b6302fd80562f68a90bf3a9559 Mon Sep 17 00:00:00 2001 From: mscherer Date: Mon, 25 Jun 2018 10:47:11 +0200 Subject: [PATCH 1208/2059] Adjust App::className() to be clean nullable return. --- BehaviorRegistry.php | 6 +++--- Locator/TableLocator.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index a72389e6..6c823e57 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -95,7 +95,7 @@ public static function className($class) $result = App::className($class, 'ORM/Behavior', 'Behavior'); } - return $result ?: null; + return $result; } /** @@ -104,11 +104,11 @@ public static function className($class) * Part of the template method for Cake\Core\ObjectRegistry::load() * * @param string $class Partial classname to resolve. - * @return string|false Either the correct classname or false. + * @return string|null Either the correct class name or null. */ protected function _resolveClassName($class) { - return static::className($class) ?: false; + return static::className($class); } /** diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 55b19a59..ba119ec8 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -201,7 +201,7 @@ public function get($alias, array $options = []) * * @param string $alias The alias name you want to get. * @param array $options Table options array. - * @return string|false + * @return string|null */ protected function _getClassName($alias, array $options = []) { From dd38c1160c8193ed70c3f9c479b8548c1e77689f Mon Sep 17 00:00:00 2001 From: "vladimir.reznichenko" Date: Thu, 28 Jun 2018 13:20:00 +0200 Subject: [PATCH 1209/2059] Php Inspections (EA Ultimate): minor code tweaks --- Marshaller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index 45ae6136..7eb25518 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -569,7 +569,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) $errors = $this->_validate($data + $keys, $options, $isNew); $options['isMerge'] = true; $propertyMap = $this->_buildPropertyMap($data, $options); - $properties = $marshalledAssocs = []; + $properties = []; foreach ($data as $key => $value) { if (!empty($errors[$key])) { if ($entity instanceof InvalidPropertyInterface) { From a2e8a64e6980b2f72567e686ad30b4c258eac1ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joshua=20Lu=CC=88ckers?= Date: Wed, 4 Jul 2018 13:08:56 +0200 Subject: [PATCH 1210/2059] If an entity has error messages we set the property _hasErrors to true. We use this property to check if the Entity can be saved without going trough all the errors. --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index d2646d43..1b018bda 100644 --- a/Table.php +++ b/Table.php @@ -1902,7 +1902,7 @@ public function save(EntityInterface $entity, $options = []) '_primary' => true ]); - if ($entity->getErrors()) { + if ($entity->hasErrors() === true) { return false; } From 39c16031db03cac468566d271550da620b72c327 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joshua=20Lu=CC=88ckers?= Date: Wed, 4 Jul 2018 15:09:17 +0200 Subject: [PATCH 1211/2059] Renamed the property _hasErrors and method hasErrors() to respectively _canBePersisted and canBePersisted(). --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 1b018bda..dbcfb1af 100644 --- a/Table.php +++ b/Table.php @@ -1902,7 +1902,7 @@ public function save(EntityInterface $entity, $options = []) '_primary' => true ]); - if ($entity->hasErrors() === true) { + if ($entity->canBePersisted() === false) { return false; } From 6af938641935cfe4296482656414f6a20e5848c7 Mon Sep 17 00:00:00 2001 From: Mark Sch Date: Thu, 5 Jul 2018 13:43:48 +0200 Subject: [PATCH 1212/2059] Explain shortcut --- Association/BelongsToMany.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 506801ab..9e114ecf 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -931,14 +931,14 @@ function () use ($sourceEntity, $targetEntities, $options) { * * `$article->get('tags')` will contain only `[$tag4]` after deleting in the database * - * @param \Cake\Datasource\EntityInterface $sourceEntity an entity persisted in the source table for - * this association - * @param array $targetEntities list of entities persisted in the target table for - * this association - * @param array|bool $options list of options to be passed to the internal `delete` call, - * or a `boolean` - * @throws \InvalidArgumentException if non persisted entities are passed or if - * any of them is lacking a primary key value + * @param \Cake\Datasource\EntityInterface $sourceEntity An entity persisted in the source table for + * this association. + * @param array $targetEntities List of entities persisted in the target table for + * this association. + * @param array|bool $options List of options to be passed to the internal `delete` call, + * or a `boolean` as `cleanProperty` key shortcut. + * @throws \InvalidArgumentException If non persisted entities are passed or if + * any of them is lacking a primary key value. * @return bool Success */ public function unlink(EntityInterface $sourceEntity, array $targetEntities, $options = []) From 3ae664af100d9fa9ceedf042ad87a0af5661ecc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joshua=20Lu=CC=88ckers?= Date: Thu, 5 Jul 2018 21:09:05 +0200 Subject: [PATCH 1213/2059] We want one single source of truth when we want to know if an entity has errors. Nested entities should also be taken in consideration. --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index dbcfb1af..f5f218da 100644 --- a/Table.php +++ b/Table.php @@ -1902,7 +1902,7 @@ public function save(EntityInterface $entity, $options = []) '_primary' => true ]); - if ($entity->canBePersisted() === false) { + if ($entity->hasErrors($options['associated']) === true) { return false; } From 5f49e3d2e066e1fcbe2482c1688375c605bcc594 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joshua=20Lu=CC=88ckers?= Date: Fri, 6 Jul 2018 09:55:10 +0200 Subject: [PATCH 1214/2059] There is no need to type check for booleans in if statements when the return values are already a boolean. --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index f5f218da..37d1848e 100644 --- a/Table.php +++ b/Table.php @@ -1902,7 +1902,7 @@ public function save(EntityInterface $entity, $options = []) '_primary' => true ]); - if ($entity->hasErrors($options['associated']) === true) { + if ($entity->hasErrors($options['associated'])) { return false; } From f17d21767cf478a0fbd5f1e49e1a5a622f5d7991 Mon Sep 17 00:00:00 2001 From: Moez Bouhlel Date: Sat, 7 Jul 2018 13:46:33 +0100 Subject: [PATCH 1215/2059] Fix typo on Table save() function documentation demo --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index d2646d43..8feb704a 100644 --- a/Table.php +++ b/Table.php @@ -1866,7 +1866,7 @@ public function exists($conditions) * * ``` * // Only save the comments association - * $articles->save($entity, ['associated' => ['Comments']); + * $articles->save($entity, ['associated' => ['Comments']]); * * // Save the company, the employees and related addresses for each of them. * // For employees do not check the entity rules From 9f89fc62c30b3d05e593587f9c3f791726ee0316 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 10 Jul 2018 22:25:51 -0400 Subject: [PATCH 1216/2059] Add types to datasource package and direct dependents Refs #11935 --- Query.php | 2 +- Table.php | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Query.php b/Query.php index be15bed1..40b1d207 100644 --- a/Query.php +++ b/Query.php @@ -865,7 +865,7 @@ public function __clone() * modified, and the count has already been performed the cached * value is returned */ - public function count() + public function count(): int { if ($this->_resultsCount === null) { $this->_resultsCount = $this->_performCount(); diff --git a/Table.php b/Table.php index 6474bf6b..9e2e04f5 100644 --- a/Table.php +++ b/Table.php @@ -367,7 +367,7 @@ public function getTable() * @param string $alias Table alias * @return $this */ - public function setAlias($alias) + public function setAlias(string $alias) { $this->_alias = $alias; @@ -379,7 +379,7 @@ public function setAlias($alias) * * @return string */ - public function getAlias() + public function getAlias(): string { if ($this->_alias === null) { $alias = namespaceSplit(get_class($this)); @@ -539,7 +539,7 @@ protected function _initializeSchema(TableSchema $schema) * @param string $field The field to check for. * @return bool True if the field exists, false if it does not. */ - public function hasField($field) + public function hasField(string $field): bool { $schema = $this->getSchema(); @@ -1568,7 +1568,7 @@ public function query() /** * {@inheritDoc} */ - public function updateAll($fields, $conditions) + public function updateAll($fields, $conditions): int { $query = $this->query(); $query->update() @@ -1583,7 +1583,7 @@ public function updateAll($fields, $conditions) /** * {@inheritDoc} */ - public function deleteAll($conditions) + public function deleteAll($conditions): int { $query = $this->query() ->delete() @@ -1597,7 +1597,7 @@ public function deleteAll($conditions) /** * {@inheritDoc} */ - public function exists($conditions) + public function exists($conditions): bool { return (bool)count( $this->find('all') @@ -1709,7 +1709,7 @@ public function save(EntityInterface $entity, $options = []) '_primary' => true ]); - if ($entity->hasErrors($options['associated'])) { + if ($entity->hasErrors((bool)$options['associated'])) { return false; } @@ -2077,7 +2077,7 @@ public function saveMany($entities, $options = []) * the options used in the delete operation. * */ - public function delete(EntityInterface $entity, $options = []) + public function delete(EntityInterface $entity, $options = []): bool { $options = new ArrayObject($options + [ 'atomic' => true, @@ -2153,7 +2153,7 @@ protected function _processDelete($entity, $options) ]); if ($event->isStopped()) { - return $event->getResult(); + return (bool)$event->getResult(); } $this->_associations->cascadeDelete( From ec77d431ea996bad6bbcee07a1bc6d7a652542a0 Mon Sep 17 00:00:00 2001 From: mark_story Date: Wed, 11 Jul 2018 13:23:37 -0400 Subject: [PATCH 1217/2059] Fix array type to be nullable. Fix a missing type in TableSchema too. --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 9e2e04f5..81c0127b 100644 --- a/Table.php +++ b/Table.php @@ -2413,7 +2413,7 @@ public function marshaller() * You can use the `Model.beforeMarshal` event to modify request data * before it is converted into entities. */ - public function newEntity($data = null, array $options = []) + public function newEntity(?array $data = null, array $options = []) { if ($data === null) { $class = $this->getEntityClass(); From 5ad0e17f470b30d204328c0e6e4cf60b92a147a6 Mon Sep 17 00:00:00 2001 From: AlPri78 <30730774+AlPri78@users.noreply.github.com> Date: Sat, 21 Jul 2018 19:04:54 +0200 Subject: [PATCH 1218/2059] Allowing IDE to recognize Collection/sortBy() with Query (#12381) Improved documentation for CollectionTrait --- Query.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Query.php b/Query.php index 746f2578..29712964 100644 --- a/Query.php +++ b/Query.php @@ -33,6 +33,7 @@ * * @see \Cake\Collection\CollectionInterface For a full description of the collection methods supported by this class * @method \Cake\Collection\CollectionInterface each(callable $c) Passes each of the query results to the callable + * @method \Cake\Collection\CollectionInterface sortBy($callback, $dir = SORT_DESC, $type = \SORT_NUMERIC) Sorts the query with the callback * @method \Cake\Collection\CollectionInterface filter(callable $c = null) Keeps the results using passing the callable test * @method \Cake\Collection\CollectionInterface reject(callable $c) Removes the results passing the callable test * @method bool every(callable $c) Returns true if all the results pass the callable test From 4697a40b9e7618b7eea95e8ff0916cd53ba93aef Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 22 Jul 2018 19:44:54 +0200 Subject: [PATCH 1219/2059] Added strict types to ORM\Rule refs #11935 --- Rule/ExistsIn.php | 6 ++++-- Rule/IsUnique.php | 5 +++-- Rule/ValidCount.php | 5 +++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 104f9a86..649b3d6a 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -1,4 +1,5 @@ _repository)) { if (!$options['repository']->hasAssociation($this->_repository)) { @@ -144,7 +146,7 @@ public function __invoke(EntityInterface $entity, array $options) * @param \Cake\ORM\Table $source The table to use schema from. * @return bool */ - protected function _fieldsAreNull($entity, $source) + protected function _fieldsAreNull(EntityInterface $entity, Table $source) : bool { $nulls = 0; $schema = $source->getSchema(); diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index d8ade414..7a840279 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -1,4 +1,5 @@ extract($this->_fields, true)) { return true; @@ -92,7 +93,7 @@ public function __invoke(EntityInterface $entity, array $options) * @param bool $multipleNulls Whether or not to allow multiple nulls. * @return array */ - protected function _alias($alias, $conditions, $multipleNulls) + protected function _alias(string $alias, array $conditions, bool $multipleNulls) : array { $aliased = []; foreach ($conditions as $key => $value) { diff --git a/Rule/ValidCount.php b/Rule/ValidCount.php index 33696ad7..d8fd5d6b 100644 --- a/Rule/ValidCount.php +++ b/Rule/ValidCount.php @@ -1,4 +1,5 @@ _field = $field; } @@ -47,7 +48,7 @@ public function __construct($field) * @param array $options Options passed to the check. * @return bool True if successful, else false. */ - public function __invoke(EntityInterface $entity, array $options) + public function __invoke(EntityInterface $entity, array $options) : bool { $value = $entity->{$this->_field}; if (!is_array($value) && !$value instanceof Countable) { From ccac8493564a2bc511d283c02e84596869d9b324 Mon Sep 17 00:00:00 2001 From: stickler-ci Date: Sun, 22 Jul 2018 17:46:40 +0000 Subject: [PATCH 1220/2059] Fixing style errors. --- Rule/ExistsIn.php | 4 ++-- Rule/IsUnique.php | 4 ++-- Rule/ValidCount.php | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 649b3d6a..1bebdc1c 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -78,7 +78,7 @@ public function __construct($fields, $repository, array $options = []) * @throws \RuntimeException When the rule refers to an undefined association. * @return bool */ - public function __invoke(EntityInterface $entity, array $options) : bool + public function __invoke(EntityInterface $entity, array $options): bool { if (is_string($this->_repository)) { if (!$options['repository']->hasAssociation($this->_repository)) { @@ -146,7 +146,7 @@ public function __invoke(EntityInterface $entity, array $options) : bool * @param \Cake\ORM\Table $source The table to use schema from. * @return bool */ - protected function _fieldsAreNull(EntityInterface $entity, Table $source) : bool + protected function _fieldsAreNull(EntityInterface $entity, Table $source): bool { $nulls = 0; $schema = $source->getSchema(); diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index 7a840279..9660fb31 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -62,7 +62,7 @@ public function __construct(array $fields, array $options = []) * @param array $options Options passed to the check, * @return bool */ - public function __invoke(EntityInterface $entity, array $options) : bool + public function __invoke(EntityInterface $entity, array $options): bool { if (!$entity->extract($this->_fields, true)) { return true; @@ -93,7 +93,7 @@ public function __invoke(EntityInterface $entity, array $options) : bool * @param bool $multipleNulls Whether or not to allow multiple nulls. * @return array */ - protected function _alias(string $alias, array $conditions, bool $multipleNulls) : array + protected function _alias(string $alias, array $conditions, bool $multipleNulls): array { $aliased = []; foreach ($conditions as $key => $value) { diff --git a/Rule/ValidCount.php b/Rule/ValidCount.php index d8fd5d6b..727536f8 100644 --- a/Rule/ValidCount.php +++ b/Rule/ValidCount.php @@ -48,7 +48,7 @@ public function __construct(string $field) * @param array $options Options passed to the check. * @return bool True if successful, else false. */ - public function __invoke(EntityInterface $entity, array $options) : bool + public function __invoke(EntityInterface $entity, array $options): bool { $value = $entity->{$this->_field}; if (!is_array($value) && !$value instanceof Countable) { From c23b7428c1672a52b543534d4f6bd5abbb6d6cf5 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 23 Jul 2018 00:58:13 +0530 Subject: [PATCH 1221/2059] Add strict typing to association related files. Refs #11935. --- Association.php | 86 ++++++++++---------- Association/BelongsTo.php | 16 ++-- Association/BelongsToMany.php | 72 ++++++++-------- Association/DependentDeleteHelper.php | 3 +- Association/HasMany.php | 41 +++++----- Association/HasOne.php | 14 ++-- Association/Loader/SelectLoader.php | 33 ++++---- Association/Loader/SelectWithPivotLoader.php | 8 +- AssociationCollection.php | 35 ++++---- AssociationsNormalizerTrait.php | 5 +- 10 files changed, 166 insertions(+), 147 deletions(-) diff --git a/Association.php b/Association.php index 4e77887f..de8b2aca 100644 --- a/Association.php +++ b/Association.php @@ -1,4 +1,5 @@ _targetTable !== null) { $alias = $this->_targetTable->getAlias(); @@ -264,7 +266,7 @@ public function setName($name) * * @return string */ - public function getName() + public function getName(): string { return $this->_name; } @@ -275,7 +277,7 @@ public function getName() * @param bool $cascadeCallbacks cascade callbacks switch value * @return $this */ - public function setCascadeCallbacks($cascadeCallbacks) + public function setCascadeCallbacks(bool $cascadeCallbacks) { $this->_cascadeCallbacks = $cascadeCallbacks; @@ -287,7 +289,7 @@ public function setCascadeCallbacks($cascadeCallbacks) * * @return bool */ - public function getCascadeCallbacks() + public function getCascadeCallbacks(): bool { return $this->_cascadeCallbacks; } @@ -297,7 +299,7 @@ public function getCascadeCallbacks() * * @return string */ - public function className() + public function className(): string { return $this->_className; } @@ -320,7 +322,7 @@ public function setSource(Table $table) * * @return \Cake\ORM\Table */ - public function getSource() + public function getSource(): Table { return $this->_sourceTable; } @@ -343,10 +345,10 @@ public function setTarget(Table $table) * * @return \Cake\ORM\Table */ - public function getTarget() + public function getTarget(): Table { if (!$this->_targetTable) { - if (strpos($this->_className, '.')) { + if (strpos((string)$this->_className, '.')) { list($plugin) = pluginSplit($this->_className, true); $registryAlias = $plugin . $this->_name; } else { @@ -476,7 +478,7 @@ public function setForeignKey($key) * @param bool $dependent Set the dependent mode. Use null to read the current state. * @return $this */ - public function setDependent($dependent) + public function setDependent(bool $dependent) { $this->_dependent = $dependent; @@ -491,7 +493,7 @@ public function setDependent($dependent) * * @return bool */ - public function getDependent() + public function getDependent(): bool { return $this->_dependent; } @@ -502,7 +504,7 @@ public function getDependent() * @param array $options custom options key that could alter the return value * @return bool */ - public function canBeJoined(array $options = []) + public function canBeJoined(array $options = []): bool { $strategy = $options['strategy'] ?? $this->getStrategy(); @@ -515,7 +517,7 @@ public function canBeJoined(array $options = []) * @param string $type the join type to be used (e.g. INNER) * @return $this */ - public function setJoinType($type) + public function setJoinType(string $type) { $this->_joinType = $type; @@ -527,7 +529,7 @@ public function setJoinType($type) * * @return string */ - public function getJoinType() + public function getJoinType(): string { return $this->_joinType; } @@ -539,7 +541,7 @@ public function getJoinType() * @param string $name The name of the association property. Use null to read the current value. * @return $this */ - public function setProperty($name) + public function setProperty(string $name) { $this->_propertyName = $name; @@ -552,7 +554,7 @@ public function setProperty($name) * * @return string */ - public function getProperty() + public function getProperty(): string { if (!$this->_propertyName) { $this->_propertyName = $this->_propertyName(); @@ -574,7 +576,7 @@ public function getProperty() * * @return string */ - protected function _propertyName() + protected function _propertyName(): string { list(, $name) = pluginSplit($this->_name); @@ -590,7 +592,7 @@ protected function _propertyName() * @return $this * @throws \InvalidArgumentException When an invalid strategy is provided. */ - public function setStrategy($name) + public function setStrategy(string $name) { if (!in_array($name, $this->_validStrategies)) { throw new InvalidArgumentException( @@ -609,7 +611,7 @@ public function setStrategy($name) * * @return string */ - public function getStrategy() + public function getStrategy(): string { return $this->_strategy; } @@ -619,7 +621,7 @@ public function getStrategy() * * @return string */ - public function getFinder() + public function getFinder(): string { return $this->_finder; } @@ -630,7 +632,7 @@ public function getFinder() * @param string $finder the finder name to use * @return $this */ - public function setFinder($finder) + public function setFinder(string $finder) { $this->_finder = $finder; @@ -644,7 +646,7 @@ public function setFinder($finder) * @param array $options List of options used for initialization * @return void */ - protected function _options(array $options) + protected function _options(array $options): void { } @@ -677,7 +679,7 @@ protected function _options(array $options) * @throws \RuntimeException if the query builder passed does not return a query * object */ - public function attachTo(Query $query, array $options = []) + public function attachTo(Query $query, array $options = []): void { $target = $this->getTarget(); $joinType = empty($options['joinType']) ? $this->getJoinType() : $options['joinType']; @@ -736,7 +738,7 @@ public function attachTo(Query $query, array $options = []) * @param array $options Options array containing the `negateMatch` key. * @return void */ - protected function _appendNotMatching($query, $options) + protected function _appendNotMatching(QueryInterface $query, array $options): void { $target = $this->_targetTable; if (!empty($options['negateMatch'])) { @@ -762,7 +764,7 @@ protected function _appendNotMatching($query, $options) * data shuld be nested in. Will use the default one if not provided. * @return array */ - public function transformRow($row, $nestKey, $joined, $targetProperty = null) + public function transformRow(array $row, string $nestKey, bool $joined, ?string $targetProperty = null): array { $sourceAlias = $this->getSource()->getAlias(); $nestKey = $nestKey ?: $this->_name; @@ -785,7 +787,7 @@ public function transformRow($row, $nestKey, $joined, $targetProperty = null) * with this association * @return array */ - public function defaultRowValue($row, $joined) + public function defaultRowValue(array $row, bool $joined): array { $sourceAlias = $this->getSource()->getAlias(); if (isset($row[$sourceAlias])) { @@ -806,7 +808,7 @@ public function defaultRowValue($row, $joined) * @see \Cake\ORM\Table::find() * @return \Cake\ORM\Query */ - public function find($type = null, array $options = []) + public function find($type = null, array $options = []): \Cake\ORM\Query { $type = $type ?: $this->getFinder(); list($type, $opts) = $this->_extractFinder($type); @@ -825,7 +827,7 @@ public function find($type = null, array $options = []) * @see \Cake\ORM\Table::exists() * @return bool */ - public function exists($conditions) + public function exists($conditions): bool { if ($this->_conditions) { $conditions = $this @@ -845,7 +847,7 @@ public function exists($conditions) * @see \Cake\ORM\Table::updateAll() * @return int Count Returns the affected rows. */ - public function updateAll($fields, $conditions) + public function updateAll(array $fields, $conditions): int { $target = $this->getTarget(); $expression = $target->query() @@ -864,7 +866,7 @@ public function updateAll($fields, $conditions) * @return int Returns the number of affected rows. * @see \Cake\ORM\Table::deleteAll() */ - public function deleteAll($conditions) + public function deleteAll($conditions): int { $target = $this->getTarget(); $expression = $target->query() @@ -882,7 +884,7 @@ public function deleteAll($conditions) * @param array $options The options containing the strategy to be used. * @return bool true if a list of keys will be required */ - public function requiresKeys(array $options = []) + public function requiresKeys(array $options = []): bool { $strategy = $options['strategy'] ?? $this->getStrategy(); @@ -896,7 +898,7 @@ public function requiresKeys(array $options = []) * @param \Cake\ORM\Query $query the query this association is attaching itself to * @return void */ - protected function _dispatchBeforeFind($query) + protected function _dispatchBeforeFind(Query $query): void { $query->triggerBeforeFind(); } @@ -910,7 +912,7 @@ protected function _dispatchBeforeFind($query) * @param array $options options passed to the method `attachTo` * @return void */ - protected function _appendFields($query, $surrogate, $options) + protected function _appendFields(Query $query, Query $surrogate, array $options): void { if ($query->getEagerLoader()->isAutoFieldsEnabled() === false) { return; @@ -950,7 +952,7 @@ protected function _appendFields($query, $surrogate, $options) * @param array $options options passed to the method `attachTo` * @return void */ - protected function _formatAssociationResults($query, $surrogate, $options) + protected function _formatAssociationResults(Query $query, Query $surrogate, array $options): void { $formatters = $surrogate->getResultFormatters(); @@ -995,7 +997,7 @@ protected function _formatAssociationResults($query, $surrogate, $options) * @param array $options options passed to the method `attachTo` * @return void */ - protected function _bindNewAssociations($query, $surrogate, $options) + protected function _bindNewAssociations(Query $query, Query $surrogate, array $options): void { $loader = $surrogate->getEagerLoader(); $contain = $loader->getContain(); @@ -1033,7 +1035,7 @@ protected function _bindNewAssociations($query, $surrogate, $options) * @throws \RuntimeException if the number of columns in the foreignKey do not * match the number of columns in the source table primaryKey */ - protected function _joinCondition($options) + protected function _joinCondition(array $options): array { $conditions = []; $tAlias = $this->_name; @@ -1085,7 +1087,7 @@ protected function _joinCondition($options) * and options as value. * @return array */ - protected function _extractFinder($finderData) + protected function _extractFinder($finderData): array { $finderData = (array)$finderData; @@ -1103,7 +1105,7 @@ protected function _extractFinder($finderData) * @param array $options Table options array. * @return string */ - protected function _getClassName($alias, array $options = []) + protected function _getClassName(string $alias, array $options = []): string { if (empty($options['className'])) { $options['className'] = Inflector::camelize($alias); @@ -1157,7 +1159,7 @@ public function __call($method, $argument) * * @return string Constant of either ONE_TO_ONE, MANY_TO_ONE, ONE_TO_MANY or MANY_TO_MANY. */ - abstract public function type(); + abstract public function type(): string; /** * Eager loads a list of records in the target table that are related to another @@ -1189,7 +1191,7 @@ abstract public function type(); * @param array $options The options for eager loading. * @return \Closure */ - abstract public function eagerLoader(array $options); + abstract public function eagerLoader(array $options): Closure; /** * Handles cascading a delete from an associated model. @@ -1201,7 +1203,7 @@ abstract public function eagerLoader(array $options); * @param array $options The options for the original delete. * @return bool Success */ - abstract public function cascadeDelete(EntityInterface $entity, array $options = []); + abstract public function cascadeDelete(EntityInterface $entity, array $options = []): bool; /** * Returns whether or not the passed table is the owning side for this @@ -1211,7 +1213,7 @@ abstract public function cascadeDelete(EntityInterface $entity, array $options = * @param \Cake\ORM\Table $side The potential Table with ownership * @return bool */ - abstract public function isOwningSide(Table $side); + abstract public function isOwningSide(Table $side): bool; /** * Extract the target's association data our from the passed entity and proxies diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 9c8284b9..74a9c247 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -1,4 +1,5 @@ _name); @@ -88,7 +90,7 @@ protected function _propertyName() * @param \Cake\ORM\Table $side The potential Table with ownership * @return bool */ - public function isOwningSide(Table $side) + public function isOwningSide(Table $side): bool { return $side === $this->getTarget(); } @@ -98,7 +100,7 @@ public function isOwningSide(Table $side) * * @return string */ - public function type() + public function type(): string { return self::MANY_TO_ONE; } @@ -146,7 +148,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) * @throws \RuntimeException if the number of columns in the foreignKey do not * match the number of columns in the target table primaryKey */ - protected function _joinCondition($options) + protected function _joinCondition(array $options): array { $conditions = []; $tAlias = $this->_name; @@ -183,7 +185,7 @@ protected function _joinCondition($options) * * @return \Closure */ - public function eagerLoader(array $options) + public function eagerLoader(array $options): Closure { $loader = new SelectLoader([ 'alias' => $this->getAlias(), diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 01c383fc..5b1c03c1 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1,4 +1,5 @@ getSource()->getAlias(); if (isset($row[$sourceAlias])) { @@ -252,7 +254,7 @@ public function defaultRowValue($row, $joined) * @param string|\Cake\ORM\Table|null $table Name or instance for the join table * @return \Cake\ORM\Table */ - public function junction($table = null) + public function junction($table = null): Table { if ($table === null && $this->_junctionTable) { return $this->_junctionTable; @@ -306,7 +308,7 @@ public function junction($table = null) * @param \Cake\ORM\Table $target The target table. * @return void */ - protected function _generateTargetAssociations($junction, $source, $target) + protected function _generateTargetAssociations(Table $junction, Table $source, Table $target): void { $junctionAlias = $junction->getAlias(); $sAlias = $source->getAlias(); @@ -345,7 +347,7 @@ protected function _generateTargetAssociations($junction, $source, $target) * @param \Cake\ORM\Table $source The source table. * @return void */ - protected function _generateSourceAssociations($junction, $source) + protected function _generateSourceAssociations(Table $junction, Table $source): void { $junctionAlias = $junction->getAlias(); if (!$source->hasAssociation($junctionAlias)) { @@ -373,7 +375,7 @@ protected function _generateSourceAssociations($junction, $source) * @param \Cake\ORM\Table $target The target table. * @return void */ - protected function _generateJunctionAssociations($junction, $source, $target) + protected function _generateJunctionAssociations(Table $junction, Table $source, Table $target): void { $tAlias = $target->getAlias(); $sAlias = $source->getAlias(); @@ -409,7 +411,7 @@ protected function _generateJunctionAssociations($junction, $source, $target) * @param array $options Any extra options or overrides to be taken in account * @return void */ - public function attachTo(Query $query, array $options = []) + public function attachTo(Query $query, array $options = []): void { if (!empty($options['negateMatch'])) { $this->_appendNotMatching($query, $options); @@ -448,7 +450,7 @@ public function attachTo(Query $query, array $options = []) /** * {@inheritDoc} */ - protected function _appendNotMatching($query, $options) + protected function _appendNotMatching(QueryInterface $query, array $options): void { if (empty($options['negateMatch'])) { return; @@ -497,7 +499,7 @@ protected function _appendNotMatching($query, $options) * * @return string */ - public function type() + public function type(): string { return self::MANY_TO_MANY; } @@ -506,11 +508,11 @@ public function type() * Return false as join conditions are defined in the junction table * * @param array $options list of options passed to attachTo method - * @return bool false + * @return array */ - protected function _joinCondition($options) + protected function _joinCondition(array $options): array { - return false; + return []; } /** @@ -518,7 +520,7 @@ protected function _joinCondition($options) * * @return \Closure */ - public function eagerLoader(array $options) + public function eagerLoader(array $options): Closure { $name = $this->_junctionAssociationName(); $loader = new SelectWithPivotLoader([ @@ -549,7 +551,7 @@ public function eagerLoader(array $options) * @param array $options The options for the original delete. * @return bool Success. */ - public function cascadeDelete(EntityInterface $entity, array $options = []) + public function cascadeDelete(EntityInterface $entity, array $options = []): bool { if (!$this->getDependent()) { return true; @@ -586,7 +588,7 @@ public function cascadeDelete(EntityInterface $entity, array $options = []) * @param \Cake\ORM\Table $side The potential Table with ownership * @return bool */ - public function isOwningSide(Table $side) + public function isOwningSide(Table $side): bool { return true; } @@ -598,7 +600,7 @@ public function isOwningSide(Table $side) * @throws \InvalidArgumentException if an invalid strategy name is passed * @return $this */ - public function setSaveStrategy($strategy) + public function setSaveStrategy(string $strategy): self { if (!in_array($strategy, [self::SAVE_APPEND, self::SAVE_REPLACE])) { $msg = sprintf('Invalid save strategy "%s"', $strategy); @@ -615,7 +617,7 @@ public function setSaveStrategy($strategy) * * @return string the strategy to be used for saving */ - public function getSaveStrategy() + public function getSaveStrategy(): string { return $this->_saveStrategy; } @@ -749,7 +751,7 @@ protected function _saveTarget(EntityInterface $parentEntity, $entities, $option * @param array $options list of options accepted by `Table::save()` * @return bool success */ - protected function _saveLinks(EntityInterface $sourceEntity, $targetEntities, $options) + protected function _saveLinks(EntityInterface $sourceEntity, array $targetEntities, array $options): bool { $target = $this->getTarget(); $junction = $this->junction(); @@ -825,7 +827,7 @@ protected function _saveLinks(EntityInterface $sourceEntity, $targetEntities, $o * detected to not be already persisted * @return bool true on success, false otherwise */ - public function link(EntityInterface $sourceEntity, array $targetEntities, array $options = []) + public function link(EntityInterface $sourceEntity, array $targetEntities, array $options = []): bool { $this->_checkPersistenceStatus($sourceEntity, $targetEntities); $property = $this->getProperty(); @@ -876,7 +878,7 @@ function () use ($sourceEntity, $targetEntities, $options) { * any of them is lacking a primary key value. * @return bool Success */ - public function unlink(EntityInterface $sourceEntity, array $targetEntities, $options = []) + public function unlink(EntityInterface $sourceEntity, array $targetEntities, $options = []): bool { if (is_bool($options)) { $options = [ @@ -890,7 +892,7 @@ public function unlink(EntityInterface $sourceEntity, array $targetEntities, $op $property = $this->getProperty(); $this->junction()->getConnection()->transactional( - function () use ($sourceEntity, $targetEntities, $options) { + function () use ($sourceEntity, $targetEntities, $options): void { $links = $this->_collectJointEntities($sourceEntity, $targetEntities); foreach ($links as $entity) { $this->_junctionTable->delete($entity, $options); @@ -937,7 +939,7 @@ public function setConditions($conditions) * @param string|\Cake\ORM\Table $through Name of the Table instance or the instance itself * @return $this */ - public function setThrough($through) + public function setThrough($through): self { $this->_through = $through; @@ -992,7 +994,7 @@ protected function targetConditions() * * @return array */ - protected function junctionConditions() + protected function junctionConditions(): array { if ($this->_junctionConditions !== null) { return $this->_junctionConditions; @@ -1032,7 +1034,7 @@ protected function junctionConditions() * @see \Cake\ORM\Table::find() * @return \Cake\ORM\Query */ - public function find($type = null, array $options = []) + public function find($type = null, array $options = []): Query { $type = $type ?: $this->getFinder(); list($type, $opts) = $this->_extractFinder($type); @@ -1061,7 +1063,7 @@ public function find($type = null, array $options = []) * @param string|array $conditions The query conditions to use. * @return \Cake\ORM\Query The modified query. */ - protected function _appendJunctionJoin($query, $conditions) + protected function _appendJunctionJoin(Query $query, $conditions): Query { $name = $this->_junctionAssociationName(); $joins = $query->clause('join'); @@ -1130,7 +1132,7 @@ protected function _appendJunctionJoin($query, $conditions) * any of them is lacking a primary key value * @return bool success */ - public function replaceLinks(EntityInterface $sourceEntity, array $targetEntities, array $options = []) + public function replaceLinks(EntityInterface $sourceEntity, array $targetEntities, array $options = []): bool { $bindingKey = (array)$this->getBindingKey(); $primaryValue = $sourceEntity->extract($bindingKey); @@ -1191,7 +1193,7 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { * @param array $options list of options accepted by `Table::delete()` * @return array */ - protected function _diffLinks($existing, $jointEntities, $targetEntities, $options = []) + protected function _diffLinks(Query $existing, array $jointEntities, array $targetEntities, array $options = []): array { $junction = $this->junction(); $target = $this->getTarget(); @@ -1257,7 +1259,7 @@ protected function _diffLinks($existing, $jointEntities, $targetEntities, $optio * @return bool * @throws \InvalidArgumentException */ - protected function _checkPersistenceStatus($sourceEntity, array $targetEntities) + protected function _checkPersistenceStatus(EntityInterface $sourceEntity, array $targetEntities): bool { if ($sourceEntity->isNew()) { $error = 'Source entity needs to be persisted before links can be created or removed.'; @@ -1286,7 +1288,7 @@ protected function _checkPersistenceStatus($sourceEntity, array $targetEntities) * key value * @return array */ - protected function _collectJointEntities($sourceEntity, $targetEntities) + protected function _collectJointEntities(EntityInterface $sourceEntity, array $targetEntities): array { $target = $this->getTarget(); $source = $this->getSource(); @@ -1342,7 +1344,7 @@ protected function _collectJointEntities($sourceEntity, $targetEntities) * * @return string */ - protected function _junctionAssociationName() + protected function _junctionAssociationName(): string { if (!$this->_junctionAssociationName) { $this->_junctionAssociationName = $this->getTarget() @@ -1361,7 +1363,7 @@ protected function _junctionAssociationName() * @param string|null $name The name of the junction table. * @return string */ - protected function _junctionTableName($name = null) + protected function _junctionTableName(?string $name = null): string { if ($name === null) { if (empty($this->_junctionTableName)) { @@ -1385,7 +1387,7 @@ protected function _junctionTableName($name = null) * @param array $opts original list of options passed in constructor * @return void */ - protected function _options(array $opts) + protected function _options(array $opts): void { if (!empty($opts['targetForeignKey'])) { $this->setTargetForeignKey($opts['targetForeignKey']); diff --git a/Association/DependentDeleteHelper.php b/Association/DependentDeleteHelper.php index 7e395c56..8b405960 100644 --- a/Association/DependentDeleteHelper.php +++ b/Association/DependentDeleteHelper.php @@ -1,4 +1,5 @@ getDependent()) { return true; diff --git a/Association/HasMany.php b/Association/HasMany.php index 6dbe2872..f604c557 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -1,4 +1,5 @@ getSource(); } @@ -106,7 +108,7 @@ public function isOwningSide(Table $side) * @throws \InvalidArgumentException if an invalid strategy name is passed * @return $this */ - public function setSaveStrategy($strategy) + public function setSaveStrategy(string $strategy) { if (!in_array($strategy, [self::SAVE_APPEND, self::SAVE_REPLACE])) { $msg = sprintf('Invalid save strategy "%s"', $strategy); @@ -123,7 +125,7 @@ public function setSaveStrategy($strategy) * * @return string the strategy to be used for saving */ - public function getSaveStrategy() + public function getSaveStrategy(): string { return $this->_saveStrategy; } @@ -197,7 +199,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) * @param array $options list of options accepted by `Table::save()`. * @return bool `true` on success, `false` otherwise. */ - protected function _saveTarget(array $foreignKeyReference, EntityInterface $parentEntity, $entities, array $options) + protected function _saveTarget(array $foreignKeyReference, EntityInterface $parentEntity, $entities, array $options): bool { $foreignKey = array_keys($foreignKeyReference); $table = $this->getTarget(); @@ -259,7 +261,7 @@ protected function _saveTarget(array $foreignKeyReference, EntityInterface $pare * @param array $options list of options to be passed to the internal `save` call * @return bool true on success, false otherwise */ - public function link(EntityInterface $sourceEntity, array $targetEntities, array $options = []) + public function link(EntityInterface $sourceEntity, array $targetEntities, array $options = []): bool { $saveStrategy = $this->getSaveStrategy(); $this->setSaveStrategy(self::SAVE_APPEND); @@ -324,12 +326,13 @@ public function link(EntityInterface $sourceEntity, array $targetEntities, array * this association * @param array $targetEntities list of entities persisted in the target table for * this association - * @param array $options list of options to be passed to the internal `delete` call + * @param array|bool $options list of options to be passed to the internal `delete` call. + * If boolean it will be used a value for "cleanProperty" option. * @throws \InvalidArgumentException if non persisted entities are passed or if * any of them is lacking a primary key value * @return void */ - public function unlink(EntityInterface $sourceEntity, array $targetEntities, $options = []) + public function unlink(EntityInterface $sourceEntity, array $targetEntities, $options = []): void { if (is_bool($options)) { $options = [ @@ -417,7 +420,7 @@ function ($assoc) use ($targetEntities) { * any of them is lacking a primary key value * @return bool success */ - public function replace(EntityInterface $sourceEntity, array $targetEntities, array $options = []) + public function replace(EntityInterface $sourceEntity, array $targetEntities, array $options = []): bool { $property = $this->getProperty(); $sourceEntity->set($property, $targetEntities); @@ -446,7 +449,7 @@ public function replace(EntityInterface $sourceEntity, array $targetEntities, ar * @param array $options list of options accepted by `Table::delete()` * @return bool success */ - protected function _unlinkAssociated(array $foreignKeyReference, EntityInterface $entity, Table $target, array $remainingEntities = [], array $options = []) + protected function _unlinkAssociated(array $foreignKeyReference, EntityInterface $entity, Table $target, array $remainingEntities = [], array $options = []): bool { $primaryKey = (array)$target->getPrimaryKey(); $exclusions = new Collection($remainingEntities); @@ -486,14 +489,14 @@ function ($v) { * @param array $options list of options accepted by `Table::delete()` * @return bool success */ - protected function _unlink(array $foreignKey, Table $target, array $conditions = [], array $options = []) + protected function _unlink(array $foreignKey, Table $target, array $conditions = [], array $options = []): bool { $mustBeDependent = (!$this->_foreignKeyAcceptsNull($target, $foreignKey) || $this->getDependent()); if ($mustBeDependent) { if ($this->_cascadeCallbacks) { $conditions = new QueryExpression($conditions); - $conditions->traverse(function ($entry) use ($target) { + $conditions->traverse(function ($entry) use ($target): void { if ($entry instanceof FieldInterface) { $entry->setField($target->aliasField($entry->getField())); } @@ -527,7 +530,7 @@ protected function _unlink(array $foreignKey, Table $target, array $conditions = * @param array $properties the list of fields that compose the foreign key * @return bool */ - protected function _foreignKeyAcceptsNull(Table $table, array $properties) + protected function _foreignKeyAcceptsNull(Table $table, array $properties): bool { return !in_array( false, @@ -545,7 +548,7 @@ function ($prop) use ($table) { * * @return string */ - public function type() + public function type(): string { return self::ONE_TO_MANY; } @@ -557,7 +560,7 @@ public function type() * @return bool if the 'matching' key in $option is true then this function * will return true, false otherwise */ - public function canBeJoined(array $options = []) + public function canBeJoined(array $options = []): bool { return !empty($options['matching']); } @@ -565,7 +568,7 @@ public function canBeJoined(array $options = []) /** * Gets the name of the field representing the foreign key to the source table. * - * @return string + * @return string|array */ public function getForeignKey() { @@ -602,7 +605,7 @@ public function getSort() /** * {@inheritDoc} */ - public function defaultRowValue($row, $joined) + public function defaultRowValue(array $row, bool $joined): array { $sourceAlias = $this->getSource()->getAlias(); if (isset($row[$sourceAlias])) { @@ -618,7 +621,7 @@ public function defaultRowValue($row, $joined) * @param array $opts original list of options passed in constructor * @return void */ - protected function _options(array $opts) + protected function _options(array $opts): void { if (!empty($opts['saveStrategy'])) { $this->setSaveStrategy($opts['saveStrategy']); @@ -633,7 +636,7 @@ protected function _options(array $opts) * * @return \Closure */ - public function eagerLoader(array $options) + public function eagerLoader(array $options): Closure { $loader = new SelectLoader([ 'alias' => $this->getAlias(), @@ -653,7 +656,7 @@ public function eagerLoader(array $options) /** * {@inheritDoc} */ - public function cascadeDelete(EntityInterface $entity, array $options = []) + public function cascadeDelete(EntityInterface $entity, array $options = []): bool { $helper = new DependentDeleteHelper(); diff --git a/Association/HasOne.php b/Association/HasOne.php index ce680c55..e438e5a4 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -1,4 +1,5 @@ _name); @@ -72,7 +74,7 @@ protected function _propertyName() * @param \Cake\ORM\Table $side The potential Table with ownership * @return bool */ - public function isOwningSide(Table $side) + public function isOwningSide(Table $side): bool { return $side === $this->getSource(); } @@ -82,7 +84,7 @@ public function isOwningSide(Table $side) * * @return string */ - public function type() + public function type(): string { return self::ONE_TO_ONE; } @@ -126,7 +128,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) * * @return \Closure */ - public function eagerLoader(array $options) + public function eagerLoader(array $options): Closure { $loader = new SelectLoader([ 'alias' => $this->getAlias(), @@ -145,7 +147,7 @@ public function eagerLoader(array $options) /** * {@inheritDoc} */ - public function cascadeDelete(EntityInterface $entity, array $options = []) + public function cascadeDelete(EntityInterface $entity, array $options = []): bool { $helper = new DependentDeleteHelper(); diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 2efd9b55..f84f887f 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -1,4 +1,5 @@ _defaultOptions(); $fetchQuery = $this->_buildQuery($options); @@ -131,7 +134,7 @@ public function buildEagerLoader(array $options) * * @return array */ - protected function _defaultOptions() + protected function _defaultOptions(): array { return [ 'foreignKey' => $this->foreignKey, @@ -151,7 +154,7 @@ protected function _defaultOptions() * @return \Cake\ORM\Query * @throws \InvalidArgumentException When a key is required for associations but not selected. */ - protected function _buildQuery($options) + protected function _buildQuery(array $options): Query { $key = $this->_linkField($options); $filter = $options['keys']; @@ -215,7 +218,7 @@ protected function _buildQuery($options) * and options as value. * @return array */ - protected function _extractFinder($finderData) + protected function _extractFinder($finderData): array { $finderData = (array)$finderData; @@ -236,7 +239,7 @@ protected function _extractFinder($finderData) * @return void * @throws \InvalidArgumentException */ - protected function _assertFieldsPresent($fetchQuery, $key) + protected function _assertFieldsPresent(Query $fetchQuery, array $key): void { $select = $fetchQuery->aliasFields($fetchQuery->clause('select')); if (empty($select)) { @@ -279,7 +282,7 @@ protected function _assertFieldsPresent($fetchQuery, $key) * @param \Cake\ORM\Query $subquery The Subquery to use for filtering * @return \Cake\ORM\Query */ - protected function _addFilteringJoin($query, $key, $subquery) + protected function _addFilteringJoin(Query $query, $key, $subquery): Query { $filter = []; $aliasedTable = $this->sourceAlias; @@ -317,7 +320,7 @@ protected function _addFilteringJoin($query, $key, $subquery) * @param mixed $filter The value that should be used to match for $key * @return \Cake\ORM\Query */ - protected function _addFilteringCondition($query, $key, $filter) + protected function _addFilteringCondition(Query $query, $key, $filter): Query { if (is_array($key)) { $conditions = $this->_createTupleCondition($query, $key, $filter, 'IN'); @@ -338,7 +341,7 @@ protected function _addFilteringCondition($query, $key, $filter) * @param string $operator The operator for comparing the tuples * @return \Cake\Database\Expression\TupleComparison */ - protected function _createTupleCondition($query, $keys, $filter, $operator) + protected function _createTupleCondition(Query $query, array $keys, $filter, $operator): TupleComparison { $types = []; $defaults = $query->getDefaultTypes(); @@ -358,7 +361,7 @@ protected function _createTupleCondition($query, $keys, $filter, $operator) * @param array $options The options for getting the link field. * @return string|array */ - protected function _linkField($options) + protected function _linkField(array $options) { $links = []; $name = $this->alias; @@ -392,7 +395,7 @@ protected function _linkField($options) * @param \Cake\ORM\Query $query the original query used to load source records * @return \Cake\ORM\Query */ - protected function _buildSubquery($query) + protected function _buildSubquery(Query $query): Query { $filterQuery = clone $query; $filterQuery->enableAutoFields(false); @@ -423,7 +426,7 @@ protected function _buildSubquery($query) * @param \Cake\ORM\Query $query The query to get fields from. * @return array The list of fields for the subquery. */ - protected function _subqueryFields($query) + protected function _subqueryFields(Query $query): array { $keys = (array)$this->bindingKey; @@ -437,7 +440,7 @@ protected function _subqueryFields($query) $order = $query->clause('order'); if ($order) { $columns = $query->clause('select'); - $order->iterateParts(function ($direction, $field) use (&$fields, $columns) { + $order->iterateParts(function ($direction, $field) use (&$fields, $columns): void { if (isset($columns[$field])) { $fields[$field] = $columns[$field]; } @@ -455,7 +458,7 @@ protected function _subqueryFields($query) * @param array $options The options passed to the eager loader * @return array */ - protected function _buildResultMap($fetchQuery, $options) + protected function _buildResultMap(Query $fetchQuery, array $options): array { $resultMap = []; $singleResult = in_array($this->associationType, [Association::MANY_TO_ONE, Association::ONE_TO_ONE]); @@ -489,7 +492,7 @@ protected function _buildResultMap($fetchQuery, $options) * @param array $options The options passed to the eagerLoader method * @return \Closure */ - protected function _resultInjector($fetchQuery, $resultMap, $options) + protected function _resultInjector(Query $fetchQuery, array $resultMap, array $options): Closure { $keys = $this->associationType === Association::MANY_TO_ONE ? $this->foreignKey : @@ -527,7 +530,7 @@ protected function _resultInjector($fetchQuery, $resultMap, $options) * @param string $nestKey The key under which results should be nested * @return \Closure */ - protected function _multiKeysInjector($resultMap, $sourceKeys, $nestKey) + protected function _multiKeysInjector(array $resultMap, array $sourceKeys, string $nestKey): Closure { return function ($row) use ($resultMap, $sourceKeys, $nestKey) { $values = []; diff --git a/Association/Loader/SelectWithPivotLoader.php b/Association/Loader/SelectWithPivotLoader.php index 9564bc09..f979837e 100644 --- a/Association/Loader/SelectWithPivotLoader.php +++ b/Association/Loader/SelectWithPivotLoader.php @@ -1,4 +1,5 @@ junctionAssociationName; $assoc = $this->junctionAssoc; @@ -134,7 +136,7 @@ protected function _buildQuery($options) * @param array $options the options to use for getting the link field. * @return array|string */ - protected function _linkField($options) + protected function _linkField(array $options) { $links = []; $name = $this->junctionAssociationName; @@ -159,7 +161,7 @@ protected function _linkField($options) * @return array * @throws \RuntimeException when the association property is not part of the results set. */ - protected function _buildResultMap($fetchQuery, $options) + protected function _buildResultMap(Query $fetchQuery, array $options): array { $resultMap = []; $key = (array)$options['foreignKey']; diff --git a/AssociationCollection.php b/AssociationCollection.php index c95f7613..ca882efc 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -1,4 +1,5 @@ $this->getTableLocator(), @@ -101,7 +102,7 @@ public function load($className, $associated, array $options = []) * @param string $alias The association alias to get. * @return \Cake\ORM\Association|null Either the association or null. */ - public function get($alias) + public function get(string $alias): ?Association { $alias = strtolower($alias); if (isset($this->_items[$alias])) { @@ -117,7 +118,7 @@ public function get($alias) * @param string $prop The property to find an association by. * @return \Cake\ORM\Association|null Either the association or null. */ - public function getByProperty($prop) + public function getByProperty(string $prop): ?Association { foreach ($this->_items as $assoc) { if ($assoc->getProperty() === $prop) { @@ -134,7 +135,7 @@ public function getByProperty($prop) * @param string $alias The association alias to get. * @return bool Whether or not the association exists. */ - public function has($alias) + public function has(string $alias): bool { return isset($this->_items[strtolower($alias)]); } @@ -144,7 +145,7 @@ public function has($alias) * * @return array */ - public function keys() + public function keys(): array { return array_keys($this->_items); } @@ -157,7 +158,7 @@ public function keys() * @return array An array of Association objects. * @since 3.5.3 */ - public function getByType($class) + public function getByType($class): array { $class = array_map('strtolower', (array)$class); @@ -178,7 +179,7 @@ public function getByType($class) * @param string $alias The alias name. * @return void */ - public function remove($alias) + public function remove(string $alias): void { unset($this->_items[strtolower($alias)]); } @@ -190,7 +191,7 @@ public function remove($alias) * * @return void */ - public function removeAll() + public function removeAll(): void { foreach ($this->_items as $alias => $object) { $this->remove($alias); @@ -210,7 +211,7 @@ public function removeAll() * @param array $options The options for the save operation. * @return bool Success */ - public function saveParents(Table $table, EntityInterface $entity, $associations, array $options = []) + public function saveParents(Table $table, EntityInterface $entity, array $associations, array $options = []): bool { if (empty($associations)) { return true; @@ -232,7 +233,7 @@ public function saveParents(Table $table, EntityInterface $entity, $associations * @param array $options The options for the save operation. * @return bool Success */ - public function saveChildren(Table $table, EntityInterface $entity, array $associations, array $options) + public function saveChildren(Table $table, EntityInterface $entity, array $associations, array $options): bool { if (empty($associations)) { return true; @@ -253,7 +254,7 @@ public function saveChildren(Table $table, EntityInterface $entity, array $assoc * @return bool Success * @throws \InvalidArgumentException When an unknown alias is used. */ - protected function _saveAssociations($table, $entity, $associations, $options, $owningSide) + protected function _saveAssociations(Table $table, EntityInterface $entity, array $associations, array $options, bool $owningSide): bool { unset($options['associated']); foreach ($associations as $alias => $nested) { @@ -290,7 +291,7 @@ protected function _saveAssociations($table, $entity, $associations, $options, $ * @param array $options Original options * @return bool Success */ - protected function _save($association, $entity, $nested, $options) + protected function _save(Association $association, EntityInterface $entity, array $nested, array $options): bool { if (!$entity->isDirty($association->getProperty())) { return true; @@ -310,7 +311,7 @@ protected function _save($association, $entity, $nested, $options) * @param array $options The options used in the delete operation. * @return void */ - public function cascadeDelete(EntityInterface $entity, array $options) + public function cascadeDelete(EntityInterface $entity, array $options): void { $noCascade = $this->_getNoCascadeItems($entity, $options); foreach ($noCascade as $assoc) { @@ -325,7 +326,7 @@ public function cascadeDelete(EntityInterface $entity, array $options) * @param array $options The options used in the delete operation. * @return \Cake\ORM\Association[] */ - protected function _getNoCascadeItems($entity, $options) + protected function _getNoCascadeItems(EntityInterface $entity, array $options) { $noCascade = []; foreach ($this->_items as $assoc) { @@ -347,7 +348,7 @@ protected function _getNoCascadeItems($entity, $options) * @param bool|array $keys the list of association names to normalize * @return array */ - public function normalizeKeys($keys) + public function normalizeKeys($keys): array { if ($keys === true) { $keys = $this->keys(); @@ -365,7 +366,7 @@ public function normalizeKeys($keys) * * @return \ArrayIterator */ - public function getIterator() + public function getIterator(): ArrayIterator { return new ArrayIterator($this->_items); } diff --git a/AssociationsNormalizerTrait.php b/AssociationsNormalizerTrait.php index dc262f11..2e5a67f0 100644 --- a/AssociationsNormalizerTrait.php +++ b/AssociationsNormalizerTrait.php @@ -1,4 +1,5 @@ $options) { From 67329905bd54376ba2622269eac07acbf43de67b Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 23 Jul 2018 00:24:02 +0530 Subject: [PATCH 1222/2059] Add strict typing to behaviors. Refs #11935 --- Behavior.php | 15 ++++---- Behavior/CounterCacheBehavior.php | 14 ++++---- Behavior/TimestampBehavior.php | 11 +++--- Behavior/Translate/TranslateTrait.php | 3 +- Behavior/TranslateBehavior.php | 36 ++++++++++--------- Behavior/TreeBehavior.php | 51 ++++++++++++++------------- 6 files changed, 69 insertions(+), 61 deletions(-) diff --git a/Behavior.php b/Behavior.php index 37f4270f..3693a712 100644 --- a/Behavior.php +++ b/Behavior.php @@ -1,4 +1,5 @@ _table; } @@ -197,7 +198,7 @@ public function getTable() * @param array $config The customized method mappings. * @return array A de-duped list of config data. */ - protected function _resolveMethodAliases($key, $defaults, $config) + protected function _resolveMethodAliases(string $key, array $defaults, array $config): array { if (!isset($defaults[$key], $config[$key])) { return $config; @@ -230,7 +231,7 @@ protected function _resolveMethodAliases($key, $defaults, $config) * @return void * @throws \Cake\Core\Exception\Exception if config are invalid */ - public function verifyConfig() + public function verifyConfig(): void { $keys = ['implementedFinders', 'implementedMethods']; foreach ($keys as $key) { @@ -316,7 +317,7 @@ public function implementedEvents(): array * @return array * @throws \ReflectionException */ - public function implementedFinders() + public function implementedFinders(): array { $methods = $this->getConfig('implementedFinders'); if (isset($methods)) { @@ -348,7 +349,7 @@ public function implementedFinders() * @return array * @throws \ReflectionException */ - public function implementedMethods() + public function implementedMethods(): array { $methods = $this->getConfig('implementedMethods'); if (isset($methods)) { @@ -368,7 +369,7 @@ public function implementedMethods() * @return array * @throws \ReflectionException */ - protected function _reflectionCache() + protected function _reflectionCache(): array { $class = get_class($this); if (isset(self::$_reflectionCache[$class])) { diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 2271cc98..630abfeb 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -1,4 +1,5 @@ _config as $assoc => $settings) { $assoc = $this->_table->getAssociation($assoc); @@ -208,7 +210,7 @@ protected function _processAssociations(EventInterface $event, EntityInterface $ * @return void * @throws \RuntimeException If invalid callable is passed. */ - protected function _processAssociation(EventInterface $event, EntityInterface $entity, Association $assoc, array $settings) + protected function _processAssociation(EventInterface $event, EntityInterface $entity, Association $assoc, array $settings): void { $foreignKeys = (array)$assoc->getForeignKey(); $primaryKeys = (array)$assoc->getBindingKey(); @@ -267,7 +269,7 @@ protected function _processAssociation(EventInterface $event, EntityInterface $e * @param array $conditions Additional conditions given to the query * @return int The number of relations matching the given config and conditions */ - protected function _getCount(array $config, array $conditions) + protected function _getCount(array $config, array $conditions): int { $finder = 'all'; if (!empty($config['finder'])) { diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index cdf0117d..22e665b0 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -1,4 +1,5 @@ setConfig('events', $config['events'], false); @@ -91,7 +92,7 @@ public function initialize(array $config) * @return bool Returns true irrespective of the behavior logic, the save will not be prevented. * @throws \UnexpectedValueException When the value for an event is not 'always', 'new' or 'existing' */ - public function handleEvent(EventInterface $event, EntityInterface $entity) + public function handleEvent(EventInterface $event, EntityInterface $entity): bool { $eventName = $event->getName(); $events = $this->_config['events']; @@ -139,7 +140,7 @@ public function implementedEvents(): array * @param bool $refreshTimestamp If true timestamp is refreshed. * @return \DateTime */ - public function timestamp(?DateTime $ts = null, $refreshTimestamp = false) + public function timestamp(?DateTime $ts = null, bool $refreshTimestamp = false): DateTime { if ($ts) { if ($this->_config['refreshTimestamp']) { @@ -164,7 +165,7 @@ public function timestamp(?DateTime $ts = null, $refreshTimestamp = false) * @param string $eventName Event name. * @return bool true if a field is updated, false if no action performed */ - public function touch(EntityInterface $entity, $eventName = 'Model.beforeSave') + public function touch(EntityInterface $entity, string $eventName = 'Model.beforeSave'): bool { $events = $this->_config['events']; if (empty($events[$eventName])) { @@ -193,7 +194,7 @@ public function touch(EntityInterface $entity, $eventName = 'Model.beforeSave') * @param bool $refreshTimestamp Whether to refresh timestamp. * @return void */ - protected function _updateField($entity, $field, $refreshTimestamp) + protected function _updateField(EntityInterface $entity, string $field, bool $refreshTimestamp): void { if ($entity->isDirty($field)) { return; diff --git a/Behavior/Translate/TranslateTrait.php b/Behavior/Translate/TranslateTrait.php index 9ce6126e..4aecbb88 100644 --- a/Behavior/Translate/TranslateTrait.php +++ b/Behavior/Translate/TranslateTrait.php @@ -1,4 +1,5 @@ get('_locale')) { return $this; diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index e55ca6fd..ded89c1c 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -1,4 +1,5 @@ _translationTable = $this->getTableLocator()->get($this->_config['translationTable']); @@ -145,7 +147,7 @@ public function initialize(array $config) * * @return void */ - public function setupFieldAssociations($fields, $table, $model, $strategy) + public function setupFieldAssociations(array $fields, string $table, string $model, string $strategy): void { $targetAlias = $this->_translationTable->getAlias(); $alias = $this->_table->getAlias(); @@ -207,7 +209,7 @@ public function setupFieldAssociations($fields, $table, $model, $strategy) * @param \ArrayObject $options The options for the query * @return void */ - public function beforeFind(EventInterface $event, Query $query, $options) + public function beforeFind(EventInterface $event, Query $query, ArrayObject $options): void { $locale = $this->getLocale(); @@ -271,7 +273,7 @@ public function beforeFind(EventInterface $event, Query $query, $options) * @param \ArrayObject $options the options passed to the save method * @return void */ - public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options) + public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void { $locale = $entity->get('_locale') ?: $this->getLocale(); $newOptions = [$this->_translationTable->getAlias() => ['validate' => false]]; @@ -366,7 +368,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved * @return void */ - public function afterSave(EventInterface $event, EntityInterface $entity) + public function afterSave(EventInterface $event, EntityInterface $entity): void { $entity->unsetProperty('_i18n'); } @@ -433,7 +435,7 @@ public function buildMarshalMap($marshaller, $map, $options) * @link https://book.cakephp.org/3.0/en/orm/behaviors/translate.html#retrieving-one-language-without-using-i18n-locale * @link https://book.cakephp.org/3.0/en/orm/behaviors/translate.html#saving-in-another-language */ - public function setLocale($locale) + public function setLocale(?string $locale) { $this->_locale = $locale; @@ -450,7 +452,7 @@ public function setLocale($locale) * @see \Cake\I18n\I18n::getLocale() * @see \Cake\ORM\Behavior\TranslateBehavior::setLocale() */ - public function getLocale() + public function getLocale(): string { return $this->_locale ?: I18n::getLocale(); } @@ -465,7 +467,7 @@ public function getLocale() * @param string $field Field name to be aliased. * @return string */ - public function translationField($field) + public function translationField(string $field): string { $table = $this->_table; if ($this->getLocale() === $this->getConfig('defaultLocale')) { @@ -502,7 +504,7 @@ public function translationField($field) * @param array $options Options * @return \Cake\ORM\Query */ - public function findTranslations(Query $query, array $options) + public function findTranslations(Query $query, array $options): Query { $locales = $options['locales'] ?? []; $targetAlias = $this->_translationTable->getAlias(); @@ -530,7 +532,7 @@ public function findTranslations(Query $query, array $options) * @param \Cake\ORM\Table $table The table class to get a reference name for. * @return string */ - protected function _referenceName(Table $table) + protected function _referenceName(Table $table): string { $name = namespaceSplit(get_class($table)); $name = substr(end($name), 0, -5); @@ -546,11 +548,11 @@ protected function _referenceName(Table $table) * Modifies the results from a table find in order to merge the translated fields * into each entity for a given locale. * - * @param \Cake\Datasource\ResultSetInterface $results Results to map. + * @param \Cake\Datasource\ResultSetInterface|\Cake\Collection\CollectionInterface $results Results to map. * @param string $locale Locale string * @return \Cake\Collection\CollectionInterface */ - protected function _rowMapper($results, $locale) + protected function _rowMapper($results, string $locale): CollectionInterface { return $results->map(function ($row) use ($locale) { if ($row === null) { @@ -589,10 +591,10 @@ protected function _rowMapper($results, $locale) * Modifies the results from a table find in order to merge full translation records * into each entity under the `_translations` key * - * @param \Cake\Datasource\ResultSetInterface $results Results to modify. + * @param \Cake\Datasource\ResultSetInterface|\Cake\Collection\CollectionInterface $results Results to modify. * @return \Cake\Collection\CollectionInterface */ - public function groupTranslations($results) + public function groupTranslations($results): CollectionInterface { return $results->map(function ($row) { if (!$row instanceof EntityInterface) { @@ -632,7 +634,7 @@ public function groupTranslations($results) * @param \Cake\Datasource\EntityInterface $entity Entity * @return void */ - protected function _bundleTranslatedFields($entity) + protected function _bundleTranslatedFields(EntityInterface $entity): void { $translations = (array)$entity->get('_translations'); @@ -686,7 +688,7 @@ protected function _bundleTranslatedFields($entity) * @param \Cake\Datasource\EntityInterface $entity The entity to check for empty translations fields inside. * @return void */ - protected function _unsetEmptyFields(EntityInterface $entity) + protected function _unsetEmptyFields(EntityInterface $entity): void { $translations = (array)$entity->get('_translations'); foreach ($translations as $locale => $translation) { @@ -720,7 +722,7 @@ protected function _unsetEmptyFields(EntityInterface $entity) * @param array $ruleSet an array of arary of conditions to be used for finding each * @return array */ - protected function _findExistingTranslations($ruleSet) + protected function _findExistingTranslations(array $ruleSet): array { $association = $this->_table->getAssociation($this->_translationTable->getAlias()); diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 5f0a4f7f..45a12cd1 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -1,4 +1,5 @@ _config['leftField'] = new IdentifierExpression($this->_config['left']); $this->_config['rightField'] = new IdentifierExpression($this->_config['right']); @@ -93,7 +94,7 @@ public function initialize(array $config) * @return void * @throws \RuntimeException if the parent to set for the node is invalid */ - public function beforeSave(EventInterface $event, EntityInterface $entity) + public function beforeSave(EventInterface $event, EntityInterface $entity): void { $isNew = $entity->isNew(); $config = $this->getConfig(); @@ -161,7 +162,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity) * @param \Cake\Datasource\EntityInterface $entity the entity that is going to be saved * @return void */ - public function afterSave(EventInterface $event, EntityInterface $entity) + public function afterSave(EventInterface $event, EntityInterface $entity): void { if (!$this->_config['level'] || $entity->isNew()) { return; @@ -176,7 +177,7 @@ public function afterSave(EventInterface $event, EntityInterface $entity) * @param \Cake\Datasource\EntityInterface $entity The entity whose descendants need to be updated. * @return void */ - protected function _setChildrenLevel($entity) + protected function _setChildrenLevel(EntityInterface $entity): void { $config = $this->getConfig(); @@ -214,7 +215,7 @@ protected function _setChildrenLevel($entity) * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved * @return void */ - public function beforeDelete(EventInterface $event, EntityInterface $entity) + public function beforeDelete(EventInterface $event, EntityInterface $entity): void { $config = $this->getConfig(); $this->_ensureFields($entity); @@ -248,7 +249,7 @@ public function beforeDelete(EventInterface $event, EntityInterface $entity) * @return void * @throws \RuntimeException if the parent to set to the entity is not valid */ - protected function _setParent($entity, $parent) + protected function _setParent(EntityInterface $entity, $parent): void { $config = $this->getConfig(); $parentNode = $this->_getNode($parent); @@ -308,7 +309,7 @@ protected function _setParent($entity, $parent) * @param \Cake\Datasource\EntityInterface $entity The entity to set as a new root * @return void */ - protected function _setAsRoot($entity) + protected function _setAsRoot(EntityInterface $entity): void { $config = $this->getConfig(); $edge = $this->_getMax(); @@ -341,7 +342,7 @@ protected function _setAsRoot($entity) * * @return void */ - protected function _unmarkInternalTree() + protected function _unmarkInternalTree(): void { $config = $this->getConfig(); $this->_table->updateAll( @@ -372,7 +373,7 @@ function ($exp) use ($config) { * @return \Cake\ORM\Query * @throws \InvalidArgumentException If the 'for' key is missing in options */ - public function findPath(Query $query, array $options) + public function findPath(Query $query, array $options): Query { if (empty($options['for'])) { throw new InvalidArgumentException("The 'for' key is required for find('path')"); @@ -404,7 +405,7 @@ function ($field) { * direct children * @return int Number of children nodes. */ - public function childCount(EntityInterface $node, $direct = false) + public function childCount(EntityInterface $node, bool $direct = false): int { $config = $this->getConfig(); $parent = $this->_table->aliasField($config['parent']); @@ -436,7 +437,7 @@ public function childCount(EntityInterface $node, $direct = false) * @return \Cake\ORM\Query * @throws \InvalidArgumentException When the 'for' key is not passed in $options */ - public function findChildren(Query $query, array $options) + public function findChildren(Query $query, array $options): Query { $config = $this->getConfig(); $options += ['for' => null, 'direct' => false]; @@ -487,7 +488,7 @@ function ($field) { * @param array $options Array of options as described above. * @return \Cake\ORM\Query */ - public function findTreeList(Query $query, array $options) + public function findTreeList(Query $query, array $options): Query { $left = $this->_table->aliasField($this->getConfig('left')); @@ -517,7 +518,7 @@ public function findTreeList(Query $query, array $options) * @param array $options Array of options as described above. * @return \Cake\ORM\Query Augmented query. */ - public function formatTreeList(Query $query, array $options = []) + public function formatTreeList(Query $query, array $options = []): Query { return $query->formatResults(function ($results) use ($options) { /* @var \Cake\Collection\CollectionTrait $results */ @@ -560,7 +561,7 @@ public function removeFromTree(EntityInterface $node) * @return \Cake\Datasource\EntityInterface|false the node after being removed from the tree or * false on error */ - protected function _removeFromTree($node) + protected function _removeFromTree(EntityInterface $node) { $config = $this->getConfig(); $left = $node->get($config['left']); @@ -626,7 +627,7 @@ public function moveUp(EntityInterface $node, $number = 1) * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found * @return \Cake\Datasource\EntityInterface|bool $node The node after being moved or false on failure */ - protected function _moveUp($node, $number) + protected function _moveUp(EntityInterface $node, $number) { $config = $this->getConfig(); list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']]; @@ -716,7 +717,7 @@ public function moveDown(EntityInterface $node, $number = 1) * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found * @return \Cake\Datasource\EntityInterface|bool $node The node after being moved or false on failure */ - protected function _moveDown($node, $number) + protected function _moveDown(EntityInterface $node, $number) { $config = $this->getConfig(); list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']]; @@ -781,7 +782,7 @@ protected function _moveDown($node, $number) * @return \Cake\Datasource\EntityInterface * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found */ - protected function _getNode($id) + protected function _getNode($id): EntityInterface { $config = $this->getConfig(); list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']]; @@ -809,9 +810,9 @@ protected function _getNode($id) * * @return void */ - public function recover() + public function recover(): void { - $this->_table->getConnection()->transactional(function () { + $this->_table->getConnection()->transactional(function (): void { $this->_recoverTree(); }); } @@ -824,7 +825,7 @@ public function recover() * @param int $level Node level * @return int The next value to use for the left column */ - protected function _recoverTree($counter = 0, $parentId = null, $level = -1) + protected function _recoverTree(int $counter = 0, $parentId = null, $level = -1): int { $config = $this->getConfig(); list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']]; @@ -867,7 +868,7 @@ protected function _recoverTree($counter = 0, $parentId = null, $level = -1) * * @return int */ - protected function _getMax() + protected function _getMax(): int { $field = $this->_config['right']; $rightField = $this->_config['rightField']; @@ -895,7 +896,7 @@ protected function _getMax() * modified by future calls to this function. * @return void */ - protected function _sync($shift, $dir, $conditions, $mark = false) + protected function _sync(int $shift, string $dir, string $conditions, bool $mark = false): void { $config = $this->_config; @@ -929,7 +930,7 @@ protected function _sync($shift, $dir, $conditions, $mark = false) * @param \Cake\ORM\Query $query the Query to modify * @return \Cake\ORM\Query */ - protected function _scope($query) + protected function _scope(Query $query): Query { $scope = $this->getConfig('scope'); @@ -950,7 +951,7 @@ protected function _scope($query) * @param \Cake\Datasource\EntityInterface $entity The entity to ensure fields for * @return void */ - protected function _ensureFields($entity) + protected function _ensureFields(EntityInterface $entity): void { $config = $this->getConfig(); $fields = [$config['left'], $config['right']]; @@ -972,7 +973,7 @@ protected function _ensureFields($entity) * * @return string */ - protected function _getPrimaryKey() + protected function _getPrimaryKey(): string { if (!$this->_primaryKey) { $primaryKey = (array)$this->_table->getPrimaryKey(); From 8d89ab1b4e7e70f668086b1485b7b82ccff08dbf Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 23 Jul 2018 13:47:18 +0530 Subject: [PATCH 1223/2059] Use DateTimeInterface instead of concrete class DateTime. --- Behavior/TimestampBehavior.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 22e665b0..449e63ac 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -21,7 +21,7 @@ use Cake\Event\EventInterface; use Cake\I18n\Time; use Cake\ORM\Behavior; -use DateTime; +use DateTimeInterface; use RuntimeException; use UnexpectedValueException; @@ -63,7 +63,7 @@ class TimestampBehavior extends Behavior /** * Current timestamp * - * @var \DateTime + * @var \DateTimeInterface */ protected $_ts; @@ -136,11 +136,11 @@ public function implementedEvents(): array * If an explicit date time is passed, the config option `refreshTimestamp` is * automatically set to false. * - * @param \DateTime|null $ts Timestamp + * @param \DateTimeInterface|null $ts Timestamp * @param bool $refreshTimestamp If true timestamp is refreshed. - * @return \DateTime + * @return \DateTimeInterface */ - public function timestamp(?DateTime $ts = null, bool $refreshTimestamp = false): DateTime + public function timestamp(?DateTimeInterface $ts = null, bool $refreshTimestamp = false): DateTimeInterface { if ($ts) { if ($this->_config['refreshTimestamp']) { From 433e76e92181a7b31d4a596e62812f8c75062778 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 24 Jul 2018 18:14:34 +0530 Subject: [PATCH 1224/2059] Fix entity class name inflection. Closes #12390. --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 196e669b..483b2cea 100644 --- a/Table.php +++ b/Table.php @@ -773,7 +773,7 @@ public function getEntityClass() return $this->_entityClass = $default; } - $alias = Inflector::classify(Inflector::singularize(Inflector::underscore(substr(array_pop($parts), 0, -5)))); + $alias = Inflector::classify(Inflector::underscore(substr(array_pop($parts), 0, -5))); $name = implode('\\', array_slice($parts, 0, -1)) . '\\Entity\\' . $alias; if (!class_exists($name)) { return $this->_entityClass = $default; From 2049cdcc8239c29fc1156e98f0d3d03236f52112 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 24 Jul 2018 19:58:58 +0530 Subject: [PATCH 1225/2059] Enable strict typing for all ORM files. --- Behavior/TranslateBehavior.php | 3 +- BehaviorRegistry.php | 17 +-- EagerLoadable.php | 27 ++--- EagerLoader.php | 46 ++++---- Exception/PersistenceFailedException.php | 5 +- LazyEagerLoader.php | 10 +- Locator/LocatorAwareTrait.php | 3 +- Locator/LocatorInterface.php | 13 ++- Locator/TableLocator.php | 19 ++-- Marshaller.php | 29 ++--- PropertyMarshalInterface.php | 3 +- Query.php | 71 ++++++------ ResultSet.php | 22 ++-- RulesChecker.php | 7 +- SaveOptionsBuilder.php | 23 ++-- Table.php | 138 ++++++++++++----------- TableRegistry.php | 19 ++-- 17 files changed, 239 insertions(+), 216 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index ded89c1c..12b99b2e 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -25,6 +25,7 @@ use Cake\ORM\Behavior; use Cake\ORM\Entity; use Cake\ORM\Locator\LocatorAwareTrait; +use Cake\ORM\Marshaller; use Cake\ORM\PropertyMarshalInterface; use Cake\ORM\Query; use Cake\ORM\Table; @@ -380,7 +381,7 @@ public function afterSave(EventInterface $event, EntityInterface $entity): void * * {@inheritDoc} */ - public function buildMarshalMap($marshaller, $map, $options) + public function buildMarshalMap(Marshaller $marshaller, array $map, array $options): array { if (isset($options['translations']) && !$options['translations']) { return []; diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 2ac03634..8675eac0 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -1,4 +1,5 @@ setTable($table); @@ -71,7 +72,7 @@ public function __construct($table = null) * @param \Cake\ORM\Table $table The table this registry is attached to. * @return void */ - public function setTable(Table $table) + public function setTable(Table $table): void { $this->_table = $table; $eventManager = $table->getEventManager(); @@ -87,7 +88,7 @@ public function setTable(Table $table) * @return string|null Either the correct classname or null. * @since 3.5.7 */ - public static function className($class) + public static function className(string $class): ?string { $result = App::className($class, 'Model/Behavior', 'Behavior'); if (!$result) { @@ -167,7 +168,7 @@ protected function _create($class, $alias, $config) * @return array A list of implemented finders and methods. * @throws \LogicException when duplicate methods are connected. */ - protected function _getMethods(Behavior $instance, $class, $alias) + protected function _getMethods(Behavior $instance, string $class, string $alias): array { $finders = array_change_key_case($instance->implementedFinders()); $methods = array_change_key_case($instance->implementedMethods()); @@ -212,7 +213,7 @@ protected function _getMethods(Behavior $instance, $class, $alias) * @param string $method The method to check for. * @return bool */ - public function hasMethod($method) + public function hasMethod(string $method): bool { $method = strtolower($method); @@ -228,7 +229,7 @@ public function hasMethod($method) * @param string $method The method to check for. * @return bool */ - public function hasFinder($method) + public function hasFinder(string $method): bool { $method = strtolower($method); @@ -243,7 +244,7 @@ public function hasFinder($method) * @return mixed The return value depends on the underlying behavior method. * @throws \BadMethodCallException When the method is unknown. */ - public function call($method, array $args = []) + public function call(string $method, array $args = []) { $method = strtolower($method); if ($this->hasMethod($method) && $this->has($this->_methodMap[$method][0])) { @@ -265,7 +266,7 @@ public function call($method, array $args = []) * @return mixed The return value depends on the underlying behavior method. * @throws \BadMethodCallException When the method is unknown. */ - public function callFinder($type, array $args = []) + public function callFinder(string $type, array $args = []) { $type = strtolower($type); diff --git a/EagerLoadable.php b/EagerLoadable.php index 5cc7e7f5..690e2061 100644 --- a/EagerLoadable.php +++ b/EagerLoadable.php @@ -1,4 +1,5 @@ _name = $name; $allowed = [ @@ -147,7 +148,7 @@ public function __construct($name, array $config = []) * @param \Cake\ORM\EagerLoadable $association The association to load. * @return void */ - public function addAssociation($name, EagerLoadable $association) + public function addAssociation(string $name, EagerLoadable $association): void { $this->_associations[$name] = $association; } @@ -157,7 +158,7 @@ public function addAssociation($name, EagerLoadable $association) * * @return array */ - public function associations() + public function associations(): array { return $this->_associations; } @@ -167,7 +168,7 @@ public function associations() * * @return \Cake\ORM\Association|null */ - public function instance() + public function instance(): ?Association { return $this->_instance; } @@ -178,7 +179,7 @@ public function instance() * * @return string|null */ - public function aliasPath() + public function aliasPath(): ?string { return $this->_aliasPath; } @@ -197,7 +198,7 @@ public function aliasPath() * * @return string|null */ - public function propertyPath() + public function propertyPath(): ?string { return $this->_propertyPath; } @@ -208,7 +209,7 @@ public function propertyPath() * @param bool $possible The value to set. * @return $this */ - public function setCanBeJoined($possible) + public function setCanBeJoined(bool $possible): self { $this->_canBeJoined = (bool)$possible; @@ -220,7 +221,7 @@ public function setCanBeJoined($possible) * * @return bool */ - public function canBeJoined() + public function canBeJoined(): bool { return $this->_canBeJoined; } @@ -232,7 +233,7 @@ public function canBeJoined() * @param array $config The value to set. * @return $this */ - public function setConfig(array $config) + public function setConfig(array $config): self { $this->_config = $config; @@ -245,7 +246,7 @@ public function setConfig(array $config) * * @return array */ - public function getConfig() + public function getConfig(): array { return $this->_config; } @@ -256,7 +257,7 @@ public function getConfig() * * @return bool|null */ - public function forMatching() + public function forMatching(): ?bool { return $this->_forMatching; } @@ -275,7 +276,7 @@ public function forMatching() * * @return string|null */ - public function targetProperty() + public function targetProperty(): ?string { return $this->_targetProperty; } @@ -286,7 +287,7 @@ public function targetProperty() * * @return array */ - public function asContainArray() + public function asContainArray(): array { $associations = []; foreach ($this->_associations as $assoc) { diff --git a/EagerLoader.php b/EagerLoader.php index f5564481..1280e1d8 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -1,4 +1,5 @@ _containments; } @@ -176,7 +178,7 @@ public function getContain() * * @return void */ - public function clearContain() + public function clearContain(): void { $this->_containments = []; $this->_normalized = null; @@ -190,7 +192,7 @@ public function clearContain() * @param bool $enable The value to set. * @return $this */ - public function enableAutoFields($enable = true) + public function enableAutoFields(bool $enable = true): self { $this->_autoFields = (bool)$enable; @@ -202,7 +204,7 @@ public function enableAutoFields($enable = true) * * @return bool The current value. */ - public function isAutoFieldsEnabled() + public function isAutoFieldsEnabled(): bool { return $this->_autoFields; } @@ -224,7 +226,7 @@ public function isAutoFieldsEnabled() * @param array $options Extra options for the association matching. * @return $this */ - public function setMatching($assoc, ?callable $builder = null, $options = []) + public function setMatching(string $assoc, ?callable $builder = null, array $options = []): self { if ($this->_matching === null) { $this->_matching = new static(); @@ -258,7 +260,7 @@ public function setMatching($assoc, ?callable $builder = null, $options = []) * * @return array The resulting containments array */ - public function getMatching() + public function getMatching(): array { if ($this->_matching === null) { $this->_matching = new static(); @@ -283,7 +285,7 @@ public function getMatching() * will be normalized * @return array */ - public function normalized(Table $repository) + public function normalized(Table $repository): array { if ($this->_normalized !== null || empty($this->_containments)) { return (array)$this->_normalized; @@ -316,7 +318,7 @@ public function normalized(Table $repository) * with the new one * @return array */ - protected function _reformatContain($associations, $original) + protected function _reformatContain(array $associations, array $original): array { $result = $original; @@ -394,7 +396,7 @@ protected function _reformatContain($associations, $original) * per association in the containments array * @return void */ - public function attachAssociations(Query $query, Table $repository, $includeFields) + public function attachAssociations(Query $query, Table $repository, bool $includeFields): void { if (empty($this->_containments) && $this->_matching === null) { return; @@ -427,7 +429,7 @@ public function attachAssociations(Query $query, Table $repository, $includeFiel * attached * @return array */ - public function attachableAssociations(Table $repository) + public function attachableAssociations(Table $repository): array { $contain = $this->normalized($repository); $matching = $this->_matching ? $this->_matching->normalized($repository) : []; @@ -445,7 +447,7 @@ public function attachableAssociations(Table $repository) * to be loaded * @return \Cake\ORM\EagerLoadable[] */ - public function externalAssociations(Table $repository) + public function externalAssociations(Table $repository): array { if ($this->_loadExternal) { return $this->_loadExternal; @@ -470,7 +472,7 @@ public function externalAssociations(Table $repository) * @return \Cake\ORM\EagerLoadable Object with normalized associations * @throws \InvalidArgumentException When containments refer to associations that do not exist. */ - protected function _normalizeContain(Table $parent, $alias, $options, $paths) + protected function _normalizeContain(Table $parent, string $alias, array $options, array $paths): EagerLoadable { $defaults = $this->_containOptions; $instance = $parent->getAssociation($alias); @@ -523,7 +525,7 @@ protected function _normalizeContain(Table $parent, $alias, $options, $paths) * * @return void */ - protected function _fixStrategies() + protected function _fixStrategies(): void { foreach ($this->_aliasList as $aliases) { foreach ($aliases as $configs) { @@ -547,7 +549,7 @@ protected function _fixStrategies() * @param \Cake\ORM\EagerLoadable $loadable The association config * @return void */ - protected function _correctStrategy($loadable) + protected function _correctStrategy(EagerLoadable $loadable): void { $config = $loadable->getConfig(); $currentStrategy = $config['strategy'] ?? @@ -570,7 +572,7 @@ protected function _correctStrategy($loadable) * @param array $matching list of associations that should be forcibly joined. * @return array */ - protected function _resolveJoins($associations, $matching = []) + protected function _resolveJoins(array $associations, array $matching = []): array { $result = []; foreach ($matching as $table => $loadable) { @@ -605,7 +607,7 @@ protected function _resolveJoins($associations, $matching = []) * @param \Cake\Database\StatementInterface $statement The statement created after executing the $query * @return \Cake\Database\StatementInterface statement modified statement with extra loaders */ - public function loadExternal($query, $statement) + public function loadExternal(Query $query, StatementInterface $statement): StatementInterface { $external = $this->externalAssociations($query->getRepository()); if (empty($external)) { @@ -657,7 +659,7 @@ public function loadExternal($query, $statement) * will be normalized * @return array */ - public function associationsMap($table) + public function associationsMap(Table $table): array { $map = []; @@ -681,7 +683,7 @@ public function associationsMap($table) * @param bool $matching Whether or not it is an association loaded through `matching()`. * @return array */ - protected function _buildAssociationsMap($map, $level, $matching = false) + protected function _buildAssociationsMap(array $map, array $level, bool $matching = false): array { /* @var \Cake\ORM\EagerLoadable $meta */ foreach ($level as $assoc => $meta) { @@ -720,7 +722,7 @@ protected function _buildAssociationsMap($map, $level, $matching = false) * If not passed, the default property for the association will be used. * @return void */ - public function addToJoinsMap($alias, Association $assoc, $asMatching = false, $targetProperty = null) + public function addToJoinsMap(string $alias, Association $assoc, bool $asMatching = false, ?string $targetProperty = null): void { $this->_joinsMap[$alias] = new EagerLoadable($alias, [ 'aliasPath' => $alias, @@ -740,7 +742,7 @@ public function addToJoinsMap($alias, Association $assoc, $asMatching = false, $ * @param \Cake\Database\Statement\BufferedStatement $statement The statement to work on * @return array */ - protected function _collectKeys($external, $query, $statement) + protected function _collectKeys(array $external, Query $query, $statement): array { $collectKeys = []; /* @var \Cake\ORM\EagerLoadable $meta */ @@ -782,7 +784,7 @@ protected function _collectKeys($external, $query, $statement) * @param array $collectKeys The keys to collect * @return array */ - protected function _groupKeys($statement, $collectKeys) + protected function _groupKeys(BufferedStatement $statement, array $collectKeys): array { $keys = []; while ($result = $statement->fetch('assoc')) { diff --git a/Exception/PersistenceFailedException.php b/Exception/PersistenceFailedException.php index d5814d81..bc13b851 100644 --- a/Exception/PersistenceFailedException.php +++ b/Exception/PersistenceFailedException.php @@ -1,4 +1,5 @@ _entity; } diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 04b18088..a96c6461 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -1,4 +1,5 @@ getPrimaryKey(); $method = is_string($primaryKey) ? 'get' : 'extract'; @@ -112,7 +114,7 @@ protected function _getQuery($objects, $contain, $source) * @param array $associations The name of the top level associations * @return array */ - protected function _getPropertyMap($source, $associations) + protected function _getPropertyMap(Table $source, array $associations): array { $map = []; $container = $source->associations(); @@ -127,13 +129,13 @@ protected function _getPropertyMap($source, $associations) * Injects the results of the eager loader query into the original list of * entities. * - * @param array|\Traversable $objects The original list of entities + * @param iterable $objects The original list of entities * @param \Cake\Collection\CollectionInterface|\Cake\Database\Query $results The loaded results * @param array $associations The top level associations that were loaded * @param \Cake\ORM\Table $source The table where the entities came from * @return array */ - protected function _injectResults($objects, $results, $associations, $source) + protected function _injectResults(iterable $objects, $results, array $associations, Table $source): array { $injected = []; $properties = $this->_getPropertyMap($source, $associations); diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index f79ac115..980f1d77 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -1,4 +1,5 @@ _tableLocator) { $this->_tableLocator = TableRegistry::getTableLocator(); diff --git a/Locator/LocatorInterface.php b/Locator/LocatorInterface.php index 4bc4ef70..187eda11 100644 --- a/Locator/LocatorInterface.php +++ b/Locator/LocatorInterface.php @@ -1,4 +1,5 @@ _config; @@ -135,7 +136,7 @@ public function getConfig($alias = null) * @return \Cake\ORM\Table * @throws \RuntimeException When you try to configure an alias that already exists. */ - public function get($alias, array $options = []) + public function get(string $alias, array $options = []): Table { if (isset($this->_instances[$alias])) { if (!empty($options) && $this->_options[$alias] !== $options) { @@ -202,7 +203,7 @@ public function get($alias, array $options = []) * @param array $options Table options array. * @return string|null */ - protected function _getClassName($alias, array $options = []) + protected function _getClassName(string $alias, array $options = []): ?string { if (empty($options['className'])) { $options['className'] = Inflector::camelize($alias); @@ -217,7 +218,7 @@ protected function _getClassName($alias, array $options = []) * @param array $options The alias to check for. * @return \Cake\ORM\Table */ - protected function _create(array $options) + protected function _create(array $options): Table { return new $options['className']($options); } @@ -225,7 +226,7 @@ protected function _create(array $options) /** * {@inheritDoc} */ - public function exists($alias) + public function exists(string $alias): bool { return isset($this->_instances[$alias]); } @@ -233,7 +234,7 @@ public function exists($alias) /** * {@inheritDoc} */ - public function set($alias, Table $object) + public function set(string $alias, Table $object): Table { return $this->_instances[$alias] = $object; } @@ -241,7 +242,7 @@ public function set($alias, Table $object) /** * {@inheritDoc} */ - public function clear() + public function clear(): void { $this->_instances = []; $this->_config = []; @@ -256,7 +257,7 @@ public function clear() * * @return \Cake\ORM\Table[] */ - public function genericInstances() + public function genericInstances(): array { return $this->_fallbacked; } @@ -264,7 +265,7 @@ public function genericInstances() /** * {@inheritDoc} */ - public function remove($alias) + public function remove(string $alias): void { unset( $this->_instances[$alias], diff --git a/Marshaller.php b/Marshaller.php index e7dd851a..33dc44ec 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -1,4 +1,5 @@ _table->getSchema(); @@ -163,7 +164,7 @@ protected function _buildPropertyMap($data, $options) * @see \Cake\ORM\Table::newEntity() * @see \Cake\ORM\Entity::$_accessible */ - public function one(array $data, array $options = []) + public function one(array $data, array $options = []): EntityInterface { list($data, $options) = $this->_prepareDataAndOptions($data, $options); @@ -234,7 +235,7 @@ public function one(array $data, array $options = []) * @return array The list of validation errors. * @throws \RuntimeException If no validator can be created. */ - protected function _validate($data, $options, $isNew) + protected function _validate(array $data, array $options, bool $isNew): array { if (!$options['validate']) { return []; @@ -265,7 +266,7 @@ protected function _validate($data, $options, $isNew) * @param array $options The options passed to this marshaller. * @return array An array containing prepared data and options. */ - protected function _prepareDataAndOptions($data, $options) + protected function _prepareDataAndOptions(array $data, array $options): array { $options += ['validate' => true]; @@ -290,7 +291,7 @@ protected function _prepareDataAndOptions($data, $options) * @param array $options List of options. * @return \Cake\Datasource\EntityInterface|\Cake\Datasource\EntityInterface[]|null */ - protected function _marshalAssociation($assoc, $value, $options) + protected function _marshalAssociation(Association $assoc, $value, array $options) { if (!is_array($value)) { return null; @@ -340,7 +341,7 @@ protected function _marshalAssociation($assoc, $value, $options) * @see \Cake\ORM\Table::newEntities() * @see \Cake\ORM\Entity::$_accessible */ - public function many(array $data, array $options = []) + public function many(array $data, array $options = []): array { $output = []; foreach ($data as $record) { @@ -367,7 +368,7 @@ public function many(array $data, array $options = []) * @throws \InvalidArgumentException * @throws \RuntimeException */ - protected function _belongsToMany(BelongsToMany $assoc, array $data, $options = []) + protected function _belongsToMany(BelongsToMany $assoc, array $data, array $options = []): array { $associated = $options['associated'] ?? []; $forceNew = $options['forceNew'] ?? false; @@ -459,7 +460,7 @@ protected function _belongsToMany(BelongsToMany $assoc, array $data, $options = * @param array $ids The list of ids to load. * @return \Cake\Datasource\EntityInterface[] An array of entities. */ - protected function _loadAssociatedByIds($assoc, $ids) + protected function _loadAssociatedByIds(Association $assoc, array $ids): array { if (empty($ids)) { return []; @@ -520,7 +521,7 @@ protected function _loadAssociatedByIds($assoc, $ids) * @return \Cake\Datasource\EntityInterface * @see \Cake\ORM\Entity::$_accessible */ - public function merge(EntityInterface $entity, array $data, array $options = []) + public function merge(EntityInterface $entity, array $data, array $options = []): EntityInterface { list($data, $options) = $this->_prepareDataAndOptions($data, $options); @@ -624,7 +625,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) * @return \Cake\Datasource\EntityInterface[] * @see \Cake\ORM\Entity::$_accessible */ - public function mergeMany($entities, array $data, array $options = []) + public function mergeMany($entities, array $data, array $options = []): array { $primary = (array)$this->_table->getPrimaryKey(); @@ -662,7 +663,7 @@ public function mergeMany($entities, array $data, array $options = []) $conditions = (new Collection($indexed)) ->map(function ($data, $key) { - return explode(';', $key); + return explode(';', (string)$key); }) ->filter(function ($keys) use ($primary) { return count(array_filter($keys, 'strlen')) === count($primary); @@ -704,7 +705,7 @@ public function mergeMany($entities, array $data, array $options = []) * @param array $options List of options. * @return \Cake\Datasource\EntityInterface|\Cake\Datasource\EntityInterface[]|null */ - protected function _mergeAssociation($original, $assoc, $value, $options) + protected function _mergeAssociation($original, Association $assoc, $value, array $options) { if (!$original) { return $this->_marshalAssociation($assoc, $value, $options); @@ -736,7 +737,7 @@ protected function _mergeAssociation($original, $assoc, $value, $options) * @param array $options List of options. * @return \Cake\Datasource\EntityInterface[] */ - protected function _mergeBelongsToMany($original, $assoc, $value, $options) + protected function _mergeBelongsToMany($original, Association $assoc, $value, array $options): array { $associated = $options['associated'] ?? []; @@ -766,7 +767,7 @@ protected function _mergeBelongsToMany($original, $assoc, $value, $options) * @param array $options List of options. * @return \Cake\Datasource\EntityInterface[] An array of entities */ - protected function _mergeJoinData($original, $assoc, $value, $options) + protected function _mergeJoinData($original, BelongsToMany $assoc, array $value, array $options): array { $associated = $options['associated'] ?? []; $extra = []; diff --git a/PropertyMarshalInterface.php b/PropertyMarshalInterface.php index bf2c6d56..9ddd02f5 100644 --- a/PropertyMarshalInterface.php +++ b/PropertyMarshalInterface.php @@ -1,4 +1,5 @@ callable]` of additional properties to marshal. */ - public function buildMarshalMap($marshaller, $map, $options); + public function buildMarshalMap(Marshaller $marshaller, array $map, array $options): array; } diff --git a/Query.php b/Query.php index 34301b82..35fc875a 100644 --- a/Query.php +++ b/Query.php @@ -15,6 +15,7 @@ namespace Cake\ORM; use ArrayObject; +use Cake\Database\Connection; use Cake\Database\ExpressionInterface; use Cake\Database\Query as DatabaseQuery; use Cake\Database\TypedResultInterface; @@ -22,8 +23,10 @@ use Cake\Database\ValueBinder; use Cake\Datasource\QueryInterface; use Cake\Datasource\QueryTrait; +use Cake\Datasource\ResultSetInterface; use JsonSerializable; use RuntimeException; +use Traversable; /** * Extends the base Query class to provide new methods related to association @@ -110,7 +113,7 @@ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface * Tracks whether or not the original query should include * fields from the top level table. * - * @var bool + * @var bool|null */ protected $_autoFields; @@ -159,7 +162,7 @@ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface * @param \Cake\Database\Connection $connection The connection object * @param \Cake\ORM\Table $table The table this query is starting on */ - public function __construct($connection, $table) + public function __construct(Connection $connection, Table $table) { parent::__construct($connection); $this->repository($table); @@ -209,7 +212,7 @@ public function __construct($connection, $table) * @param bool $overwrite whether to reset fields with passed list or not * @return $this */ - public function select($fields = [], $overwrite = false) + public function select($fields = [], $overwrite = false): self { if ($fields instanceof Association) { $fields = $fields->getTarget(); @@ -237,7 +240,7 @@ public function select($fields = [], $overwrite = false) * @return Query * @throws \InvalidArgumentException If Association|Table is not passed in first argument */ - public function selectAllExcept($table, array $excludedFields, $overwrite = false) + public function selectAllExcept($table, array $excludedFields, $overwrite = false): Query { if ($table instanceof Association) { $table = $table->getTarget(); @@ -264,7 +267,7 @@ public function selectAllExcept($table, array $excludedFields, $overwrite = fals * @param \Cake\ORM\Table $table The table to pull types from * @return $this */ - public function addDefaultTypes(Table $table) + public function addDefaultTypes(Table $table): self { $alias = $table->getAlias(); $map = $table->getSchema()->typeMap(); @@ -284,7 +287,7 @@ public function addDefaultTypes(Table $table) * @param \Cake\ORM\EagerLoader $instance The eager loader to use. * @return $this */ - public function setEagerLoader(EagerLoader $instance) + public function setEagerLoader(EagerLoader $instance): self { $this->_eagerLoader = $instance; @@ -296,7 +299,7 @@ public function setEagerLoader(EagerLoader $instance) * * @return \Cake\ORM\EagerLoader */ - public function getEagerLoader() + public function getEagerLoader(): EagerLoader { if ($this->_eagerLoader === null) { $this->_eagerLoader = new EagerLoader(); @@ -448,7 +451,7 @@ public function contain($associations, $override = false) /** * @return array */ - public function getContain() + public function getContain(): array { return $this->getEagerLoader()->getContain(); } @@ -458,7 +461,7 @@ public function getContain() * * @return $this */ - public function clearContain() + public function clearContain(): self { $this->getEagerLoader()->clearContain(); $this->_dirty(); @@ -476,7 +479,7 @@ public function clearContain() * @param array $associations The nested tree of associations to walk. * @return void */ - protected function _addAssociationsToTypeMap($table, $typeMap, $associations) + protected function _addAssociationsToTypeMap(Table $table, TypeMap $typeMap, array $associations): void { foreach ($associations as $name => $nested) { if (!$table->hasAssociation($name)) { @@ -544,7 +547,7 @@ protected function _addAssociationsToTypeMap($table, $typeMap, $associations) * that can be used to add custom conditions or selecting some fields * @return $this */ - public function matching($assoc, ?callable $builder = null) + public function matching(string $assoc, ?callable $builder = null): self { $result = $this->getEagerLoader()->setMatching($assoc, $builder)->getMatching(); $this->_addAssociationsToTypeMap($this->getRepository(), $this->getTypeMap(), $result); @@ -616,7 +619,7 @@ public function matching($assoc, ?callable $builder = null) * that can be used to add custom conditions or selecting some fields * @return $this */ - public function leftJoinWith($assoc, ?callable $builder = null) + public function leftJoinWith(string $assoc, ?callable $builder = null): self { $result = $this->getEagerLoader() ->setMatching($assoc, $builder, [ @@ -665,7 +668,7 @@ public function leftJoinWith($assoc, ?callable $builder = null) * @return $this * @see \Cake\ORM\Query::matching() */ - public function innerJoinWith($assoc, ?callable $builder = null) + public function innerJoinWith(string $assoc, ?callable $builder = null): self { $result = $this->getEagerLoader() ->setMatching($assoc, $builder, [ @@ -729,7 +732,7 @@ public function innerJoinWith($assoc, ?callable $builder = null) * that can be used to add custom conditions or selecting some fields * @return $this */ - public function notMatching($assoc, ?callable $builder = null) + public function notMatching(string $assoc, ?callable $builder = null): self { $result = $this->getEagerLoader() ->setMatching($assoc, $builder, [ @@ -826,7 +829,7 @@ public function applyOptions(array $options) * * @return \Cake\ORM\Query */ - public function cleanCopy() + public function cleanCopy(): Query { $clone = clone $this; $clone->setEagerLoader(clone $this->getEagerLoader()); @@ -879,7 +882,7 @@ public function count(): int * * @return int */ - protected function _performCount() + protected function _performCount(): int { $query = $this->cleanCopy(); $counter = $this->_counter; @@ -950,7 +953,7 @@ protected function _performCount() * @param callable|null $counter The counter value * @return $this */ - public function counter($counter) + public function counter(?callable $counter): self { $this->_counter = $counter; @@ -965,7 +968,7 @@ public function counter($counter) * @param bool $enable Use a boolean to set the hydration mode. * @return $this */ - public function enableHydration($enable = true) + public function enableHydration(bool $enable = true): self { $this->_dirty(); $this->_hydrate = (bool)$enable; @@ -978,7 +981,7 @@ public function enableHydration($enable = true) * * @return bool */ - public function isHydrationEnabled() + public function isHydrationEnabled(): bool { return $this->_hydrate; } @@ -989,7 +992,7 @@ public function isHydrationEnabled() * @return $this * @throws \RuntimeException When you attempt to cache a non-select query. */ - public function cache($key, $config = 'default') + public function cache(string $key, $config = 'default'): self { if ($this->_type !== 'select' && $this->_type !== null) { throw new RuntimeException('You cannot cache the results of non-select queries.'); @@ -1021,7 +1024,7 @@ public function all() * * @return void */ - public function triggerBeforeFind() + public function triggerBeforeFind(): void { if (!$this->_beforeFindFired && $this->_type === 'select') { $table = $this->getRepository(); @@ -1038,7 +1041,7 @@ public function triggerBeforeFind() /** * {@inheritDoc} */ - public function sql(?ValueBinder $binder = null) + public function sql(?ValueBinder $binder = null): string { $this->triggerBeforeFind(); @@ -1052,9 +1055,9 @@ public function sql(?ValueBinder $binder = null) * This will also setup the correct statement class in order to eager load deep * associations. * - * @return \Cake\ORM\ResultSet + * @return \Cake\Datasource\ResultSetInterface */ - protected function _execute() + protected function _execute(): ResultSetInterface { $this->triggerBeforeFind(); if ($this->_results) { @@ -1080,7 +1083,7 @@ protected function _execute() * @see \Cake\Database\Query::execute() * @return void */ - protected function _transformQuery() + protected function _transformQuery(): void { if (!$this->_dirty || $this->_type !== 'select') { return; @@ -1100,7 +1103,7 @@ protected function _transformQuery() * * @return void */ - protected function _addDefaultFields() + protected function _addDefaultFields(): void { $select = $this->clause('select'); $this->_hasFields = true; @@ -1120,7 +1123,7 @@ protected function _addDefaultFields() * * @return void */ - protected function _addDefaultSelectTypes() + protected function _addDefaultSelectTypes(): void { $typeMap = $this->getTypeMap()->getDefaults(); $select = $this->clause('select'); @@ -1157,7 +1160,7 @@ public function find($finder, array $options = []) * * @return void */ - protected function _dirty() + protected function _dirty(): void { $this->_results = null; $this->_resultsCount = null; @@ -1210,7 +1213,7 @@ public function delete($table = null) * @param array $types A map between columns & their datatypes. * @return $this */ - public function insert(array $columns, array $types = []) + public function insert(array $columns, array $types = []): self { $table = $this->getRepository()->getTable(); $this->into($table); @@ -1260,7 +1263,7 @@ public function __debugInfo() * * @return \Cake\Datasource\ResultSetInterface The data to convert to JSON. */ - public function jsonSerialize() + public function jsonSerialize(): ResultSetInterface { return $this->all(); } @@ -1274,7 +1277,7 @@ public function jsonSerialize() * @param bool $value Set true to enable, false to disable. * @return $this */ - public function enableAutoFields($value = true) + public function enableAutoFields(bool $value = true): self { $this->_autoFields = (bool)$value; @@ -1287,9 +1290,9 @@ public function enableAutoFields($value = true) * By default calling select() will disable auto-fields. You can re-enable * auto-fields with enableAutoFields(). * - * @return bool The current value. + * @return bool|null The current value. */ - public function isAutoFieldsEnabled() + public function isAutoFieldsEnabled(): ?bool { return $this->_autoFields; } @@ -1300,7 +1303,7 @@ public function isAutoFieldsEnabled() * @param \Traversable $result Original results * @return \Cake\Datasource\ResultSetInterface */ - protected function _decorateResults($result) + protected function _decorateResults(Traversable $result): ResultSetInterface { $result = $this->_applyDecorators($result); diff --git a/ResultSet.php b/ResultSet.php index 151873b3..18bbcd46 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -1,4 +1,5 @@ getRepository(); $this->_statement = $statement; @@ -202,7 +204,7 @@ public function current() * * @return int */ - public function key() + public function key(): int { return $this->_index; } @@ -214,7 +216,7 @@ public function key() * * @return void */ - public function next() + public function next(): void { $this->_index++; } @@ -227,7 +229,7 @@ public function next() * @throws \Cake\Database\Exception * @return void */ - public function rewind() + public function rewind(): void { if ($this->_index === 0) { return; @@ -248,7 +250,7 @@ public function rewind() * * @return bool */ - public function valid() + public function valid(): bool { if ($this->_useBuffering) { $valid = $this->_index < $this->_count; @@ -300,7 +302,7 @@ public function first() * * @return string Serialized object */ - public function serialize() + public function serialize(): string { if (!$this->_useBuffering) { $msg = 'You cannot serialize an un-buffered ResultSet. Use Query::bufferResults() to get a buffered ResultSet.'; @@ -366,7 +368,7 @@ public function count(): int * @param \Cake\ORM\Query $query The query from where to derive the associations * @return void */ - protected function _calculateAssociationMap($query) + protected function _calculateAssociationMap(Query $query): void { $map = $query->getEagerLoader()->associationsMap($this->_defaultTable); $this->_matchingMap = (new Collection($map)) @@ -387,7 +389,7 @@ protected function _calculateAssociationMap($query) * @param \Cake\ORM\Query $query The query from where to derive the column map * @return void */ - protected function _calculateColumnMap($query) + protected function _calculateColumnMap(Query $query): void { $map = []; foreach ($query->clause('select') as $key => $field) { @@ -437,9 +439,9 @@ protected function _fetchResult() * Correctly nests results keys including those coming from associations * * @param array $row Array containing columns and values or false if there is no results - * @return array Results + * @return array|\Cake\Datasource\EntityInterface Results */ - protected function _groupResult($row) + protected function _groupResult(array $row) { $defaultAlias = $this->_defaultAlias; $results = $presentAliases = []; diff --git a/RulesChecker.php b/RulesChecker.php index 34305b44..1097b754 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -1,4 +1,5 @@ ', $message = null) + public function validCount(string $field, int $count = 0, string $operator = '>', ?string $message = null): callable { if (!$message) { if ($this->_useI18n) { diff --git a/SaveOptionsBuilder.php b/SaveOptionsBuilder.php index 470ed502..3e111c5e 100644 --- a/SaveOptionsBuilder.php +++ b/SaveOptionsBuilder.php @@ -1,4 +1,5 @@ $value) { $this->{$key}($value); @@ -81,7 +82,7 @@ public function parseArrayOptions($array) * @param string|array $associated String or array of associations. * @return \Cake\ORM\SaveOptionsBuilder */ - public function associated($associated) + public function associated($associated): SaveOptionsBuilder { $associated = $this->_normalizeAssociations($associated); $this->_associated($this->_table, $associated); @@ -97,7 +98,7 @@ public function associated($associated) * @param array $associations An associations array. * @return void */ - protected function _associated(Table $table, array $associations) + protected function _associated(Table $table, array $associations): void { foreach ($associations as $key => $associated) { if (is_int($key)) { @@ -120,7 +121,7 @@ protected function _associated(Table $table, array $associations) * @param string $association Association name. * @return void */ - protected function _checkAssociation(Table $table, $association) + protected function _checkAssociation(Table $table, string $association): void { if (!$table->associations()->has($association)) { throw new RuntimeException(sprintf('Table `%s` is not associated with `%s`', get_class($table), $association)); @@ -133,7 +134,7 @@ protected function _checkAssociation(Table $table, $association) * @param bool $guard Guard the properties or not. * @return \Cake\ORM\SaveOptionsBuilder */ - public function guard($guard) + public function guard(bool $guard): SaveOptionsBuilder { $this->_options['guard'] = (bool)$guard; @@ -146,7 +147,7 @@ public function guard($guard) * @param string $validate Name of the validation rule set to use. * @return \Cake\ORM\SaveOptionsBuilder */ - public function validate($validate) + public function validate(string $validate): SaveOptionsBuilder { $this->_table->getValidator($validate); $this->_options['validate'] = $validate; @@ -160,7 +161,7 @@ public function validate($validate) * @param bool $checkExisting Guard the properties or not. * @return \Cake\ORM\SaveOptionsBuilder */ - public function checkExisting($checkExisting) + public function checkExisting(bool $checkExisting): SaveOptionsBuilder { $this->_options['checkExisting'] = (bool)$checkExisting; @@ -173,7 +174,7 @@ public function checkExisting($checkExisting) * @param bool $checkRules Check the rules or not. * @return \Cake\ORM\SaveOptionsBuilder */ - public function checkRules($checkRules) + public function checkRules(bool $checkRules): SaveOptionsBuilder { $this->_options['checkRules'] = (bool)$checkRules; @@ -186,7 +187,7 @@ public function checkRules($checkRules) * @param bool $atomic Atomic or not. * @return \Cake\ORM\SaveOptionsBuilder */ - public function atomic($atomic) + public function atomic(bool $atomic): SaveOptionsBuilder { $this->_options['atomic'] = (bool)$atomic; @@ -196,7 +197,7 @@ public function atomic($atomic) /** * @return array */ - public function toArray() + public function toArray(): array { return $this->_options; } @@ -208,7 +209,7 @@ public function toArray() * @param mixed $value Option value. * @return \Cake\ORM\SaveOptionsBuilder */ - public function set($option, $value) + public function set(string $option, $value): SaveOptionsBuilder { if (method_exists($this, $option)) { return $this->{$option}($value); diff --git a/Table.php b/Table.php index 5bdab131..85f37872 100644 --- a/Table.php +++ b/Table.php @@ -1,4 +1,5 @@ _table = $table; @@ -346,7 +348,7 @@ public function setTable($table) * * @return string */ - public function getTable() + public function getTable(): string { if ($this->_table === null) { $table = namespaceSplit(get_class($this)); @@ -397,7 +399,7 @@ public function getAlias(): string * @param string $field The field to alias. * @return string The field prefixed with the table alias. */ - public function aliasField($field) + public function aliasField(string $field): string { if (strpos($field, '.') !== false) { return $field; @@ -412,7 +414,7 @@ public function aliasField($field) * @param string $registryAlias The key used to access this object. * @return $this */ - public function setRegistryAlias($registryAlias) + public function setRegistryAlias(string $registryAlias) { $this->_registryAlias = $registryAlias; @@ -424,7 +426,7 @@ public function setRegistryAlias($registryAlias) * * @return string */ - public function getRegistryAlias() + public function getRegistryAlias(): string { if ($this->_registryAlias === null) { $this->_registryAlias = $this->getAlias(); @@ -449,9 +451,9 @@ public function setConnection(ConnectionInterface $connection) /** * Returns the connection instance. * - * @return \Cake\Database\Connection + * @return \Cake\Database\Connection|null */ - public function getConnection() + public function getConnection(): ?Connection { return $this->_connection; } @@ -461,7 +463,7 @@ public function getConnection() * * @return \Cake\Database\Schema\TableSchema */ - public function getSchema() + public function getSchema(): TableSchema { if ($this->_schema === null) { $this->_schema = $this->_initializeSchema( @@ -524,7 +526,7 @@ public function setSchema($schema) * @param \Cake\Database\Schema\TableSchema $schema The table definition fetched from database. * @return \Cake\Database\Schema\TableSchema the altered schema */ - protected function _initializeSchema(TableSchema $schema) + protected function _initializeSchema(TableSchema $schema): TableSchema { return $schema; } @@ -579,12 +581,12 @@ public function getPrimaryKey() /** * Sets the display field. * - * @param string $key Name to be used as display field. + * @param string|array $fields Name to be used as display field. * @return $this */ - public function setDisplayField($key) + public function setDisplayField($field): self { - $this->_displayField = $key; + $this->_displayField = $field; return $this; } @@ -592,7 +594,7 @@ public function setDisplayField($key) /** * Returns the display field. * - * @return string + * @return string|array */ public function getDisplayField() { @@ -616,7 +618,7 @@ public function getDisplayField() * * @return string */ - public function getEntityClass() + public function getEntityClass(): string { if (!$this->_entityClass) { $default = Entity::class; @@ -651,7 +653,7 @@ public function getEntityClass() * @throws \Cake\ORM\Exception\MissingEntityException when the entity class cannot be found * @return $this */ - public function setEntityClass($name) + public function setEntityClass(string $name): self { $class = App::className($name, 'Model/Entity'); if (!$class) { @@ -687,7 +689,7 @@ public function setEntityClass($name) * @throws \RuntimeException If a behavior is being reloaded. * @see \Cake\ORM\Behavior */ - public function addBehavior($name, array $options = []) + public function addBehavior(string $name, array $options = []): self { $this->_behaviors->load($name, $options); @@ -710,7 +712,7 @@ public function addBehavior($name, array $options = []) * @return $this * @throws \RuntimeException If a behavior is being reloaded. */ - public function addBehaviors(array $behaviors) + public function addBehaviors(array $behaviors): self { foreach ($behaviors as $name => $options) { if (is_int($name)) { @@ -739,7 +741,7 @@ public function addBehaviors(array $behaviors) * @return $this * @see \Cake\ORM\Behavior */ - public function removeBehavior($name) + public function removeBehavior(string $name) { $this->_behaviors->unload($name); @@ -751,7 +753,7 @@ public function removeBehavior($name) * * @return \Cake\ORM\BehaviorRegistry The BehaviorRegistry instance. */ - public function behaviors() + public function behaviors(): BehaviorRegistry { return $this->_behaviors; } @@ -763,7 +765,7 @@ public function behaviors() * @return \Cake\ORM\Behavior * @throws \InvalidArgumentException If the behavior does not exist. */ - public function getBehavior($name) + public function getBehavior(string $name): Behavior { /** @var \Cake\ORM\Behavior $behavior */ $behavior = $this->_behaviors->get($name); @@ -784,7 +786,7 @@ public function getBehavior($name) * @param string $name The behavior alias to check. * @return bool Whether or not the behavior exists. */ - public function hasBehavior($name) + public function hasBehavior(string $name): bool { return $this->_behaviors->has($name); } @@ -806,7 +808,7 @@ public function hasBehavior($name) * @return \Cake\ORM\Association The association. * @throws \InvalidArgumentException */ - public function getAssociation($name) + public function getAssociation(string $name): Association { $association = $this->findAssociation($name); if (!$association) { @@ -828,7 +830,7 @@ public function getAssociation($name) * @param string $name The alias used for the association. * @return bool */ - public function hasAssociation($name) + public function hasAssociation(string $name): bool { return $this->findAssociation($name) !== null; } @@ -845,7 +847,7 @@ public function hasAssociation($name) * @param string $name The alias used for the association. * @return \Cake\ORM\Association|null Either the association or null. */ - protected function findAssociation($name) + protected function findAssociation(string $name): ?Association { if (strpos($name, '.') === false) { return $this->_associations->get($name); @@ -866,7 +868,7 @@ protected function findAssociation($name) * * @return \Cake\ORM\AssociationCollection|\Cake\ORM\Association[] The collection of association objects. */ - public function associations() + public function associations(): iterable { return $this->_associations; } @@ -898,7 +900,7 @@ public function associations() * @see \Cake\ORM\Table::hasMany() * @see \Cake\ORM\Table::belongsToMany() */ - public function addAssociations(array $params) + public function addAssociations(array $params): self { foreach ($params as $assocType => $tables) { foreach ($tables as $associated => $options) { @@ -943,7 +945,7 @@ public function addAssociations(array $params) * @param array $options list of options to configure the association definition * @return \Cake\ORM\Association\BelongsTo */ - public function belongsTo($associated, array $options = []) + public function belongsTo(string $associated, array $options = []): BelongsTo { $options += ['sourceTable' => $this]; @@ -989,7 +991,7 @@ public function belongsTo($associated, array $options = []) * @param array $options list of options to configure the association definition * @return \Cake\ORM\Association\HasOne */ - public function hasOne($associated, array $options = []) + public function hasOne(string $associated, array $options = []): HasOne { $options += ['sourceTable' => $this]; @@ -1041,7 +1043,7 @@ public function hasOne($associated, array $options = []) * @param array $options list of options to configure the association definition * @return \Cake\ORM\Association\HasMany */ - public function hasMany($associated, array $options = []) + public function hasMany(string $associated, array $options = []): HasMany { $options += ['sourceTable' => $this]; @@ -1095,7 +1097,7 @@ public function hasMany($associated, array $options = []) * @param array $options list of options to configure the association definition * @return \Cake\ORM\Association\BelongsToMany */ - public function belongsToMany($associated, array $options = []) + public function belongsToMany(string $associated, array $options = []): BelongsToMany { $options += ['sourceTable' => $this]; @@ -1163,7 +1165,7 @@ public function belongsToMany($associated, array $options = []) * @param array|\ArrayAccess $options An array that will be passed to Query::applyOptions() * @return \Cake\ORM\Query The query builder */ - public function find($type = 'all', $options = []) + public function find(string $type = 'all', $options = []): Query { $query = $this->query(); $query->select(); @@ -1181,7 +1183,7 @@ public function find($type = 'all', $options = []) * @param array $options The options to use for the find * @return \Cake\ORM\Query The query builder */ - public function findAll(Query $query, array $options) + public function findAll(Query $query, array $options): Query { return $query; } @@ -1243,7 +1245,7 @@ public function findAll(Query $query, array $options) * @param array $options The options for the find * @return \Cake\ORM\Query The query builder */ - public function findList(Query $query, array $options) + public function findList(Query $query, array $options): Query { $options += [ 'keyField' => $this->getPrimaryKey(), @@ -1306,7 +1308,7 @@ public function findList(Query $query, array $options) * @param array $options The options to find with * @return \Cake\ORM\Query The query builder */ - public function findThreaded(Query $query, array $options) + public function findThreaded(Query $query, array $options): Query { $options += [ 'keyField' => $this->getPrimaryKey(), @@ -1335,7 +1337,7 @@ public function findThreaded(Query $query, array $options) * the associated value * @return array */ - protected function _setFieldMatchers($options, $keys) + protected function _setFieldMatchers(array $options, array $keys): array { foreach ($keys as $field) { if (!is_array($options[$field])) { @@ -1375,7 +1377,7 @@ protected function _setFieldMatchers($options, $keys) * @throws \Cake\Datasource\Exception\InvalidPrimaryKeyException When $primaryKey has an * incorrect number of elements. */ - public function get($primaryKey, $options = []) + public function get($primaryKey, $options = []): EntityInterface { $key = (array)$this->getPrimaryKey(); $alias = $this->getAlias(); @@ -1426,7 +1428,7 @@ public function get($primaryKey, $options = []) * @param bool $atomic Whether to execute the worker inside a database transaction. * @return mixed */ - protected function _executeTransaction(callable $worker, $atomic = true) + protected function _executeTransaction(callable $worker, bool $atomic = true) { if ($atomic) { return $this->getConnection()->transactional(function () use ($worker) { @@ -1444,7 +1446,7 @@ protected function _executeTransaction(callable $worker, $atomic = true) * @param bool $primary True if a primary was used. * @return bool Returns true if a transaction was committed. */ - protected function _transactionCommitted($atomic, $primary) + protected function _transactionCommitted(bool $atomic, bool $primary): bool { return !$this->getConnection()->inTransaction() && ($atomic || (!$atomic && $primary)); } @@ -1482,7 +1484,7 @@ protected function _transactionCommitted($atomic, $primary) * @param array $options The options to use when saving. * @return \Cake\Datasource\EntityInterface An entity. */ - public function findOrCreate($search, ?callable $callback = null, $options = []) + public function findOrCreate($search, ?callable $callback = null, $options = []): EntityInterface { $options = new ArrayObject($options + [ 'atomic' => true, @@ -1511,7 +1513,7 @@ public function findOrCreate($search, ?callable $callback = null, $options = []) * @param array $options The options to use when saving. * @return \Cake\Datasource\EntityInterface An entity. */ - protected function _processFindOrCreate($search, ?callable $callback = null, $options = []) + protected function _processFindOrCreate($search, ?callable $callback = null, $options = []): EntityInterface { if (is_callable($search)) { $query = $this->find(); @@ -1545,7 +1547,7 @@ protected function _processFindOrCreate($search, ?callable $callback = null, $op * @param array|\Cake\ORM\Query|string $search The criteria to find existing records by. * @return \Cake\ORM\Query */ - protected function _getFindOrCreateQuery($search) + protected function _getFindOrCreateQuery($search): Query { if ($search instanceof Query) { return $search; @@ -1559,7 +1561,7 @@ protected function _getFindOrCreateQuery($search) * * @return \Cake\ORM\Query */ - public function query() + public function query(): Query { return new Query($this->getConnection(), $this); } @@ -1744,7 +1746,7 @@ public function save(EntityInterface $entity, $options = []) * @throws \Cake\ORM\Exception\PersistenceFailedException When the entity couldn't be saved * @see \Cake\ORM\Table::save() */ - public function saveOrFail(EntityInterface $entity, $options = []) + public function saveOrFail(EntityInterface $entity, $options = []): EntityInterface { $saved = $this->save($entity, $options); if ($saved === false) { @@ -1764,7 +1766,7 @@ public function saveOrFail(EntityInterface $entity, $options = []) * @throws \Cake\ORM\Exception\RolledbackTransactionException If the transaction * is aborted in the afterSave event. */ - protected function _processSave($entity, $options) + protected function _processSave(EntityInterface $entity, ArrayObject $options) { $primaryColumns = (array)$this->getPrimaryKey(); @@ -1831,7 +1833,7 @@ protected function _processSave($entity, $options) * @throws \Cake\ORM\Exception\RolledbackTransactionException If the transaction * is aborted in the afterSave event. */ - protected function _onSaveSuccess($entity, $options) + protected function _onSaveSuccess(EntityInterface $entity, ArrayObject $options): bool { $success = $this->_associations->saveChildren( $this, @@ -1868,7 +1870,7 @@ protected function _onSaveSuccess($entity, $options) * @throws \RuntimeException if not all the primary keys where supplied or could * be generated when the table has composite primary keys. Or when the table has no primary key. */ - protected function _insert($entity, $data) + protected function _insert(EntityInterface $entity, array $data) { $primary = (array)$this->getPrimaryKey(); if (empty($primary)) { @@ -1944,7 +1946,7 @@ protected function _insert($entity, $data) * @param array $primary The primary key columns to get a new ID for. * @return null|string|array Either null or the primary key value or a list of primary key values. */ - protected function _newId($primary) + protected function _newId(array $primary) { if (!$primary || count((array)$primary) > 1) { return null; @@ -1963,7 +1965,7 @@ protected function _newId($primary) * @return \Cake\Datasource\EntityInterface|bool * @throws \InvalidArgumentException When primary key data is missing. */ - protected function _update($entity, $data) + protected function _update(EntityInterface $entity, array $data) { $primaryColumns = (array)$this->getPrimaryKey(); $primaryKey = $entity->extract($primaryColumns); @@ -2012,10 +2014,10 @@ protected function _update($entity, $data) * @param array|\ArrayAccess $options Options used when calling Table::save() for each entity. * @return bool|\Cake\Datasource\EntityInterface[]|\Cake\ORM\ResultSet False on failure, entities list on success. */ - public function saveMany($entities, $options = []) + public function saveMany(iterable $entities, $options = []) { $isNew = []; - $cleanup = function ($entities) use (&$isNew) { + $cleanup = function ($entities) use (&$isNew): void { foreach ($entities as $key => $entity) { if (isset($isNew[$key]) && $isNew[$key]) { $entity->unsetProperty($this->getPrimaryKey()); @@ -2108,7 +2110,7 @@ public function delete(EntityInterface $entity, $options = []): bool * @throws \Cake\ORM\Exception\PersistenceFailedException * @see \Cake\ORM\Table::delete() */ - public function deleteOrFail(EntityInterface $entity, $options = []) + public function deleteOrFail(EntityInterface $entity, $options = []): bool { $deleted = $this->delete($entity, $options); if ($deleted === false) { @@ -2130,7 +2132,7 @@ public function deleteOrFail(EntityInterface $entity, $options = []) * passed entity * @return bool success */ - protected function _processDelete($entity, $options) + protected function _processDelete(EntityInterface $entity, ArrayObject $options): bool { if ($entity->isNew()) { return false; @@ -2186,7 +2188,7 @@ protected function _processDelete($entity, $options) * * @return bool */ - public function hasFinder($type) + public function hasFinder(string $type): bool { $finder = 'find' . $type; @@ -2203,7 +2205,7 @@ public function hasFinder($type) * @return \Cake\ORM\Query * @throws \BadMethodCallException */ - public function callFinder($type, Query $query, array $options = []) + public function callFinder(string $type, Query $query, array $options = []): Query { $query->applyOptions($options); $options = $query->getOptions(); @@ -2230,7 +2232,7 @@ public function callFinder($type, Query $query, array $options = []) * @throws \BadMethodCallException when there are missing arguments, or when * and & or are combined. */ - protected function _dynamicFinder($method, $args) + protected function _dynamicFinder(string $method, array $args) { $method = Inflector::underscore($method); preg_match('/^find_([\w]+)_by_/', $method, $matches); @@ -2353,7 +2355,7 @@ public function __isset($property) * @return \Cake\ORM\Marshaller * @see \Cake\ORM\Marshaller */ - public function marshaller() + public function marshaller(): Marshaller { return new Marshaller($this); } @@ -2412,7 +2414,7 @@ public function marshaller() * You can use the `Model.beforeMarshal` event to modify request data * before it is converted into entities. */ - public function newEntity(?array $data = null, array $options = []) + public function newEntity(?array $data = null, array $options = []): EntityInterface { if ($data === null) { $class = $this->getEntityClass(); @@ -2455,7 +2457,7 @@ public function newEntity(?array $data = null, array $options = []) * You can use the `Model.beforeMarshal` event to modify request data * before it is converted into entities. */ - public function newEntities(array $data, array $options = []) + public function newEntities(array $data, array $options = []): array { if (!isset($options['associated'])) { $options['associated'] = $this->_associations->keys(); @@ -2501,7 +2503,7 @@ public function newEntities(array $data, array $options = []) * property will not be marked as dirty. This is an optimization to prevent unnecessary field * updates when persisting entities. */ - public function patchEntity(EntityInterface $entity, array $data, array $options = []) + public function patchEntity(EntityInterface $entity, array $data, array $options = []): EntityInterface { if (!isset($options['associated'])) { $options['associated'] = $this->_associations->keys(); @@ -2536,7 +2538,7 @@ public function patchEntity(EntityInterface $entity, array $data, array $options * You can use the `Model.beforeMarshal` event to modify request data * before it is converted into entities. */ - public function patchEntities($entities, array $data, array $options = []) + public function patchEntities($entities, array $data, array $options = []): array { if (!isset($options['associated'])) { $options['associated'] = $this->_associations->keys(); @@ -2580,7 +2582,7 @@ public function patchEntities($entities, array $data, array $options = []) * @param array|null $context Either the validation context or null. * @return bool True if the value is unique, or false if a non-scalar, non-unique value was given. */ - public function validateUnique($value, array $options, ?array $context = null) + public function validateUnique($value, array $options, ?array $context = null): bool { if ($context === null) { $context = $options; @@ -2666,7 +2668,7 @@ public function implementedEvents(): array * @param \Cake\ORM\RulesChecker $rules The rules object to be modified. * @return \Cake\ORM\RulesChecker */ - public function buildRules(RulesChecker $rules) + public function buildRules(RulesChecker $rules): RulesChecker { return $rules; } @@ -2677,7 +2679,7 @@ public function buildRules(RulesChecker $rules) * @param array $options Options to parse by the builder. * @return \Cake\ORM\SaveOptionsBuilder */ - public function getSaveOptionsBuilder(array $options = []) + public function getSaveOptionsBuilder(array $options = []): SaveOptionsBuilder { return new SaveOptionsBuilder($this, $options); } @@ -2720,7 +2722,7 @@ public function loadInto($entities, array $contain) /** * {@inheritDoc} */ - protected function validationMethodExists($method) + protected function validationMethodExists(string $method): bool { return method_exists($this, $method) || $this->behaviors()->hasMethod($method); } diff --git a/TableRegistry.php b/TableRegistry.php index 25bfb737..c924ad97 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -1,4 +1,5 @@ get($alias, $options); } @@ -157,7 +158,7 @@ public static function get($alias, array $options = []) * @return bool * @deprecated 3.6.0 Use \Cake\ORM\Locator\TableLocator::exists() instead. */ - public static function exists($alias) + public static function exists(string $alias): bool { return static::getTableLocator()->exists($alias); } @@ -170,7 +171,7 @@ public static function exists($alias) * @return \Cake\ORM\Table * @deprecated 3.6.0 Use \Cake\ORM\Locator\TableLocator::set() instead. */ - public static function set($alias, Table $object) + public static function set(string $alias, Table $object): Table { return static::getTableLocator()->set($alias, $object); } @@ -182,7 +183,7 @@ public static function set($alias, Table $object) * @return void * @deprecated 3.6.0 Use \Cake\ORM\Locator\TableLocator::remove() instead. */ - public static function remove($alias) + public static function remove(string $alias): void { static::getTableLocator()->remove($alias); } @@ -193,7 +194,7 @@ public static function remove($alias) * @return void * @deprecated 3.6.0 Use \Cake\ORM\Locator\TableLocator::clear() instead. */ - public static function clear() + public static function clear(): void { static::getTableLocator()->clear(); } From bfec068d61e18f4cba25e7be0f2f6ce5d25bbbe0 Mon Sep 17 00:00:00 2001 From: stickler-ci Date: Tue, 24 Jul 2018 14:37:57 +0000 Subject: [PATCH 1226/2059] Fixing style errors. --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 85f37872..28dc3dc3 100644 --- a/Table.php +++ b/Table.php @@ -581,7 +581,7 @@ public function getPrimaryKey() /** * Sets the display field. * - * @param string|array $fields Name to be used as display field. + * @param array $field Name to be used as display field. * @return $this */ public function setDisplayField($field): self From de140f044c83d70534338ac5b0798970e17e0332 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 24 Jul 2018 20:11:28 +0530 Subject: [PATCH 1227/2059] Fix docblock. --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 28dc3dc3..d0653fe8 100644 --- a/Table.php +++ b/Table.php @@ -581,7 +581,7 @@ public function getPrimaryKey() /** * Sets the display field. * - * @param array $field Name to be used as display field. + * @param string|array $field Name to be used as display field. * @return $this */ public function setDisplayField($field): self From 194e1da03131605aa59e518cbb299eac7d17351e Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 24 Jul 2018 22:12:22 +0530 Subject: [PATCH 1228/2059] Add missing strict types declarations and fix tests. --- Query.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Query.php b/Query.php index 35fc875a..c2c0650a 100644 --- a/Query.php +++ b/Query.php @@ -1,4 +1,5 @@ Date: Wed, 25 Jul 2018 21:54:23 -0400 Subject: [PATCH 1229/2059] Update types and doc blocks. data going into association merging could be many different things. --- Marshaller.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 33dc44ec..4a9c4810 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -287,7 +287,7 @@ protected function _prepareDataAndOptions(array $data, array $options): array * Create a new sub-marshaller and marshal the associated data. * * @param \Cake\ORM\Association $assoc The association to marshall - * @param array $value The data to hydrate + * @param mixed $value The data to hydrate. If not an array, this method will return null. * @param array $options List of options. * @return \Cake\Datasource\EntityInterface|\Cake\Datasource\EntityInterface[]|null */ @@ -701,7 +701,7 @@ public function mergeMany($entities, array $data, array $options = []): array * * @param \Cake\Datasource\EntityInterface|\Cake\Datasource\EntityInterface[] $original The original entity * @param \Cake\ORM\Association $assoc The association to merge - * @param array $value The data to hydrate + * @param mixed $value The array of data to hydrate. If not an array, this method will return null. * @param array $options List of options. * @return \Cake\Datasource\EntityInterface|\Cake\Datasource\EntityInterface[]|null */ From e47c68df595107afd3029302a3fcc853d148f8a4 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Wed, 25 Jul 2018 22:22:15 -0400 Subject: [PATCH 1230/2059] Enable `_ids` to work when updating has many associations Previously the `_ids` key could only be used to update hasMany associations if the association property *was not* populated. Now you can use `_ids` to overwrite existing association data. This makes hasMany associations consistent with belongs to many. Refs #12373 --- Marshaller.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Marshaller.php b/Marshaller.php index 7eb25518..567870a1 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -753,6 +753,17 @@ protected function _mergeAssociation($original, $assoc, $value, $options) return $marshaller->_mergeBelongsToMany($original, $assoc, $value, (array)$options); } + if ($assoc->type() === Association::ONE_TO_MANY) { + $hasIds = array_key_exists('_ids', $value); + $onlyIds = array_key_exists('onlyIds', $options) && $options['onlyIds']; + if ($hasIds && is_array($value['_ids'])) { + return $this->_loadAssociatedByIds($assoc, $value['_ids']); + } + if ($hasIds || $onlyIds) { + return []; + } + } + return $marshaller->mergeMany($original, $value, (array)$options); } From cc86c50332a29624f332b9f0dc5e8b51466bde4b Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 27 Jul 2018 00:18:34 +0530 Subject: [PATCH 1231/2059] Update phpstan to v0.10. --- Association/BelongsToMany.php | 2 ++ Behavior/TimestampBehavior.php | 4 ++-- Marshaller.php | 1 + Query.php | 41 ++++++++++++++++++++++++---------- ResultSet.php | 3 ++- Table.php | 4 ++-- 6 files changed, 38 insertions(+), 17 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 9e114ecf..62e916e8 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1129,6 +1129,7 @@ public function find($type = null, array $options = []) protected function _appendJunctionJoin($query, $conditions) { $name = $this->_junctionAssociationName(); + /** @var array $joins */ $joins = $query->join(); $matching = [ $name => [ @@ -1386,6 +1387,7 @@ protected function _collectJointEntities($sourceEntity, $targetEntities) $assocForeignKey = (array)$belongsTo->getForeignKey(); $sourceKey = $sourceEntity->extract((array)$source->getPrimaryKey()); + $unions = []; foreach ($missing as $key) { $unions[] = $hasMany->find('all') ->where(array_combine($foreignKey, $sourceKey)) diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 68e24af5..6d2d54fe 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -61,7 +61,7 @@ class TimestampBehavior extends Behavior /** * Current timestamp * - * @var \DateTime + * @var \Cake\I18n\Time */ protected $_ts; @@ -136,7 +136,7 @@ public function implementedEvents() * * @param \DateTime|null $ts Timestamp * @param bool $refreshTimestamp If true timestamp is refreshed. - * @return \DateTime + * @return \Cake\I18n\Time */ public function timestamp(DateTime $ts = null, $refreshTimestamp = false) { diff --git a/Marshaller.php b/Marshaller.php index 7eb25518..a38fecb4 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -248,6 +248,7 @@ protected function _validate($data, $options, $isNew) } elseif (is_string($options['validate'])) { $validator = $this->_table->getValidator($options['validate']); } elseif (is_object($options['validate'])) { + /** @var \Cake\Validation\Validator $validator */ $validator = $options['validate']; } diff --git a/Query.php b/Query.php index 29712964..a79905c8 100644 --- a/Query.php +++ b/Query.php @@ -1084,10 +1084,11 @@ public function all() public function triggerBeforeFind() { if (!$this->_beforeFindFired && $this->_type === 'select') { - $table = $this->getRepository(); $this->_beforeFindFired = true; - /* @var \Cake\Event\EventDispatcherInterface $table */ - $table->dispatchEvent('Model.beforeFind', [ + + /** @var \Cake\Event\EventDispatcherInterface $repository */ + $repository = $this->getRepository(); + $repository->dispatchEvent('Model.beforeFind', [ $this, new ArrayObject($this->_options), !$this->isEagerLoaded() @@ -1146,11 +1147,14 @@ protected function _transformQuery() return; } + /** @var \Cake\ORM\Table $repository */ + $repository = $this->getRepository(); + if (empty($this->_parts['from'])) { - $this->from([$this->_repository->getAlias() => $this->_repository->getTable()]); + $this->from([$repository->getAlias() => $repository->getTable()]); } $this->_addDefaultFields(); - $this->getEagerLoader()->attachAssociations($this, $this->_repository, !$this->_hasFields); + $this->getEagerLoader()->attachAssociations($this, $repository, !$this->_hasFields); $this->_addDefaultSelectTypes(); } @@ -1165,13 +1169,16 @@ protected function _addDefaultFields() $select = $this->clause('select'); $this->_hasFields = true; + /** @var \Cake\ORM\Table $repository */ + $repository = $this->getRepository(); + if (!count($select) || $this->_autoFields === true) { $this->_hasFields = false; - $this->select($this->getRepository()->getSchema()->columns()); + $this->select($repository->getSchema()->columns()); $select = $this->clause('select'); } - $aliased = $this->aliasFields($select, $this->getRepository()->getAlias()); + $aliased = $this->aliasFields($select, $repository->getAlias()); $this->select($aliased, true); } @@ -1208,7 +1215,10 @@ protected function _addDefaultSelectTypes() */ public function find($finder, array $options = []) { - return $this->getRepository()->callFinder($finder, $this, $options); + /** @var \Cake\ORM\Table $table */ + $table = $this->getRepository(); + + return $table->callFinder($finder, $this, $options); } /** @@ -1235,7 +1245,11 @@ protected function _dirty() */ public function update($table = null) { - $table = $table ?: $this->getRepository()->getTable(); + if (!$table) { + /** @var \Cake\ORM\Table $repository */ + $repository = $this->getRepository(); + $table = $repository->getTable(); + } return parent::update($table); } @@ -1251,8 +1265,9 @@ public function update($table = null) */ public function delete($table = null) { - $repo = $this->getRepository(); - $this->from([$repo->getAlias() => $repo->getTable()]); + /** @var \Cake\ORM\Table $repository */ + $repository = $this->getRepository(); + $this->from([$repository->getAlias() => $repository->getTable()]); return parent::delete(); } @@ -1272,7 +1287,9 @@ public function delete($table = null) */ public function insert(array $columns, array $types = []) { - $table = $this->getRepository()->getTable(); + /** @var \Cake\ORM\Table $repository */ + $repository = $this->getRepository(); + $table = $repository->getTable(); $this->into($table); return parent::insert($columns, $types); diff --git a/ResultSet.php b/ResultSet.php index 725d583c..5f622cd8 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -175,6 +175,7 @@ class ResultSet implements ResultSetInterface */ public function __construct($query, $statement) { + /** @var \Cake\ORM\Table $repository */ $repository = $query->getRepository(); $this->_statement = $statement; $this->_driver = $query->getConnection()->getDriver(); @@ -447,7 +448,7 @@ protected function _getTypes($table, $fields) { $types = []; $schema = $table->getSchema(); - $map = array_keys(Type::map() + ['string' => 1, 'text' => 1, 'boolean' => 1]); + $map = array_keys((array)Type::map() + ['string' => 1, 'text' => 1, 'boolean' => 1]); $typeMap = array_combine( $map, array_map(['Cake\Database\Type', 'build'], $map) diff --git a/Table.php b/Table.php index 483b2cea..6729bd3a 100644 --- a/Table.php +++ b/Table.php @@ -1894,7 +1894,7 @@ public function save(EntityInterface $entity, $options = []) $options = $options->toArray(); } - $options = new ArrayObject($options + [ + $options = new ArrayObject((array)$options + [ 'atomic' => true, 'associated' => true, 'checkRules' => true, @@ -2272,7 +2272,7 @@ public function saveMany($entities, $options = []) */ public function delete(EntityInterface $entity, $options = []) { - $options = new ArrayObject($options + [ + $options = new ArrayObject((array)$options + [ 'atomic' => true, 'checkRules' => true, '_primary' => true, From 57f05a9039b901e1bd1078ee841eb8be1916bfd1 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 27 Jul 2018 02:07:56 +0530 Subject: [PATCH 1232/2059] Update usage of deprecated method. --- ResultSet.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ResultSet.php b/ResultSet.php index 5f622cd8..18d4a362 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -448,7 +448,7 @@ protected function _getTypes($table, $fields) { $types = []; $schema = $table->getSchema(); - $map = array_keys((array)Type::map() + ['string' => 1, 'text' => 1, 'boolean' => 1]); + $map = array_keys((array)Type::getMap() + ['string' => 1, 'text' => 1, 'boolean' => 1]); $typeMap = array_combine( $map, array_map(['Cake\Database\Type', 'build'], $map) From aaca4bbd271e5d5ca4fdca04e2609523cfb17211 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 27 Jul 2018 18:08:26 +0530 Subject: [PATCH 1233/2059] Ensure FQCN in annotations. --- Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Query.php b/Query.php index 34301b82..ea827a50 100644 --- a/Query.php +++ b/Query.php @@ -234,7 +234,7 @@ public function select($fields = [], $overwrite = false) * @param \Cake\ORM\Table|\Cake\ORM\Association $table The table to use to get an array of columns * @param array $excludedFields The un-aliased column names you do not want selected from $table * @param bool $overwrite Whether to reset/remove previous selected fields - * @return Query + * @return \Cake\ORM\Query * @throws \InvalidArgumentException If Association|Table is not passed in first argument */ public function selectAllExcept($table, array $excludedFields, $overwrite = false) From fb224fd5d2825019f237829d993cf666f382fb68 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 30 Jul 2018 23:38:06 +0530 Subject: [PATCH 1234/2059] Update type hint for registry classes. --- BehaviorRegistry.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 2ac03634..216b56c6 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -19,6 +19,7 @@ use Cake\Core\ObjectRegistry; use Cake\Event\EventDispatcherInterface; use Cake\Event\EventDispatcherTrait; +use Cake\ORM\Behavior; use Cake\ORM\Exception\MissingBehaviorException; use LogicException; @@ -117,11 +118,11 @@ protected function _resolveClassName($class) * and Cake\Core\ObjectRegistry::unload() * * @param string $class The classname that is missing. - * @param string $plugin The plugin the behavior is missing in. + * @param string|null $plugin The plugin the behavior is missing in. * @return void * @throws \Cake\ORM\Exception\MissingBehaviorException */ - protected function _throwMissingClassError($class, $plugin) + protected function _throwMissingClassError(string $class, ?string $plugin): void { throw new MissingBehaviorException([ 'class' => $class . 'Behavior', @@ -140,7 +141,7 @@ protected function _throwMissingClassError($class, $plugin) * @param array $config An array of config to use for the behavior. * @return \Cake\ORM\Behavior The constructed behavior class. */ - protected function _create($class, $alias, $config) + protected function _create($class, string $alias, array $config): Behavior { $instance = new $class($this->_table, $config); $enable = $config['enabled'] ?? true; From 5366711db8238a57cbb8e6c2e0819d5ef82ba111 Mon Sep 17 00:00:00 2001 From: stickler-ci Date: Mon, 30 Jul 2018 18:12:04 +0000 Subject: [PATCH 1235/2059] Fixing style errors. --- BehaviorRegistry.php | 1 - 1 file changed, 1 deletion(-) diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 216b56c6..ab6a78a3 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -19,7 +19,6 @@ use Cake\Core\ObjectRegistry; use Cake\Event\EventDispatcherInterface; use Cake\Event\EventDispatcherTrait; -use Cake\ORM\Behavior; use Cake\ORM\Exception\MissingBehaviorException; use LogicException; From 3adfd63a394f7d6c27d1063f81365d5f7c731aaf Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 2 Aug 2018 20:57:21 +0530 Subject: [PATCH 1236/2059] Use "iterable" type hint and is_iterable() check. --- Association/BelongsToMany.php | 9 +-------- Association/HasMany.php | 6 ++---- Marshaller.php | 2 +- Table.php | 2 +- 4 files changed, 5 insertions(+), 14 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 29ac1f1d..90a26b9d 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -28,7 +28,6 @@ use Closure; use InvalidArgumentException; use SplObjectStorage; -use Traversable; /** * Represents an M - N relationship where there exists a junction - or join - table @@ -684,7 +683,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) * @return \Cake\Datasource\EntityInterface|bool The parent entity after all links have been * created if no errors happened, false otherwise */ - protected function _saveTarget(EntityInterface $parentEntity, $entities, $options) + protected function _saveTarget(EntityInterface $parentEntity, iterable $entities, $options) { $joinAssociations = false; if (!empty($options['associated'][$this->_junctionProperty]['associated'])) { @@ -692,12 +691,6 @@ protected function _saveTarget(EntityInterface $parentEntity, $entities, $option } unset($options['associated'][$this->_junctionProperty]); - if (!(is_array($entities) || $entities instanceof Traversable)) { - $name = $this->getProperty(); - $message = sprintf('Could not save %s, it cannot be traversed', $name); - throw new InvalidArgumentException($message); - } - $table = $this->getTarget(); $original = $entities; $persisted = []; diff --git a/Association/HasMany.php b/Association/HasMany.php index f604c557..31ffc590 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -158,9 +158,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) $targetEntities = []; } - if (!is_array($targetEntities) && - !($targetEntities instanceof Traversable) - ) { + if (!is_iterable($targetEntities)) { $name = $this->getProperty(); $message = sprintf('Could not save %s, it cannot be traversed', $name); throw new InvalidArgumentException($message); @@ -199,7 +197,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) * @param array $options list of options accepted by `Table::save()`. * @return bool `true` on success, `false` otherwise. */ - protected function _saveTarget(array $foreignKeyReference, EntityInterface $parentEntity, $entities, array $options): bool + protected function _saveTarget(array $foreignKeyReference, EntityInterface $parentEntity, iterable $entities, array $options): bool { $foreignKey = array_keys($foreignKeyReference); $table = $this->getTarget(); diff --git a/Marshaller.php b/Marshaller.php index 214311cf..3e11a26e 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -626,7 +626,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) * @return \Cake\Datasource\EntityInterface[] * @see \Cake\ORM\Entity::$_accessible */ - public function mergeMany($entities, array $data, array $options = []): array + public function mergeMany(iterable $entities, array $data, array $options = []): array { $primary = (array)$this->_table->getPrimaryKey(); diff --git a/Table.php b/Table.php index 994f7e57..3d4ba2ed 100644 --- a/Table.php +++ b/Table.php @@ -2538,7 +2538,7 @@ public function patchEntity(EntityInterface $entity, array $data, array $options * You can use the `Model.beforeMarshal` event to modify request data * before it is converted into entities. */ - public function patchEntities($entities, array $data, array $options = []): array + public function patchEntities(iterable $entities, array $data, array $options = []): array { if (!isset($options['associated'])) { $options['associated'] = $this->_associations->keys(); From fa9f2aadcae0cba6dd244d8064a19274f8d34bb2 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 2 Aug 2018 23:30:17 +0530 Subject: [PATCH 1237/2059] Update docblocks to use type "iterator". --- Association/BelongsToMany.php | 2 +- Association/HasMany.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 90a26b9d..62c78b57 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -675,7 +675,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) * * @param \Cake\Datasource\EntityInterface $parentEntity the source entity containing the target * entities to be saved. - * @param array|\Traversable $entities list of entities to persist in target table and to + * @param iterable $entities list of entities to persist in target table and to * link to the parent entity * @param array $options list of options accepted by `Table::save()` * @throws \InvalidArgumentException if the property representing the association diff --git a/Association/HasMany.php b/Association/HasMany.php index 31ffc590..a9a76999 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -192,7 +192,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) * target entity, and the parent entity. * @param \Cake\Datasource\EntityInterface $parentEntity The source entity containing the target * entities to be saved. - * @param array|\Traversable $entities list of entities to persist in target table and to + * @param iterable $entities list of entities to persist in target table and to * link to the parent entity * @param array $options list of options accepted by `Table::save()`. * @return bool `true` on success, `false` otherwise. From 3e3eb5508733dc9cc2f4244ca299d5baceea27ef Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 3 Aug 2018 01:34:39 +0530 Subject: [PATCH 1238/2059] Fix CS error. --- Association/HasMany.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index a9a76999..02f99526 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -26,7 +26,6 @@ use Cake\ORM\Table; use Closure; use InvalidArgumentException; -use Traversable; /** * Represents an N - 1 relationship where the target side of the relationship From e89c5fa386056dfc8d702d6e0932547ca52623c9 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 5 Aug 2018 18:11:54 -0400 Subject: [PATCH 1239/2059] Fix inconsistent type causing php7.1 errors. --- Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Query.php b/Query.php index 618f7faf..43e39d8b 100644 --- a/Query.php +++ b/Query.php @@ -1207,7 +1207,7 @@ public function update($table = null) * @param string|null $table Unused parameter. * @return $this */ - public function delete($table = null) + public function delete(?string $table = null) { /** @var \Cake\ORM\Table $repository */ $repository = $this->getRepository(); From a8fd86adfa5d0b1062e4903cd4cd9d2d123fbc9f Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 10 Aug 2018 01:34:48 +0530 Subject: [PATCH 1240/2059] Fix few errors reported by phpstan level 3. --- Association.php | 2 +- Behavior/TimestampBehavior.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Association.php b/Association.php index de8b2aca..d5215020 100644 --- a/Association.php +++ b/Association.php @@ -392,7 +392,7 @@ public function getTarget(): Table * * @param array|callable $conditions list of conditions to be used * @see \Cake\Database\Query::where() for examples on the format of the array - * @return $this + * @return \Cake\ORM\Association */ public function setConditions($conditions) { diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index b064b34e..da0abee1 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -63,7 +63,7 @@ class TimestampBehavior extends Behavior /** * Current timestamp * - * @var \DateTimeInterface + * @var \Cake\I18n\Time */ protected $_ts; From a76aaf2cceca608962848d9c0533c10c4cf529cb Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 10 Aug 2018 23:57:00 +0530 Subject: [PATCH 1241/2059] Strict typing for translate behavior strategy. --- Behavior/Translate/EavStrategy.php | 10 +++++----- Behavior/Translate/ShadowTableStrategy.php | 6 +++--- Behavior/Translate/TranslateStrategyInterface.php | 9 +++++---- Behavior/Translate/TranslateStrategyTrait.php | 6 +++--- Behavior/TranslateBehavior.php | 4 ++-- 5 files changed, 18 insertions(+), 17 deletions(-) diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index 2239ec23..f2c0f289 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -1,4 +1,5 @@ getLocale(); @@ -222,7 +222,7 @@ public function beforeFind(EventInterface $event, Query $query, ArrayObject $opt * @param \ArrayObject $options the options passed to the save method * @return void */ - public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void + public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options) { $locale = $entity->get('_locale') ?: $this->getLocale(); $newOptions = [$this->translationTable->getAlias() => ['validate' => false]]; @@ -353,14 +353,14 @@ protected function rowMapper($results, $locale) foreach ($this->_config['fields'] as $field) { $name = $field . '_translation'; - $translation = isset($row[$name]) ? $row[$name] : null; + $translation = $row[$name] ?? null; if ($translation === null || $translation === false) { unset($row[$name]); continue; } - $content = isset($translation['content']) ? $translation['content'] : null; + $content = $translation['content'] ?? null; if ($content !== null) { $row[$field] = $content; } diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 78a6b2e3..dcf698b8 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -1,4 +1,5 @@ getLocale(); @@ -301,7 +301,7 @@ protected function traverseClause($query, $name = '', $config = []) * @param \ArrayObject $options the options passed to the save method. * @return void */ - public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void + public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options) { $locale = $entity->get('_locale') ?: $this->getLocale(); $newOptions = [$this->translationTable->getAlias() => ['validate' => false]]; diff --git a/Behavior/Translate/TranslateStrategyInterface.php b/Behavior/Translate/TranslateStrategyInterface.php index 786cd140..ad884244 100644 --- a/Behavior/Translate/TranslateStrategyInterface.php +++ b/Behavior/Translate/TranslateStrategyInterface.php @@ -1,4 +1,5 @@ locale = $locale; @@ -184,7 +184,7 @@ public function buildMarshalMap(Marshaller $marshaller, array $map, array $optio * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved * @return void */ - public function afterSave(EventInterface $event, EntityInterface $entity): void + public function afterSave(EventInterface $event, EntityInterface $entity) { $entity->unsetProperty('_i18n'); } diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index ef67a24e..3b31d2a1 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -215,7 +215,7 @@ public function buildMarshalMap(Marshaller $marshaller, array $map, array $optio * @link https://book.cakephp.org/3.0/en/orm/behaviors/translate.html#retrieving-one-language-without-using-i18n-locale * @link https://book.cakephp.org/3.0/en/orm/behaviors/translate.html#saving-in-another-language */ - public function setLocale(?string $locale) + public function setLocale(?string $locale): self { $this->getStrategy()->setLocale($locale); @@ -314,7 +314,7 @@ public function __call($method, $args) * @param \Cake\ORM\Table $table The table class to get a reference name for. * @return string */ - protected function referenceName(Table $table) + protected function referenceName(Table $table): string { $name = namespaceSplit(get_class($table)); $name = substr(end($name), 0, -5); From aebcd39ad5d6e3683ecd8cf1d2b9ce714bc374a0 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 11 Aug 2018 23:09:53 +0530 Subject: [PATCH 1242/2059] Remove return type hint for before* behavior methods. These methods can return values to be used as event result and short circuit the operation. Adding return type would prevent someone from extending and overriding these methods to return a non void value. --- Behavior/CounterCacheBehavior.php | 2 +- Behavior/TranslateBehavior.php | 4 ++-- Behavior/TreeBehavior.php | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 630abfeb..c93723aa 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -119,7 +119,7 @@ class CounterCacheBehavior extends Behavior * @param \ArrayObject $options The options for the query * @return void */ - public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void + public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options) { if (isset($options['ignoreCounterCache']) && $options['ignoreCounterCache'] === true) { return; diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 12b99b2e..d5d7a72a 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -210,7 +210,7 @@ public function setupFieldAssociations(array $fields, string $table, string $mod * @param \ArrayObject $options The options for the query * @return void */ - public function beforeFind(EventInterface $event, Query $query, ArrayObject $options): void + public function beforeFind(EventInterface $event, Query $query, ArrayObject $options) { $locale = $this->getLocale(); @@ -274,7 +274,7 @@ public function beforeFind(EventInterface $event, Query $query, ArrayObject $opt * @param \ArrayObject $options the options passed to the save method * @return void */ - public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void + public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options) { $locale = $entity->get('_locale') ?: $this->getLocale(); $newOptions = [$this->_translationTable->getAlias() => ['validate' => false]]; diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 45a12cd1..44cbdc84 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -94,7 +94,7 @@ public function initialize(array $config): void * @return void * @throws \RuntimeException if the parent to set for the node is invalid */ - public function beforeSave(EventInterface $event, EntityInterface $entity): void + public function beforeSave(EventInterface $event, EntityInterface $entity) { $isNew = $entity->isNew(); $config = $this->getConfig(); @@ -215,7 +215,7 @@ protected function _setChildrenLevel(EntityInterface $entity): void * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved * @return void */ - public function beforeDelete(EventInterface $event, EntityInterface $entity): void + public function beforeDelete(EventInterface $event, EntityInterface $entity) { $config = $this->getConfig(); $this->_ensureFields($entity); From 7736f186db615dbc58db51a3bcc1e2e342337ddb Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 12 Aug 2018 18:09:51 +0530 Subject: [PATCH 1243/2059] Remove return type from all event callback meethods. --- Behavior/CounterCacheBehavior.php | 2 +- Behavior/TranslateBehavior.php | 2 +- Behavior/TreeBehavior.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index c93723aa..e8a2175f 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -176,7 +176,7 @@ public function afterSave(EventInterface $event, EntityInterface $entity, ArrayO * @param \ArrayObject $options The options for the query * @return void */ - public function afterDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options): void + public function afterDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options) { if (isset($options['ignoreCounterCache']) && $options['ignoreCounterCache'] === true) { return; diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index d5d7a72a..08ef0183 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -369,7 +369,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved * @return void */ - public function afterSave(EventInterface $event, EntityInterface $entity): void + public function afterSave(EventInterface $event, EntityInterface $entity) { $entity->unsetProperty('_i18n'); } diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 44cbdc84..ba74b8e9 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -162,7 +162,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity) * @param \Cake\Datasource\EntityInterface $entity the entity that is going to be saved * @return void */ - public function afterSave(EventInterface $event, EntityInterface $entity): void + public function afterSave(EventInterface $event, EntityInterface $entity) { if (!$this->_config['level'] || $entity->isNew()) { return; From 4a1b4c4624fdba966d59b70af0d3cca1c2e4713c Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 22 Aug 2018 01:06:28 +0530 Subject: [PATCH 1244/2059] Add missing declarations for enabling strict types. --- Entity.php | 1 + Exception/MissingBehaviorException.php | 1 + Exception/MissingEntityException.php | 1 + Exception/MissingTableClassException.php | 1 + Exception/RolledbackTransactionException.php | 1 + 5 files changed, 5 insertions(+) diff --git a/Entity.php b/Entity.php index add57bc7..25380305 100644 --- a/Entity.php +++ b/Entity.php @@ -1,4 +1,5 @@ Date: Wed, 22 Aug 2018 01:16:19 +0530 Subject: [PATCH 1245/2059] Fix CS errors. --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 3d4ba2ed..057f1045 100644 --- a/Table.php +++ b/Table.php @@ -2716,7 +2716,7 @@ public function getSaveOptionsBuilder(array $options = []): SaveOptionsBuilder */ public function loadInto($entities, array $contain) { - return (new LazyEagerLoader)->loadInto($entities, $contain, $this); + return (new LazyEagerLoader())->loadInto($entities, $contain, $this); } /** From 242821224079a246af3dc5cc77b6c9d92dff784f Mon Sep 17 00:00:00 2001 From: ndm2 Date: Thu, 23 Aug 2018 11:54:51 +0200 Subject: [PATCH 1246/2059] Add missing callable support hints. --- Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Query.php b/Query.php index a79905c8..1f52d831 100644 --- a/Query.php +++ b/Query.php @@ -205,7 +205,7 @@ public function __construct($connection, $table) * all the fields in the schema of the table or the association will be added to * the select clause. * - * @param array|\Cake\Database\ExpressionInterface|string|\Cake\ORM\Table|\Cake\ORM\Association $fields fields + * @param array|\Cake\Database\ExpressionInterface|callable|string|\Cake\ORM\Table|\Cake\ORM\Association $fields fields * to be added to the list. * @param bool $overwrite whether to reset fields with passed list or not * @return $this From a9c11219ead96b727358c430317d24957786a333 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 24 Aug 2018 11:24:19 +0530 Subject: [PATCH 1247/2059] Don't use FQCN in type hints. --- Association.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association.php b/Association.php index d5215020..bbbe40f4 100644 --- a/Association.php +++ b/Association.php @@ -808,7 +808,7 @@ public function defaultRowValue(array $row, bool $joined): array * @see \Cake\ORM\Table::find() * @return \Cake\ORM\Query */ - public function find($type = null, array $options = []): \Cake\ORM\Query + public function find($type = null, array $options = []): Query { $type = $type ?: $this->getFinder(); list($type, $opts) = $this->_extractFinder($type); From e2a84abf92f9409fde97ae9dea16fe79145e3126 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sat, 25 Aug 2018 21:13:46 +0200 Subject: [PATCH 1248/2059] Improved the ORM Readme --- README.md | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/README.md b/README.md index 9cbcbac4..906264f3 100644 --- a/README.md +++ b/README.md @@ -161,8 +161,76 @@ $cacheConfig = [ 'prefix' => 'orm_', ], Cache::setConfig('_cake_model_', $cacheConfig); + +## Creating Custom Table and Entity Classes + +By default, the Cake ORM uses the `\Cake\ORM\Table` and `\Cake\ORM\Entity` classes to +intercat with the database. While using the default classes makes sense for +quick scripts and small applications, you will often want to use your own +classes for adding your custom logic. + +When using the ORM as a standalone package, you are free to choose where to +store these classes. For example, you could use the `Data` folder for this: + +```php +setEntityClass(Article::class); + $this->belongsTo('Users', ['className' => UsersTable::class]); + } +} +``` + +This table class is now setup to connect to the `articles` table in your +database and return instances of `Article` when fetching results. In order to +get an instance of this class, as shown before, you can use the `TableLocator`: + +```php +get('Articles', ['className' => ArticlesTable::class]); ``` +### Using Conventions-Based Loading + +It may get quite tedious having to specify each time the class name to load. So +the Cake ORM can do most of the work for you if you give it some configuration. + +The convention is to have all ORM related classes inside the `src/Model` folder, +that is the `Model` sub-namespace for your app. So you will usually have the +`src/Model/Table` and `src/Model/Entity` folders in your project. But first, we +need to inform Cake of the namespace your application lives in: + +```php + Date: Sat, 25 Aug 2018 21:43:24 +0200 Subject: [PATCH 1249/2059] Fixed mistake --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 906264f3..590ee4ff 100644 --- a/README.md +++ b/README.md @@ -177,8 +177,8 @@ store these classes. For example, you could use the `Data` folder for this: // in src/Data/Table/ArticlesTable.php namespace Acme\Data\Table; -use Acme\Data\Entity\Article.php; -use Acme\Data\Table\UsersTable.php; +use Acme\Data\Entity\Article; +use Acme\Data\Table\UsersTable; use Cake\ORM\Table; class ArticlesTable extends Table From 99e8306fae43686aebc6f0351df821c6341f938e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Lorenzo=20Rodr=C3=ADguez?= Date: Sun, 26 Aug 2018 09:25:00 +0200 Subject: [PATCH 1250/2059] Fixed typos --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 590ee4ff..a78d58e9 100644 --- a/README.md +++ b/README.md @@ -165,7 +165,7 @@ Cache::setConfig('_cake_model_', $cacheConfig); ## Creating Custom Table and Entity Classes By default, the Cake ORM uses the `\Cake\ORM\Table` and `\Cake\ORM\Entity` classes to -intercat with the database. While using the default classes makes sense for +interact with the database. While using the default classes makes sense for quick scripts and small applications, you will often want to use your own classes for adding your custom logic. @@ -186,7 +186,7 @@ class ArticlesTable extends Table public function initialize() { $this->setEntityClass(Article::class); - $this->belongsTo('Users', ['className' => UsersTable::class]); + $this->belongsTo('Users', ['className' => UsersTable::class]); } } ``` From 05dfe2978f0955fab066ef49d5ce32ce6527ce2b Mon Sep 17 00:00:00 2001 From: saeideng Date: Sun, 26 Aug 2018 12:29:31 +0430 Subject: [PATCH 1251/2059] use space instead of tab --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a78d58e9..6bdf5c2f 100644 --- a/README.md +++ b/README.md @@ -186,7 +186,7 @@ class ArticlesTable extends Table public function initialize() { $this->setEntityClass(Article::class); - $this->belongsTo('Users', ['className' => UsersTable::class]); + $this->belongsTo('Users', ['className' => UsersTable::class]); } } ``` From 9678cf56a7ec92b0bffcab059bd73f613cb819a6 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 26 Aug 2018 16:58:16 +0530 Subject: [PATCH 1252/2059] Fix CS error. --- EagerLoader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EagerLoader.php b/EagerLoader.php index 1280e1d8..8573429e 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -697,7 +697,7 @@ protected function _buildAssociationsMap(array $map, array $level, bool $matchin 'canBeJoined' => $canBeJoined, 'entityClass' => $instance->getTarget()->getEntityClass(), 'nestKey' => $canBeJoined ? $assoc : $meta->aliasPath(), - 'matching' => $forMatching !== null ? $forMatching : $matching, + 'matching' => $forMatching ?? $matching, 'targetProperty' => $meta->targetProperty(), ]; if ($canBeJoined && $associations) { From 8b7bc96f201ab40ebdb89363150598c03f810d17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Lorenzo=20Rodr=C3=ADguez?= Date: Sun, 26 Aug 2018 14:38:21 +0200 Subject: [PATCH 1253/2059] Fixed cod block --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6bdf5c2f..19ac4a00 100644 --- a/README.md +++ b/README.md @@ -161,6 +161,7 @@ $cacheConfig = [ 'prefix' => 'orm_', ], Cache::setConfig('_cake_model_', $cacheConfig); +``` ## Creating Custom Table and Entity Classes From b9137741dea319d431b1a7e130edf8a004861348 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 30 Aug 2018 20:24:15 +0530 Subject: [PATCH 1254/2059] Fix errors in ORM. --- Association/Loader/SelectLoader.php | 3 +-- LazyEagerLoader.php | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index f84f887f..cb1ae54c 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -301,10 +301,9 @@ protected function _addFilteringJoin(Query $query, $key, $subquery): Query $conditions = $this->_createTupleCondition($query, $key, $filter, '='); } else { $filter = current($filter); + $conditions = $query->newExpr([$key => $filter]); } - $conditions = $conditions ?: $query->newExpr([(string)$key => $filter]); - return $query->innerJoin( [$aliasedTable => $subquery], $conditions diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index a96c6461..6e220fcc 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -140,6 +140,7 @@ protected function _injectResults(iterable $objects, $results, array $associatio $injected = []; $properties = $this->_getPropertyMap($source, $associations); $primaryKey = (array)$source->getPrimaryKey(); + /** @param \Cake\Collection\CollectionInterface */ $results = $results ->indexBy(function ($e) use ($primaryKey) { return implode(';', $e->extract($primaryKey)); From 89c6eb06955e0d3c65e703937c02d3cf8c9bc3f2 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 31 Aug 2018 00:21:24 +0530 Subject: [PATCH 1255/2059] Remove/fix annotations. --- LazyEagerLoader.php | 1 - 1 file changed, 1 deletion(-) diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 6e220fcc..a96c6461 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -140,7 +140,6 @@ protected function _injectResults(iterable $objects, $results, array $associatio $injected = []; $properties = $this->_getPropertyMap($source, $associations); $primaryKey = (array)$source->getPrimaryKey(); - /** @param \Cake\Collection\CollectionInterface */ $results = $results ->indexBy(function ($e) use ($primaryKey) { return implode(';', $e->extract($primaryKey)); From 2f5ba631516ee039677f0304a8decdeba0631024 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 31 Aug 2018 00:52:31 +0530 Subject: [PATCH 1256/2059] Bump up to psalm level 7. --- Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Query.php b/Query.php index f705ac93..794594f8 100644 --- a/Query.php +++ b/Query.php @@ -1059,7 +1059,7 @@ public function sql(?ValueBinder $binder = null): string * * @return \Cake\Datasource\ResultSetInterface */ - protected function _execute(): ResultSetInterface + protected function _execute() { $this->triggerBeforeFind(); if ($this->_results) { From ea4b51d97bc379e32b7c0e00c5910c16357f98ca Mon Sep 17 00:00:00 2001 From: itosho Date: Sun, 2 Sep 2018 14:17:21 +0900 Subject: [PATCH 1257/2059] Fixed "commited" is a misspelling of "committed" --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 6729bd3a..065e635d 100644 --- a/Table.php +++ b/Table.php @@ -1848,7 +1848,7 @@ public function exists($conditions) * listeners will receive the entity and the options array as arguments. The type * of operation performed (insert or update) can be determined by checking the * entity's method `isNew`, true meaning an insert and false an update. - * - Model.afterSaveCommit: Will be triggered after the transaction is commited + * - Model.afterSaveCommit: Will be triggered after the transaction is committed * for atomic save, listeners will receive the entity and the options array * as arguments. * From 13d67b6d9b20ddaf082d5779f898255cccb09346 Mon Sep 17 00:00:00 2001 From: ndm2 Date: Thu, 20 Sep 2018 17:11:13 +0200 Subject: [PATCH 1258/2059] Add getter/setter for `Association::className()`. refs #9978 --- Association.php | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/Association.php b/Association.php index eda333f5..88f8ca44 100644 --- a/Association.php +++ b/Association.php @@ -334,14 +334,53 @@ public function cascadeCallbacks($cascadeCallbacks = null) return $this->getCascadeCallbacks(); } + /** + * Sets the class name of the target table object. + * + * @param string $className Class name to set. + * @return $this + * @throws \InvalidArgumentException In case the class name is set after the target table has been + * resolved, and it doesn't match the target table's class name. + */ + public function setClassName($className) + { + if ($this->_targetTable !== null && + get_class($this->_targetTable) !== App::className($className, 'Model/Table', 'Table') + ) { + throw new InvalidArgumentException( + 'The class name doesn\'t match the target table\'s class name.' + ); + } + + $this->_className = $className; + + return $this; + } + + /** + * Gets the class name of the target table object. + * + * @return string + */ + public function getClassName() + { + return $this->_className; + } + /** * The class name of the target table object * + * @deprecated 3.7.0 Use getClassName() instead. * @return string */ public function className() { - return $this->_className; + deprecationWarning( + get_called_class() . '::className() is deprecated. ' . + 'Use getClassName() instead.' + ); + + return $this->getClassName(); } /** From 277914388f29461a8da66ce099ddf6a552c7610b Mon Sep 17 00:00:00 2001 From: mscherer Date: Fri, 28 Sep 2018 12:56:48 +0200 Subject: [PATCH 1259/2059] Fix phpstan reported doc block and casting issues. --- ResultSet.php | 2 +- Table.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ResultSet.php b/ResultSet.php index 18d4a362..9f365e25 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -65,7 +65,7 @@ class ResultSet implements ResultSetInterface /** * Default table instance * - * @var \Cake\ORM\Table + * @var \Cake\ORM\Table|\Cake\Datasource\RepositoryInterface */ protected $_defaultTable; diff --git a/Table.php b/Table.php index 065e635d..3d7c1f89 100644 --- a/Table.php +++ b/Table.php @@ -494,7 +494,7 @@ public function registryAlias($registryAlias = null) /** * Sets the connection instance. * - * @param \Cake\Database\Connection|\Cake\Datasource\ConnectionInterface $connection The connection instance + * @param \Cake\Database\Connection $connection The connection instance * @return $this */ public function setConnection(ConnectionInterface $connection) From 1f36cee178bec4d60a8bb3beac7f22ef04b7aa06 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 30 Sep 2018 20:45:00 +0530 Subject: [PATCH 1260/2059] Remove deprecated method. --- Association.php | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/Association.php b/Association.php index 013705fe..43a62e4f 100644 --- a/Association.php +++ b/Association.php @@ -327,22 +327,6 @@ public function getClassName(): string return $this->_className; } - /** - * The class name of the target table object - * - * @deprecated 3.7.0 Use getClassName() instead. - * @return string - */ - public function className(): string - { - deprecationWarning( - get_called_class() . '::className() is deprecated. ' . - 'Use getClassName() instead.' - ); - - return $this->getClassName(); - } - /** * Sets the table instance for the source side of the association. * From da94062571e0018a91469d526a55100a9ea0dd9f Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 1 Oct 2018 11:57:49 +0530 Subject: [PATCH 1261/2059] Fix typehint. --- Association.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Association.php b/Association.php index 43a62e4f..cd6a4f5b 100644 --- a/Association.php +++ b/Association.php @@ -320,9 +320,9 @@ public function setClassName(string $className) /** * Gets the class name of the target table object. * - * @return string + * @return string|null */ - public function getClassName(): string + public function getClassName(): ?string { return $this->_className; } From 7beefa58bb54f005acc9874c65d2f876950adb53 Mon Sep 17 00:00:00 2001 From: nojimage Date: Tue, 2 Oct 2018 16:57:46 +0900 Subject: [PATCH 1262/2059] In BelongsToMany, linking entities will set the junction table registry alias --- Association/BelongsToMany.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 62e916e8..9352e829 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -825,12 +825,12 @@ protected function _saveLinks(EntityInterface $sourceEntity, $targetEntities, $o $targetPrimaryKey = (array)$target->getPrimaryKey(); $bindingKey = (array)$this->getBindingKey(); $jointProperty = $this->_junctionProperty; - $junctionAlias = $junction->getAlias(); + $junctionRegistryAlias = $junction->getRegistryAlias(); foreach ($targetEntities as $e) { $joint = $e->get($jointProperty); if (!$joint || !($joint instanceof EntityInterface)) { - $joint = new $entityClass([], ['markNew' => true, 'source' => $junctionAlias]); + $joint = new $entityClass([], ['markNew' => true, 'source' => $junctionRegistryAlias]); } $sourceKeys = array_combine($foreignKey, $sourceEntity->extract($bindingKey)); $targetKeys = array_combine($assocForeignKey, $e->extract($targetPrimaryKey)); From 716ced97d3b9f0eabd5258b7ce8dd0d0b99a6490 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 10 Oct 2018 01:56:37 +0530 Subject: [PATCH 1263/2059] Remove deprecated TableRegistry methods and update tests. --- TableRegistry.php | 64 ----------------------------------------------- 1 file changed, 64 deletions(-) diff --git a/TableRegistry.php b/TableRegistry.php index c924ad97..44946381 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -64,26 +64,6 @@ class TableRegistry */ protected static $_defaultLocatorClass = 'Cake\ORM\Locator\TableLocator'; - /** - * Sets and returns a singleton instance of LocatorInterface implementation. - * - * @param \Cake\ORM\Locator\LocatorInterface|null $locator Instance of a locator to use. - * @return \Cake\ORM\Locator\LocatorInterface - * @deprecated 3.5.0 Use getTableLocator()/setTableLocator() instead. - */ - public static function locator(?LocatorInterface $locator = null): LocatorInterface - { - deprecationWarning( - 'TableRegistry::locator() is deprecated. ' . - 'Use setTableLocator()/getTableLocator() instead.' - ); - if ($locator) { - static::setTableLocator($locator); - } - - return static::getTableLocator(); - } - /** * Returns a singleton instance of LocatorInterface implementation. * @@ -109,33 +89,6 @@ public static function setTableLocator(LocatorInterface $tableLocator): void static::$_locator = $tableLocator; } - /** - * Stores a list of options to be used when instantiating an object - * with a matching alias. - * - * @param string|null $alias Name of the alias - * @param array|null $options list of options for the alias - * @return array The config data. - * @deprecated 3.6.0 Use \Cake\ORM\Locator\TableLocator::getConfig()/setConfig() instead. - */ - public static function config(?string $alias = null, ?array $options = null): array - { - deprecationWarning( - 'TableRegistry::config() is deprecated. ' . - 'Use \Cake\ORM\Locator\TableLocator::getConfig()/setConfig() instead.' - ); - - if ($alias !== null) { - if (is_string($alias) && $options === null) { - return static::getTableLocator()->getConfig($alias); - } - - static::getTableLocator()->setConfig($alias, $options); - } - - return static::getTableLocator()->getConfig($alias); - } - /** * Get a table instance from the registry. * @@ -198,21 +151,4 @@ public static function clear(): void { static::getTableLocator()->clear(); } - - /** - * Proxy for static calls on a locator. - * - * @param string $name Method name. - * @param array $arguments Method arguments. - * @return mixed - */ - public static function __callStatic($name, $arguments) - { - deprecationWarning( - 'TableRegistry::' . $name . '() is deprecated. ' . - 'Use \Cake\ORM\Locator\TableLocator::' . $name . '() instead.' - ); - - return static::getTableLocator()->$name(...$arguments); - } } From b3586d5dd0d74f602e5afc9ff94b04c5c23f4ada Mon Sep 17 00:00:00 2001 From: mscherer Date: Tue, 30 Oct 2018 18:27:47 +0100 Subject: [PATCH 1264/2059] Fix class name usage to const --- Table.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index 3d7c1f89..40aaa681 100644 --- a/Table.php +++ b/Table.php @@ -150,7 +150,14 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc * * @var string */ - const RULES_CLASS = 'Cake\ORM\RulesChecker'; + const RULES_CLASS = RulesChecker::class; + + /** + * The IsUnique class name that is used. + * + * @var string + */ + const IS_UNIQUE_CLASS = IsUnique::class; /** * Name of the table as it can be found in the database @@ -2797,7 +2804,8 @@ public function validateUnique($value, array $options, array $context = null) return false; } } - $rule = new IsUnique($fields, $options); + $class = static::IS_UNIQUE_CLASS; + $rule = new $class($fields, $options); return $rule($entity, ['repository' => $this]); } From 9b4b7ff503d4ef5c058c214c9069fbf1e2d007b4 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 1 Nov 2018 19:10:18 +0530 Subject: [PATCH 1265/2059] Add Cake\ORM\Query::disableHydration(). --- Behavior/TranslateBehavior.php | 2 +- Behavior/TreeBehavior.php | 2 +- Query.php | 16 ++++++++++++++++ Table.php | 2 +- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 77f9dcce..bede13e2 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -752,7 +752,7 @@ protected function _findExistingTranslations($ruleSet) $query = $association->find() ->select(['id', 'num' => 0]) ->where(current($ruleSet)) - ->enableHydration(false) + ->disableHydration() ->enableBufferedResults(false); unset($ruleSet[0]); diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 70714be2..44b32daa 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -837,7 +837,7 @@ protected function _recoverTree($counter = 0, $parentId = null, $level = -1) ->select([$aliasedPrimaryKey]) ->where([$this->_table->aliasField($parent) . ' IS' => $parentId]) ->order($order) - ->enableHydration(false); + ->disableHydration(); $leftCounter = $counter; $nextLevel = $level + 1; diff --git a/Query.php b/Query.php index 1f52d831..01dcab2e 100644 --- a/Query.php +++ b/Query.php @@ -1010,6 +1010,22 @@ public function enableHydration($enable = true) return $this; } + /** + * Disable hydrating entities. + * + * Disabling hydration will cause array results to be returned for the query + * instead of entities. + * + * @return $this + */ + public function disableHydration() + { + $this->_dirty(); + $this->_hydrate = false; + + return $this; + } + /** * Returns the current hydration mode. * diff --git a/Table.php b/Table.php index 03919431..ea9087ed 100644 --- a/Table.php +++ b/Table.php @@ -1797,7 +1797,7 @@ public function exists($conditions) ->select(['existing' => 1]) ->where($conditions) ->limit(1) - ->enableHydration(false) + ->disableHydration() ->toArray() ); } From 2b2c3eb38bf1437995363e1be646edafede86323 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 1 Nov 2018 19:21:14 +0530 Subject: [PATCH 1266/2059] Add Cake\Database\Query::disableBufferedResults(). --- Behavior/TranslateBehavior.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index bede13e2..9d3219ad 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -334,7 +334,7 @@ public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $o 'foreign_key' => $key, 'model' => $model ]) - ->enableBufferedResults(false) + ->disableBufferedResults() ->all() ->indexBy('field'); @@ -753,7 +753,7 @@ protected function _findExistingTranslations($ruleSet) ->select(['id', 'num' => 0]) ->where(current($ruleSet)) ->disableHydration() - ->enableBufferedResults(false); + ->disableBufferedResults(); unset($ruleSet[0]); foreach ($ruleSet as $i => $conditions) { From 1b5b25a85884c17689b1a37b5ddc573c7225c148 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 2 Nov 2018 00:05:43 +0530 Subject: [PATCH 1267/2059] Add EagerLoader::disableAutoFields() and Query::disableAutoFields(). --- Association/Loader/SelectLoader.php | 2 +- EagerLoader.php | 12 ++++++++++++ Query.php | 12 ++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 2c01816d..5bd2dd2c 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -396,7 +396,7 @@ protected function _linkField($options) protected function _buildSubquery($query) { $filterQuery = clone $query; - $filterQuery->enableAutoFields(false); + $filterQuery->disableAutoFields(); $filterQuery->mapReduce(null, null, true); $filterQuery->formatResults(null, true); $filterQuery->contain([], true); diff --git a/EagerLoader.php b/EagerLoader.php index 749f4366..510f2ebe 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -209,6 +209,18 @@ public function enableAutoFields($enable = true) return $this; } + /** + * Disable auto loading fields of contained associations. + * + * @return $this + */ + public function disableAutoFields() + { + $this->_autoFields = false; + + return $this; + } + /** * Gets whether or not contained associations will load fields automatically. * diff --git a/Query.php b/Query.php index 01dcab2e..941e981a 100644 --- a/Query.php +++ b/Query.php @@ -1374,6 +1374,18 @@ public function enableAutoFields($value = true) return $this; } + /** + * Disables automatically appending fields. + * + * @return $this + */ + public function disableAutoFields() + { + $this->_autoFields = false; + + return $this; + } + /** * Gets whether or not the ORM should automatically append fields. * From 1ff6e1f0a048e01b5b55da33d0256a8b0a8a8bfe Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 4 Nov 2018 16:27:11 -0500 Subject: [PATCH 1268/2059] Remove deprecated code from 4.x Remove code that has been deprecated in 3.6. Adding notes to methods that will survive until 5.0 --- TableRegistry.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/TableRegistry.php b/TableRegistry.php index 44946381..c0c723d5 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -97,7 +97,7 @@ public static function setTableLocator(LocatorInterface $tableLocator): void * @param string $alias The alias name you want to get. * @param array $options The options you want to build the table with. * @return \Cake\ORM\Table - * @deprecated 3.6.0 Use \Cake\ORM\Locator\TableLocator::get() instead. + * @deprecated 3.6.0 Use \Cake\ORM\Locator\TableLocator::get() instead. Will be removed in 5.0 */ public static function get(string $alias, array $options = []): Table { @@ -109,7 +109,7 @@ public static function get(string $alias, array $options = []): Table * * @param string $alias The alias to check for. * @return bool - * @deprecated 3.6.0 Use \Cake\ORM\Locator\TableLocator::exists() instead. + * @deprecated 3.6.0 Use \Cake\ORM\Locator\TableLocator::exists() instead. Will be removed in 5.0 */ public static function exists(string $alias): bool { @@ -122,7 +122,7 @@ public static function exists(string $alias): bool * @param string $alias The alias to set. * @param \Cake\ORM\Table $object The table to set. * @return \Cake\ORM\Table - * @deprecated 3.6.0 Use \Cake\ORM\Locator\TableLocator::set() instead. + * @deprecated 3.6.0 Use \Cake\ORM\Locator\TableLocator::set() instead. Will be removed in 5.0 */ public static function set(string $alias, Table $object): Table { @@ -134,7 +134,7 @@ public static function set(string $alias, Table $object): Table * * @param string $alias The alias to remove. * @return void - * @deprecated 3.6.0 Use \Cake\ORM\Locator\TableLocator::remove() instead. + * @deprecated 3.6.0 Use \Cake\ORM\Locator\TableLocator::remove() instead. Will be removed in 5.0 */ public static function remove(string $alias): void { @@ -145,7 +145,7 @@ public static function remove(string $alias): void * Clears the registry of configuration and instances. * * @return void - * @deprecated 3.6.0 Use \Cake\ORM\Locator\TableLocator::clear() instead. + * @deprecated 3.6.0 Use \Cake\ORM\Locator\TableLocator::clear() instead. Will be removed in 5.0 */ public static function clear(): void { From 373198de07133a9792ae40e04f7e0b5fc8c25de3 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 4 Nov 2018 21:02:33 -0500 Subject: [PATCH 1269/2059] Fix PHPCS errors. --- Table.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index 74e0bd77..0c094ba8 100644 --- a/Table.php +++ b/Table.php @@ -151,14 +151,14 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc * * @var string */ - const RULES_CLASS = RulesChecker::class; + public const RULES_CLASS = RulesChecker::class; /** * The IsUnique class name that is used. * * @var string */ - const IS_UNIQUE_CLASS = IsUnique::class; + public const IS_UNIQUE_CLASS = IsUnique::class; /** * Name of the table as it can be found in the database From 8141bcb898e0417dbd2d0ded482e068d403bc25a Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 4 Nov 2018 21:13:44 -0500 Subject: [PATCH 1270/2059] Fix psalm failure. --- Table.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Table.php b/Table.php index 0c094ba8..8839588f 100644 --- a/Table.php +++ b/Table.php @@ -2613,6 +2613,7 @@ public function validateUnique($value, array $options, ?array $context = null): } } $class = static::IS_UNIQUE_CLASS; + /** @var \Cake\ORM\Rule\IsUnique $rule */ $rule = new $class($fields, $options); return $rule($entity, ['repository' => $this]); From 2d8dcf39a77fec8f85f64789794d83e026f07ea2 Mon Sep 17 00:00:00 2001 From: mscherer Date: Tue, 13 Nov 2018 14:13:42 +0100 Subject: [PATCH 1271/2059] Resolves the first part of object variable naming cleanup. --- Behavior.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Behavior.php b/Behavior.php index 3693a712..b14f5036 100644 --- a/Behavior.php +++ b/Behavior.php @@ -307,8 +307,8 @@ public function implementedEvents(): array * ] * ``` * - * With the above example, a call to `$Table->find('this')` will call `$Behavior->findThis()` - * and a call to `$Table->find('alias')` will call `$Behavior->findMethodName()` + * With the above example, a call to `$table->find('this')` will call `$behavior->findThis()` + * and a call to `$table->find('alias')` will call `$behavior->findMethodName()` * * It is recommended, though not required, to define implementedFinders in the config property * of child classes such that it is not necessary to use reflections to derive the available @@ -335,12 +335,12 @@ public function implementedFinders(): array * ``` * [ * 'method' => 'method', - * 'aliasedmethod' => 'somethingElse' + * 'aliasedMethod' => 'somethingElse' * ] * ``` * - * With the above example, a call to `$Table->method()` will call `$Behavior->method()` - * and a call to `$Table->aliasedmethod()` will call `$Behavior->somethingElse()` + * With the above example, a call to `$table->method()` will call `$behavior->method()` + * and a call to `$table->aliasedMethod()` will call `$behavior->somethingElse()` * * It is recommended, though not required, to define implementedFinders in the config property * of child classes such that it is not necessary to use reflections to derive the available From 1445b67c4dc34e4d6f4ad2dfc7ecfd99ff0a75fe Mon Sep 17 00:00:00 2001 From: Ian den Hartog Date: Fri, 16 Nov 2018 23:51:58 +0100 Subject: [PATCH 1272/2059] Update some @methods in Query --- Query.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Query.php b/Query.php index 4deab1e3..e4641f1f 100644 --- a/Query.php +++ b/Query.php @@ -37,7 +37,7 @@ * * @see \Cake\Collection\CollectionInterface For a full description of the collection methods supported by this class * @method \Cake\Collection\CollectionInterface each(callable $c) Passes each of the query results to the callable - * @method \Cake\Collection\CollectionInterface sortBy($callback, $dir = SORT_DESC, $type = \SORT_NUMERIC) Sorts the query with the callback + * @method \Cake\Collection\CollectionInterface sortBy($callback, int $dir = SORT_DESC, int $type = \SORT_NUMERIC) Sorts the query with the callback * @method \Cake\Collection\CollectionInterface filter(callable $c = null) Keeps the results using passing the callable test * @method \Cake\Collection\CollectionInterface reject(callable $c) Removes the results passing the callable test * @method bool every(callable $c) Returns true if all the results pass the callable test @@ -45,15 +45,15 @@ * @method \Cake\Collection\CollectionInterface map(callable $c) Modifies each of the results using the callable * @method mixed reduce(callable $c, $zero = null) Folds all the results into a single value using the callable. * @method \Cake\Collection\CollectionInterface extract($field) Extracts a single column from each row - * @method mixed max($field, $type = SORT_NUMERIC) Returns the maximum value for a single column in all the results. - * @method mixed min($field, $type = SORT_NUMERIC) Returns the minimum value for a single column in all the results. + * @method mixed max($field, int $type = SORT_NUMERIC) Returns the maximum value for a single column in all the results. + * @method mixed min($field, int $type = SORT_NUMERIC) Returns the minimum value for a single column in all the results. * @method \Cake\Collection\CollectionInterface groupBy(string|callable $field) In-memory group all results by the value of a column. * @method \Cake\Collection\CollectionInterface indexBy(string|callable $field) Returns the results indexed by the value of a column. - * @method int countBy(string|callable $field) Returns the number of unique values for a column + * @method \Cake\Collection\CollectionInterface countBy(string|callable $field) Returns the number of unique values for a column * @method float sumOf(string|callable $field) Returns the sum of all values for a single column * @method \Cake\Collection\CollectionInterface shuffle() In-memory randomize the order the results are returned - * @method \Cake\Collection\CollectionInterface sample($size = 10) In-memory shuffle the results and return a subset of them. - * @method \Cake\Collection\CollectionInterface take($size = 1, $from = 0) In-memory limit and offset for the query results. + * @method \Cake\Collection\CollectionInterface sample(int $size = 10) In-memory shuffle the results and return a subset of them. + * @method \Cake\Collection\CollectionInterface take(int $size = 1, int $from = 0) In-memory limit and offset for the query results. * @method \Cake\Collection\CollectionInterface skip(int $howMany) Skips some rows from the start of the query result. * @method mixed last() Return the last row of the query result * @method \Cake\Collection\CollectionInterface append(array|\Traversable $items) Appends more rows to the result of the query. @@ -68,7 +68,7 @@ * then the second results and so on. * @method \Cake\Collection\CollectionInterface zipWith($collections, callable $callable) Returns each of the results out of calling $c * with the first rows of the query and each of the items, then the second rows and so on. - * @method \Cake\Collection\CollectionInterface chunk($size) Groups the results in arrays of $size rows each. + * @method \Cake\Collection\CollectionInterface chunk(int $size) Groups the results in arrays of $size rows each. * @method bool isEmpty() Returns true if this query found no results. * @mixin \Cake\Datasource\QueryTrait */ From 2c6815f41a89499f9a7918a5b4e8914c12d817b6 Mon Sep 17 00:00:00 2001 From: Ian den Hartog Date: Fri, 16 Nov 2018 23:53:32 +0100 Subject: [PATCH 1273/2059] Fix return type in of `countBy` @method annotation in Query --- Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Query.php b/Query.php index 1f52d831..5e7cdbec 100644 --- a/Query.php +++ b/Query.php @@ -45,7 +45,7 @@ * @method mixed min($field, $type = SORT_NUMERIC) Returns the minimum value for a single column in all the results. * @method \Cake\Collection\CollectionInterface groupBy(string|callable $field) In-memory group all results by the value of a column. * @method \Cake\Collection\CollectionInterface indexBy(string|callable $field) Returns the results indexed by the value of a column. - * @method int countBy(string|callable $field) Returns the number of unique values for a column + * @method \Cake\Collection\CollectionInterface countBy(string|callable $field) Returns the number of unique values for a column * @method float sumOf(string|callable $field) Returns the sum of all values for a single column * @method \Cake\Collection\CollectionInterface shuffle() In-memory randomize the order the results are returned * @method \Cake\Collection\CollectionInterface sample($size = 10) In-memory shuffle the results and return a subset of them. From 2659b7d194afc5a2f5979a5a711f7605f82c7a4e Mon Sep 17 00:00:00 2001 From: mscherer Date: Fri, 23 Nov 2018 00:22:53 +0100 Subject: [PATCH 1274/2059] Raise phpstan to level 3. WIP --- Association/BelongsToMany.php | 6 +++--- Association/HasMany.php | 4 ++-- Association/HasOne.php | 2 +- Behavior/TreeBehavior.php | 2 +- Exception/PersistenceFailedException.php | 2 +- Locator/TableLocator.php | 8 ++++---- Query.php | 4 ++-- Table.php | 8 ++++---- 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index ad6e1fdc..c6a815ee 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -234,7 +234,7 @@ public function getSort() } /** - * {@inheritDoc} + * @inheritDoc */ public function defaultRowValue(array $row, bool $joined): array { @@ -447,7 +447,7 @@ public function attachTo(Query $query, array $options = []): void } /** - * {@inheritDoc} + * @inheritDoc */ protected function _appendNotMatching(QueryInterface $query, array $options): void { @@ -916,7 +916,7 @@ function () use ($sourceEntity, $targetEntities, $options): void { } /** - * {@inheritDoc} + * @inheritDoc */ public function setConditions($conditions) { diff --git a/Association/HasMany.php b/Association/HasMany.php index 02f99526..7ee90606 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -600,7 +600,7 @@ public function getSort() } /** - * {@inheritDoc} + * @inheritDoc */ public function defaultRowValue(array $row, bool $joined): array { @@ -651,7 +651,7 @@ public function eagerLoader(array $options): Closure } /** - * {@inheritDoc} + * @inheritDoc */ public function cascadeDelete(EntityInterface $entity, array $options = []): bool { diff --git a/Association/HasOne.php b/Association/HasOne.php index e438e5a4..3baa4a09 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -145,7 +145,7 @@ public function eagerLoader(array $options): Closure } /** - * {@inheritDoc} + * @inheritDoc */ public function cascadeDelete(EntityInterface $entity, array $options = []): bool { diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index fa15fc87..6563d7ba 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -76,7 +76,7 @@ class TreeBehavior extends Behavior ]; /** - * {@inheritDoc} + * @inheritDoc */ public function initialize(array $config): void { diff --git a/Exception/PersistenceFailedException.php b/Exception/PersistenceFailedException.php index bc13b851..a3b434e3 100644 --- a/Exception/PersistenceFailedException.php +++ b/Exception/PersistenceFailedException.php @@ -29,7 +29,7 @@ class PersistenceFailedException extends Exception protected $_entity; /** - * {@inheritDoc} + * @inheritDoc */ protected $_messageTemplate = 'Entity %s failure.'; diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index e8ef8e26..47dfbace 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -224,7 +224,7 @@ protected function _create(array $options): Table } /** - * {@inheritDoc} + * @inheritDoc */ public function exists(string $alias): bool { @@ -232,7 +232,7 @@ public function exists(string $alias): bool } /** - * {@inheritDoc} + * @inheritDoc */ public function set(string $alias, Table $object): Table { @@ -240,7 +240,7 @@ public function set(string $alias, Table $object): Table } /** - * {@inheritDoc} + * @inheritDoc */ public function clear(): void { @@ -263,7 +263,7 @@ public function genericInstances(): array } /** - * {@inheritDoc} + * @inheritDoc */ public function remove(string $alias): void { diff --git a/Query.php b/Query.php index e4641f1f..c3d4028c 100644 --- a/Query.php +++ b/Query.php @@ -1057,7 +1057,7 @@ public function triggerBeforeFind(): void } /** - * {@inheritDoc} + * @inheritDoc */ public function sql(?ValueBinder $binder = null): string { @@ -1272,7 +1272,7 @@ public function __call($method, $arguments) } /** - * {@inheritDoc} + * @inheritDoc */ public function __debugInfo() { diff --git a/Table.php b/Table.php index 8839588f..9145c0bf 100644 --- a/Table.php +++ b/Table.php @@ -1574,7 +1574,7 @@ public function query(): Query } /** - * {@inheritDoc} + * @inheritDoc */ public function updateAll($fields, $conditions): int { @@ -1589,7 +1589,7 @@ public function updateAll($fields, $conditions): int } /** - * {@inheritDoc} + * @inheritDoc */ public function deleteAll($conditions): int { @@ -1603,7 +1603,7 @@ public function deleteAll($conditions): int } /** - * {@inheritDoc} + * @inheritDoc */ public function exists($conditions): bool { @@ -2729,7 +2729,7 @@ public function loadInto($entities, array $contain) } /** - * {@inheritDoc} + * @inheritDoc */ protected function validationMethodExists(string $method): bool { From bc8a1065a374e949655cb9d3ac2ddf974bd6b517 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 26 Nov 2018 13:04:30 +0530 Subject: [PATCH 1275/2059] Limit line lenghts to 120 character for code. This is a soft limit set by PSR12 which is good to follow. --- Association.php | 3 ++- Association/BelongsToMany.php | 8 +++++-- Association/HasMany.php | 25 ++++++++++++++------ AssociationCollection.php | 23 ++++++++++++++---- Behavior.php | 6 ++++- Behavior/CounterCacheBehavior.php | 8 +++++-- Behavior/TimestampBehavior.php | 7 +++--- Behavior/TranslateBehavior.php | 4 +++- EagerLoader.php | 8 +++++-- Exception/RolledbackTransactionException.php | 1 + Query.php | 2 +- ResultSet.php | 6 +++-- Rule/ExistsIn.php | 4 ++-- SaveOptionsBuilder.php | 6 ++++- 14 files changed, 81 insertions(+), 30 deletions(-) diff --git a/Association.php b/Association.php index cd6a4f5b..dc91591e 100644 --- a/Association.php +++ b/Association.php @@ -392,7 +392,8 @@ public function getTarget(): Table if (!$this->_targetTable instanceof $className) { $errorMessage = '%s association "%s" of type "%s" to "%s" doesn\'t match the expected class "%s". '; - $errorMessage .= 'You can\'t have an association of the same name with a different target "className" option anywhere in your app.'; + $errorMessage .= 'You can\'t have an association of the same name with a different target '; + $errorMessage .= '"className" option anywhere in your app.'; throw new RuntimeException(sprintf( $errorMessage, diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index ad6e1fdc..c0148e97 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1187,8 +1187,12 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { * @param array $options list of options accepted by `Table::delete()` * @return array */ - protected function _diffLinks(Query $existing, array $jointEntities, array $targetEntities, array $options = []): array - { + protected function _diffLinks( + Query $existing, + array $jointEntities, + array $targetEntities, + array $options = [] + ): array { $junction = $this->junction(); $target = $this->getTarget(); $belongsTo = $junction->getAssociation($target->getAlias()); diff --git a/Association/HasMany.php b/Association/HasMany.php index 02f99526..89249955 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -196,8 +196,12 @@ public function saveAssociated(EntityInterface $entity, array $options = []) * @param array $options list of options accepted by `Table::save()`. * @return bool `true` on success, `false` otherwise. */ - protected function _saveTarget(array $foreignKeyReference, EntityInterface $parentEntity, iterable $entities, array $options): bool - { + protected function _saveTarget( + array $foreignKeyReference, + EntityInterface $parentEntity, + iterable $entities, + array $options + ): bool { $foreignKey = array_keys($foreignKeyReference); $table = $this->getTarget(); $original = $entities; @@ -435,8 +439,8 @@ public function replace(EntityInterface $sourceEntity, array $targetEntities, ar } /** - * Deletes/sets null the related objects according to the dependency between source and targets and foreign key nullability - * Skips deleting records present in $remainingEntities + * Deletes/sets null the related objects according to the dependency between source and targets + * and foreign key nullability. Skips deleting records present in $remainingEntities * * @param array $foreignKeyReference The foreign key reference defining the link between the * target entity, and the parent entity. @@ -446,8 +450,13 @@ public function replace(EntityInterface $sourceEntity, array $targetEntities, ar * @param array $options list of options accepted by `Table::delete()` * @return bool success */ - protected function _unlinkAssociated(array $foreignKeyReference, EntityInterface $entity, Table $target, array $remainingEntities = [], array $options = []): bool - { + protected function _unlinkAssociated( + array $foreignKeyReference, + EntityInterface $entity, + Table $target, + array $remainingEntities = [], + array $options = [] + ): bool { $primaryKey = (array)$target->getPrimaryKey(); $exclusions = new Collection($remainingEntities); $exclusions = $exclusions->map( @@ -478,7 +487,9 @@ function ($v) { /** * Deletes/sets null the related objects matching $conditions. - * The action which is taken depends on the dependency between source and targets and also on foreign key nullability + * + * The action which is taken depends on the dependency between source and + * targets and also on foreign key nullability. * * @param array $foreignKey array of foreign key properties * @param \Cake\ORM\Table $target The associated table diff --git a/AssociationCollection.php b/AssociationCollection.php index ca882efc..4e12bb12 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -89,7 +89,11 @@ public function load(string $className, string $associated, array $options = []) $association = new $className($associated, $options); if (!$association instanceof Association) { - $message = sprintf('The association must extend `%s` class, `%s` given.', Association::class, get_class($association)); + $message = sprintf( + 'The association must extend `%s` class, `%s` given.', + Association::class, + get_class($association) + ); throw new InvalidArgumentException($message); } @@ -254,8 +258,13 @@ public function saveChildren(Table $table, EntityInterface $entity, array $assoc * @return bool Success * @throws \InvalidArgumentException When an unknown alias is used. */ - protected function _saveAssociations(Table $table, EntityInterface $entity, array $associations, array $options, bool $owningSide): bool - { + protected function _saveAssociations( + Table $table, + EntityInterface $entity, + array $associations, + array $options, + bool $owningSide + ): bool { unset($options['associated']); foreach ($associations as $alias => $nested) { if (is_int($alias)) { @@ -291,8 +300,12 @@ protected function _saveAssociations(Table $table, EntityInterface $entity, arra * @param array $options Original options * @return bool Success */ - protected function _save(Association $association, EntityInterface $entity, array $nested, array $options): bool - { + protected function _save( + Association $association, + EntityInterface $entity, + array $nested, + array $options + ): bool { if (!$entity->isDirty($association->getProperty())) { return true; } diff --git a/Behavior.php b/Behavior.php index b14f5036..077140d5 100644 --- a/Behavior.php +++ b/Behavior.php @@ -241,7 +241,11 @@ public function verifyConfig(): void foreach ($this->_config[$key] as $method) { if (!is_callable([$this, $method])) { - throw new Exception(sprintf('The method %s is not callable on class %s', $method, get_class($this))); + throw new Exception(sprintf( + 'The method %s is not callable on class %s', + $method, + get_class($this) + )); } } } diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index e8a2175f..25f97cd1 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -210,8 +210,12 @@ protected function _processAssociations(EventInterface $event, EntityInterface $ * @return void * @throws \RuntimeException If invalid callable is passed. */ - protected function _processAssociation(EventInterface $event, EntityInterface $entity, Association $assoc, array $settings): void - { + protected function _processAssociation( + EventInterface $event, + EntityInterface $entity, + Association $assoc, + array $settings + ): void { $foreignKeys = (array)$assoc->getForeignKey(); $primaryKeys = (array)$assoc->getBindingKey(); $countConditions = $entity->extract($foreignKeys); diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index da0abee1..40c8a4ba 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -102,9 +102,10 @@ public function handleEvent(EventInterface $event, EntityInterface $entity): boo foreach ($events[$eventName] as $field => $when) { if (!in_array($when, ['always', 'new', 'existing'])) { - throw new UnexpectedValueException( - sprintf('When should be one of "always", "new" or "existing". The passed value "%s" is invalid', $when) - ); + throw new UnexpectedValueException(sprintf( + 'When should be one of "always", "new" or "existing". The passed value "%s" is invalid', + $when + )); } if ($when === 'always' || ($when === 'new' && $new) || diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 8a2b0999..47a543e7 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -254,7 +254,9 @@ public function beforeFind(EventInterface $event, Query $query, ArrayObject $opt ); if ($changeFilter) { - $filter = $options['filterByCurrentLocale'] ? QueryInterface::JOIN_TYPE_INNER : QueryInterface::JOIN_TYPE_LEFT; + $filter = $options['filterByCurrentLocale'] + ? QueryInterface::JOIN_TYPE_INNER + : QueryInterface::JOIN_TYPE_LEFT; $contain[$name]['joinType'] = $filter; } } diff --git a/EagerLoader.php b/EagerLoader.php index 1de585fa..d98dbe0d 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -734,8 +734,12 @@ protected function _buildAssociationsMap(array $map, array $level, bool $matchin * If not passed, the default property for the association will be used. * @return void */ - public function addToJoinsMap(string $alias, Association $assoc, bool $asMatching = false, ?string $targetProperty = null): void - { + public function addToJoinsMap( + string $alias, + Association $assoc, + bool $asMatching = false, + ?string $targetProperty = null + ): void { $this->_joinsMap[$alias] = new EagerLoadable($alias, [ 'aliasPath' => $alias, 'instance' => $assoc, diff --git a/Exception/RolledbackTransactionException.php b/Exception/RolledbackTransactionException.php index b0ae0a24..a8f802ed 100644 --- a/Exception/RolledbackTransactionException.php +++ b/Exception/RolledbackTransactionException.php @@ -20,5 +20,6 @@ */ class RolledbackTransactionException extends Exception { + // phpcs:ignore Generic.Files.LineLength protected $_messageTemplate = 'The afterSave event in "%s" is aborting the transaction before the save process is done.'; } diff --git a/Query.php b/Query.php index e4641f1f..76169313 100644 --- a/Query.php +++ b/Query.php @@ -208,7 +208,7 @@ public function __construct(Connection $connection, Table $table) * all the fields in the schema of the table or the association will be added to * the select clause. * - * @param array|\Cake\Database\ExpressionInterface|callable|string|\Cake\ORM\Table|\Cake\ORM\Association $fields fields + * @param array|\Cake\Database\ExpressionInterface|callable|string|\Cake\ORM\Table|\Cake\ORM\Association $fields Fields * to be added to the list. * @param bool $overwrite whether to reset fields with passed list or not * @return $this diff --git a/ResultSet.php b/ResultSet.php index a7f3bfac..675b5389 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -237,7 +237,8 @@ public function rewind(): void } if (!$this->_useBuffering) { - $msg = 'You cannot rewind an un-buffered ResultSet. Use Query::bufferResults() to get a buffered ResultSet.'; + $msg = 'You cannot rewind an un-buffered ResultSet. ' + . 'Use Query::bufferResults() to get a buffered ResultSet.'; throw new Exception($msg); } @@ -306,7 +307,8 @@ public function first() public function serialize(): string { if (!$this->_useBuffering) { - $msg = 'You cannot serialize an un-buffered ResultSet. Use Query::bufferResults() to get a buffered ResultSet.'; + $msg = 'You cannot serialize an un-buffered ResultSet. ' + . 'Use Query::bufferResults() to get a buffered ResultSet.'; throw new Exception($msg); } diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 1bebdc1c..df5244c6 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -54,8 +54,8 @@ class ExistsIn * Set to true to accept composite foreign keys where one or more nullable columns are null. * * @param string|array $fields The field or fields to check existence as primary key. - * @param \Cake\Datasource\RepositoryInterface|\Cake\ORM\Association|string $repository The repository where the field will be looked for, - * or the association name for the repository. + * @param \Cake\Datasource\RepositoryInterface|\Cake\ORM\Association|string $repository The repository where the + * field will be looked for, or the association name for the repository. * @param array $options The options that modify the rules behavior. * Options 'allowNullableNulls' will make the rule pass if given foreign keys are set to `null`. * Notice: allowNullableNulls cannot pass by database columns set to `NOT NULL`. diff --git a/SaveOptionsBuilder.php b/SaveOptionsBuilder.php index 3e111c5e..b17db774 100644 --- a/SaveOptionsBuilder.php +++ b/SaveOptionsBuilder.php @@ -124,7 +124,11 @@ protected function _associated(Table $table, array $associations): void protected function _checkAssociation(Table $table, string $association): void { if (!$table->associations()->has($association)) { - throw new RuntimeException(sprintf('Table `%s` is not associated with `%s`', get_class($table), $association)); + throw new RuntimeException(sprintf( + 'Table `%s` is not associated with `%s`', + get_class($table), + $association + )); } } From 3ff480ffe911815e812ba5891817d644c2611426 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 28 Nov 2018 00:13:35 +0530 Subject: [PATCH 1276/2059] Fix CS errors. --- Behavior/Translate/EavStrategy.php | 4 +- Behavior/Translate/ShadowTableStrategy.php | 60 ++++++++++++---------- 2 files changed, 35 insertions(+), 29 deletions(-) diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index 06f65887..cf16a6c4 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -202,7 +202,9 @@ public function beforeFind(EventInterface $event, Query $query, ArrayObject $opt ); if ($changeFilter) { - $filter = $options['filterByCurrentLocale'] ? QueryInterface::JOIN_TYPE_INNER : QueryInterface::JOIN_TYPE_LEFT; + $filter = $options['filterByCurrentLocale'] + ? QueryInterface::JOIN_TYPE_INNER + : QueryInterface::JOIN_TYPE_LEFT; $contain[$name]['joinType'] = $filter; } } diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 596c4bb0..565080fd 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -225,20 +225,22 @@ protected function iterateClause($query, $name = '', $config = []) $mainTableFields = $this->mainFields(); $joinRequired = false; - $clause->iterateParts(function ($c, &$field) use ($fields, $alias, $mainTableAlias, $mainTableFields, &$joinRequired) { - if (!is_string($field) || strpos($field, '.')) { - return $c; - } + $clause->iterateParts( + function ($c, &$field) use ($fields, $alias, $mainTableAlias, $mainTableFields, &$joinRequired) { + if (!is_string($field) || strpos($field, '.')) { + return $c; + } - if (in_array($field, $fields)) { - $joinRequired = true; - $field = "$alias.$field"; - } elseif (in_array($field, $mainTableFields)) { - $field = "$mainTableAlias.$field"; - } + if (in_array($field, $fields)) { + $joinRequired = true; + $field = "$alias.$field"; + } elseif (in_array($field, $mainTableFields)) { + $field = "$mainTableAlias.$field"; + } - return $c; - }); + return $c; + } + ); return $joinRequired; } @@ -268,26 +270,28 @@ protected function traverseClause($query, $name = '', $config = []) $mainTableFields = $this->mainFields(); $joinRequired = false; - $clause->traverse(function ($expression) use ($fields, $alias, $mainTableAlias, $mainTableFields, &$joinRequired) { - if (!($expression instanceof FieldInterface)) { - return; - } - $field = $expression->getField(); - if (!is_string($field) || strpos($field, '.')) { - return; - } + $clause->traverse( + function ($expression) use ($fields, $alias, $mainTableAlias, $mainTableFields, &$joinRequired) { + if (!($expression instanceof FieldInterface)) { + return; + } + $field = $expression->getField(); + if (!is_string($field) || strpos($field, '.')) { + return; + } - if (in_array($field, $fields)) { - $joinRequired = true; - $expression->setField("$alias.$field"); + if (in_array($field, $fields)) { + $joinRequired = true; + $expression->setField("$alias.$field"); - return; - } + return; + } - if (in_array($field, $mainTableFields)) { - $expression->setField("$mainTableAlias.$field"); + if (in_array($field, $mainTableFields)) { + $expression->setField("$mainTableAlias.$field"); + } } - }); + ); return $joinRequired; } From a9609438ae91c1b54ded77aaa23e48b7b6c15c40 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 27 Nov 2018 22:22:23 -0500 Subject: [PATCH 1277/2059] Fix incorrect alias name on joined associations When loading joined associations but not selecting any columns on the root table results would have the incorrect property set for associations. Instead of the association property, the association alias would be used. By hydrating an empty array for the root table we can correctly nest data. This change modifies an existing test added in #6339 which ensured that the current but broken behavior exists. Reviewing those changes I should have noticed the association property was wrong. Whilst this change could break existing applications the current behavior is not expected and likely more frustrating. Refs #11580 --- ResultSet.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ResultSet.php b/ResultSet.php index 9f365e25..615e3e72 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -528,6 +528,13 @@ protected function _groupResult($row) $presentAliases[$table] = true; } + // If the default table is not in the results, set + // it to an empty array so that any contained + // associations hydrate correctly. + if (!isset($results[$defaultAlias])) { + $results[$defaultAlias] = []; + } + unset($presentAliases[$defaultAlias]); foreach ($this->_containMap as $assoc) { From 621606944bbb1be475eb8fa0bc5059c66a295648 Mon Sep 17 00:00:00 2001 From: Hideki Kinjyo Date: Wed, 28 Nov 2018 13:02:11 +0900 Subject: [PATCH 1278/2059] Fix Association::$finder type --- Association.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Association.php b/Association.php index eda333f5..bcae5f99 100644 --- a/Association.php +++ b/Association.php @@ -179,8 +179,9 @@ abstract class Association /** * The default finder name to use for fetching rows from the target table + * With array value, finder name and default options are allowed. * - * @var string + * @var string|array */ protected $_finder = 'all'; @@ -869,7 +870,7 @@ public function getFinder() /** * Sets the default finder to use for fetching rows from the target table. * - * @param string $finder the finder name to use + * @param string|array $finder the finder name to use or array of finder name and option. * @return $this */ public function setFinder($finder) @@ -886,7 +887,7 @@ public function setFinder($finder) * * @deprecated 3.4.0 Use setFinder()/getFinder() instead. * @param string|null $finder the finder name to use - * @return string + * @return string|array */ public function finder($finder = null) { From 69af3013394592d7c24260d8b05df5053acd8515 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 28 Nov 2018 10:39:10 +0530 Subject: [PATCH 1279/2059] Update docblocks --- Behavior/Translate/TranslateStrategyTrait.php | 2 +- Behavior/TranslateBehavior.php | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Behavior/Translate/TranslateStrategyTrait.php b/Behavior/Translate/TranslateStrategyTrait.php index 132399c5..34dba326 100644 --- a/Behavior/Translate/TranslateStrategyTrait.php +++ b/Behavior/Translate/TranslateStrategyTrait.php @@ -150,7 +150,7 @@ public function buildMarshalMap(Marshaller $marshaller, array $map, array $optio return [ '_translations' => function ($value, $entity) use ($marshaller, $options) { - /* @var \Cake\Datasource\EntityInterface $entity */ + /** @var \Cake\Datasource\EntityInterface $entity */ $translations = $entity->get('_translations'); foreach ($this->_config['fields'] as $field) { $options['validate'] = $this->_config['validator']; diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 3b31d2a1..d42b585c 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -110,6 +110,7 @@ public function initialize(array $config): void * * @param string $class Class name. * @return void + * @since 4.0.0 */ public static function setDefaultStrategyClass(string $class) { @@ -120,6 +121,7 @@ public static function setDefaultStrategyClass(string $class) * Get default strategy class name. * * @return string + * @since 4.0.0 */ public static function getDefaultStrategyClass(): string { @@ -130,6 +132,7 @@ public static function getDefaultStrategyClass(): string * Get strategy class instance. * * @return \Cake\ORM\Behavior\Translate\TranslateStrategyInterface + * @since 4.0.0 */ public function getStrategy(): TranslateStrategyInterface { @@ -144,6 +147,7 @@ public function getStrategy(): TranslateStrategyInterface * Create strategy instance. * * @return \Cake\ORM\Behavior\Translate\TranslateStrategyInterface + * @since 4.0.0 */ protected function createStrategy() { @@ -161,6 +165,7 @@ protected function createStrategy() * * @param \Cake\ORM\Behavior\Translate\TranslateStrategyInterface $strategy Strategy class instance. * @return $this + * @since 4.0.0 */ public function setStrategy(TranslateStrategyInterface $strategy) { From 087435a6659fce0964bae881596088965f8be9f8 Mon Sep 17 00:00:00 2001 From: Hideki Kinjyo Date: Wed, 28 Nov 2018 20:07:27 +0900 Subject: [PATCH 1280/2059] Fix getFinder() return type --- Association.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association.php b/Association.php index bcae5f99..f97cfe45 100644 --- a/Association.php +++ b/Association.php @@ -860,7 +860,7 @@ public function strategy($name = null) /** * Gets the default finder to use for fetching rows from the target table. * - * @return string + * @return string|array */ public function getFinder() { From 729f6218fe958fda998aec481be02fe5fa08c75a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Fri, 7 Dec 2018 09:52:34 +0100 Subject: [PATCH 1281/2059] Support multiple table namespaces. --- Locator/TableLocator.php | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 7597cef8..7d306785 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -27,6 +27,13 @@ class TableLocator implements LocatorInterface { + /** + * Contains a list of namespaces where table classes should be looked for. + * + * @var array + */ + protected $_namespaces = []; + /** * Configuration for aliases. * @@ -56,6 +63,23 @@ class TableLocator implements LocatorInterface */ protected $_options = []; + /** + * Constructor. + * + * @param array|null $namespaces Namespaces where tables should be located located. + * If none provided, the default `Model\Table` under your app's namespace is used. + */ + public function __construct(array $namespaces = null) + { + if ($namespaces === null) { + $namespaces = [ + 'Model/Table', + ]; + } + + $this->_namespaces = $namespaces; + } + /** * Stores a list of options to be used when instantiating an object * with a matching alias. @@ -243,7 +267,18 @@ protected function _getClassName($alias, array $options = []) $options['className'] = Inflector::camelize($alias); } - return App::className($options['className'], 'Model/Table', 'Table'); + if (class_exists($options['className'])) { + return $options['className']; + } + + foreach ($this->_namespaces as $namespace) { + $class = App::className($options['className'], $namespace, 'Table'); + if ($class !== null) { + return $class; + } + } + + return null; } /** From 3c5da7cd8a6d387bbc7582339269cf684944253d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Fri, 7 Dec 2018 10:18:21 +0100 Subject: [PATCH 1282/2059] Adds TableLocator::addNamespace method. --- Locator/TableLocator.php | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 7d306785..5dc1aa0c 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -66,7 +66,7 @@ class TableLocator implements LocatorInterface /** * Constructor. * - * @param array|null $namespaces Namespaces where tables should be located located. + * @param array|null $namespaces Namespaces where tables should be located. * If none provided, the default `Model\Table` under your app's namespace is used. */ public function __construct(array $namespaces = null) @@ -77,7 +77,9 @@ public function __construct(array $namespaces = null) ]; } - $this->_namespaces = $namespaces; + foreach ($namespaces as $namespace) { + $this->addNamespace($namespace); + } } /** @@ -342,4 +344,18 @@ public function remove($alias) $this->_fallbacked[$alias] ); } + + /** + * Adds a namespace where tables should be looked for. + * + * @param string $namespace Namespace to add. + * @return $this + */ + public function addNamespace($namespace) + { + $namespace = str_replace('\\', '/', $namespace); + $this->_namespaces[] = trim($namespace, '/'); + + return $this; + } } From 1675cfc9441ef917909c02c888cc4123831bb2db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Fri, 7 Dec 2018 11:22:29 +0100 Subject: [PATCH 1283/2059] CS fixes --- Locator/TableLocator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 5dc1aa0c..4255331a 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -348,7 +348,7 @@ public function remove($alias) /** * Adds a namespace where tables should be looked for. * - * @param string $namespace Namespace to add. + * @param string $namespace Namespace to add. * @return $this */ public function addNamespace($namespace) From df9b9e6b044154d7ee87f41f283dfc131a7dce28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Fri, 7 Dec 2018 11:24:38 +0100 Subject: [PATCH 1284/2059] Fixes --- Locator/TableLocator.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 4255331a..ce014112 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -269,13 +269,13 @@ protected function _getClassName($alias, array $options = []) $options['className'] = Inflector::camelize($alias); } - if (class_exists($options['className'])) { + if (strpos($options['className'], '\\') !== false && class_exists($options['className'])) { return $options['className']; } foreach ($this->_namespaces as $namespace) { $class = App::className($options['className'], $namespace, 'Table'); - if ($class !== null) { + if ($class !== false) { return $class; } } From 5244bc0af7b8d8c6203546d1c9c1ece9193e5c74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Fri, 7 Dec 2018 13:39:54 +0100 Subject: [PATCH 1285/2059] Rename namespace to location. --- Locator/TableLocator.php | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index ce014112..f1fa6312 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -28,11 +28,11 @@ class TableLocator implements LocatorInterface { /** - * Contains a list of namespaces where table classes should be looked for. + * Contains a list of locations where table classes should be looked for. * * @var array */ - protected $_namespaces = []; + protected $_locations = []; /** * Configuration for aliases. @@ -66,19 +66,19 @@ class TableLocator implements LocatorInterface /** * Constructor. * - * @param array|null $namespaces Namespaces where tables should be located. + * @param array|null $locations Locations where tables should be looked for. * If none provided, the default `Model\Table` under your app's namespace is used. */ - public function __construct(array $namespaces = null) + public function __construct(array $locations = null) { - if ($namespaces === null) { - $namespaces = [ + if ($locations === null) { + $locations = [ 'Model/Table', ]; } - foreach ($namespaces as $namespace) { - $this->addNamespace($namespace); + foreach ($locations as $location) { + $this->addLocation($location); } } @@ -273,14 +273,14 @@ protected function _getClassName($alias, array $options = []) return $options['className']; } - foreach ($this->_namespaces as $namespace) { - $class = App::className($options['className'], $namespace, 'Table'); + foreach ($this->_locations as $location) { + $class = App::className($options['className'], $location, 'Table'); if ($class !== false) { return $class; } } - return null; + return false; } /** @@ -346,15 +346,15 @@ public function remove($alias) } /** - * Adds a namespace where tables should be looked for. + * Adds a location where tables should be looked for. * - * @param string $namespace Namespace to add. + * @param string $location Location to add. * @return $this */ - public function addNamespace($namespace) + public function addLocation($location) { - $namespace = str_replace('\\', '/', $namespace); - $this->_namespaces[] = trim($namespace, '/'); + $location = str_replace('\\', '/', $location); + $this->_locations[] = trim($location, '/'); return $this; } From 350cfa07abbd7c4c14914c61b9ddf4a44ed83a25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Mon, 10 Dec 2018 08:20:03 +0100 Subject: [PATCH 1286/2059] Remove undersocre form protected property name. --- Locator/TableLocator.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index f1fa6312..4fb75901 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -32,7 +32,7 @@ class TableLocator implements LocatorInterface * * @var array */ - protected $_locations = []; + protected $locations = []; /** * Configuration for aliases. @@ -273,7 +273,7 @@ protected function _getClassName($alias, array $options = []) return $options['className']; } - foreach ($this->_locations as $location) { + foreach ($this->locations as $location) { $class = App::className($options['className'], $location, 'Table'); if ($class !== false) { return $class; @@ -354,7 +354,7 @@ public function remove($alias) public function addLocation($location) { $location = str_replace('\\', '/', $location); - $this->_locations[] = trim($location, '/'); + $this->locations[] = trim($location, '/'); return $this; } From dded1aab4a4e5de0ee94db3eb404e2494777d560 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 13 Dec 2018 12:18:12 +0530 Subject: [PATCH 1287/2059] Fix errors reported by psalm. --- Rule/ExistsIn.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index df5244c6..de108990 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -36,7 +36,7 @@ class ExistsIn /** * The repository where the field will be looked for * - * @var \Cake\Datasource\RepositoryInterface|\Cake\ORM\Association|string + * @var \Cake\ORM\Table|\Cake\ORM\Association|string */ protected $_repository; @@ -54,7 +54,7 @@ class ExistsIn * Set to true to accept composite foreign keys where one or more nullable columns are null. * * @param string|array $fields The field or fields to check existence as primary key. - * @param \Cake\Datasource\RepositoryInterface|\Cake\ORM\Association|string $repository The repository where the + * @param \Cake\ORM\Table|\Cake\ORM\Association|string $repository The repository where the * field will be looked for, or the association name for the repository. * @param array $options The options that modify the rules behavior. * Options 'allowNullableNulls' will make the rule pass if given foreign keys are set to `null`. @@ -95,9 +95,13 @@ public function __invoke(EntityInterface $entity, array $options): bool $fields = $this->_fields; $source = $target = $this->_repository; - $isAssociation = $target instanceof Association; - $bindingKey = $isAssociation ? (array)$target->getBindingKey() : (array)$target->getPrimaryKey(); - $realTarget = $isAssociation ? $target->getTarget() : $target; + if ($target instanceof Association) { + $bindingKey = (array)$target->getBindingKey(); + $realTarget = $target->getTarget(); + } else { + $bindingKey = (array)$target->getPrimaryKey(); + $realTarget = $target; + } if (!empty($options['_sourceTable']) && $realTarget === $options['_sourceTable']) { return true; From 0046e582ddb7fe95bc426a1b65921df87e4e5798 Mon Sep 17 00:00:00 2001 From: mscherer Date: Fri, 14 Dec 2018 00:20:56 +0100 Subject: [PATCH 1288/2059] Fix array doc blocks to be more precise where applicable. --- AssociationCollection.php | 2 +- LazyEagerLoader.php | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/AssociationCollection.php b/AssociationCollection.php index 574648f0..a6ccca21 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -143,7 +143,7 @@ public function has($alias) /** * Get the names of all the associations in the collection. * - * @return array + * @return string[] */ public function keys() { diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 092a6534..337b8d02 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -110,7 +110,7 @@ protected function _getQuery($objects, $contain, $source) * in the top level entities. * * @param \Cake\ORM\Table $source The table having the top level associations - * @param array $associations The name of the top level associations + * @param string[] $associations The name of the top level associations * @return array */ protected function _getPropertyMap($source, $associations) @@ -128,9 +128,9 @@ protected function _getPropertyMap($source, $associations) * Injects the results of the eager loader query into the original list of * entities. * - * @param array|\Traversable $objects The original list of entities + * @param \Cake\Datasource\EntityInterface[]|\Traversable $objects The original list of entities * @param \Cake\Collection\CollectionInterface|\Cake\Database\Query $results The loaded results - * @param array $associations The top level associations that were loaded + * @param string[] $associations The top level associations that were loaded * @param \Cake\ORM\Table $source The table where the entities came from * @return array */ @@ -141,6 +141,7 @@ protected function _injectResults($objects, $results, $associations, $source) $primaryKey = (array)$source->getPrimaryKey(); $results = $results ->indexBy(function ($e) use ($primaryKey) { + /** @var \Cake\Datasource\EntityInterface $e */ return implode(';', $e->extract($primaryKey)); }) ->toArray(); @@ -152,6 +153,7 @@ protected function _injectResults($objects, $results, $associations, $source) continue; } + /** @var \Cake\Datasource\EntityInterface $loaded */ $loaded = $results[$key]; foreach ($associations as $assoc) { $property = $properties[$assoc]; From c839863beafd150ff573f59a204fc35c722b2f21 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 16 Dec 2018 21:23:19 +0530 Subject: [PATCH 1289/2059] Fix CS errors. --- Association.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Association.php b/Association.php index be4c55b3..7c8c53b3 100644 --- a/Association.php +++ b/Association.php @@ -646,7 +646,7 @@ public function getStrategy(): string * * @return string|array */ - public function getFinder(): string + public function getFinder() { return $this->_finder; } @@ -657,7 +657,7 @@ public function getFinder(): string * @param string|array $finder the finder name to use or array of finder name and option. * @return $this */ - public function setFinder(string $finder) + public function setFinder($finder) { $this->_finder = $finder; From acf563d2991c70b467875256207105da9c952193 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 26 Dec 2018 17:32:35 +0530 Subject: [PATCH 1290/2059] Remove redundant conditional checks. --- Marshaller.php | 4 ++-- Query.php | 4 ++-- Table.php | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 3e11a26e..2f957362 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -654,7 +654,7 @@ public function mergeMany(iterable $entities, array $data, array $options = []): } $key = implode(';', $entity->extract($primary)); - if ($key === null || !isset($indexed[$key])) { + if (!isset($indexed[$key])) { continue; } @@ -822,7 +822,7 @@ protected function _mergeJoinData($original, BelongsToMany $assoc, array $value, // Marshal data into the old object, or make a new joinData object. if (isset($extra[$hash])) { $record->set('_joinData', $marshaller->merge($extra[$hash], $value, $nested)); - } elseif (is_array($value)) { + } else { $joinData = $marshaller->one($value, $nested); $record->set('_joinData', $joinData); } diff --git a/Query.php b/Query.php index 1f469c2a..dc88b8d3 100644 --- a/Query.php +++ b/Query.php @@ -1283,8 +1283,8 @@ public function __debugInfo() 'buffered' => $this->_useBufferedResults, 'formatters' => count($this->_formatters), 'mapReducers' => count($this->_mapReduce), - 'contain' => $eagerLoader ? $eagerLoader->getContain() : [], - 'matching' => $eagerLoader ? $eagerLoader->getMatching() : [], + 'contain' => $eagerLoader->getContain(), + 'matching' => $eagerLoader->getMatching(), 'extraOptions' => $this->_options, 'repository' => $this->_repository, ]; diff --git a/Table.php b/Table.php index 9145c0bf..94eb0c52 100644 --- a/Table.php +++ b/Table.php @@ -1455,7 +1455,7 @@ protected function _executeTransaction(callable $worker, bool $atomic = true) */ protected function _transactionCommitted(bool $atomic, bool $primary): bool { - return !$this->getConnection()->inTransaction() && ($atomic || (!$atomic && $primary)); + return !$this->getConnection()->inTransaction() && ($atomic || ($primary && !$atomic)); } /** From 432ec887529f419485fc6aecc3515517dd0a028c Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 26 Dec 2018 18:00:32 +0530 Subject: [PATCH 1291/2059] Remove redundant check. --- BehaviorRegistry.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 13156a2e..a634b615 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -75,10 +75,7 @@ public function __construct(?Table $table = null) public function setTable(Table $table): void { $this->_table = $table; - $eventManager = $table->getEventManager(); - if ($eventManager !== null) { - $this->setEventManager($eventManager); - } + $this->setEventManager($table->getEventManager()); } /** From deec4715e624c938ad7abd5791c8002b1624fb07 Mon Sep 17 00:00:00 2001 From: mscherer Date: Fri, 4 Jan 2019 18:46:09 +0100 Subject: [PATCH 1292/2059] Fix docblocks. --- Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Query.php b/Query.php index dc88b8d3..d9a45a11 100644 --- a/Query.php +++ b/Query.php @@ -423,7 +423,7 @@ public function getEagerLoader(): EagerLoader * if associations is an array, a bool on whether to override previous list * with the one passed * defaults to merging previous list with the new one. - * @return array|$this + * @return $this */ public function contain($associations, $override = false) { From 3f46ba6024d19dd57924ef12d03ff7d7b5d5cc67 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 5 Jan 2019 12:19:34 +0530 Subject: [PATCH 1293/2059] Remove unneeded error supressions. --- Association.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association.php b/Association.php index 7c8c53b3..d228313a 100644 --- a/Association.php +++ b/Association.php @@ -401,7 +401,7 @@ public function getTarget(): Table $this->_sourceTable ? get_class($this->_sourceTable) : 'null', $this->getName(), $this->type(), - $this->_targetTable ? get_class($this->_targetTable) : 'null', + get_class($this->_targetTable), $className )); } From 4d49988b2c6cab2d230618405c2a797b08e3b0c2 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 5 Jan 2019 17:15:56 +0530 Subject: [PATCH 1294/2059] Fix typehint. --- Association/BelongsToMany.php | 4 ++-- Association/HasMany.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 4ab4be03..dd60aef4 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -675,7 +675,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) * * @param \Cake\Datasource\EntityInterface $parentEntity the source entity containing the target * entities to be saved. - * @param iterable $entities list of entities to persist in target table and to + * @param array $entities list of entities to persist in target table and to * link to the parent entity * @param array $options list of options accepted by `Table::save()` * @throws \InvalidArgumentException if the property representing the association @@ -683,7 +683,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) * @return \Cake\Datasource\EntityInterface|bool The parent entity after all links have been * created if no errors happened, false otherwise */ - protected function _saveTarget(EntityInterface $parentEntity, iterable $entities, $options) + protected function _saveTarget(EntityInterface $parentEntity, array $entities, $options) { $joinAssociations = false; if (!empty($options['associated'][$this->_junctionProperty]['associated'])) { diff --git a/Association/HasMany.php b/Association/HasMany.php index 334ca090..1aad0160 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -191,7 +191,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) * target entity, and the parent entity. * @param \Cake\Datasource\EntityInterface $parentEntity The source entity containing the target * entities to be saved. - * @param iterable $entities list of entities to persist in target table and to + * @param array $entities list of entities to persist in target table and to * link to the parent entity * @param array $options list of options accepted by `Table::save()`. * @return bool `true` on success, `false` otherwise. @@ -199,7 +199,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) protected function _saveTarget( array $foreignKeyReference, EntityInterface $parentEntity, - iterable $entities, + array $entities, array $options ): bool { $foreignKey = array_keys($foreignKeyReference); From f6249aaed0ba67efc6b6cf8f4da1198e8c1b6519 Mon Sep 17 00:00:00 2001 From: mscherer Date: Sat, 5 Jan 2019 22:08:56 +0100 Subject: [PATCH 1295/2059] Explain unusual parent call --- Query.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Query.php b/Query.php index 06f59980..d7c22886 100644 --- a/Query.php +++ b/Query.php @@ -1285,6 +1285,7 @@ public function delete($table = null) $repository = $this->getRepository(); $this->from([$repository->getAlias() => $repository->getTable()]); + // We do not pass $table to parent class here return parent::delete(); } From 6684be6ed69124c6474700eec73af1ca46d23a52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Tue, 8 Jan 2019 12:15:17 +0100 Subject: [PATCH 1296/2059] Update since tags. --- Locator/TableLocator.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 4fb75901..215d1f6e 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -350,6 +350,8 @@ public function remove($alias) * * @param string $location Location to add. * @return $this + * + * @since 3.8.0 */ public function addLocation($location) { From fd4b3fb8619617980e2bad112008503c529a1166 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 10 Jan 2019 22:54:35 +0530 Subject: [PATCH 1297/2059] Fix arguments type and add typehint. --- Marshaller.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 2f957362..a0995503 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -743,13 +743,13 @@ protected function _mergeAssociation($original, Association $assoc, $value, arra * Creates a new sub-marshaller and merges the associated data for a BelongstoMany * association. * - * @param \Cake\Datasource\EntityInterface $original The original entity + * @param \Cake\Datasource\EntityInterface[] $original The original entities list. * @param \Cake\ORM\Association $assoc The association to marshall * @param array $value The data to hydrate * @param array $options List of options. * @return \Cake\Datasource\EntityInterface[] */ - protected function _mergeBelongsToMany($original, Association $assoc, $value, array $options): array + protected function _mergeBelongsToMany(array $original, Association $assoc, $value, array $options): array { $associated = $options['associated'] ?? []; @@ -773,13 +773,13 @@ protected function _mergeBelongsToMany($original, Association $assoc, $value, ar /** * Merge the special _joinData property into the entity set. * - * @param \Cake\Datasource\EntityInterface $original The original entity + * @param \Cake\Datasource\EntityInterface[] $original The original entities list. * @param \Cake\ORM\Association\BelongsToMany $assoc The association to marshall * @param array $value The data to hydrate * @param array $options List of options. * @return \Cake\Datasource\EntityInterface[] An array of entities */ - protected function _mergeJoinData($original, BelongsToMany $assoc, array $value, array $options): array + protected function _mergeJoinData(array $original, BelongsToMany $assoc, array $value, array $options): array { $associated = $options['associated'] ?? []; $extra = []; From 1d67c7fbf84d5f1b121c4c90c5ae7b40817464d7 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 12 Jan 2019 22:31:47 +0530 Subject: [PATCH 1298/2059] Fix types. --- Locator/TableLocator.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 6794802d..4e4b6579 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -69,7 +69,7 @@ class TableLocator implements LocatorInterface * @param array|null $locations Locations where tables should be looked for. * If none provided, the default `Model\Table` under your app's namespace is used. */ - public function __construct(array $locations = null) + public function __construct(?array $locations = null) { if ($locations === null) { $locations = [ @@ -241,12 +241,12 @@ protected function _getClassName(string $alias, array $options = []): ?string foreach ($this->locations as $location) { $class = App::className($options['className'], $location, 'Table'); - if ($class !== false) { + if ($class !== null) { return $class; } } - return false; + return null; } /** @@ -319,7 +319,7 @@ public function remove(string $alias): void * * @since 3.8.0 */ - public function addLocation($location) + public function addLocation(string $location) { $location = str_replace('\\', '/', $location); $this->locations[] = trim($location, '/'); From 15e25081608d8c3b50d3cb2b97e5692c05020574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1si=20Benjamin?= Date: Wed, 16 Jan 2019 15:16:43 +0000 Subject: [PATCH 1299/2059] Fix docblock on ORM/Table.php Change \Cake\ORM\ResultSet to \Cake\Datasource\ResultSetInterface in the docblock of saveMany --- Table.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index 77706b00..512a96a7 100644 --- a/Table.php +++ b/Table.php @@ -2209,9 +2209,9 @@ protected function _update($entity, $data) * any one of the records fails to save due to failed validation or database * error. * - * @param \Cake\Datasource\EntityInterface[]|\Cake\ORM\ResultSet $entities Entities to save. + * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to save. * @param array|\ArrayAccess $options Options used when calling Table::save() for each entity. - * @return bool|\Cake\Datasource\EntityInterface[]|\Cake\ORM\ResultSet False on failure, entities list on success. + * @return bool|\Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface False on failure, entities list on success. */ public function saveMany($entities, $options = []) { From 6c8523118dde56057cc210cb2477ac4071a45587 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1si=20Benjamin?= Date: Wed, 16 Jan 2019 15:20:32 +0000 Subject: [PATCH 1300/2059] Add @throws --- Table.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Table.php b/Table.php index 512a96a7..8b5583c0 100644 --- a/Table.php +++ b/Table.php @@ -2212,6 +2212,7 @@ protected function _update($entity, $data) * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to save. * @param array|\ArrayAccess $options Options used when calling Table::save() for each entity. * @return bool|\Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface False on failure, entities list on success. + * @throws \Exception */ public function saveMany($entities, $options = []) { From 6553c36dcd0ae3d64a538253372da7038258846d Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Fri, 18 Jan 2019 10:45:02 -0600 Subject: [PATCH 1301/2059] Patching search data in findOrCreate to ensure valid entity --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 77706b00..7dc51f92 100644 --- a/Table.php +++ b/Table.php @@ -1730,7 +1730,7 @@ protected function _processFindOrCreate($search, callable $callback = null, $opt } $entity = $this->newEntity(); if ($options['defaults'] && is_array($search)) { - $entity->set($search, ['guard' => false]); + $entity = $this->patchEntity($entity, $search, ['guard' => false]); } if ($callback !== null) { $entity = $callback($entity) ?: $entity; From c19a39b996f3a993b0be52eb96dbbc9605e27d65 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Tue, 22 Jan 2019 09:55:51 -0600 Subject: [PATCH 1302/2059] Throwing PersistenceFailedException when findOrCreate fails to save --- Table.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 7dc51f92..4866cacd 100644 --- a/Table.php +++ b/Table.php @@ -1737,7 +1737,13 @@ protected function _processFindOrCreate($search, callable $callback = null, $opt } unset($options['defaults']); - return $this->save($entity, $options) ?: $entity; + $result = $this->save($entity, $options); + + if ($result === false) { + throw new PersistenceFailedException($entity, ['findOrCreate']); + } + + return $entity; } /** From 31c30a88999b6ff98beb81e12a5ea1404a127d9a Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 22 Jan 2019 22:50:52 -0500 Subject: [PATCH 1303/2059] Fix warnings on complex keys Fix warnings being emitted when complex data types are used for primary key values. Fixes #12920 --- Table.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 8b5583c0..be4d91d6 100644 --- a/Table.php +++ b/Table.php @@ -2086,7 +2086,9 @@ protected function _insert($entity, $data) $primary = array_combine($primary, $id); $primary = array_intersect_key($data, $primary) + $primary; - $filteredKeys = array_filter($primary, 'strlen'); + $filteredKeys = array_filter($primary, function ($v) { + return $v !== null; + }); $data += $filteredKeys; if (count($primary) > 1) { From 79febcd0822d8d69d2edef7819ae71849874baf5 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 25 Jan 2019 23:31:29 -0500 Subject: [PATCH 1304/2059] Use find() in association internals. Using find() internally allows us to deprecate the `conditions` option in the future and recommend only finders. Finders are preferrable as they don't require duplicating conditions/logic. --- Association.php | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/Association.php b/Association.php index d228313a..32678f42 100644 --- a/Association.php +++ b/Association.php @@ -854,11 +854,9 @@ public function find($type = null, array $options = []): Query */ public function exists($conditions): bool { - if ($this->_conditions) { - $conditions = $this - ->find('all', ['conditions' => $conditions]) - ->clause('where'); - } + $conditions = $this->find() + ->where($conditions) + ->clause('where'); return $this->getTarget()->exists($conditions); } @@ -874,13 +872,11 @@ public function exists($conditions): bool */ public function updateAll(array $fields, $conditions): int { - $target = $this->getTarget(); - $expression = $target->query() - ->where($this->getConditions()) + $expression = $this->find() ->where($conditions) ->clause('where'); - return $target->updateAll($fields, $expression); + return $this->getTarget()->updateAll($fields, $expression); } /** @@ -893,13 +889,11 @@ public function updateAll(array $fields, $conditions): int */ public function deleteAll($conditions): int { - $target = $this->getTarget(); - $expression = $target->query() - ->where($this->getConditions()) + $expression = $this->find() ->where($conditions) ->clause('where'); - return $target->deleteAll($expression); + return $this->getTarget()->deleteAll($expression); } /** From 68800a102eacbbf151d1ac43762a382ed1adc792 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 26 Jan 2019 00:14:49 -0500 Subject: [PATCH 1305/2059] Update HasMany to use proxy methods Use the association proxy methods for save replace operations. This allows finders to be applied to the deletes done by save_replace. Refs #12859 --- Association/DependentDeleteHelper.php | 3 +-- Association/HasMany.php | 8 +++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/Association/DependentDeleteHelper.php b/Association/DependentDeleteHelper.php index 8b405960..110f3a36 100644 --- a/Association/DependentDeleteHelper.php +++ b/Association/DependentDeleteHelper.php @@ -52,8 +52,7 @@ public function cascadeDelete(Association $association, EntityInterface $entity, return true; } - $conditions = array_merge($conditions, $association->getConditions()); - return (bool)$table->deleteAll($conditions); + return (bool)$association->deleteAll($conditions); } } diff --git a/Association/HasMany.php b/Association/HasMany.php index 1aad0160..4d22f43d 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -509,7 +509,7 @@ protected function _unlink(array $foreignKey, Table $target, array $conditions = $entry->setField($target->aliasField($entry->getField())); } }); - $query = $this->find('all')->where($conditions); + $query = $this->find()->where($conditions); $ok = true; foreach ($query as $assoc) { $ok = $ok && $target->delete($assoc, $options); @@ -518,15 +518,13 @@ protected function _unlink(array $foreignKey, Table $target, array $conditions = return $ok; } - $conditions = array_merge($conditions, $this->getConditions()); - $target->deleteAll($conditions); + $this->deleteAll($conditions); return true; } $updateFields = array_fill_keys($foreignKey, null); - $conditions = array_merge($conditions, $this->getConditions()); - $target->updateAll($updateFields, $conditions); + $this->updateAll($updateFields, $conditions); return true; } From e4f93df31aed9707f30b4d0b22016011270c2c24 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 27 Jan 2019 23:54:28 +0530 Subject: [PATCH 1306/2059] Use ::class constant wherever possible. --- Behavior.php | 4 ++-- Behavior/Translate/TranslateTrait.php | 2 +- Table.php | 14 +++++++------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Behavior.php b/Behavior.php index 077140d5..b561b308 100644 --- a/Behavior.php +++ b/Behavior.php @@ -244,7 +244,7 @@ public function verifyConfig(): void throw new Exception(sprintf( 'The method %s is not callable on class %s', $method, - get_class($this) + static::class )); } } @@ -375,7 +375,7 @@ public function implementedMethods(): array */ protected function _reflectionCache(): array { - $class = get_class($this); + $class = static::class; if (isset(self::$_reflectionCache[$class])) { return self::$_reflectionCache[$class]; } diff --git a/Behavior/Translate/TranslateTrait.php b/Behavior/Translate/TranslateTrait.php index 4aecbb88..5ce7cc09 100644 --- a/Behavior/Translate/TranslateTrait.php +++ b/Behavior/Translate/TranslateTrait.php @@ -47,7 +47,7 @@ public function translation(string $language) } if ($created || empty($i18n[$language]) || !($i18n[$language] instanceof EntityInterface)) { - $className = get_class($this); + $className = static::class; $i18n[$language] = new $className(); $created = true; diff --git a/Table.php b/Table.php index 94eb0c52..0100e195 100644 --- a/Table.php +++ b/Table.php @@ -358,7 +358,7 @@ public function setTable(string $table) public function getTable(): string { if ($this->_table === null) { - $table = namespaceSplit(get_class($this)); + $table = namespaceSplit(static::class); $table = substr(end($table), 0, -5); if (!$table) { $table = $this->getAlias(); @@ -390,7 +390,7 @@ public function setAlias(string $alias) public function getAlias(): string { if ($this->_alias === null) { - $alias = namespaceSplit(get_class($this)); + $alias = namespaceSplit(static::class); $alias = substr(end($alias), 0, -5) ?: $this->_table; $this->_alias = $alias; } @@ -629,10 +629,10 @@ public function getEntityClass(): string { if (!$this->_entityClass) { $default = Entity::class; - $self = get_called_class(); + $self = static::class; $parts = explode('\\', $self); - if ($self === __CLASS__ || count($parts) < 3) { + if ($self === self::class || count($parts) < 3) { return $this->_entityClass = $default; } @@ -780,7 +780,7 @@ public function getBehavior(string $name): Behavior throw new InvalidArgumentException(sprintf( 'The %s behavior is not defined on %s.', $name, - get_class($this) + static::class )); } @@ -1856,7 +1856,7 @@ protected function _onSaveSuccess(EntityInterface $entity, ArrayObject $options) $this->dispatchEvent('Model.afterSave', compact('entity', 'options')); if ($options['atomic'] && !$this->getConnection()->inTransaction()) { - throw new RolledbackTransactionException(['table' => get_class($this)]); + throw new RolledbackTransactionException(['table' => static::class]); } if (!$options['atomic'] && !$options['_primary']) { @@ -2333,7 +2333,7 @@ public function __get($property) if (!$association) { throw new RuntimeException(sprintf( 'Table "%s" is not associated with "%s"', - get_class($this), + static::class, $property )); } From 26d0ee9d5ed8e83cb4112f201c194fce4589e8e6 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 27 Jan 2019 14:22:47 -0500 Subject: [PATCH 1307/2059] Update BelongsToMany to use proxy finder When finding existing junction records during replace operations the ORM will now apply the `finder` if set. This allows junction records to be limited by the conditions define in the finder. An additional join to the association target will automatically be added if the association defines a finder or conditions. This incurs some overhead but is necessary as the association finder likely has conditions on the target table. I've refactored how the junction joins are added as we had the same code in two places. --- Association/BelongsToMany.php | 64 +++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index dd60aef4..a9569d3f 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -463,18 +463,13 @@ protected function _appendNotMatching(QueryInterface $query, array $options): vo $subquery = $this->find() ->select(array_values($conds)) - ->where($options['conditions']) - ->andWhere($this->junctionConditions()); + ->where($options['conditions']); if (!empty($options['queryBuilder'])) { $subquery = $options['queryBuilder']($subquery); } - $assoc = $junction->getAssociation($this->getTarget()->getAlias()); - $conditions = $assoc->_joinCondition([ - 'foreignKey' => $this->getTargetForeignKey(), - ]); - $subquery = $this->_appendJunctionJoin($subquery, $conditions); + $subquery = $this->_appendJunctionJoin($subquery); $query ->andWhere(function ($exp) use ($subquery, $conds) { @@ -1018,7 +1013,7 @@ protected function junctionConditions(): array * and modifies the query accordingly based of this association * configuration. * - * If your association includes conditions, the junction table will be + * If your association includes conditions or a finder, the junction table will be * included in the query's contained associations. * * @param string|array|null $type the type of query to perform, if an array is passed, @@ -1036,28 +1031,30 @@ public function find($type = null, array $options = []): Query ->where($this->targetConditions()) ->addDefaultTypes($this->getTarget()); - if (!$this->junctionConditions()) { + if (!($this->junctionConditions() || $this->getFinder())) { return $query; } - $belongsTo = $this->junction()->getAssociation($this->getTarget()->getAlias()); - $conditions = $belongsTo->_joinCondition([ - 'foreignKey' => $this->getTargetForeignKey(), - ]); - $conditions += $this->junctionConditions(); - - return $this->_appendJunctionJoin($query, $conditions); + return $this->_appendJunctionJoin($query); } /** * Append a join to the junction table. * * @param \Cake\ORM\Query $query The query to append. - * @param string|array $conditions The query conditions to use. + * @param array|null $conditions The query conditions to use. * @return \Cake\ORM\Query The modified query. */ - protected function _appendJunctionJoin(Query $query, $conditions): Query + protected function _appendJunctionJoin(Query $query, ?array $conditions = null): Query { + if ($conditions === null) { + $belongsTo = $this->junction()->getAssociation($this->getTarget()->getAlias()); + $conditions = $belongsTo->_joinCondition([ + 'foreignKey' => $this->getTargetForeignKey(), + ]); + $conditions += $this->junctionConditions(); + } + $name = $this->_junctionAssociationName(); /** @var array $joins */ $joins = $query->clause('join'); @@ -1138,17 +1135,26 @@ public function replaceLinks(EntityInterface $sourceEntity, array $targetEntitie return $this->junction()->getConnection()->transactional( function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { - $foreignKey = array_map([$this->_junctionTable, 'aliasField'], (array)$this->getForeignKey()); - $hasMany = $this->getSource()->getAssociation($this->_junctionTable->getAlias()); - $existing = $hasMany->find('all') - ->where(array_combine($foreignKey, $primaryValue)); - - $associationConditions = $this->getConditions(); - if ($associationConditions) { - $existing->contain($this->getTarget()->getAlias()); - $existing->andWhere($associationConditions); + $junction = $this->junction(); + $target = $this->getTarget(); + + $foreignKey = (array)$this->getForeignKey(); + $prefixedForeignKey = array_map([$junction, 'aliasField'], $foreignKey); + + $junctionPrimaryKey = (array)$junction->getPrimaryKey(); + $assocForeignKey = (array)$junction->getAssociation($target->getAlias())->getForeignKey(); + + $keys = array_combine($foreignKey, $prefixedForeignKey); + foreach (array_merge($assocForeignKey, $junctionPrimaryKey) as $key) { + $keys[$key] = $junction->aliasField($key); } + // Find existing rows so that we can diff with new entities. + // Only hydrate primary/foreign key columns to save time. + $existing = $this->find() + ->select($keys) + ->where(array_combine($prefixedForeignKey, $primaryValue)); + $jointEntities = $this->_collectJointEntities($sourceEntity, $targetEntities); $inserts = $this->_diffLinks($existing, $jointEntities, $targetEntities, $options); @@ -1323,9 +1329,9 @@ protected function _collectJointEntities(EntityInterface $sourceEntity, array $t $unions = []; foreach ($missing as $key) { - $unions[] = $hasMany->find('all') + $unions[] = $hasMany->find() ->where(array_combine($foreignKey, $sourceKey)) - ->andWhere(array_combine($assocForeignKey, $key)); + ->where(array_combine($assocForeignKey, $key)); } $query = array_shift($unions); From 9a23a01c80006ff75830154baac8b2498595eb21 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 28 Jan 2019 23:44:05 -0500 Subject: [PATCH 1308/2059] Improve error messages when persistence fails When persistence fails error messages should include all the validation errors recursively. Fixes #12931 --- Exception/PersistenceFailedException.php | 25 ++++-------------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/Exception/PersistenceFailedException.php b/Exception/PersistenceFailedException.php index 4bfb1db0..8596f1e9 100644 --- a/Exception/PersistenceFailedException.php +++ b/Exception/PersistenceFailedException.php @@ -14,6 +14,7 @@ use Cake\Core\Exception\Exception; use Cake\Datasource\EntityInterface; +use Cake\Utility\Hash; /** * Used when a strict save or delete fails @@ -47,35 +48,17 @@ public function __construct(EntityInterface $entity, $message, $code = null, $pr $this->_entity = $entity; if (is_array($message)) { $errors = []; - foreach ($entity->getErrors() as $field => $error) { - $errors[] = $field . ': "' . $this->buildError($error) . '"'; + foreach (Hash::flatten($entity->getErrors()) as $field => $error) { + $errors[] = $field . ': "' . $error . '"'; } if ($errors) { $message[] = implode(', ', $errors); - $this->_messageTemplate = 'Entity %s failure (%s).'; + $this->_messageTemplate = 'Entity %s failure. Found the following errors (%s).'; } } parent::__construct($message, $code, $previous); } - /** - * @param string|array $error Error message. - * @return string - */ - protected function buildError($error) - { - if (!is_array($error)) { - return $error; - } - - $errors = []; - foreach ($error as $key => $message) { - $errors[] = is_int($key) ? $message : $key; - } - - return implode(', ', $errors); - } - /** * Get the passed in entity * From ef2ef999661e8d28ed88c68f8f144d070b4741cb Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Tue, 29 Jan 2019 15:37:49 -0600 Subject: [PATCH 1309/2059] Using accessibleFields when patching during findOrCreate --- Table.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 4866cacd..a6d086f0 100644 --- a/Table.php +++ b/Table.php @@ -1730,7 +1730,8 @@ protected function _processFindOrCreate($search, callable $callback = null, $opt } $entity = $this->newEntity(); if ($options['defaults'] && is_array($search)) { - $entity = $this->patchEntity($entity, $search, ['guard' => false]); + $accessibleFields = array_combine(array_keys($search), array_fill(0, count($search), true)); + $entity = $this->patchEntity($entity, $search, ['accessibleFields' => $accessibleFields]); } if ($callback !== null) { $entity = $callback($entity) ?: $entity; From 69b8065569375d6c6a876207c4e845fb266beb0d Mon Sep 17 00:00:00 2001 From: mscherer Date: Tue, 5 Feb 2019 10:43:54 +0100 Subject: [PATCH 1310/2059] Use field instead of property for Entity. --- Association/BelongsToMany.php | 2 +- Association/HasOne.php | 2 +- Behavior/Translate/ShadowTableStrategy.php | 3 ++- Behavior/Translate/TranslateStrategyTrait.php | 7 ++++--- Entity.php | 2 +- Marshaller.php | 2 +- Table.php | 5 +++-- 7 files changed, 13 insertions(+), 10 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index a9569d3f..014170e4 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -770,7 +770,7 @@ protected function _saveLinks(EntityInterface $sourceEntity, array $targetEntiti // or if we are updating an existing link. if ($changedKeys) { $joint->isNew(true); - $joint->unsetProperty($junction->getPrimaryKey()) + $joint->unsetField($junction->getPrimaryKey()) ->set(array_merge($sourceKeys, $targetKeys), ['guard' => false]); } $saved = $junction->save($joint, $options); diff --git a/Association/HasOne.php b/Association/HasOne.php index 3baa4a09..04c35b07 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -115,7 +115,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) $targetEntity->set($properties, ['guard' => false]); if (!$this->getTarget()->save($targetEntity, $options)) { - $targetEntity->unsetProperty(array_keys($properties)); + $targetEntity->unsetField(array_keys($properties)); return false; } diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 565080fd..eaa7c2e3 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -450,9 +450,10 @@ protected function rowMapper($results, $locale) return $row; } + /** @var \Cake\ORM\Entity $translation|array */ $translation = $row['translation']; - $keys = $hydrated ? $translation->visibleProperties() : array_keys($translation); + $keys = $hydrated ? $translation->getVisible() : array_keys($translation); foreach ($keys as $field) { if ($field === 'locale') { diff --git a/Behavior/Translate/TranslateStrategyTrait.php b/Behavior/Translate/TranslateStrategyTrait.php index 34dba326..018e9030 100644 --- a/Behavior/Translate/TranslateStrategyTrait.php +++ b/Behavior/Translate/TranslateStrategyTrait.php @@ -105,12 +105,13 @@ public function getLocale(): string */ protected function unsetEmptyFields($entity) { + /** @var \Cake\ORM\Entity[] $translations */ $translations = (array)$entity->get('_translations'); foreach ($translations as $locale => $translation) { $fields = $translation->extract($this->_config['fields'], false); foreach ($fields as $field => $value) { if (strlen($value) === 0) { - $translation->unsetProperty($field); + $translation->unsetField($field); } } @@ -126,7 +127,7 @@ protected function unsetEmptyFields($entity) // If now, the whole _translations property is empty, // unset it completely and return if (empty($entity->get('_translations'))) { - $entity->unsetProperty('_translations'); + $entity->unsetField('_translations'); } } @@ -186,6 +187,6 @@ public function buildMarshalMap(Marshaller $marshaller, array $map, array $optio */ public function afterSave(EventInterface $event, EntityInterface $entity) { - $entity->unsetProperty('_i18n'); + $entity->unsetField('_i18n'); } } diff --git a/Entity.php b/Entity.php index 25380305..79ef7c0f 100644 --- a/Entity.php +++ b/Entity.php @@ -65,7 +65,7 @@ public function __construct(array $properties = [], array $options = []) } if (!empty($properties) && $options['markClean'] && !$options['useSetters']) { - $this->_properties = $properties; + $this->_fields = $properties; return; } diff --git a/Marshaller.php b/Marshaller.php index a0995503..528aa82d 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -815,7 +815,7 @@ protected function _mergeJoinData(array $original, BelongsToMany $assoc, array $ // Scalar data can't be handled if (!is_array($value)) { - $record->unsetProperty('_joinData'); + $record->unsetField('_joinData'); continue; } diff --git a/Table.php b/Table.php index 0100e195..e464f823 100644 --- a/Table.php +++ b/Table.php @@ -1823,7 +1823,7 @@ protected function _processSave(EntityInterface $entity, ArrayObject $options) } if (!$success && $isNew) { - $entity->unsetProperty($this->getPrimaryKey()); + $entity->unsetField($this->getPrimaryKey()); $entity->isNew(true); } @@ -2025,9 +2025,10 @@ public function saveMany(iterable $entities, $options = []) { $isNew = []; $cleanup = function ($entities) use (&$isNew): void { + /** @var \Cake\Datasource\EntityInterface[] $entities */ foreach ($entities as $key => $entity) { if (isset($isNew[$key]) && $isNew[$key]) { - $entity->unsetProperty($this->getPrimaryKey()); + $entity->unsetField($this->getPrimaryKey()); $entity->isNew(true); } } From e1d769128e19533f9deead46e5f9808cd33e72d5 Mon Sep 17 00:00:00 2001 From: mscherer Date: Tue, 5 Feb 2019 14:29:41 +0100 Subject: [PATCH 1311/2059] Remove noise docblock annotations. --- Behavior.php | 1 - Query.php | 2 -- 2 files changed, 3 deletions(-) diff --git a/Behavior.php b/Behavior.php index ab11b674..00e927dd 100644 --- a/Behavior.php +++ b/Behavior.php @@ -109,7 +109,6 @@ * * @see \Cake\ORM\Table::addBehavior() * @see \Cake\Event\EventManager - * @mixin \Cake\Core\InstanceConfigTrait */ class Behavior implements EventListenerInterface { diff --git a/Query.php b/Query.php index d7c22886..f14e7515 100644 --- a/Query.php +++ b/Query.php @@ -66,11 +66,9 @@ * with the first rows of the query and each of the items, then the second rows and so on. * @method \Cake\Collection\CollectionInterface chunk($size) Groups the results in arrays of $size rows each. * @method bool isEmpty() Returns true if this query found no results. - * @mixin \Cake\Datasource\QueryTrait */ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface { - use QueryTrait { cache as private _cache; all as private _all; From a2ceb26e852db2f4f6ca21c8ce970615577e619a Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 5 Feb 2019 23:17:08 -0500 Subject: [PATCH 1312/2059] Add more context to a few errors in Table. --- Table.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Table.php b/Table.php index 0100e195..00297af7 100644 --- a/Table.php +++ b/Table.php @@ -1530,7 +1530,10 @@ protected function _processFindOrCreate($search, ?callable $callback = null, $op } elseif ($search instanceof Query) { $query = $search; } else { - throw new InvalidArgumentException('Search criteria must be an array, callable or Query'); + throw new InvalidArgumentException(sprintf( + 'Search criteria must be an array, callable or Query. Got "%s"', + getTypeName($search) + )); } $row = $query->first(); if ($row !== null) { @@ -2225,9 +2228,11 @@ public function callFinder(string $type, Query $query, array $options = []): Que return $this->_behaviors->callFinder($type, [$query, $options]); } - throw new BadMethodCallException( - sprintf('Unknown finder method "%s"', $type) - ); + throw new BadMethodCallException(sprintf( + 'Unknown finder method "%s" on %s.', + $type, + static::class + )); } /** @@ -2315,7 +2320,7 @@ public function __call($method, $args) } throw new BadMethodCallException( - sprintf('Unknown method "%s"', $method) + sprintf('Unknown method "%s" called on %s', $method, static::class) ); } From 6dcf55015bfd2c50b94dac8e62bbc0453b2e1c31 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 5 Feb 2019 23:20:51 -0500 Subject: [PATCH 1313/2059] Add more context to Association errors --- Association.php | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/Association.php b/Association.php index 32678f42..00797f89 100644 --- a/Association.php +++ b/Association.php @@ -252,7 +252,11 @@ public function setName(string $name) if ($this->_targetTable !== null) { $alias = $this->_targetTable->getAlias(); if ($alias !== $name) { - throw new InvalidArgumentException('Association name does not match target table alias.'); + throw new InvalidArgumentException(sprintf( + 'Association name "%s" does not match target table alias "%s".', + $name, + $alias + )); } } @@ -308,9 +312,11 @@ public function setClassName(string $className) if ($this->_targetTable !== null && get_class($this->_targetTable) !== App::className($className, 'Model/Table', 'Table') ) { - throw new InvalidArgumentException( - 'The class name doesn\'t match the target table\'s class name.' - ); + throw new InvalidArgumentException(sprintf( + 'The class name of "%s" doesn\'t match the target table\'s class name of "%s".', + $className, + get_class($this->_targetTable) + )); } $this->_className = $className; @@ -620,9 +626,11 @@ protected function _propertyName(): string public function setStrategy(string $name) { if (!in_array($name, $this->_validStrategies)) { - throw new InvalidArgumentException( - sprintf('Invalid strategy "%s" was provided', $name) - ); + throw new InvalidArgumentException(sprintf( + 'Invalid strategy "%s" was provided. Valid options are (%s).', + $name, + implode(', ', $this->_validStrategies) + )); } $this->_strategy = $name; From 7b6ed36edfe4d4df57c4083c1721da7491d0c568 Mon Sep 17 00:00:00 2001 From: mscherer Date: Wed, 6 Feb 2019 11:43:42 +0100 Subject: [PATCH 1314/2059] Use unset() as per code review. --- Association/BelongsToMany.php | 2 +- Association/HasOne.php | 2 +- Behavior/Translate/TranslateStrategyTrait.php | 6 +++--- Marshaller.php | 2 +- Table.php | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 014170e4..8f7eacb9 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -770,7 +770,7 @@ protected function _saveLinks(EntityInterface $sourceEntity, array $targetEntiti // or if we are updating an existing link. if ($changedKeys) { $joint->isNew(true); - $joint->unsetField($junction->getPrimaryKey()) + $joint->unset($junction->getPrimaryKey()) ->set(array_merge($sourceKeys, $targetKeys), ['guard' => false]); } $saved = $junction->save($joint, $options); diff --git a/Association/HasOne.php b/Association/HasOne.php index 04c35b07..f5d6c578 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -115,7 +115,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) $targetEntity->set($properties, ['guard' => false]); if (!$this->getTarget()->save($targetEntity, $options)) { - $targetEntity->unsetField(array_keys($properties)); + $targetEntity->unset(array_keys($properties)); return false; } diff --git a/Behavior/Translate/TranslateStrategyTrait.php b/Behavior/Translate/TranslateStrategyTrait.php index 018e9030..d5cb1c59 100644 --- a/Behavior/Translate/TranslateStrategyTrait.php +++ b/Behavior/Translate/TranslateStrategyTrait.php @@ -111,7 +111,7 @@ protected function unsetEmptyFields($entity) $fields = $translation->extract($this->_config['fields'], false); foreach ($fields as $field => $value) { if (strlen($value) === 0) { - $translation->unsetField($field); + $translation->unset($field); } } @@ -127,7 +127,7 @@ protected function unsetEmptyFields($entity) // If now, the whole _translations property is empty, // unset it completely and return if (empty($entity->get('_translations'))) { - $entity->unsetField('_translations'); + $entity->unset('_translations'); } } @@ -187,6 +187,6 @@ public function buildMarshalMap(Marshaller $marshaller, array $map, array $optio */ public function afterSave(EventInterface $event, EntityInterface $entity) { - $entity->unsetField('_i18n'); + $entity->unset('_i18n'); } } diff --git a/Marshaller.php b/Marshaller.php index 528aa82d..ea0ec2b2 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -815,7 +815,7 @@ protected function _mergeJoinData(array $original, BelongsToMany $assoc, array $ // Scalar data can't be handled if (!is_array($value)) { - $record->unsetField('_joinData'); + $record->unset('_joinData'); continue; } diff --git a/Table.php b/Table.php index e464f823..452733ba 100644 --- a/Table.php +++ b/Table.php @@ -1823,7 +1823,7 @@ protected function _processSave(EntityInterface $entity, ArrayObject $options) } if (!$success && $isNew) { - $entity->unsetField($this->getPrimaryKey()); + $entity->unset($this->getPrimaryKey()); $entity->isNew(true); } @@ -2028,7 +2028,7 @@ public function saveMany(iterable $entities, $options = []) /** @var \Cake\Datasource\EntityInterface[] $entities */ foreach ($entities as $key => $entity) { if (isset($isNew[$key]) && $isNew[$key]) { - $entity->unsetField($this->getPrimaryKey()); + $entity->unset($this->getPrimaryKey()); $entity->isNew(true); } } From c79f3f7880e2eb7bd9f196ff527bcfdb6f0e55d0 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Wed, 6 Feb 2019 14:07:39 -0500 Subject: [PATCH 1315/2059] Tweak error messages more --- Association.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association.php b/Association.php index 00797f89..e4cf79fc 100644 --- a/Association.php +++ b/Association.php @@ -313,7 +313,7 @@ public function setClassName(string $className) get_class($this->_targetTable) !== App::className($className, 'Model/Table', 'Table') ) { throw new InvalidArgumentException(sprintf( - 'The class name of "%s" doesn\'t match the target table\'s class name of "%s".', + 'The class name "%s" doesn\'t match the target table class name of "%s".', $className, get_class($this->_targetTable) )); From 21219bad8a8b970d4f2852632d180a0c6ce99df7 Mon Sep 17 00:00:00 2001 From: mscherer Date: Sat, 16 Feb 2019 14:26:48 +0100 Subject: [PATCH 1316/2059] Better getter usage for internal framework handling. --- Table.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index 25acce9d..cf35e79e 100644 --- a/Table.php +++ b/Table.php @@ -22,6 +22,7 @@ use Cake\Database\Schema\TableSchema; use Cake\Database\TypeFactory; use Cake\Datasource\ConnectionInterface; +use Cake\Datasource\ConnectionManager; use Cake\Datasource\EntityInterface; use Cake\Datasource\Exception\InvalidPrimaryKeyException; use Cake\Datasource\RepositoryInterface; @@ -458,10 +459,14 @@ public function setConnection(ConnectionInterface $connection) /** * Returns the connection instance. * - * @return \Cake\Database\Connection|null + * @return \Cake\Database\Connection */ - public function getConnection(): ?Connection + public function getConnection(): Connection { + if (!$this->_connection) { + $this->_connection = ConnectionManager::get(static::defaultConnectionName()); + } + return $this->_connection; } From b5b2a729c37b573b342197c98a694fbcd2ccbdc9 Mon Sep 17 00:00:00 2001 From: mscherer Date: Sat, 16 Feb 2019 15:58:29 +0100 Subject: [PATCH 1317/2059] Fix phpstan. --- Table.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Table.php b/Table.php index cf35e79e..149bc497 100644 --- a/Table.php +++ b/Table.php @@ -464,7 +464,9 @@ public function setConnection(ConnectionInterface $connection) public function getConnection(): Connection { if (!$this->_connection) { - $this->_connection = ConnectionManager::get(static::defaultConnectionName()); + /** @var \Cake\Database\Connection $connection */ + $connection = ConnectionManager::get(static::defaultConnectionName()); + $this->_connection = $connection; } return $this->_connection; From 10fc6bfefad2bfa148688fe9078f753f41949863 Mon Sep 17 00:00:00 2001 From: mscherer Date: Sat, 16 Feb 2019 16:20:43 +0100 Subject: [PATCH 1318/2059] Fix psalm. --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 149bc497..df0c29c9 100644 --- a/Table.php +++ b/Table.php @@ -2779,7 +2779,7 @@ public function __debugInfo() 'associations' => $associations ? $associations->keys() : false, 'behaviors' => $behaviors ? $behaviors->loaded() : false, 'defaultConnection' => static::defaultConnectionName(), - 'connectionName' => $conn ? $conn->configName() : null, + 'connectionName' => $conn->configName(), ]; } } From c16830994654dc10e8dd6ee55b6e57f40867964a Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 16 Feb 2019 18:00:15 +0530 Subject: [PATCH 1319/2059] Fix return type issues reported by psalm. --- Behavior/TreeBehavior.php | 1 + Table.php | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 6563d7ba..174317f0 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -801,6 +801,7 @@ protected function _getNode($id): EntityInterface throw new RecordNotFoundException("Node \"{$id}\" was not found in the tree."); } + /** @psalm-suppress InvalidReturnStatement */ return $node; } diff --git a/Table.php b/Table.php index df0c29c9..726eaf85 100644 --- a/Table.php +++ b/Table.php @@ -1432,6 +1432,7 @@ public function get($primaryKey, $options = []): EntityInterface $query->cache($cacheKey, $cacheConfig); } + /** @psalm-suppress InvalidReturnStatement */ return $query->firstOrFail(); } @@ -1525,9 +1526,9 @@ public function findOrCreate($search, ?callable $callback = null, $options = []) * created entities. This callback will be called *before* the entity * is persisted. * @param array $options The options to use when saving. - * @return \Cake\Datasource\EntityInterface An entity. + * @return \Cake\Datasource\EntityInterface|array An entity. */ - protected function _processFindOrCreate($search, ?callable $callback = null, $options = []): EntityInterface + protected function _processFindOrCreate($search, ?callable $callback = null, $options = []) { if (is_callable($search)) { $query = $this->find(); From 2a0b17d3cf6b9af04c11bfa2e576a74a5e6d1e64 Mon Sep 17 00:00:00 2001 From: mscherer Date: Mon, 18 Feb 2019 11:25:54 +0100 Subject: [PATCH 1320/2059] Add short list sniffer rule. --- Association.php | 10 +++++----- Association/BelongsTo.php | 2 +- Association/BelongsToMany.php | 2 +- Association/HasOne.php | 2 +- Association/Loader/SelectLoader.php | 2 +- AssociationCollection.php | 4 ++-- Behavior/Translate/ShadowTableStrategy.php | 2 +- Behavior/TreeBehavior.php | 22 +++++++++++----------- BehaviorRegistry.php | 4 ++-- EagerLoader.php | 2 +- Locator/TableLocator.php | 4 ++-- Marshaller.php | 4 ++-- Table.php | 2 +- 13 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Association.php b/Association.php index e4cf79fc..214bd7eb 100644 --- a/Association.php +++ b/Association.php @@ -230,7 +230,7 @@ public function __construct(string $alias, array $options = []) $this->_className = $alias; } - list(, $name) = pluginSplit($alias); + [, $name] = pluginSplit($alias); $this->_name = $name; $this->_options($options); @@ -379,7 +379,7 @@ public function getTarget(): Table { if (!$this->_targetTable) { if (strpos((string)$this->_className, '.')) { - list($plugin) = pluginSplit($this->_className, true); + [$plugin] = pluginSplit($this->_className, true); $registryAlias = $plugin . $this->_name; } else { $registryAlias = $this->_name; @@ -609,7 +609,7 @@ public function getProperty(): string */ protected function _propertyName(): string { - list(, $name) = pluginSplit($this->_name); + [, $name] = pluginSplit($this->_name); return Inflector::underscore($name); } @@ -735,7 +735,7 @@ public function attachTo(Query $query, array $options = []): void } } - list($finder, $opts) = $this->_extractFinder($options['finder']); + [$finder, $opts] = $this->_extractFinder($options['finder']); $dummy = $this ->find($finder, $opts) ->eagerLoaded(true); @@ -844,7 +844,7 @@ public function defaultRowValue(array $row, bool $joined): array public function find($type = null, array $options = []): Query { $type = $type ?: $this->getFinder(); - list($type, $opts) = $this->_extractFinder($type); + [$type, $opts] = $this->_extractFinder($type); return $this->getTarget() ->find($type, $options + $opts) diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 74a9c247..c6d23a08 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -77,7 +77,7 @@ public function cascadeDelete(EntityInterface $entity, array $options = []): boo */ protected function _propertyName(): string { - list(, $name) = pluginSplit($this->_name); + [, $name] = pluginSplit($this->_name); return Inflector::underscore(Inflector::singularize($name)); } diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 8f7eacb9..eb54136d 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1025,7 +1025,7 @@ protected function junctionConditions(): array public function find($type = null, array $options = []): Query { $type = $type ?: $this->getFinder(); - list($type, $opts) = $this->_extractFinder($type); + [$type, $opts] = $this->_extractFinder($type); $query = $this->getTarget() ->find($type, $options + $opts) ->where($this->targetConditions()) diff --git a/Association/HasOne.php b/Association/HasOne.php index f5d6c578..42273b2b 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -61,7 +61,7 @@ public function getForeignKey() */ protected function _propertyName(): string { - list(, $name) = pluginSplit($this->_name); + [, $name] = pluginSplit($this->_name); return Inflector::underscore(Inflector::singularize($name)); } diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 02aeb972..5312974e 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -168,7 +168,7 @@ protected function _buildQuery(array $options): Query /* @var \Cake\ORM\Query $query */ $query = $finder(); if (isset($options['finder'])) { - list($finderName, $opts) = $this->_extractFinder($options['finder']); + [$finderName, $opts] = $this->_extractFinder($options['finder']); $query = $query->find($finderName, $opts); } diff --git a/AssociationCollection.php b/AssociationCollection.php index e7e8ecae..9a46febe 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -67,7 +67,7 @@ public function __construct(?LocatorInterface $tableLocator = null) */ public function add(string $alias, Association $association): Association { - list(, $alias) = pluginSplit($alias); + [, $alias] = pluginSplit($alias); return $this->_items[strtolower($alias)] = $association; } @@ -167,7 +167,7 @@ public function getByType($class): array $class = array_map('strtolower', (array)$class); $out = array_filter($this->_items, function ($assoc) use ($class) { - list(, $name) = namespaceSplit(get_class($assoc)); + [, $name] = namespaceSplit(get_class($assoc)); return in_array(strtolower($name), $class, true); }); diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index eaa7c2e3..fe257b5a 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -65,7 +65,7 @@ class ShadowTableStrategy implements TranslateStrategyInterface public function __construct(Table $table, array $config = []) { $tableAlias = $table->getAlias(); - list($plugin) = pluginSplit($table->getRegistryAlias(), true); + [$plugin] = pluginSplit($table->getRegistryAlias(), true); $tableReferenceName = $config['referenceName']; $config += [ diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 6563d7ba..9dcc20d8 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -380,7 +380,7 @@ public function findPath(Query $query, array $options): Query } $config = $this->getConfig(); - list($left, $right) = array_map( + [$left, $right] = array_map( function ($field) { return $this->_table->aliasField($field); }, @@ -441,14 +441,14 @@ public function findChildren(Query $query, array $options): Query { $config = $this->getConfig(); $options += ['for' => null, 'direct' => false]; - list($parent, $left, $right) = array_map( + [$parent, $left, $right] = array_map( function ($field) { return $this->_table->aliasField($field); }, [$config['parent'], $config['left'], $config['right']] ); - list($for, $direct) = [$options['for'], $options['direct']]; + [$for, $direct] = [$options['for'], $options['direct']]; if (empty($for)) { throw new InvalidArgumentException("The 'for' key is required for find('children')"); @@ -630,8 +630,8 @@ public function moveUp(EntityInterface $node, $number = 1) protected function _moveUp(EntityInterface $node, $number) { $config = $this->getConfig(); - list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']]; - list($nodeParent, $nodeLeft, $nodeRight) = array_values($node->extract([$parent, $left, $right])); + [$parent, $left, $right] = [$config['parent'], $config['left'], $config['right']]; + [$nodeParent, $nodeLeft, $nodeRight] = array_values($node->extract([$parent, $left, $right])); $targetNode = null; if ($number !== true) { @@ -664,7 +664,7 @@ protected function _moveUp(EntityInterface $node, $number) } } - list($targetLeft) = array_values($targetNode->extract([$left, $right])); + [$targetLeft] = array_values($targetNode->extract([$left, $right])); $edge = $this->_getMax(); $leftBoundary = $targetLeft; $rightBoundary = $nodeLeft - 1; @@ -720,8 +720,8 @@ public function moveDown(EntityInterface $node, $number = 1) protected function _moveDown(EntityInterface $node, $number) { $config = $this->getConfig(); - list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']]; - list($nodeParent, $nodeLeft, $nodeRight) = array_values($node->extract([$parent, $left, $right])); + [$parent, $left, $right] = [$config['parent'], $config['left'], $config['right']]; + [$nodeParent, $nodeLeft, $nodeRight] = array_values($node->extract([$parent, $left, $right])); $targetNode = null; if ($number !== true) { @@ -754,7 +754,7 @@ protected function _moveDown(EntityInterface $node, $number) } } - list(, $targetRight) = array_values($targetNode->extract([$left, $right])); + [, $targetRight] = array_values($targetNode->extract([$left, $right])); $edge = $this->_getMax(); $leftBoundary = $nodeRight + 1; $rightBoundary = $targetRight; @@ -785,7 +785,7 @@ protected function _moveDown(EntityInterface $node, $number) protected function _getNode($id): EntityInterface { $config = $this->getConfig(); - list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']]; + [$parent, $left, $right] = [$config['parent'], $config['left'], $config['right']]; $primaryKey = $this->_getPrimaryKey(); $fields = [$parent, $left, $right]; if ($config['level']) { @@ -828,7 +828,7 @@ public function recover(): void protected function _recoverTree(int $counter = 0, $parentId = null, $level = -1): int { $config = $this->getConfig(); - list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']]; + [$parent, $left, $right] = [$config['parent'], $config['left'], $config['right']]; $primaryKey = $this->_getPrimaryKey(); $aliasedPrimaryKey = $this->_table->aliasField($primaryKey); $order = $config['recoverOrder'] ?: $aliasedPrimaryKey; diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index a634b615..67f3cba7 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -245,7 +245,7 @@ public function call(string $method, array $args = []) { $method = strtolower($method); if ($this->hasMethod($method) && $this->has($this->_methodMap[$method][0])) { - list($behavior, $callMethod) = $this->_methodMap[$method]; + [$behavior, $callMethod] = $this->_methodMap[$method]; return call_user_func_array([$this->_loaded[$behavior], $callMethod], $args); } @@ -268,7 +268,7 @@ public function callFinder(string $type, array $args = []) $type = strtolower($type); if ($this->hasFinder($type) && $this->has($this->_finderMap[$type][0])) { - list($behavior, $callMethod) = $this->_finderMap[$type]; + [$behavior, $callMethod] = $this->_finderMap[$type]; return call_user_func_array([$this->_loaded[$behavior], $callMethod], $args); } diff --git a/EagerLoader.php b/EagerLoader.php index d98dbe0d..ce5e518c 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -627,7 +627,7 @@ public function loadExternal(Query $query, StatementInterface $statement): State } $driver = $query->getConnection()->getDriver(); - list($collected, $statement) = $this->_collectKeys($external, $query, $statement); + [$collected, $statement] = $this->_collectKeys($external, $query, $statement); foreach ($external as $meta) { $contain = $meta->associations(); diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 4e4b6579..215fc34f 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -176,7 +176,7 @@ public function get(string $alias, array $options = []): Table } $this->_options[$alias] = $options; - list(, $classAlias) = pluginSplit($alias); + [, $classAlias] = pluginSplit($alias); $options = ['alias' => $classAlias] + $options; if (isset($this->_config[$alias])) { @@ -191,7 +191,7 @@ public function get(string $alias, array $options = []): Table $options['className'] = Inflector::camelize($alias); } if (!isset($options['table']) && strpos($options['className'], '\\') === false) { - list(, $table) = pluginSplit($options['className']); + [, $table] = pluginSplit($options['className']); $options['table'] = Inflector::underscore($table); } $options['className'] = 'Cake\ORM\Table'; diff --git a/Marshaller.php b/Marshaller.php index ea0ec2b2..1e05caf4 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -166,7 +166,7 @@ protected function _buildPropertyMap(array $data, array $options): array */ public function one(array $data, array $options = []): EntityInterface { - list($data, $options) = $this->_prepareDataAndOptions($data, $options); + [$data, $options] = $this->_prepareDataAndOptions($data, $options); $primaryKey = (array)$this->_table->getPrimaryKey(); $entityClass = $this->_table->getEntityClass(); @@ -524,7 +524,7 @@ protected function _loadAssociatedByIds(Association $assoc, array $ids): array */ public function merge(EntityInterface $entity, array $data, array $options = []): EntityInterface { - list($data, $options) = $this->_prepareDataAndOptions($data, $options); + [$data, $options] = $this->_prepareDataAndOptions($data, $options); $isNew = $entity->isNew(); $keys = []; diff --git a/Table.php b/Table.php index df0c29c9..eb1da1a6 100644 --- a/Table.php +++ b/Table.php @@ -867,7 +867,7 @@ protected function findAssociation(string $name): ?Association return $this->_associations->get($name); } - list($name, $next) = array_pad(explode('.', $name, 2), 2, null); + [$name, $next] = array_pad(explode('.', $name, 2), 2, null); $result = $this->_associations->get($name); if ($result !== null && $next !== null) { From 0955d42a5bc9d6d5f2f5def0f278ada1257cfc81 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 28 Feb 2019 11:07:22 +0530 Subject: [PATCH 1321/2059] Update docblocks. --- EagerLoader.php | 3 +-- Table.php | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index ce5e518c..4507f933 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -753,7 +753,7 @@ public function addToJoinsMap( * Helper function used to return the keys from the query records that will be used * to eagerly load associations. * - * @param array $external the list of external associations to be loaded + * @param \Cake\ORM\EagerLoadable[] $external the list of external associations to be loaded * @param \Cake\ORM\Query $query The query from which the results where generated * @param \Cake\Database\Statement\BufferedStatement $statement The statement to work on * @return array @@ -761,7 +761,6 @@ public function addToJoinsMap( protected function _collectKeys(array $external, Query $query, $statement): array { $collectKeys = []; - /* @var \Cake\ORM\EagerLoadable $meta */ foreach ($external as $meta) { $instance = $meta->instance(); if (!$instance->requiresKeys($meta->getConfig())) { diff --git a/Table.php b/Table.php index 65f2afcf..c37f3b6c 100644 --- a/Table.php +++ b/Table.php @@ -1347,7 +1347,7 @@ public function findThreaded(Query $query, array $options): Query * composite keys when comparing values. * * @param array $options the original options passed to a finder - * @param array $keys the keys to check in $options to build matchers from + * @param string[] $keys the keys to check in $options to build matchers from * the associated value * @return array */ @@ -2259,7 +2259,7 @@ public function callFinder(string $type, Query $query, array $options = []): Que * * @param string $method The method name that was fired. * @param array $args List of arguments passed to the function. - * @return mixed + * @return \Cake\ORM\Query * @throws \BadMethodCallException when there are missing arguments, or when * and & or are combined. */ From 44f89ef1372cc0fc5be84d14999cbac1f9bdc319 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 2 Mar 2019 13:08:11 +0530 Subject: [PATCH 1322/2059] Fix errors reported by psalm. --- EagerLoader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EagerLoader.php b/EagerLoader.php index 4507f933..35f1a841 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -83,7 +83,7 @@ class EagerLoader /** * Another EagerLoader instance that will be used for 'matching' associations. * - * @var \Cake\ORM\EagerLoader + * @var \Cake\ORM\EagerLoader|null */ protected $_matching; From c452d5e8d072a50e52c26de9148df9f1fb62ae9d Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 4 Mar 2019 13:08:53 +0530 Subject: [PATCH 1323/2059] Fix errors reported by phpstan level 4. --- Association.php | 4 ++-- Association/BelongsToMany.php | 4 ++-- EagerLoader.php | 5 ----- Locator/LocatorAwareTrait.php | 2 +- Query.php | 8 ++++---- ResultSet.php | 6 +++--- Table.php | 18 ++++++++---------- TableRegistry.php | 2 +- 8 files changed, 21 insertions(+), 28 deletions(-) diff --git a/Association.php b/Association.php index 214bd7eb..b19ca8d9 100644 --- a/Association.php +++ b/Association.php @@ -377,7 +377,7 @@ public function setTarget(Table $table) */ public function getTarget(): Table { - if (!$this->_targetTable) { + if ($this->_targetTable === null) { if (strpos((string)$this->_className, '.')) { [$plugin] = pluginSplit($this->_className, true); $registryAlias = $plugin . $this->_name; @@ -404,7 +404,7 @@ public function getTarget(): Table throw new RuntimeException(sprintf( $errorMessage, - $this->_sourceTable ? get_class($this->_sourceTable) : 'null', + $this->_sourceTable === null ? 'null' : get_class($this->_sourceTable), $this->getName(), $this->type(), get_class($this->_targetTable), diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index eb54136d..c1944dec 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -255,7 +255,7 @@ public function defaultRowValue(array $row, bool $joined): array */ public function junction($table = null): Table { - if ($table === null && $this->_junctionTable) { + if ($table === null && $this->_junctionTable !== null) { return $this->_junctionTable; } @@ -711,7 +711,7 @@ protected function _saveTarget(EntityInterface $parentEntity, array $entities, $ if (!empty($options['atomic'])) { $original[$k]->setErrors($entity->getErrors()); } - if (!$saved) { + if ($saved === false) { return false; } } diff --git a/EagerLoader.php b/EagerLoader.php index 35f1a841..6abb6a91 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -488,11 +488,6 @@ protected function _normalizeContain(Table $parent, string $alias, array $option { $defaults = $this->_containOptions; $instance = $parent->getAssociation($alias); - if (!$instance) { - throw new InvalidArgumentException( - sprintf('%s is not associated with %s', $parent->getAlias(), $alias) - ); - } $paths += ['aliasPath' => '', 'propertyPath' => '', 'root' => $alias]; $paths['aliasPath'] .= '.' . $alias; diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index 980f1d77..6022fc04 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -49,7 +49,7 @@ public function setTableLocator(LocatorInterface $tableLocator) */ public function getTableLocator(): LocatorInterface { - if (!$this->_tableLocator) { + if ($this->_tableLocator === null) { $this->_tableLocator = TableRegistry::getTableLocator(); } diff --git a/Query.php b/Query.php index 676b1dcb..009e2f6f 100644 --- a/Query.php +++ b/Query.php @@ -128,7 +128,7 @@ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface * A callable function that can be used to calculate the total amount of * records this query will match when not using `limit` * - * @var callable + * @var callable|null */ protected $_counter; @@ -167,7 +167,7 @@ public function __construct(Connection $connection, Table $table) parent::__construct($connection); $this->repository($table); - if ($this->_repository) { + if ($this->_repository !== null) { $this->addDefaultTypes($this->_repository); } } @@ -856,7 +856,7 @@ public function cleanCopy(): Query public function __clone() { parent::__clone(); - if ($this->_eagerLoader) { + if ($this->_eagerLoader !== null) { $this->_eagerLoader = clone $this->_eagerLoader; } } @@ -886,7 +886,7 @@ protected function _performCount(): int { $query = $this->cleanCopy(); $counter = $this->_counter; - if ($counter) { + if ($counter !== null) { $query->counter(null); return (int)$counter($query); diff --git a/ResultSet.php b/ResultSet.php index 7af25fca..6065a774 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -289,7 +289,7 @@ public function valid(): bool public function first() { foreach ($this as $result) { - if ($this->_statement && !$this->_useBuffering) { + if ($this->_statement !== null && !$this->_useBuffering) { $this->_statement->closeCursor(); } @@ -351,7 +351,7 @@ public function count(): int if ($this->_count !== null) { return $this->_count; } - if ($this->_statement) { + if ($this->_statement !== null) { return $this->_count = $this->_statement->rowCount(); } @@ -426,7 +426,7 @@ protected function _calculateColumnMap(Query $query): void */ protected function _fetchResult() { - if (!$this->_statement) { + if ($this->_statement === null) { return false; } diff --git a/Table.php b/Table.php index c37f3b6c..db5f0e07 100644 --- a/Table.php +++ b/Table.php @@ -781,7 +781,7 @@ public function behaviors(): BehaviorRegistry */ public function getBehavior(string $name): Behavior { - /** @var \Cake\ORM\Behavior $behavior */ + /** @var \Cake\ORM\Behavior|null $behavior */ $behavior = $this->_behaviors->get($name); if ($behavior === null) { throw new InvalidArgumentException(sprintf( @@ -1490,7 +1490,7 @@ protected function _transactionCommitted(bool $atomic, bool $primary): bool * transaction (default: true) * - defaults: Whether to use the search criteria as default values for the new entity (default: true) * - * @param array|\Cake\ORM\Query $search The criteria to find existing + * @param array|callable|\Cake\ORM\Query $search The criteria to find existing * records by. Note that when you pass a query object you'll have to use * the 2nd arg of the method to modify the entity data before saving. * @param callable|null $callback A callback that will be invoked for newly @@ -1520,7 +1520,7 @@ public function findOrCreate($search, ?callable $callback = null, $options = []) /** * Performs the actual find and/or create of an entity based on the passed options. * - * @param array|callable $search The criteria to find an existing record by, or a callable tha will + * @param array|callable|\Cake\ORM\Query $search The criteria to find an existing record by, or a callable tha will * customize the find query. * @param callable|null $callback A callback that will be invoked for newly * created entities. This callback will be called *before* the entity @@ -2221,7 +2221,7 @@ public function hasFinder(string $type): bool { $finder = 'find' . $type; - return method_exists($this, $finder) || ($this->_behaviors && $this->_behaviors->hasFinder($type)); + return method_exists($this, $finder) || $this->_behaviors->hasFinder($type); } /** @@ -2243,7 +2243,7 @@ public function callFinder(string $type, Query $query, array $options = []): Que return $this->{$finder}($query, $options); } - if ($this->_behaviors && $this->_behaviors->hasFinder($type)) { + if ($this->_behaviors->hasFinder($type)) { return $this->_behaviors->callFinder($type, [$query, $options]); } @@ -2331,7 +2331,7 @@ protected function _dynamicFinder(string $method, array $args) */ public function __call($method, $args) { - if ($this->_behaviors && $this->_behaviors->hasMethod($method)) { + if ($this->_behaviors->hasMethod($method)) { return $this->_behaviors->call($method, $args); } if (preg_match('/^find(?:\w+)?By/', $method) > 0) { @@ -2769,16 +2769,14 @@ protected function validationMethodExists(string $method): bool public function __debugInfo() { $conn = $this->getConnection(); - $associations = $this->_associations; - $behaviors = $this->_behaviors; return [ 'registryAlias' => $this->getRegistryAlias(), 'table' => $this->getTable(), 'alias' => $this->getAlias(), 'entityClass' => $this->getEntityClass(), - 'associations' => $associations ? $associations->keys() : false, - 'behaviors' => $behaviors ? $behaviors->loaded() : false, + 'associations' => $this->_associations->keys(), + 'behaviors' => $this->_behaviors->loaded(), 'defaultConnection' => static::defaultConnectionName(), 'connectionName' => $conn->configName(), ]; diff --git a/TableRegistry.php b/TableRegistry.php index c0c723d5..9b693064 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -71,7 +71,7 @@ class TableRegistry */ public static function getTableLocator(): LocatorInterface { - if (!static::$_locator) { + if (static::$_locator === null) { static::$_locator = new static::$_defaultLocatorClass(); } From 040314f18c7c34ed9ff757465083d6a7867490e8 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 4 Mar 2019 22:18:14 +0530 Subject: [PATCH 1324/2059] Fix errors reported by pslam and update it's config. --- Association.php | 4 ++-- Association/BelongsToMany.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Association.php b/Association.php index b19ca8d9..958b1f87 100644 --- a/Association.php +++ b/Association.php @@ -767,11 +767,11 @@ public function attachTo(Query $query, array $options = []): void * Conditionally adds a condition to the passed Query that will make it find * records where there is no match with this association. * - * @param \Cake\Datasource\QueryInterface $query The query to modify + * @param \Cake\ORM\Query $query The query to modify * @param array $options Options array containing the `negateMatch` key. * @return void */ - protected function _appendNotMatching(QueryInterface $query, array $options): void + protected function _appendNotMatching(Query $query, array $options): void { $target = $this->_targetTable; if (!empty($options['negateMatch'])) { diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index c1944dec..caf222d1 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -449,7 +449,7 @@ public function attachTo(Query $query, array $options = []): void /** * @inheritDoc */ - protected function _appendNotMatching(QueryInterface $query, array $options): void + protected function _appendNotMatching(Query $query, array $options): void { if (empty($options['negateMatch'])) { return; From ebc4be791dd53b9035ff015a7c32be58d8e66465 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 6 Mar 2019 12:29:04 +0530 Subject: [PATCH 1325/2059] Fix errors reported by psalm. --- Behavior/TreeBehavior.php | 5 +++-- ResultSet.php | 1 + Table.php | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index f2abaf34..bbb28d14 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -781,6 +781,7 @@ protected function _moveDown(EntityInterface $node, $number) * @param mixed $id Record id. * @return \Cake\Datasource\EntityInterface * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found + * @psalm-suppress InvalidReturnType */ protected function _getNode($id): EntityInterface { @@ -878,11 +879,11 @@ protected function _getMax(): int ->orderDesc($rightField) ->first(); - if (empty($edge->{$field})) { + if (empty($edge[$field])) { return 0; } - return $edge->{$field}; + return $edge[$field]; } /** diff --git a/ResultSet.php b/ResultSet.php index 6065a774..028b8eaa 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -285,6 +285,7 @@ public function valid(): bool * This method will also close the underlying statement cursor. * * @return array|object + * @psalm-suppress InvalidNullableReturnType */ public function first() { diff --git a/Table.php b/Table.php index db5f0e07..29eb8b51 100644 --- a/Table.php +++ b/Table.php @@ -1390,6 +1390,7 @@ protected function _setFieldMatchers(array $options, array $keys): array * * @throws \Cake\Datasource\Exception\InvalidPrimaryKeyException When $primaryKey has an * incorrect number of elements. + * @psalm-suppress InvalidReturnType */ public function get($primaryKey, $options = []): EntityInterface { From 170fccd6096150d6d248ba42203210ca4024bd2a Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 11 Mar 2019 18:26:14 +0530 Subject: [PATCH 1326/2059] Run phpstan at level 4. --- Table.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Table.php b/Table.php index 29eb8b51..e086e4d2 100644 --- a/Table.php +++ b/Table.php @@ -2045,6 +2045,7 @@ protected function _update(EntityInterface $entity, array $data) */ public function saveMany(iterable $entities, $options = []) { + /** @var bool[] $isNew */ $isNew = []; $cleanup = function ($entities) use (&$isNew): void { /** @var \Cake\Datasource\EntityInterface[] $entities */ From 7fec1615eaf76c57fd9f13ac1b8ecfca8ce2125b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgaras=20Janu=C5=A1auskas?= Date: Thu, 21 Mar 2019 21:51:13 +0200 Subject: [PATCH 1327/2059] Fix PHPDoc of Table::findOrCreate() and reuse unused Table::_getFindOrCreateQuery() --- Table.php | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/Table.php b/Table.php index be4d91d6..0f8cff80 100644 --- a/Table.php +++ b/Table.php @@ -1674,7 +1674,7 @@ protected function _transactionCommitted($atomic, $primary) * transaction (default: true) * - defaults: Whether to use the search criteria as default values for the new entity (default: true) * - * @param array|\Cake\ORM\Query $search The criteria to find existing + * @param array|callable|\Cake\ORM\Query $search The criteria to find existing * records by. Note that when you pass a query object you'll have to use * the 2nd arg of the method to modify the entity data before saving. * @param callable|null $callback A callback that will be invoked for newly @@ -1704,7 +1704,7 @@ public function findOrCreate($search, callable $callback = null, $options = []) /** * Performs the actual find and/or create of an entity based on the passed options. * - * @param array|callable $search The criteria to find an existing record by, or a callable tha will + * @param array|callable|\Cake\ORM\Query $search The criteria to find an existing record by, or a callable tha will * customize the find query. * @param callable|null $callback A callback that will be invoked for newly * created entities. This callback will be called *before* the entity @@ -1714,20 +1714,12 @@ public function findOrCreate($search, callable $callback = null, $options = []) */ protected function _processFindOrCreate($search, callable $callback = null, $options = []) { - if (is_callable($search)) { - $query = $this->find(); - $search($query); - } elseif (is_array($search)) { - $query = $this->find()->where($search); - } elseif ($search instanceof Query) { - $query = $search; - } else { - throw new InvalidArgumentException('Search criteria must be an array, callable or Query'); - } + $query = $this->_getFindOrCreateQuery($search); $row = $query->first(); if ($row !== null) { return $row; } + $entity = $this->newEntity(); if ($options['defaults'] && is_array($search)) { $entity->set($search, ['guard' => false]); @@ -1743,16 +1735,23 @@ protected function _processFindOrCreate($search, callable $callback = null, $opt /** * Gets the query object for findOrCreate(). * - * @param array|\Cake\ORM\Query|string $search The criteria to find existing records by. + * @param array|callable|\Cake\ORM\Query $search The criteria to find existing records by. * @return \Cake\ORM\Query */ protected function _getFindOrCreateQuery($search) { - if ($search instanceof Query) { - return $search; + if (is_callable($search)) { + $query = $this->find(); + $search($query); + } elseif (is_array($search)) { + $query = $this->find()->where($search); + } elseif ($search instanceof Query) { + $query = $search; + } else { + throw new InvalidArgumentException('Search criteria must be an array, callable or Query'); } - return $this->find()->where($search); + return $query; } /** From 8e6d0c8a592ed80f0b6548ef7638684b66414f8a Mon Sep 17 00:00:00 2001 From: mscherer Date: Thu, 21 Mar 2019 21:31:47 +0100 Subject: [PATCH 1328/2059] Add createEntity() to make newEntity() validation save. --- Behavior/Translate/TranslateStrategyTrait.php | 8 ++++--- Table.php | 23 +++++++++++++------ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/Behavior/Translate/TranslateStrategyTrait.php b/Behavior/Translate/TranslateStrategyTrait.php index d5cb1c59..b4215ce6 100644 --- a/Behavior/Translate/TranslateStrategyTrait.php +++ b/Behavior/Translate/TranslateStrategyTrait.php @@ -161,11 +161,13 @@ public function buildMarshalMap(Marshaller $marshaller, array $map, array $optio } foreach ($value as $language => $fields) { if (!isset($translations[$language])) { - $translations[$language] = $this->table->newEntity(); + $translations[$language] = $this->table->createEntity(); } $marshaller->merge($translations[$language], $fields, $options); - if ((bool)$translations[$language]->getErrors()) { - $errors[$language] = $translations[$language]->getErrors(); + /** @var \Cake\Datasource\EntityInterface $entity */ + $entity = $translations[$language]; + if ((bool)$entity->getErrors()) { + $errors[$language] = $entity->getErrors(); } } // Set errors into the root entity, so validation errors diff --git a/Table.php b/Table.php index e086e4d2..6a4af88a 100644 --- a/Table.php +++ b/Table.php @@ -1528,6 +1528,8 @@ public function findOrCreate($search, ?callable $callback = null, $options = []) * is persisted. * @param array $options The options to use when saving. * @return \Cake\Datasource\EntityInterface|array An entity. + * @throws \Cake\ORM\Exception\PersistenceFailedException + * @throws \InvalidArgumentException */ protected function _processFindOrCreate($search, ?callable $callback = null, $options = []) { @@ -1548,7 +1550,7 @@ protected function _processFindOrCreate($search, ?callable $callback = null, $op if ($row !== null) { return $row; } - $entity = $this->newEntity(); + $entity = $this->createEntity(); if ($options['defaults'] && is_array($search)) { $accessibleFields = array_combine(array_keys($search), array_fill(0, count($search), true)); $entity = $this->patchEntity($entity, $search, ['accessibleFields' => $accessibleFields]); @@ -2393,6 +2395,18 @@ public function marshaller(): Marshaller return new Marshaller($this); } + /** + * {@inheritDoc} + * + * @return \Cake\Datasource\EntityInterface + */ + public function createEntity(): EntityInterface + { + $class = $this->getEntityClass(); + + return new $class([], ['source' => $this->getRegistryAlias()]); + } + /** * {@inheritDoc} * @@ -2447,13 +2461,8 @@ public function marshaller(): Marshaller * You can use the `Model.beforeMarshal` event to modify request data * before it is converted into entities. */ - public function newEntity(?array $data = null, array $options = []): EntityInterface + public function newEntity(array $data, array $options = []): EntityInterface { - if ($data === null) { - $class = $this->getEntityClass(); - - return new $class([], ['source' => $this->getRegistryAlias()]); - } if (!isset($options['associated'])) { $options['associated'] = $this->_associations->keys(); } From f96dde6e1aa35f9b023a1aee8235ba7825257f94 Mon Sep 17 00:00:00 2001 From: mscherer Date: Thu, 21 Mar 2019 22:28:41 +0100 Subject: [PATCH 1329/2059] Fix tests. --- Behavior/Translate/TranslateStrategyTrait.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Behavior/Translate/TranslateStrategyTrait.php b/Behavior/Translate/TranslateStrategyTrait.php index b4215ce6..8e847304 100644 --- a/Behavior/Translate/TranslateStrategyTrait.php +++ b/Behavior/Translate/TranslateStrategyTrait.php @@ -164,10 +164,10 @@ public function buildMarshalMap(Marshaller $marshaller, array $map, array $optio $translations[$language] = $this->table->createEntity(); } $marshaller->merge($translations[$language], $fields, $options); - /** @var \Cake\Datasource\EntityInterface $entity */ - $entity = $translations[$language]; - if ((bool)$entity->getErrors()) { - $errors[$language] = $entity->getErrors(); + /** @var \Cake\Datasource\EntityInterface $translation */ + $translation = $translations[$language]; + if ((bool)$translation->getErrors()) { + $errors[$language] = $translation->getErrors(); } } // Set errors into the root entity, so validation errors From 24c913d7659dc6bc4ac9d70ae49848d66eb25372 Mon Sep 17 00:00:00 2001 From: mscherer Date: Fri, 22 Mar 2019 12:22:06 +0100 Subject: [PATCH 1330/2059] Use newEmptyEntity() as more clear name. --- Behavior/Translate/TranslateStrategyTrait.php | 2 +- Table.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Behavior/Translate/TranslateStrategyTrait.php b/Behavior/Translate/TranslateStrategyTrait.php index 8e847304..107eeeb2 100644 --- a/Behavior/Translate/TranslateStrategyTrait.php +++ b/Behavior/Translate/TranslateStrategyTrait.php @@ -161,7 +161,7 @@ public function buildMarshalMap(Marshaller $marshaller, array $map, array $optio } foreach ($value as $language => $fields) { if (!isset($translations[$language])) { - $translations[$language] = $this->table->createEntity(); + $translations[$language] = $this->table->newEmptyEntity(); } $marshaller->merge($translations[$language], $fields, $options); /** @var \Cake\Datasource\EntityInterface $translation */ diff --git a/Table.php b/Table.php index 6a4af88a..3c86fc6c 100644 --- a/Table.php +++ b/Table.php @@ -1550,7 +1550,7 @@ protected function _processFindOrCreate($search, ?callable $callback = null, $op if ($row !== null) { return $row; } - $entity = $this->createEntity(); + $entity = $this->newEmptyEntity(); if ($options['defaults'] && is_array($search)) { $accessibleFields = array_combine(array_keys($search), array_fill(0, count($search), true)); $entity = $this->patchEntity($entity, $search, ['accessibleFields' => $accessibleFields]); @@ -2400,7 +2400,7 @@ public function marshaller(): Marshaller * * @return \Cake\Datasource\EntityInterface */ - public function createEntity(): EntityInterface + public function newEmptyEntity(): EntityInterface { $class = $this->getEntityClass(); From e20b565d95ad524ddc10c936f18de812c77cf056 Mon Sep 17 00:00:00 2001 From: Kyle Burton Date: Mon, 1 Apr 2019 07:47:22 +0200 Subject: [PATCH 1331/2059] Update get() method in Table.php Changing order of parameters to an implode() method for code consistency --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 0f8cff80..688c137e 100644 --- a/Table.php +++ b/Table.php @@ -1593,7 +1593,7 @@ public function get($primaryKey, $options = []) throw new InvalidPrimaryKeyException(sprintf( 'Record not found in table "%s" with primary key [%s]', $this->getTable(), - implode($primaryKey, ', ') + implode(', ', $primaryKey) )); } $conditions = array_combine($key, $primaryKey); From eccc4db375f7c1d8a25e5f81507387f3e993993b Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 3 Apr 2019 13:32:56 +0530 Subject: [PATCH 1332/2059] Fix error reported by psalm. --- Query.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Query.php b/Query.php index 009e2f6f..bd57ae54 100644 --- a/Query.php +++ b/Query.php @@ -748,8 +748,6 @@ public function notMatching(string $assoc, ?callable $builder = null): self } /** - * {@inheritDoc} - * * Populates or adds parts to current query clauses using an array. * This is handy for passing all query clauses at once. The option array accepts: * @@ -784,6 +782,9 @@ public function notMatching(string $assoc, ?callable $builder = null): self * ->where(['created >=' => '2013-01-01']) * ->limit(10) * ``` + * + * @param array $options the options to be applied + * @return $this */ public function applyOptions(array $options) { From d31594c5e52e8e699b5f13a2224760c9cc963e6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgaras=20Janu=C5=A1auskas?= Date: Mon, 8 Apr 2019 15:07:35 +0300 Subject: [PATCH 1333/2059] HasMany fails to save if associated entities array is indexed by string keys --- Association/HasMany.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index 84999d02..12b376ce 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -484,7 +484,7 @@ function ($v) { return !in_array(null, $v, true); } ) - ->toArray(); + ->toList(); $conditions = $foreignKeyReference; From 7bf485d9dd5089720575a5c60ffc36b64b8ac741 Mon Sep 17 00:00:00 2001 From: mscherer Date: Sat, 20 Apr 2019 14:18:23 +0200 Subject: [PATCH 1334/2059] Use stricter PHP internal functions. --- Association.php | 4 ++-- Association/BelongsToMany.php | 4 ++-- Association/Loader/SelectLoader.php | 6 +++--- Behavior/TimestampBehavior.php | 4 ++-- Behavior/Translate/ShadowTableStrategy.php | 10 +++++----- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Association.php b/Association.php index 958b1f87..9b857846 100644 --- a/Association.php +++ b/Association.php @@ -589,7 +589,7 @@ public function getProperty(): string { if (!$this->_propertyName) { $this->_propertyName = $this->_propertyName(); - if (in_array($this->_propertyName, $this->_sourceTable->getSchema()->columns())) { + if (in_array($this->_propertyName, $this->_sourceTable->getSchema()->columns(), true)) { $msg = 'Association property name "%s" clashes with field of same name of table "%s".' . ' You should explicitly specify the "propertyName" option.'; trigger_error( @@ -625,7 +625,7 @@ protected function _propertyName(): string */ public function setStrategy(string $name) { - if (!in_array($name, $this->_validStrategies)) { + if (!in_array($name, $this->_validStrategies, true)) { throw new InvalidArgumentException(sprintf( 'Invalid strategy "%s" was provided. Valid options are (%s).', $name, diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index caf222d1..05848e50 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -596,7 +596,7 @@ public function isOwningSide(Table $side): bool */ public function setSaveStrategy(string $strategy): self { - if (!in_array($strategy, [self::SAVE_APPEND, self::SAVE_REPLACE])) { + if (!in_array($strategy, [self::SAVE_APPEND, self::SAVE_REPLACE], true)) { $msg = sprintf('Invalid save strategy "%s"', $strategy); throw new InvalidArgumentException($msg); } @@ -1000,7 +1000,7 @@ protected function junctionConditions(): array } // Assume that operators contain junction conditions. // Trying to manage complex conditions could result in incorrect queries. - if ($isString && in_array(strtoupper($field), ['OR', 'NOT', 'AND', 'XOR'])) { + if ($isString && in_array(strtoupper($field), ['OR', 'NOT', 'AND', 'XOR'], true)) { $matching[$field] = $value; } } diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 5312974e..dbc6b594 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -371,7 +371,7 @@ protected function _linkField(array $options) throw new RuntimeException($msg); } - $keys = in_array($this->associationType, [Association::ONE_TO_ONE, Association::ONE_TO_MANY]) ? + $keys = in_array($this->associationType, [Association::ONE_TO_ONE, Association::ONE_TO_MANY], true) ? $this->foreignKey : $this->bindingKey; @@ -460,8 +460,8 @@ protected function _subqueryFields(Query $query): array protected function _buildResultMap(Query $fetchQuery, array $options): array { $resultMap = []; - $singleResult = in_array($this->associationType, [Association::MANY_TO_ONE, Association::ONE_TO_ONE]); - $keys = in_array($this->associationType, [Association::ONE_TO_ONE, Association::ONE_TO_MANY]) ? + $singleResult = in_array($this->associationType, [Association::MANY_TO_ONE, Association::ONE_TO_ONE], true); + $keys = in_array($this->associationType, [Association::ONE_TO_ONE, Association::ONE_TO_MANY], true) ? $this->foreignKey : $this->bindingKey; $key = (array)$keys; diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 40c8a4ba..930a9fd2 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -101,7 +101,7 @@ public function handleEvent(EventInterface $event, EntityInterface $entity): boo $refresh = $this->_config['refreshTimestamp']; foreach ($events[$eventName] as $field => $when) { - if (!in_array($when, ['always', 'new', 'existing'])) { + if (!in_array($when, ['always', 'new', 'existing'], true)) { throw new UnexpectedValueException(sprintf( 'When should be one of "always", "new" or "existing". The passed value "%s" is invalid', $when @@ -177,7 +177,7 @@ public function touch(EntityInterface $entity, string $eventName = 'Model.before $refresh = $this->_config['refreshTimestamp']; foreach ($events[$eventName] as $field => $when) { - if (in_array($when, ['always', 'existing'])) { + if (in_array($when, ['always', 'existing'], true)) { $return = true; $entity->setDirty($field, false); $this->_updateField($entity, $field, $refresh); diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index fe257b5a..8da607a1 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -231,10 +231,10 @@ function ($c, &$field) use ($fields, $alias, $mainTableAlias, $mainTableFields, return $c; } - if (in_array($field, $fields)) { + if (in_array($field, $fields, true)) { $joinRequired = true; $field = "$alias.$field"; - } elseif (in_array($field, $mainTableFields)) { + } elseif (in_array($field, $mainTableFields, true)) { $field = "$mainTableAlias.$field"; } @@ -280,14 +280,14 @@ function ($expression) use ($fields, $alias, $mainTableAlias, $mainTableFields, return; } - if (in_array($field, $fields)) { + if (in_array($field, $fields, true)) { $joinRequired = true; $expression->setField("$alias.$field"); return; } - if (in_array($field, $mainTableFields)) { + if (in_array($field, $mainTableFields, true)) { $expression->setField("$mainTableAlias.$field"); } } @@ -413,7 +413,7 @@ public function translationField(string $field): string } $translatedFields = $this->translatedFields(); - if (in_array($field, $translatedFields)) { + if (in_array($field, $translatedFields, true)) { return $this->getConfig('hasOneAlias') . '.' . $field; } From 4d455de1398525a9ffc42757aad5e19c9b7a6469 Mon Sep 17 00:00:00 2001 From: mscherer Date: Mon, 22 Apr 2019 22:10:57 +0200 Subject: [PATCH 1335/2059] Remove invalid chainable typehints as per cs assert. --- Association/BelongsToMany.php | 4 +-- .../Translate/TranslateStrategyInterface.php | 2 +- Behavior/Translate/TranslateStrategyTrait.php | 2 +- Behavior/TranslateBehavior.php | 2 +- EagerLoadable.php | 4 +-- EagerLoader.php | 4 +-- Query.php | 30 ++++++++--------- SaveOptionsBuilder.php | 32 +++++++++---------- Table.php | 10 +++--- 9 files changed, 45 insertions(+), 45 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 05848e50..349c8b4c 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -594,7 +594,7 @@ public function isOwningSide(Table $side): bool * @throws \InvalidArgumentException if an invalid strategy name is passed * @return $this */ - public function setSaveStrategy(string $strategy): self + public function setSaveStrategy(string $strategy) { if (!in_array($strategy, [self::SAVE_APPEND, self::SAVE_REPLACE], true)) { $msg = sprintf('Invalid save strategy "%s"', $strategy); @@ -927,7 +927,7 @@ public function setConditions($conditions) * @param string|\Cake\ORM\Table $through Name of the Table instance or the instance itself * @return $this */ - public function setThrough($through): self + public function setThrough($through) { $this->_through = $through; diff --git a/Behavior/Translate/TranslateStrategyInterface.php b/Behavior/Translate/TranslateStrategyInterface.php index ad884244..8fa644b7 100644 --- a/Behavior/Translate/TranslateStrategyInterface.php +++ b/Behavior/Translate/TranslateStrategyInterface.php @@ -50,7 +50,7 @@ public function getTranslationTable(): Table; * the behavior fall back to using the globally configured locale. * @return $this */ - public function setLocale(?string $locale): TranslateStrategyInterface; + public function setLocale(?string $locale); /** * Returns the current locale. diff --git a/Behavior/Translate/TranslateStrategyTrait.php b/Behavior/Translate/TranslateStrategyTrait.php index 107eeeb2..2ec089c5 100644 --- a/Behavior/Translate/TranslateStrategyTrait.php +++ b/Behavior/Translate/TranslateStrategyTrait.php @@ -73,7 +73,7 @@ public function getTranslationTable(): Table * the behavior fall back to using the globally configured locale. * @return $this */ - public function setLocale(?string $locale): TranslateStrategyInterface + public function setLocale(?string $locale) { $this->locale = $locale; diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index d42b585c..3c6bd39c 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -220,7 +220,7 @@ public function buildMarshalMap(Marshaller $marshaller, array $map, array $optio * @link https://book.cakephp.org/3.0/en/orm/behaviors/translate.html#retrieving-one-language-without-using-i18n-locale * @link https://book.cakephp.org/3.0/en/orm/behaviors/translate.html#saving-in-another-language */ - public function setLocale(?string $locale): self + public function setLocale(?string $locale) { $this->getStrategy()->setLocale($locale); diff --git a/EagerLoadable.php b/EagerLoadable.php index 690e2061..e5c69123 100644 --- a/EagerLoadable.php +++ b/EagerLoadable.php @@ -209,7 +209,7 @@ public function propertyPath(): ?string * @param bool $possible The value to set. * @return $this */ - public function setCanBeJoined(bool $possible): self + public function setCanBeJoined(bool $possible) { $this->_canBeJoined = (bool)$possible; @@ -233,7 +233,7 @@ public function canBeJoined(): bool * @param array $config The value to set. * @return $this */ - public function setConfig(array $config): self + public function setConfig(array $config) { $this->_config = $config; diff --git a/EagerLoader.php b/EagerLoader.php index 6abb6a91..1e020e3b 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -192,7 +192,7 @@ public function clearContain(): void * @param bool $enable The value to set. * @return $this */ - public function enableAutoFields(bool $enable = true): self + public function enableAutoFields(bool $enable = true) { $this->_autoFields = (bool)$enable; @@ -238,7 +238,7 @@ public function isAutoFieldsEnabled(): bool * @param array $options Extra options for the association matching. * @return $this */ - public function setMatching(string $assoc, ?callable $builder = null, array $options = []): self + public function setMatching(string $assoc, ?callable $builder = null, array $options = []) { if ($this->_matching === null) { $this->_matching = new static(); diff --git a/Query.php b/Query.php index bd57ae54..736cd6b2 100644 --- a/Query.php +++ b/Query.php @@ -212,7 +212,7 @@ public function __construct(Connection $connection, Table $table) * @param bool $overwrite whether to reset fields with passed list or not * @return $this */ - public function select($fields = [], $overwrite = false): self + public function select($fields = [], $overwrite = false) { if ($fields instanceof Association) { $fields = $fields->getTarget(); @@ -237,10 +237,10 @@ public function select($fields = [], $overwrite = false): self * @param \Cake\ORM\Table|\Cake\ORM\Association $table The table to use to get an array of columns * @param array $excludedFields The un-aliased column names you do not want selected from $table * @param bool $overwrite Whether to reset/remove previous selected fields - * @return \Cake\ORM\Query + * @return $this * @throws \InvalidArgumentException If Association|Table is not passed in first argument */ - public function selectAllExcept($table, array $excludedFields, $overwrite = false): Query + public function selectAllExcept($table, array $excludedFields, $overwrite = false) { if ($table instanceof Association) { $table = $table->getTarget(); @@ -267,7 +267,7 @@ public function selectAllExcept($table, array $excludedFields, $overwrite = fals * @param \Cake\ORM\Table $table The table to pull types from * @return $this */ - public function addDefaultTypes(Table $table): self + public function addDefaultTypes(Table $table) { $alias = $table->getAlias(); $map = $table->getSchema()->typeMap(); @@ -287,7 +287,7 @@ public function addDefaultTypes(Table $table): self * @param \Cake\ORM\EagerLoader $instance The eager loader to use. * @return $this */ - public function setEagerLoader(EagerLoader $instance): self + public function setEagerLoader(EagerLoader $instance) { $this->_eagerLoader = $instance; @@ -461,7 +461,7 @@ public function getContain(): array * * @return $this */ - public function clearContain(): self + public function clearContain() { $this->getEagerLoader()->clearContain(); $this->_dirty(); @@ -547,7 +547,7 @@ protected function _addAssociationsToTypeMap(Table $table, TypeMap $typeMap, arr * that can be used to add custom conditions or selecting some fields * @return $this */ - public function matching(string $assoc, ?callable $builder = null): self + public function matching(string $assoc, ?callable $builder = null) { $result = $this->getEagerLoader()->setMatching($assoc, $builder)->getMatching(); $this->_addAssociationsToTypeMap($this->getRepository(), $this->getTypeMap(), $result); @@ -619,7 +619,7 @@ public function matching(string $assoc, ?callable $builder = null): self * that can be used to add custom conditions or selecting some fields * @return $this */ - public function leftJoinWith(string $assoc, ?callable $builder = null): self + public function leftJoinWith(string $assoc, ?callable $builder = null) { $result = $this->getEagerLoader() ->setMatching($assoc, $builder, [ @@ -668,7 +668,7 @@ public function leftJoinWith(string $assoc, ?callable $builder = null): self * @return $this * @see \Cake\ORM\Query::matching() */ - public function innerJoinWith(string $assoc, ?callable $builder = null): self + public function innerJoinWith(string $assoc, ?callable $builder = null) { $result = $this->getEagerLoader() ->setMatching($assoc, $builder, [ @@ -732,7 +732,7 @@ public function innerJoinWith(string $assoc, ?callable $builder = null): self * that can be used to add custom conditions or selecting some fields * @return $this */ - public function notMatching(string $assoc, ?callable $builder = null): self + public function notMatching(string $assoc, ?callable $builder = null) { $result = $this->getEagerLoader() ->setMatching($assoc, $builder, [ @@ -954,7 +954,7 @@ protected function _performCount(): int * @param callable|null $counter The counter value * @return $this */ - public function counter(?callable $counter): self + public function counter(?callable $counter) { $this->_counter = $counter; @@ -969,7 +969,7 @@ public function counter(?callable $counter): self * @param bool $enable Use a boolean to set the hydration mode. * @return $this */ - public function enableHydration(bool $enable = true): self + public function enableHydration(bool $enable = true) { $this->_dirty(); $this->_hydrate = (bool)$enable; @@ -1009,7 +1009,7 @@ public function isHydrationEnabled(): bool * @return $this * @throws \RuntimeException When you attempt to cache a non-select query. */ - public function cache(string $key, $config = 'default'): self + public function cache(string $key, $config = 'default') { if ($this->_type !== 'select' && $this->_type !== null) { throw new RuntimeException('You cannot cache the results of non-select queries.'); @@ -1246,7 +1246,7 @@ public function delete(?string $table = null) * @param array $types A map between columns & their datatypes. * @return $this */ - public function insert(array $columns, array $types = []): self + public function insert(array $columns, array $types = []) { /** @var \Cake\ORM\Table $repository */ $repository = $this->getRepository(); @@ -1312,7 +1312,7 @@ public function jsonSerialize(): ResultSetInterface * @param bool $value Set true to enable, false to disable. * @return $this */ - public function enableAutoFields(bool $value = true): self + public function enableAutoFields(bool $value = true) { $this->_autoFields = (bool)$value; diff --git a/SaveOptionsBuilder.php b/SaveOptionsBuilder.php index b17db774..fea5b801 100644 --- a/SaveOptionsBuilder.php +++ b/SaveOptionsBuilder.php @@ -65,9 +65,9 @@ public function __construct(Table $table, array $options = []) * * @throws \InvalidArgumentException If a given option key does not exist. * @param array $array Options array. - * @return \Cake\ORM\SaveOptionsBuilder + * @return $this */ - public function parseArrayOptions(array $array): SaveOptionsBuilder + public function parseArrayOptions(array $array) { foreach ($array as $key => $value) { $this->{$key}($value); @@ -80,9 +80,9 @@ public function parseArrayOptions(array $array): SaveOptionsBuilder * Set associated options. * * @param string|array $associated String or array of associations. - * @return \Cake\ORM\SaveOptionsBuilder + * @return $this */ - public function associated($associated): SaveOptionsBuilder + public function associated($associated) { $associated = $this->_normalizeAssociations($associated); $this->_associated($this->_table, $associated); @@ -136,9 +136,9 @@ protected function _checkAssociation(Table $table, string $association): void * Set the guard option. * * @param bool $guard Guard the properties or not. - * @return \Cake\ORM\SaveOptionsBuilder + * @return $this */ - public function guard(bool $guard): SaveOptionsBuilder + public function guard(bool $guard) { $this->_options['guard'] = (bool)$guard; @@ -149,9 +149,9 @@ public function guard(bool $guard): SaveOptionsBuilder * Set the validation rule set to use. * * @param string $validate Name of the validation rule set to use. - * @return \Cake\ORM\SaveOptionsBuilder + * @return $this */ - public function validate(string $validate): SaveOptionsBuilder + public function validate(string $validate) { $this->_table->getValidator($validate); $this->_options['validate'] = $validate; @@ -163,9 +163,9 @@ public function validate(string $validate): SaveOptionsBuilder * Set check existing option. * * @param bool $checkExisting Guard the properties or not. - * @return \Cake\ORM\SaveOptionsBuilder + * @return $this */ - public function checkExisting(bool $checkExisting): SaveOptionsBuilder + public function checkExisting(bool $checkExisting) { $this->_options['checkExisting'] = (bool)$checkExisting; @@ -176,9 +176,9 @@ public function checkExisting(bool $checkExisting): SaveOptionsBuilder * Option to check the rules. * * @param bool $checkRules Check the rules or not. - * @return \Cake\ORM\SaveOptionsBuilder + * @return $this */ - public function checkRules(bool $checkRules): SaveOptionsBuilder + public function checkRules(bool $checkRules) { $this->_options['checkRules'] = (bool)$checkRules; @@ -189,9 +189,9 @@ public function checkRules(bool $checkRules): SaveOptionsBuilder * Sets the atomic option. * * @param bool $atomic Atomic or not. - * @return \Cake\ORM\SaveOptionsBuilder + * @return $this */ - public function atomic(bool $atomic): SaveOptionsBuilder + public function atomic(bool $atomic) { $this->_options['atomic'] = (bool)$atomic; @@ -211,9 +211,9 @@ public function toArray(): array * * @param string $option Option key. * @param mixed $value Option value. - * @return \Cake\ORM\SaveOptionsBuilder + * @return $this */ - public function set(string $option, $value): SaveOptionsBuilder + public function set(string $option, $value) { if (method_exists($this, $option)) { return $this->{$option}($value); diff --git a/Table.php b/Table.php index d5935baf..ba861c09 100644 --- a/Table.php +++ b/Table.php @@ -598,7 +598,7 @@ public function getPrimaryKey() * @param string|array $field Name to be used as display field. * @return $this */ - public function setDisplayField($field): self + public function setDisplayField($field) { $this->_displayField = $field; @@ -667,7 +667,7 @@ public function getEntityClass(): string * @throws \Cake\ORM\Exception\MissingEntityException when the entity class cannot be found * @return $this */ - public function setEntityClass(string $name): self + public function setEntityClass(string $name) { $class = App::className($name, 'Model/Entity'); if (!$class) { @@ -703,7 +703,7 @@ public function setEntityClass(string $name): self * @throws \RuntimeException If a behavior is being reloaded. * @see \Cake\ORM\Behavior */ - public function addBehavior(string $name, array $options = []): self + public function addBehavior(string $name, array $options = []) { $this->_behaviors->load($name, $options); @@ -726,7 +726,7 @@ public function addBehavior(string $name, array $options = []): self * @return $this * @throws \RuntimeException If a behavior is being reloaded. */ - public function addBehaviors(array $behaviors): self + public function addBehaviors(array $behaviors) { foreach ($behaviors as $name => $options) { if (is_int($name)) { @@ -914,7 +914,7 @@ public function associations(): iterable * @see \Cake\ORM\Table::hasMany() * @see \Cake\ORM\Table::belongsToMany() */ - public function addAssociations(array $params): self + public function addAssociations(array $params) { foreach ($params as $assocType => $tables) { foreach ($tables as $associated => $options) { From 123d776de40516729c9e9b0c632b92ad659510e4 Mon Sep 17 00:00:00 2001 From: mscherer Date: Mon, 22 Apr 2019 22:20:13 +0200 Subject: [PATCH 1336/2059] Remove invalid chainable typehints as per cs assert. --- Query.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Query.php b/Query.php index 736cd6b2..56204120 100644 --- a/Query.php +++ b/Query.php @@ -828,9 +828,9 @@ public function applyOptions(array $options) * * This method creates query clones that are useful when working with subqueries. * - * @return \Cake\ORM\Query + * @return static */ - public function cleanCopy(): Query + public function cleanCopy(): self { $clone = clone $this; $clone->setEagerLoader(clone $this->getEagerLoader()); From e3f9d39940fe73412509eb873e80c765ff06aff9 Mon Sep 17 00:00:00 2001 From: mscherer Date: Mon, 22 Apr 2019 22:57:44 +0200 Subject: [PATCH 1337/2059] Fix inline docblocks. --- Association.php | 2 +- Association/Loader/SelectLoader.php | 2 +- Behavior.php | 2 +- Behavior/Translate/EavStrategy.php | 2 +- Behavior/Translate/ShadowTableStrategy.php | 2 +- Behavior/TranslateBehavior.php | 2 +- Behavior/TreeBehavior.php | 18 +++++++++--------- EagerLoader.php | 4 ++-- Locator/TableLocator.php | 2 +- ResultSet.php | 6 +++--- 10 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Association.php b/Association.php index 9b857846..7c915b69 100644 --- a/Association.php +++ b/Association.php @@ -1006,7 +1006,7 @@ protected function _formatAssociationResults(Query $query, Query $surrogate, arr $extracted = new ResultSetDecorator($callable($extracted)); } - /* @var \Cake\Collection\CollectionInterface $results */ + /** @var \Cake\Collection\CollectionInterface $results */ return $results->insert($property, $extracted); }, Query::PREPEND); } diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index dbc6b594..6843b26c 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -165,7 +165,7 @@ protected function _buildQuery(array $options): Query $options['fields'] = []; } - /* @var \Cake\ORM\Query $query */ + /** @var \Cake\ORM\Query $query */ $query = $finder(); if (isset($options['finder'])) { [$finderName, $opts] = $this->_extractFinder($options['finder']); diff --git a/Behavior.php b/Behavior.php index b1e38b15..5be25a5f 100644 --- a/Behavior.php +++ b/Behavior.php @@ -383,7 +383,7 @@ protected function _reflectionCache(): array $eventMethods = []; foreach ($events as $e => $binding) { if (is_array($binding) && isset($binding['callable'])) { - /* @var string $callable */ + /** @var string $callable */ $callable = $binding['callable']; $binding = $callable; } diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index cf16a6c4..475ae235 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -372,7 +372,7 @@ protected function rowMapper($results, $locale) $row['_locale'] = $locale; if ($hydrated) { - /* @var \Cake\Datasource\EntityInterface $row */ + /** @var \Cake\Datasource\EntityInterface $row */ $row->clean(); } diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 8da607a1..1e9beb5f 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -450,7 +450,7 @@ protected function rowMapper($results, $locale) return $row; } - /** @var \Cake\ORM\Entity $translation|array */ + /** @var \Cake\ORM\Entity|array $translation */ $translation = $row['translation']; $keys = $hydrated ? $translation->getVisible() : array_keys($translation); diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index d42b585c..ca205180 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -287,7 +287,7 @@ public function findTranslations(Query $query, array $options): Query return $query ->contain([$targetAlias => function ($query) use ($locales, $targetAlias) { if ($locales) { - /* @var \Cake\Datasource\QueryInterface $query */ + /** @var \Cake\Datasource\QueryInterface $query */ $query->where(["$targetAlias.locale IN" => $locales]); } diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index bbb28d14..44d6960e 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -195,7 +195,7 @@ protected function _setChildrenLevel(EntityInterface $entity): void 'order' => $config['left'], ]); - /* @var \Cake\Datasource\EntityInterface $node */ + /** @var \Cake\Datasource\EntityInterface $node */ foreach ($children as $node) { $parentIdValue = $node->get($config['parent']); $depth = $depths[$parentIdValue] + 1; @@ -227,7 +227,7 @@ public function beforeDelete(EventInterface $event, EntityInterface $entity) $query = $this->_scope($this->_table->query()) ->delete() ->where(function ($exp) use ($config, $left, $right) { - /* @var \Cake\Database\Expression\QueryExpression $exp */ + /** @var \Cake\Database\Expression\QueryExpression $exp */ return $exp ->gte($config['leftField'], $left + 1) ->lte($config['leftField'], $right - 1); @@ -347,7 +347,7 @@ protected function _unmarkInternalTree(): void $config = $this->getConfig(); $this->_table->updateAll( function ($exp) use ($config) { - /* @var \Cake\Database\Expression\QueryExpression $exp */ + /** @var \Cake\Database\Expression\QueryExpression $exp */ $leftInverse = clone $exp; $leftInverse->setConjunction('*')->add('-1'); $rightInverse = clone $leftInverse; @@ -357,7 +357,7 @@ function ($exp) use ($config) { ->eq($config['rightField'], $rightInverse->add($config['rightField'])); }, function ($exp) use ($config) { - /* @var \Cake\Database\Expression\QueryExpression $exp */ + /** @var \Cake\Database\Expression\QueryExpression $exp */ return $exp->lt($config['leftField'], 0); } ); @@ -521,7 +521,7 @@ public function findTreeList(Query $query, array $options): Query public function formatTreeList(Query $query, array $options = []): Query { return $query->formatResults(function ($results) use ($options) { - /* @var \Cake\Collection\CollectionTrait $results */ + /** @var \Cake\Collection\CollectionTrait $results */ $options += [ 'keyPath' => $this->_getPrimaryKey(), 'valuePath' => $this->_table->getDisplayField(), @@ -639,7 +639,7 @@ protected function _moveUp(EntityInterface $node, $number) ->select([$left, $right]) ->where(["$parent IS" => $nodeParent]) ->where(function ($exp) use ($config, $nodeLeft) { - /* @var \Cake\Database\Expression\QueryExpression $exp */ + /** @var \Cake\Database\Expression\QueryExpression $exp */ return $exp->lt($config['rightField'], $nodeLeft); }) ->orderDesc($config['leftField']) @@ -652,7 +652,7 @@ protected function _moveUp(EntityInterface $node, $number) ->select([$left, $right]) ->where(["$parent IS" => $nodeParent]) ->where(function ($exp) use ($config, $nodeLeft) { - /* @var \Cake\Database\Expression\QueryExpression $exp */ + /** @var \Cake\Database\Expression\QueryExpression $exp */ return $exp->lt($config['rightField'], $nodeLeft); }) ->orderAsc($config['leftField']) @@ -729,7 +729,7 @@ protected function _moveDown(EntityInterface $node, $number) ->select([$left, $right]) ->where(["$parent IS" => $nodeParent]) ->where(function ($exp) use ($config, $nodeRight) { - /* @var \Cake\Database\Expression\QueryExpression $exp */ + /** @var \Cake\Database\Expression\QueryExpression $exp */ return $exp->gt($config['leftField'], $nodeRight); }) ->orderAsc($config['leftField']) @@ -742,7 +742,7 @@ protected function _moveDown(EntityInterface $node, $number) ->select([$left, $right]) ->where(["$parent IS" => $nodeParent]) ->where(function ($exp) use ($config, $nodeRight) { - /* @var \Cake\Database\Expression\QueryExpression $exp */ + /** @var \Cake\Database\Expression\QueryExpression $exp */ return $exp->gt($config['leftField'], $nodeRight); }) ->orderDesc($config['leftField']) diff --git a/EagerLoader.php b/EagerLoader.php index 6abb6a91..8e01be82 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -539,7 +539,7 @@ protected function _fixStrategies(): void if (count($configs) < 2) { continue; } - /* @var \Cake\ORM\EagerLoadable $loadable */ + /** @var \Cake\ORM\EagerLoadable $loadable */ foreach ($configs as $loadable) { if (strpos($loadable->aliasPath(), '.')) { $this->_correctStrategy($loadable); @@ -692,7 +692,7 @@ public function associationsMap(Table $table): array */ protected function _buildAssociationsMap(array $map, array $level, bool $matching = false): array { - /* @var \Cake\ORM\EagerLoadable $meta */ + /** @var \Cake\ORM\EagerLoadable $meta */ foreach ($level as $assoc => $meta) { $canBeJoined = $meta->canBeJoined(); $instance = $meta->instance(); diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 215fc34f..0e2a0cad 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -201,7 +201,7 @@ public function get(string $alias, array $options = []): Table if (!empty($options['connectionName'])) { $connectionName = $options['connectionName']; } else { - /* @var \Cake\ORM\Table $className */ + /** @var \Cake\ORM\Table $className */ $className = $options['className']; $connectionName = $className::defaultConnectionName(); } diff --git a/ResultSet.php b/ResultSet.php index 028b8eaa..8f7766d8 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -463,10 +463,10 @@ protected function _groupResult(array $row) array_intersect_key($row, $keys) ); if ($this->_hydrate) { - /* @var \Cake\ORM\Table $table */ + /** @var \Cake\ORM\Table $table */ $table = $matching['instance']; $options['source'] = $table->getRegistryAlias(); - /* @var \Cake\Datasource\EntityInterface $entity */ + /** @var \Cake\Datasource\EntityInterface $entity */ $entity = new $matching['entityClass']($results['_matchingData'][$alias], $options); $results['_matchingData'][$alias] = $entity; } @@ -493,7 +493,7 @@ protected function _groupResult(array $row) continue; } - /* @var \Cake\ORM\Association $instance */ + /** @var \Cake\ORM\Association $instance */ $instance = $assoc['instance']; if (!$assoc['canBeJoined'] && !isset($row[$alias])) { From 4e430d277efc5f13cbb9609070a8b4e9ca6a71d9 Mon Sep 17 00:00:00 2001 From: mscherer Date: Mon, 22 Apr 2019 23:17:03 +0200 Subject: [PATCH 1338/2059] Fix FQCN for exceptions via sniffer run. --- Behavior/Translate/ShadowTableStrategy.php | 1 + Marshaller.php | 3 ++- Query.php | 6 ++++-- Table.php | 3 ++- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 8da607a1..0a34209e 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -433,6 +433,7 @@ protected function rowMapper($results, $locale) $allowEmpty = $this->_config['allowEmptyTranslations']; return $results->map(function ($row) use ($allowEmpty) { + /** @var \Cake\Datasource\EntityInterface|array|null $row */ if ($row === null) { return $row; } diff --git a/Marshaller.php b/Marshaller.php index 1e05caf4..46e9ac03 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -22,6 +22,7 @@ use Cake\Datasource\EntityInterface; use Cake\Datasource\InvalidPropertyInterface; use Cake\ORM\Association\BelongsToMany; +use InvalidArgumentException; use RuntimeException; /** @@ -92,7 +93,7 @@ protected function _buildPropertyMap(array $data, array $options): array // it is a missing association that we should error on. if (!$this->_table->hasAssociation($key)) { if (substr($key, 0, 1) !== '_') { - throw new \InvalidArgumentException(sprintf( + throw new InvalidArgumentException(sprintf( 'Cannot marshal data for "%s" association. It is not associated with "%s".', $key, $this->_table->getAlias() diff --git a/Query.php b/Query.php index bd57ae54..cf24101f 100644 --- a/Query.php +++ b/Query.php @@ -16,6 +16,7 @@ namespace Cake\ORM; use ArrayObject; +use BadMethodCallException; use Cake\Database\Connection; use Cake\Database\ExpressionInterface; use Cake\Database\Query as DatabaseQuery; @@ -25,6 +26,7 @@ use Cake\Datasource\QueryInterface; use Cake\Datasource\QueryTrait; use Cake\Datasource\ResultSetInterface; +use InvalidArgumentException; use JsonSerializable; use RuntimeException; use Traversable; @@ -247,7 +249,7 @@ public function selectAllExcept($table, array $excludedFields, $overwrite = fals } if (!($table instanceof Table)) { - throw new \InvalidArgumentException('You must provide either an Association or a Table object'); + throw new InvalidArgumentException('You must provide either an Association or a Table object'); } $fields = array_diff($table->getSchema()->columns(), $excludedFields); @@ -1267,7 +1269,7 @@ public function __call($method, $arguments) return $this->_call($method, $arguments); } - throw new \BadMethodCallException( + throw new BadMethodCallException( sprintf('Cannot call method "%s" on a "%s" query', $method, $this->type()) ); } diff --git a/Table.php b/Table.php index d5935baf..68ea639a 100644 --- a/Table.php +++ b/Table.php @@ -42,6 +42,7 @@ use Cake\Utility\Inflector; use Cake\Validation\ValidatorAwareInterface; use Cake\Validation\ValidatorAwareTrait; +use Exception; use InvalidArgumentException; use RuntimeException; @@ -2069,7 +2070,7 @@ public function saveMany(iterable $entities, $options = []) } } }); - } catch (\Exception $e) { + } catch (Exception $e) { $cleanup($entities); throw $e; From b3dd6c25e8b3bb0eb254a43a3cc8a78856d583df Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 30 Apr 2019 13:06:39 +0530 Subject: [PATCH 1339/2059] Use ::class instead of strings for FQCN. --- Association.php | 2 +- Behavior.php | 2 +- Locator/TableLocator.php | 4 ++-- README.md | 4 ++-- TableRegistry.php | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Association.php b/Association.php index 7c915b69..f67102a5 100644 --- a/Association.php +++ b/Association.php @@ -1138,7 +1138,7 @@ protected function _getClassName(string $alias, array $options = []): string $options['className'] = Inflector::camelize($alias); } - $className = App::className($options['className'], 'Model/Table', 'Table') ?: 'Cake\ORM\Table'; + $className = App::className($options['className'], 'Model/Table', 'Table') ?: Table::class; return ltrim($className, '\\'); } diff --git a/Behavior.php b/Behavior.php index 5be25a5f..03d01429 100644 --- a/Behavior.php +++ b/Behavior.php @@ -390,7 +390,7 @@ protected function _reflectionCache(): array $eventMethods[$binding] = true; } - $baseClass = 'Cake\ORM\Behavior'; + $baseClass = self::class; if (isset(self::$_reflectionCache[$baseClass])) { $baseMethods = self::$_reflectionCache[$baseClass]; } else { diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 0e2a0cad..d9543177 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -194,7 +194,7 @@ public function get(string $alias, array $options = []): Table [, $table] = pluginSplit($options['className']); $options['table'] = Inflector::underscore($table); } - $options['className'] = 'Cake\ORM\Table'; + $options['className'] = Table::class; } if (empty($options['connection'])) { @@ -215,7 +215,7 @@ public function get(string $alias, array $options = []): Table $options['registryAlias'] = $alias; $this->_instances[$alias] = $this->_create($options); - if ($options['className'] === 'Cake\ORM\Table') { + if ($options['className'] === Table::class) { $this->_fallbacked[$alias] = $this->_instances[$alias]; } diff --git a/README.md b/README.md index 19ac4a00..7a0aa55e 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,8 @@ specify a driver to use: use Cake\Datasource\ConnectionManager; ConnectionManager::setConfig('default', [ - 'className' => 'Cake\Database\Connection', - 'driver' => 'Cake\Database\Driver\Mysql', + 'className' => \Cake\Database\Connection::class, + 'driver' => \Cake\Database\Driver\Mysql::class, 'database' => 'test', 'username' => 'root', 'password' => 'secret', diff --git a/TableRegistry.php b/TableRegistry.php index 9b693064..6e33ab0c 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -62,7 +62,7 @@ class TableRegistry * * @var string */ - protected static $_defaultLocatorClass = 'Cake\ORM\Locator\TableLocator'; + protected static $_defaultLocatorClass = Locator\TableLocator::class; /** * Returns a singleton instance of LocatorInterface implementation. From 443d454c6c9edc7f95091083df76ba3ddeef6b41 Mon Sep 17 00:00:00 2001 From: mscherer Date: Thu, 2 May 2019 22:25:42 +0200 Subject: [PATCH 1340/2059] Fix doc blocks, add object typehint and remove static typehints. --- Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Query.php b/Query.php index 4d364024..7c68d0d2 100644 --- a/Query.php +++ b/Query.php @@ -832,7 +832,7 @@ public function applyOptions(array $options) * * @return static */ - public function cleanCopy(): self + public function cleanCopy() { $clone = clone $this; $clone->setEagerLoader(clone $this->getEagerLoader()); From 63070aeb527b8c3de209fcc9548e6759020d0158 Mon Sep 17 00:00:00 2001 From: mscherer Date: Fri, 3 May 2019 00:47:17 +0200 Subject: [PATCH 1341/2059] Fix more array doc blocks to be more precise. Stricter typehints. --- Association.php | 2 +- Association/BelongsTo.php | 2 +- Association/BelongsToMany.php | 2 +- Association/HasMany.php | 2 +- Association/HasOne.php | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Association.php b/Association.php index f67102a5..64f8ee36 100644 --- a/Association.php +++ b/Association.php @@ -189,7 +189,7 @@ abstract class Association /** * Valid strategies for this association. Subclasses can narrow this down. * - * @var array + * @var string[] */ protected $_validStrategies = [ self::STRATEGY_JOIN, diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index c6d23a08..f88a5e2b 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -35,7 +35,7 @@ class BelongsTo extends Association /** * Valid strategies for this type of association * - * @var array + * @var string[] */ protected $_validStrategies = [ self::STRATEGY_JOIN, diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 349c8b4c..9ff2ade2 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -119,7 +119,7 @@ class BelongsToMany extends Association /** * Valid strategies for this type of association * - * @var array + * @var string[] */ protected $_validStrategies = [ self::STRATEGY_SELECT, diff --git a/Association/HasMany.php b/Association/HasMany.php index d59c8ff1..789a5c91 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -59,7 +59,7 @@ class HasMany extends Association /** * Valid strategies for this type of association * - * @var array + * @var string[] */ protected $_validStrategies = [ self::STRATEGY_SELECT, diff --git a/Association/HasOne.php b/Association/HasOne.php index 42273b2b..45df2372 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -33,7 +33,7 @@ class HasOne extends Association /** * Valid strategies for this type of association * - * @var array + * @var string[] */ protected $_validStrategies = [ self::STRATEGY_JOIN, From 1d8b4db557b2312620269f5f663cbbd904d80e5b Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 4 May 2019 14:34:21 +0530 Subject: [PATCH 1342/2059] Use \Throwable instead of \Exception. --- Exception/PersistenceFailedException.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Exception/PersistenceFailedException.php b/Exception/PersistenceFailedException.php index e40a78c4..11b22734 100644 --- a/Exception/PersistenceFailedException.php +++ b/Exception/PersistenceFailedException.php @@ -16,6 +16,7 @@ use Cake\Core\Exception\Exception; use Cake\Datasource\EntityInterface; use Cake\Utility\Hash; +use Throwable; /** * Used when a strict save or delete fails @@ -41,9 +42,9 @@ class PersistenceFailedException extends Exception * @param string|array $message Either the string of the error message, or an array of attributes * that are made available in the view, and sprintf()'d into Exception::$_messageTemplate * @param int $code The code of the error, is also the HTTP status code for the error. - * @param \Exception|null $previous the previous exception. + * @param \Throwable|null $previous the previous exception. */ - public function __construct(EntityInterface $entity, $message, $code = null, $previous = null) + public function __construct(EntityInterface $entity, $message, ?int $code = null, ?Throwable $previous = null) { $this->_entity = $entity; if (is_array($message)) { From 98fc6fdf3eb66bfaeab160c8fad299887ec4d785 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 5 May 2019 21:19:17 +0530 Subject: [PATCH 1343/2059] Add typehints for Datasource/ --- Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Query.php b/Query.php index 7c68d0d2..cbf050e8 100644 --- a/Query.php +++ b/Query.php @@ -1277,7 +1277,7 @@ public function __call($method, $arguments) /** * @inheritDoc */ - public function __debugInfo() + public function __debugInfo(): array { $eagerLoader = $this->getEagerLoader(); From bf74836848497698ef21f5ead4f105ba976e42b4 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 7 May 2019 09:35:02 +0530 Subject: [PATCH 1344/2059] Fix CS error. --- Association.php | 1 + Association/BelongsTo.php | 1 + Association/BelongsToMany.php | 1 + Association/DependentDeleteHelper.php | 1 + Association/HasMany.php | 1 + Association/HasOne.php | 1 + Association/Loader/SelectLoader.php | 1 + Association/Loader/SelectWithPivotLoader.php | 1 + AssociationCollection.php | 1 + AssociationsNormalizerTrait.php | 1 + Behavior.php | 1 + Behavior/CounterCacheBehavior.php | 1 + Behavior/TimestampBehavior.php | 1 + Behavior/Translate/EavStrategy.php | 1 + Behavior/Translate/ShadowTableStrategy.php | 1 + Behavior/Translate/TranslateStrategyInterface.php | 1 + Behavior/Translate/TranslateStrategyTrait.php | 1 + Behavior/Translate/TranslateTrait.php | 1 + Behavior/TranslateBehavior.php | 1 + Behavior/TreeBehavior.php | 1 + BehaviorRegistry.php | 1 + EagerLoadable.php | 1 + EagerLoader.php | 1 + Entity.php | 1 + Exception/MissingBehaviorException.php | 1 + Exception/MissingEntityException.php | 1 + Exception/MissingTableClassException.php | 1 + Exception/PersistenceFailedException.php | 1 + Exception/RolledbackTransactionException.php | 1 + LazyEagerLoader.php | 1 + Locator/LocatorAwareTrait.php | 1 + Locator/LocatorInterface.php | 1 + Locator/TableLocator.php | 1 + Marshaller.php | 1 + PropertyMarshalInterface.php | 1 + Query.php | 1 + ResultSet.php | 1 + Rule/ExistsIn.php | 1 + Rule/IsUnique.php | 1 + Rule/ValidCount.php | 1 + RulesChecker.php | 1 + SaveOptionsBuilder.php | 1 + Table.php | 1 + TableRegistry.php | 1 + 44 files changed, 44 insertions(+) diff --git a/Association.php b/Association.php index 64f8ee36..07d28da5 100644 --- a/Association.php +++ b/Association.php @@ -1,5 +1,6 @@ Date: Tue, 7 May 2019 18:15:47 +0530 Subject: [PATCH 1345/2059] Fix typehints. --- Query.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Query.php b/Query.php index cbf050e8..0ee2b3cd 100644 --- a/Query.php +++ b/Query.php @@ -1025,7 +1025,7 @@ public function cache(string $key, $config = 'default') * * @throws \RuntimeException if this method is called on a non-select Query. */ - public function all() + public function all(): ResultSetInterface { if ($this->_type !== 'select' && $this->_type !== null) { throw new RuntimeException( @@ -1077,7 +1077,7 @@ public function sql(?ValueBinder $binder = null): string * * @return \Cake\Datasource\ResultSetInterface */ - protected function _execute() + protected function _execute(): ResultSetInterface { $this->triggerBeforeFind(); if ($this->_results) { @@ -1175,7 +1175,7 @@ protected function _addDefaultSelectTypes(): void * * @see \Cake\ORM\Table::find() */ - public function find($finder, array $options = []) + public function find(string $finder, array $options = []) { /** @var \Cake\ORM\Table $table */ $table = $this->getRepository(); @@ -1358,6 +1358,7 @@ protected function _decorateResults(Traversable $result): ResultSetInterface if (!($result instanceof ResultSet) && $this->isBufferedResultsEnabled()) { $class = $this->_decoratorClass(); + /** @var \Cake\Datasource\ResultSetInterface $result */ $result = new $class($result->buffered()); } From ceefa630f2383a6d4f4697a34ff429de12d2b342 Mon Sep 17 00:00:00 2001 From: mscherer Date: Thu, 9 May 2019 22:25:03 +0200 Subject: [PATCH 1346/2059] Add saveManyOrFail(). --- Table.php | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/Table.php b/Table.php index 3f050099..20dc6516 100644 --- a/Table.php +++ b/Table.php @@ -2086,6 +2086,60 @@ public function saveMany(iterable $entities, $options = []) return $entities; } + /** + * Persists multiple entities of a table. + * + * The records will be saved in a transaction which will be rolled back if + * any one of the records fails to save due to failed validation or database + * error. + * + * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to save. + * @param array|\ArrayAccess $options Options used when calling Table::save() for each entity. + * @return \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface Entities list. + * @throws \Exception + * @throws \Cake\ORM\Exception\PersistenceFailedException + */ + public function saveManyOrFail(iterable $entities, $options = []) + { + /** @var bool[] $isNew */ + $isNew = []; + $failed = null; + $cleanup = function ($entities) use (&$isNew): void { + /** @var \Cake\Datasource\EntityInterface[] $entities */ + foreach ($entities as $key => $entity) { + if (isset($isNew[$key]) && $isNew[$key]) { + $entity->unset($this->getPrimaryKey()); + $entity->isNew(true); + } + } + }; + + try { + $return = $this->getConnection() + ->transactional(function () use ($entities, $options, &$isNew, &$failed) { + foreach ($entities as $key => $entity) { + $isNew[$key] = $entity->isNew(); + if ($this->save($entity, $options) === false) { + $failed = $entity; + return false; + } + } + }); + } catch (Exception $e) { + $cleanup($entities); + + throw $e; + } + + if ($return === false) { + $cleanup($entities); + + throw new PersistenceFailedException($failed, ['saveMany']); + } + + return $entities; + } + /** * {@inheritDoc} * From 738913db268bd4f03317189c0045933f43cb31c1 Mon Sep 17 00:00:00 2001 From: stickler-ci Date: Thu, 9 May 2019 20:26:56 +0000 Subject: [PATCH 1347/2059] Fixing style errors. --- Table.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Table.php b/Table.php index 20dc6516..3d1d2efd 100644 --- a/Table.php +++ b/Table.php @@ -2121,6 +2121,7 @@ public function saveManyOrFail(iterable $entities, $options = []) $isNew[$key] = $entity->isNew(); if ($this->save($entity, $options) === false) { $failed = $entity; + return false; } } From 960018cf82b7684c6012b18a2dee416e186071e8 Mon Sep 17 00:00:00 2001 From: mscherer Date: Thu, 9 May 2019 22:42:44 +0200 Subject: [PATCH 1348/2059] Add deleteMany() and deleteManyOrFail(). --- Table.php | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/Table.php b/Table.php index 3f050099..d9f3e04e 100644 --- a/Table.php +++ b/Table.php @@ -2135,6 +2135,94 @@ public function delete(EntityInterface $entity, $options = []): bool return $success; } + /** + * Deletes multiple entities of a table. + * + * The records will be deleted in a transaction which will be rolled back if + * any one of the records fails to delete due to failed validation or database + * error. + * + * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to save. + * @param array|\ArrayAccess $options Options used when calling Table::save() for each entity. + * @return bool|\Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface False on failure, entities list on success. + * @throws \Exception + */ + public function deleteMany(iterable $entities, $options = []) + { + $options = new ArrayObject((array)$options + [ + 'atomic' => true, + 'checkRules' => true, + '_primary' => true, + ]); + + $success = $this->_executeTransaction(function () use ($entities, $options) { + foreach ($entities as $entity) { + if (!$this->_processDelete($entity, $options)) { + return false; + } + } + return true; + }, $options['atomic']); + + if ($success && $this->_transactionCommitted($options['atomic'], $options['_primary'])) { + foreach ($entities as $entity) { + $this->dispatchEvent('Model.afterDeleteCommit', [ + 'entity' => $entity, + 'options' => $options, + ]); + } + } + + return $success; + } + + /** + * Deletes multiple entities of a table. + * + * The records will be deleted in a transaction which will be rolled back if + * any one of the records fails to delete due to failed validation or database + * error. + * + * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to save. + * @param array|\ArrayAccess $options Options used when calling Table::save() for each entity. + * @return \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface Entities list. + * @throws \Exception + */ + public function deleteManyOrFail(iterable $entities, $options = []) + { + $options = new ArrayObject((array)$options + [ + 'atomic' => true, + 'checkRules' => true, + '_primary' => true, + ]); + + $failed = null; + $success = $this->_executeTransaction(function () use ($entities, $options, &$failed) { + foreach ($entities as $entity) { + if (!$this->_processDelete($entity, $options)) { + $failed = $entity; + return false; + } + } + return true; + }, $options['atomic']); + + if ($success && $this->_transactionCommitted($options['atomic'], $options['_primary'])) { + foreach ($entities as $entity) { + $this->dispatchEvent('Model.afterDeleteCommit', [ + 'entity' => $entity, + 'options' => $options, + ]); + } + } + + if ($success === false) { + throw new PersistenceFailedException($failed, ['delete']); + } + + return $success; + } + /** * Try to delete an entity or throw a PersistenceFailedException if the entity is new, * has no primary key value, application rules checks failed or the delete was aborted by a callback. From 042dc8fbffad711a2763a86d507996a0ae3ee947 Mon Sep 17 00:00:00 2001 From: mscherer Date: Thu, 9 May 2019 22:45:52 +0200 Subject: [PATCH 1349/2059] Add deleteMany() and deleteManyOrFail(). --- Table.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Table.php b/Table.php index d9f3e04e..6421a542 100644 --- a/Table.php +++ b/Table.php @@ -2187,6 +2187,7 @@ public function deleteMany(iterable $entities, $options = []) * @param array|\ArrayAccess $options Options used when calling Table::save() for each entity. * @return \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface Entities list. * @throws \Exception + * @throws \Cake\ORM\Exception\PersistenceFailedException */ public function deleteManyOrFail(iterable $entities, $options = []) { @@ -2217,7 +2218,7 @@ public function deleteManyOrFail(iterable $entities, $options = []) } if ($success === false) { - throw new PersistenceFailedException($failed, ['delete']); + throw new PersistenceFailedException($failed, ['deleteMany']); } return $success; From d61e20cde98a568e3a897ffee5f058c8170e3519 Mon Sep 17 00:00:00 2001 From: stickler-ci Date: Thu, 9 May 2019 20:47:18 +0000 Subject: [PATCH 1350/2059] Fixing style errors. --- Table.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Table.php b/Table.php index 6421a542..cc67b99a 100644 --- a/Table.php +++ b/Table.php @@ -2161,6 +2161,7 @@ public function deleteMany(iterable $entities, $options = []) return false; } } + return true; }, $options['atomic']); @@ -2202,9 +2203,11 @@ public function deleteManyOrFail(iterable $entities, $options = []) foreach ($entities as $entity) { if (!$this->_processDelete($entity, $options)) { $failed = $entity; + return false; } } + return true; }, $options['atomic']); From 082f2964bdaba7c4a0f7f9e25a198a3fb6c6428d Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Thu, 9 May 2019 22:44:39 -0500 Subject: [PATCH 1351/2059] Updated Table::setTable documentation to show how to include database schema name. --- Table.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Table.php b/Table.php index 688c137e..60b36230 100644 --- a/Table.php +++ b/Table.php @@ -339,6 +339,9 @@ public function initialize(array $config) /** * Sets the database table name. * + * This can include the database schema name in the form 'schema.table'. + * If the name must be quoted, enable automatic identifier quoting. + * * @param string $table Table name. * @return $this */ @@ -352,6 +355,8 @@ public function setTable($table) /** * Returns the database table name. * + * This can include the database schema name if set using setTable. + * * @return string */ public function getTable() From 7446b0e7909d179ccda4ad0d2152731ae0ea372e Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Fri, 10 May 2019 04:16:00 -0500 Subject: [PATCH 1352/2059] Added formatting around setTable() --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 60b36230..e9a44799 100644 --- a/Table.php +++ b/Table.php @@ -355,7 +355,7 @@ public function setTable($table) /** * Returns the database table name. * - * This can include the database schema name if set using setTable. + * This can include the database schema name if set using `setTable()`. * * @return string */ From 0326f6f552dfffda999e07cb4e7e276ac3cf6486 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 11 May 2019 01:53:47 +0530 Subject: [PATCH 1353/2059] Update composer configs for split packages. --- composer.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 14328939..ad574a71 100644 --- a/composer.json +++ b/composer.json @@ -23,14 +23,14 @@ "source": "https://github.com/cakephp/orm" }, "require": { - "php": ">=5.6.0", - "cakephp/collection": "^3.6.0", - "cakephp/core": "^3.6.0", - "cakephp/datasource": "^3.6.0", - "cakephp/database": "^3.6.0", - "cakephp/event": "^3.6.0", - "cakephp/utility": "^3.6.0", - "cakephp/validation": "^3.6.0" + "php": ">=7.2.0", + "cakephp/collection": "^4.0", + "cakephp/core": "^4.0", + "cakephp/datasource": "^4.0", + "cakephp/database": "^4.0", + "cakephp/event": "^4.0", + "cakephp/utility": "^4.0", + "cakephp/validation": "^4.0" }, "suggest": { "cakephp/i18n": "If you are using Translate / Timestamp Behavior." From 121d9a93027e33410b79ec9a33d0b9180cb799e8 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Sat, 11 May 2019 02:23:51 -0500 Subject: [PATCH 1354/2059] Updated code example for retrieving Table instance through TableRegistry. --- TableRegistry.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/TableRegistry.php b/TableRegistry.php index 5c952687..4d7d5c92 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -38,12 +38,16 @@ * * ### Getting instances * - * You can fetch instances out of the registry using get(). One instance is stored - * per alias. Once an alias is populated the same instance will always be returned. - * This is used to make the ORM use less memory and help make cyclic references easier - * to solve. + * You can fetch instances out of the registry through the `TableLocator`. + * {@link TableLocator::get()} + * One instance is stored per alias. Once an alias is populated the same + * instance will always be returned. This reduces the ORM memory cost and + * helps make cyclic references easier to solve. * * ``` + * $table = TableRegistry::getTableLocator()->get('Users', $config); + * + * // Prior to 3.6.0 * $table = TableRegistry::get('Users', $config); * ``` */ From a8b35240d38c5478d529eee2e096b37642b1cd1d Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Sat, 11 May 2019 02:28:25 -0500 Subject: [PATCH 1355/2059] Updated code example for setting Table config through TableRegistry. --- TableRegistry.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/TableRegistry.php b/TableRegistry.php index 4d7d5c92..d4a52309 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -24,12 +24,15 @@ * * ### Configuring instances * - * You may need to configure your table objects, using TableRegistry you can + * You may need to configure your table objects. Using the `TableLocator` you can * centralize configuration. Any configuration set before instances are created * will be used when creating instances. If you modify configuration after * an instance is made, the instances *will not* be updated. * * ``` + * TableRegistry::getTableLocator()->setConfig('Users', ['table' => 'my_users']); + * + * // Prior to 3.6.0 * TableRegistry::config('Users', ['table' => 'my_users']); * ``` * From 51c29bc1a84c09f9b21d09cd9835ca0bbe5b4287 Mon Sep 17 00:00:00 2001 From: mscherer Date: Sun, 12 May 2019 19:51:05 +0200 Subject: [PATCH 1356/2059] Refactor to be more DRY. --- Table.php | 65 +++++++++++++++++++++++-------------------------------- 1 file changed, 27 insertions(+), 38 deletions(-) diff --git a/Table.php b/Table.php index cc67b99a..bfef439a 100644 --- a/Table.php +++ b/Table.php @@ -2142,39 +2142,20 @@ public function delete(EntityInterface $entity, $options = []): bool * any one of the records fails to delete due to failed validation or database * error. * - * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to save. + * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to delete. * @param array|\ArrayAccess $options Options used when calling Table::save() for each entity. * @return bool|\Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface False on failure, entities list on success. * @throws \Exception */ public function deleteMany(iterable $entities, $options = []) { - $options = new ArrayObject((array)$options + [ - 'atomic' => true, - 'checkRules' => true, - '_primary' => true, - ]); - - $success = $this->_executeTransaction(function () use ($entities, $options) { - foreach ($entities as $entity) { - if (!$this->_processDelete($entity, $options)) { - return false; - } - } - - return true; - }, $options['atomic']); + $failed = $this->_deleteMany($entities, $options); - if ($success && $this->_transactionCommitted($options['atomic'], $options['_primary'])) { - foreach ($entities as $entity) { - $this->dispatchEvent('Model.afterDeleteCommit', [ - 'entity' => $entity, - 'options' => $options, - ]); - } + if ($failed !== null) { + return false; } - return $success; + return $entities; } /** @@ -2184,34 +2165,46 @@ public function deleteMany(iterable $entities, $options = []) * any one of the records fails to delete due to failed validation or database * error. * - * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to save. + * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to delete. * @param array|\ArrayAccess $options Options used when calling Table::save() for each entity. * @return \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface Entities list. * @throws \Exception * @throws \Cake\ORM\Exception\PersistenceFailedException */ - public function deleteManyOrFail(iterable $entities, $options = []) + public function deleteManyOrFail(iterable $entities, $options = []): iterable { + $failed = $this->_deleteMany($entities, $options); + + if ($failed !== null) { + throw new PersistenceFailedException($failed, ['deleteMany']); + } + + return $entities; + } + + /** + * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to delete. + * @param array|\ArrayAccess $options Options used. + * @return EntityInterface|null + */ + protected function _deleteMany(iterable $entities, $options = []) { $options = new ArrayObject((array)$options + [ 'atomic' => true, 'checkRules' => true, '_primary' => true, ]); - $failed = null; - $success = $this->_executeTransaction(function () use ($entities, $options, &$failed) { + $failed = $this->_executeTransaction(function () use ($entities, $options) { foreach ($entities as $entity) { if (!$this->_processDelete($entity, $options)) { - $failed = $entity; - - return false; + return $entity; } } - return true; + return null; }, $options['atomic']); - if ($success && $this->_transactionCommitted($options['atomic'], $options['_primary'])) { + if ($failed === null && $this->_transactionCommitted($options['atomic'], $options['_primary'])) { foreach ($entities as $entity) { $this->dispatchEvent('Model.afterDeleteCommit', [ 'entity' => $entity, @@ -2220,11 +2213,7 @@ public function deleteManyOrFail(iterable $entities, $options = []) } } - if ($success === false) { - throw new PersistenceFailedException($failed, ['deleteMany']); - } - - return $success; + return $failed; } /** From 12174cf4768d693c25bd4cb99c3f5790e771e7be Mon Sep 17 00:00:00 2001 From: stickler-ci Date: Sun, 12 May 2019 17:53:06 +0000 Subject: [PATCH 1357/2059] Fixing style errors. --- Table.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Table.php b/Table.php index bfef439a..5cff6497 100644 --- a/Table.php +++ b/Table.php @@ -2187,7 +2187,8 @@ public function deleteManyOrFail(iterable $entities, $options = []): iterable * @param array|\ArrayAccess $options Options used. * @return EntityInterface|null */ - protected function _deleteMany(iterable $entities, $options = []) { + protected function _deleteMany(iterable $entities, $options = []) + { $options = new ArrayObject((array)$options + [ 'atomic' => true, 'checkRules' => true, From b43c544c84661bcafefba457a38e3f8e7b235fa0 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 13 May 2019 00:40:27 +0530 Subject: [PATCH 1358/2059] Add return typehint and fix CS error. --- Table.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index 5cff6497..59cf0a8f 100644 --- a/Table.php +++ b/Table.php @@ -2185,9 +2185,9 @@ public function deleteManyOrFail(iterable $entities, $options = []): iterable /** * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to delete. * @param array|\ArrayAccess $options Options used. - * @return EntityInterface|null + * @return \Cake\Datasource\EntityInterface|null */ - protected function _deleteMany(iterable $entities, $options = []) + protected function _deleteMany(iterable $entities, $options = []): ?EntityInterface { $options = new ArrayObject((array)$options + [ 'atomic' => true, From 8907625e53822fe7c7b5eba0f982e7fc7aa6bc98 Mon Sep 17 00:00:00 2001 From: mscherer Date: Tue, 14 May 2019 10:16:27 +0200 Subject: [PATCH 1359/2059] Refactor to be more DRY. --- Table.php | 54 ++++++++++++++++++------------------------------------ 1 file changed, 18 insertions(+), 36 deletions(-) diff --git a/Table.php b/Table.php index 3d1d2efd..2bf7b218 100644 --- a/Table.php +++ b/Table.php @@ -1791,7 +1791,7 @@ public function saveOrFail(EntityInterface $entity, $options = []): EntityInterf * * @param \Cake\Datasource\EntityInterface $entity the entity to be saved * @param \ArrayObject $options the options to use for the save operation - * @return \Cake\Datasource\EntityInterface|bool + * @return \Cake\Datasource\EntityInterface|false * @throws \RuntimeException When an entity is missing some of the primary keys. * @throws \Cake\ORM\Exception\RolledbackTransactionException If the transaction * is aborted in the afterSave event. @@ -1896,7 +1896,7 @@ protected function _onSaveSuccess(EntityInterface $entity, ArrayObject $options) * * @param \Cake\Datasource\EntityInterface $entity the subject entity from were $data was extracted * @param array $data The actual data that needs to be saved - * @return \Cake\Datasource\EntityInterface|bool + * @return \Cake\Datasource\EntityInterface|false * @throws \RuntimeException if not all the primary keys where supplied or could * be generated when the table has composite primary keys. Or when the table has no primary key. */ @@ -1994,7 +1994,7 @@ protected function _newId(array $primary) * * @param \Cake\Datasource\EntityInterface $entity the subject entity from were $data was extracted * @param array $data The actual data that needs to be saved - * @return \Cake\Datasource\EntityInterface|bool + * @return \Cake\Datasource\EntityInterface|false * @throws \InvalidArgumentException When primary key data is missing. */ protected function _update(EntityInterface $entity, array $data) @@ -2044,46 +2044,16 @@ protected function _update(EntityInterface $entity, array $data) * * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to save. * @param array|\ArrayAccess $options Options used when calling Table::save() for each entity. - * @return bool|\Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface False on failure, entities list on success. + * @return \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface|false False on failure, entities list on success. * @throws \Exception */ public function saveMany(iterable $entities, $options = []) { - /** @var bool[] $isNew */ - $isNew = []; - $cleanup = function ($entities) use (&$isNew): void { - /** @var \Cake\Datasource\EntityInterface[] $entities */ - foreach ($entities as $key => $entity) { - if (isset($isNew[$key]) && $isNew[$key]) { - $entity->unset($this->getPrimaryKey()); - $entity->isNew(true); - } - } - }; - try { - $return = $this->getConnection() - ->transactional(function () use ($entities, $options, &$isNew) { - foreach ($entities as $key => $entity) { - $isNew[$key] = $entity->isNew(); - if ($this->save($entity, $options) === false) { - return false; - } - } - }); - } catch (Exception $e) { - $cleanup($entities); - - throw $e; - } - - if ($return === false) { - $cleanup($entities); - + return $this->_saveMany($entities, $options); + } catch (PersistenceFailedException $exception) { return false; } - - return $entities; } /** @@ -2100,6 +2070,18 @@ public function saveMany(iterable $entities, $options = []) * @throws \Cake\ORM\Exception\PersistenceFailedException */ public function saveManyOrFail(iterable $entities, $options = []) + { + return $this->_saveMany($entities, $options); + } + + /** + * @param iterable $entities + * @param array $options + * + * @return \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface + * @throws \Cake\ORM\Exception\PersistenceFailedException + */ + public function _saveMany(iterable $entities, $options = []) { /** @var bool[] $isNew */ $isNew = []; From e6b031c0018c89f3c9c8273cede66a44783e3079 Mon Sep 17 00:00:00 2001 From: stickler-ci Date: Tue, 14 May 2019 08:18:06 +0000 Subject: [PATCH 1360/2059] Fixing style errors. --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 2bf7b218..7cc09425 100644 --- a/Table.php +++ b/Table.php @@ -2071,7 +2071,7 @@ public function saveMany(iterable $entities, $options = []) */ public function saveManyOrFail(iterable $entities, $options = []) { - return $this->_saveMany($entities, $options); + return $this->_saveMany($entities, $options); } /** From 66b83af37f216c3ccb725cfc0a85285b80904d48 Mon Sep 17 00:00:00 2001 From: mscherer Date: Tue, 14 May 2019 10:49:21 +0200 Subject: [PATCH 1361/2059] Use more precise bool return docs. --- Association.php | 2 +- Association/BelongsTo.php | 2 +- Association/BelongsToMany.php | 2 +- Association/HasMany.php | 2 +- Association/HasOne.php | 2 +- Behavior/TreeBehavior.php | 10 +++++----- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Association.php b/Association.php index 07d28da5..73d8f725 100644 --- a/Association.php +++ b/Association.php @@ -1249,7 +1249,7 @@ abstract public function isOwningSide(Table $side): bool; * * @param \Cake\Datasource\EntityInterface $entity the data to be saved * @param array $options The options for saving associated data. - * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns + * @return \Cake\Datasource\EntityInterface|false false if $entity could not be saved, otherwise it returns * the saved entity * @see \Cake\ORM\Table::save() */ diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 5e438a78..00b70ec2 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -114,7 +114,7 @@ public function type(): string * * @param \Cake\Datasource\EntityInterface $entity an entity from the source table * @param array $options options to be passed to the save method in the target table - * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns + * @return \Cake\Datasource\EntityInterface|false false if $entity could not be saved, otherwise it returns * the saved entity * @see \Cake\ORM\Table::save() */ diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 1a88e65b..7aa2eef6 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -636,7 +636,7 @@ public function getSaveStrategy(): string * @param array $options options to be passed to the save method in the target table * @throws \InvalidArgumentException if the property representing the association * in the parent entity cannot be traversed - * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns + * @return \Cake\Datasource\EntityInterface|false false if $entity could not be saved, otherwise it returns * the saved entity * @see \Cake\ORM\Table::save() * @see \Cake\ORM\Association\BelongsToMany::replaceLinks() diff --git a/Association/HasMany.php b/Association/HasMany.php index 97fa4184..ea24b1d7 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -138,7 +138,7 @@ public function getSaveStrategy(): string * * @param \Cake\Datasource\EntityInterface $entity an entity from the source table * @param array $options options to be passed to the save method in the target table - * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns + * @return \Cake\Datasource\EntityInterface|false false if $entity could not be saved, otherwise it returns * the saved entity * @see \Cake\ORM\Table::save() * @throws \InvalidArgumentException when the association data cannot be traversed. diff --git a/Association/HasOne.php b/Association/HasOne.php index 4495bb42..1a4e23eb 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -98,7 +98,7 @@ public function type(): string * * @param \Cake\Datasource\EntityInterface $entity an entity from the source table * @param array $options options to be passed to the save method in the target table - * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns + * @return \Cake\Datasource\EntityInterface|false false if $entity could not be saved, otherwise it returns * the saved entity * @see \Cake\ORM\Table::save() */ diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index d9577f49..f575ac12 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -603,7 +603,7 @@ protected function _removeFromTree(EntityInterface $node) * this method will return false * * @param \Cake\Datasource\EntityInterface $node The node to move - * @param int|bool $number How many places to move the node, or true to move to first position + * @param int|true $number How many places to move the node, or true to move to first position * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found * @return \Cake\Datasource\EntityInterface|bool $node The node after being moved or false on failure */ @@ -624,7 +624,7 @@ public function moveUp(EntityInterface $node, $number = 1) * Helper function used with the actual code for moveUp * * @param \Cake\Datasource\EntityInterface $node The node to move - * @param int|bool $number How many places to move the node, or true to move to first position + * @param int|true $number How many places to move the node, or true to move to first position * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found * @return \Cake\Datasource\EntityInterface|bool $node The node after being moved or false on failure */ @@ -693,7 +693,7 @@ protected function _moveUp(EntityInterface $node, $number) * this method will return false * * @param \Cake\Datasource\EntityInterface $node The node to move - * @param int|bool $number How many places to move the node or true to move to last position + * @param int|true $number How many places to move the node or true to move to last position * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found * @return \Cake\Datasource\EntityInterface|bool the entity after being moved or false on failure */ @@ -714,7 +714,7 @@ public function moveDown(EntityInterface $node, $number = 1) * Helper function used with the actual code for moveDown * * @param \Cake\Datasource\EntityInterface $node The node to move - * @param int|bool $number How many places to move the node, or true to move to last position + * @param int|true $number How many places to move the node, or true to move to last position * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found * @return \Cake\Datasource\EntityInterface|bool $node The node after being moved or false on failure */ @@ -990,7 +990,7 @@ protected function _getPrimaryKey(): string * Returns the depth level of a node in the tree. * * @param int|string|\Cake\Datasource\EntityInterface $entity The entity or primary key get the level of. - * @return int|bool Integer of the level or false if the node does not exist. + * @return int|false Integer of the level or false if the node does not exist. */ public function getLevel($entity) { From 2e0c2db8479838ec1cfe69fbd6010d97cf16d62d Mon Sep 17 00:00:00 2001 From: mscherer Date: Tue, 14 May 2019 12:25:06 +0200 Subject: [PATCH 1362/2059] Use more precise bool return docs. --- Association/BelongsToMany.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 7aa2eef6..400d1208 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -676,7 +676,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) * @param array $options list of options accepted by `Table::save()` * @throws \InvalidArgumentException if the property representing the association * in the parent entity cannot be traversed - * @return \Cake\Datasource\EntityInterface|bool The parent entity after all links have been + * @return \Cake\Datasource\EntityInterface|false The parent entity after all links have been * created if no errors happened, false otherwise */ protected function _saveTarget(EntityInterface $parentEntity, array $entities, $options) From c3e329d357c2bab1c66bbd7b20738ca652f096b8 Mon Sep 17 00:00:00 2001 From: mscherer Date: Tue, 14 May 2019 12:26:08 +0200 Subject: [PATCH 1363/2059] Use more precise array return docs. --- LazyEagerLoader.php | 4 ++-- Table.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 4e915624..cf1d82f2 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -36,11 +36,11 @@ class LazyEagerLoader * * The properties for the associations to be loaded will be overwritten on each entity. * - * @param \Cake\Datasource\EntityInterface|array $entities a single entity or list of entities + * @param \Cake\Datasource\EntityInterface|\Cake\Datasource\EntityInterface[] $entities a single entity or list of entities * @param array $contain A `contain()` compatible array. * @see \Cake\ORM\Query::contain() * @param \Cake\ORM\Table $source The table to use for fetching the top level entities - * @return \Cake\Datasource\EntityInterface|array + * @return \Cake\Datasource\EntityInterface|\Cake\Datasource\EntityInterface[] */ public function loadInto($entities, array $contain, Table $source) { diff --git a/Table.php b/Table.php index 8a79416b..9125d0b8 100644 --- a/Table.php +++ b/Table.php @@ -2760,10 +2760,10 @@ public function getSaveOptionsBuilder(array $options = []): SaveOptionsBuilder * * The properties for the associations to be loaded will be overwritten on each entity. * - * @param \Cake\Datasource\EntityInterface|array $entities a single entity or list of entities + * @param \Cake\Datasource\EntityInterface|\Cake\Datasource\EntityInterface[] $entities a single entity or list of entities * @param array $contain A `contain()` compatible array. * @see \Cake\ORM\Query::contain() - * @return \Cake\Datasource\EntityInterface|array + * @return \Cake\Datasource\EntityInterface|\Cake\Datasource\EntityInterface[] */ public function loadInto($entities, array $contain) { From 0c19dc1f33d055c068189c1574fb1f2f8aba9eca Mon Sep 17 00:00:00 2001 From: mscherer Date: Tue, 14 May 2019 12:39:11 +0200 Subject: [PATCH 1364/2059] $_tableLocator is nullable. --- Locator/LocatorAwareTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index e0d0553c..203d78b0 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -26,7 +26,7 @@ trait LocatorAwareTrait /** * Table locator instance * - * @var \Cake\ORM\Locator\LocatorInterface + * @var \Cake\ORM\Locator\LocatorInterface|null */ protected $_tableLocator; From 13128ade870daecf6cc701ffc0cf75dafc4bfcf1 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 14 May 2019 23:29:11 +0530 Subject: [PATCH 1365/2059] Fix return type errors reported by psalm. --- Behavior/TranslateBehavior.php | 1 + Locator/TableLocator.php | 1 + Query.php | 1 + Table.php | 1 + 4 files changed, 4 insertions(+) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index eaf36e7c..70efb305 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -158,6 +158,7 @@ protected function createStrategy() ); $className = $this->getConfig('strategyClass', static::$defaultStrategyClass); + /** @var \Cake\ORM\Behavior\Translate\TranslateStrategyInterface */ return new $className($this->_table, $config); } diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 50f1b564..9ebab6a8 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -258,6 +258,7 @@ protected function _getClassName(string $alias, array $options = []): ?string */ protected function _create(array $options): Table { + /** @var \Cake\ORM\Table */ return new $options['className']($options); } diff --git a/Query.php b/Query.php index 66a012cf..db1b05f9 100644 --- a/Query.php +++ b/Query.php @@ -1084,6 +1084,7 @@ protected function _execute(): ResultSetInterface if ($this->_results) { $decorator = $this->_decoratorClass(); + /** @var \Cake\Datasource\ResultSetInterface */ return new $decorator($this->_results); } diff --git a/Table.php b/Table.php index 9125d0b8..d56e2c4c 100644 --- a/Table.php +++ b/Table.php @@ -2411,6 +2411,7 @@ public function newEmptyEntity(): EntityInterface { $class = $this->getEntityClass(); + /** @var \Cake\Datasource\EntityInterface */ return new $class([], ['source' => $this->getRegistryAlias()]); } From b6c98cd64ef2f231abf060088c6ed3ff35820db3 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 15 May 2019 00:24:41 +0530 Subject: [PATCH 1366/2059] Fix more psalm errors --- BehaviorRegistry.php | 1 + 1 file changed, 1 insertion(+) diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 172be3ef..996d2bd2 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -141,6 +141,7 @@ protected function _throwMissingClassError(string $class, ?string $plugin): void */ protected function _create($class, string $alias, array $config): Behavior { + /** @var \Cake\ORM\Behavior $instance */ $instance = new $class($this->_table, $config); $enable = $config['enabled'] ?? true; if ($enable) { From e4cb207266b3f0c6deb4a688ecddf0eabeed1515 Mon Sep 17 00:00:00 2001 From: mscherer Date: Fri, 17 May 2019 00:53:26 +0200 Subject: [PATCH 1367/2059] Fix up get() for object registry. --- Table.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Table.php b/Table.php index d56e2c4c..7213f686 100644 --- a/Table.php +++ b/Table.php @@ -788,9 +788,7 @@ public function behaviors(): BehaviorRegistry */ public function getBehavior(string $name): Behavior { - /** @var \Cake\ORM\Behavior|null $behavior */ - $behavior = $this->_behaviors->get($name); - if ($behavior === null) { + if (!$this->_behaviors->has($name)) { throw new InvalidArgumentException(sprintf( 'The %s behavior is not defined on %s.', $name, @@ -798,6 +796,9 @@ public function getBehavior(string $name): Behavior )); } + /** @var \Cake\ORM\Behavior|null $behavior */ + $behavior = $this->_behaviors->get($name); + return $behavior; } From 49521288bf8488c4834f67f88df2d664da1816c2 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 16 May 2019 22:30:12 -0400 Subject: [PATCH 1368/2059] Update docblocks for deleteMany() --- Table.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Table.php b/Table.php index de6ce5f5..e3926d0e 100644 --- a/Table.php +++ b/Table.php @@ -2149,8 +2149,10 @@ public function delete(EntityInterface $entity, $options = []): bool * * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to delete. * @param array|\ArrayAccess $options Options used when calling Table::save() for each entity. - * @return bool|\Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface False on failure, entities list on success. + * @return bool|\Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface + * False on failure, entities list on success. * @throws \Exception + * @see \Cake\ORM\Table::delete() for options and events related to this method. */ public function deleteMany(iterable $entities, $options = []) { @@ -2175,6 +2177,7 @@ public function deleteMany(iterable $entities, $options = []) * @return \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface Entities list. * @throws \Exception * @throws \Cake\ORM\Exception\PersistenceFailedException + * @see \Cake\ORM\Table::delete() for options and events related to this method. */ public function deleteManyOrFail(iterable $entities, $options = []): iterable { From 1df9412e3af704278ccbd79535abd981b495efe8 Mon Sep 17 00:00:00 2001 From: mscherer Date: Fri, 17 May 2019 12:58:28 +0200 Subject: [PATCH 1369/2059] Fix phpstan errors. --- Table.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index 7cc09425..5f69d792 100644 --- a/Table.php +++ b/Table.php @@ -2069,7 +2069,7 @@ public function saveMany(iterable $entities, $options = []) * @throws \Exception * @throws \Cake\ORM\Exception\PersistenceFailedException */ - public function saveManyOrFail(iterable $entities, $options = []) + public function saveManyOrFail(iterable $entities, $options = []): iterable { return $this->_saveMany($entities, $options); } @@ -2081,7 +2081,7 @@ public function saveManyOrFail(iterable $entities, $options = []) * @return \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface * @throws \Cake\ORM\Exception\PersistenceFailedException */ - public function _saveMany(iterable $entities, $options = []) + protected function _saveMany(iterable $entities, $options = []): iterable { /** @var bool[] $isNew */ $isNew = []; From 3f4c5dfd421d4bb26ecedaacf17a80cbaa1cb72a Mon Sep 17 00:00:00 2001 From: mscherer Date: Fri, 17 May 2019 13:09:45 +0200 Subject: [PATCH 1370/2059] Fix phpstan errors. --- Table.php | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/Table.php b/Table.php index 5f69d792..d86e5c68 100644 --- a/Table.php +++ b/Table.php @@ -2067,7 +2067,7 @@ public function saveMany(iterable $entities, $options = []) * @param array|\ArrayAccess $options Options used when calling Table::save() for each entity. * @return \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface Entities list. * @throws \Exception - * @throws \Cake\ORM\Exception\PersistenceFailedException + * @throws \Cake\ORM\Exception\PersistenceFailedException If an entity couldn't be saved. */ public function saveManyOrFail(iterable $entities, $options = []): iterable { @@ -2075,17 +2075,15 @@ public function saveManyOrFail(iterable $entities, $options = []): iterable } /** - * @param iterable $entities - * @param array $options - * - * @return \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface - * @throws \Cake\ORM\Exception\PersistenceFailedException + * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to save. + * @param array|\ArrayAccess $options Options used when calling Table::save() for each entity. + * @return \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface Entities list. + * @throws \Cake\ORM\Exception\PersistenceFailedException If an entity couldn't be saved. */ protected function _saveMany(iterable $entities, $options = []): iterable { /** @var bool[] $isNew */ $isNew = []; - $failed = null; $cleanup = function ($entities) use (&$isNew): void { /** @var \Cake\Datasource\EntityInterface[] $entities */ foreach ($entities as $key => $entity) { @@ -2097,14 +2095,12 @@ protected function _saveMany(iterable $entities, $options = []): iterable }; try { - $return = $this->getConnection() - ->transactional(function () use ($entities, $options, &$isNew, &$failed) { + $failed = $this->getConnection() + ->transactional(function () use ($entities, $options, &$isNew) { foreach ($entities as $key => $entity) { $isNew[$key] = $entity->isNew(); if ($this->save($entity, $options) === false) { - $failed = $entity; - - return false; + return $entity; } } }); @@ -2114,7 +2110,7 @@ protected function _saveMany(iterable $entities, $options = []): iterable throw $e; } - if ($return === false) { + if ($failed !== null) { $cleanup($entities); throw new PersistenceFailedException($failed, ['saveMany']); From 087ce544c33c88eeb05d957e428597a786d72437 Mon Sep 17 00:00:00 2001 From: mscherer Date: Fri, 17 May 2019 14:43:59 +0200 Subject: [PATCH 1371/2059] Fix phpstan errors. --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 7213f686..224c4198 100644 --- a/Table.php +++ b/Table.php @@ -796,7 +796,7 @@ public function getBehavior(string $name): Behavior )); } - /** @var \Cake\ORM\Behavior|null $behavior */ + /** @var \Cake\ORM\Behavior $behavior */ $behavior = $this->_behaviors->get($name); return $behavior; From 010953a88ce27318a82a82a12c57f8fe0def381b Mon Sep 17 00:00:00 2001 From: mscherer Date: Fri, 17 May 2019 21:06:36 +0200 Subject: [PATCH 1372/2059] Fix phpstan errors on interfaces - level 5 --- ResultSet.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ResultSet.php b/ResultSet.php index 256208e2..9cd603da 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -58,7 +58,7 @@ class ResultSet implements ResultSetInterface /** * Default table instance * - * @var \Cake\ORM\Table|\Cake\Datasource\RepositoryInterface + * @var \Cake\ORM\Table */ protected $_defaultTable; @@ -172,7 +172,7 @@ public function __construct(Query $query, StatementInterface $statement) $repository = $query->getRepository(); $this->_statement = $statement; $this->_driver = $query->getConnection()->getDriver(); - $this->_defaultTable = $query->getRepository(); + $this->_defaultTable = $repository; $this->_calculateAssociationMap($query); $this->_hydrate = $query->isHydrationEnabled(); $this->_entityClass = $repository->getEntityClass(); From d0017480ff6a2a297aad053ad7c29f521698ad5c Mon Sep 17 00:00:00 2001 From: mscherer Date: Fri, 17 May 2019 21:29:09 +0200 Subject: [PATCH 1373/2059] Fix phpstan errors. --- EagerLoader.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index e0d14313..e2adde80 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -617,7 +617,9 @@ protected function _resolveJoins(array $associations, array $matching = []): arr */ public function loadExternal(Query $query, StatementInterface $statement): StatementInterface { - $external = $this->externalAssociations($query->getRepository()); + /** @var \Cake\ORM\Table $table */ + $table = $query->getRepository(); + $external = $this->externalAssociations($table); if (empty($external)) { return $statement; } @@ -751,14 +753,18 @@ public function addToJoinsMap( * * @param \Cake\ORM\EagerLoadable[] $external the list of external associations to be loaded * @param \Cake\ORM\Query $query The query from which the results where generated - * @param \Cake\Database\Statement\BufferedStatement $statement The statement to work on + * @param \Cake\Database\StatementInterface $statement The statement to work on * @return array + * @throws \RuntimeException */ protected function _collectKeys(array $external, Query $query, $statement): array { $collectKeys = []; foreach ($external as $meta) { $instance = $meta->instance(); + if (!$instance) { + throw new \RuntimeException('No instance available.'); + } if (!$instance->requiresKeys($meta->getConfig())) { continue; } From 9874ccd1a889936f5b6309bac57033f9cd043f91 Mon Sep 17 00:00:00 2001 From: mscherer Date: Fri, 17 May 2019 21:56:15 +0200 Subject: [PATCH 1374/2059] Fix phpstan errors on interfaces - level 5 --- EagerLoadable.php | 2 +- EagerLoader.php | 19 +++++++++++++------ Marshaller.php | 18 ++++++++++-------- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/EagerLoadable.php b/EagerLoadable.php index 25c28c03..6a01eb81 100644 --- a/EagerLoadable.php +++ b/EagerLoadable.php @@ -157,7 +157,7 @@ public function addAssociation(string $name, EagerLoadable $association): void /** * Returns the Association class instance to use for loading the records. * - * @return array + * @return \Cake\ORM\EagerLoadable[] */ public function associations(): array { diff --git a/EagerLoader.php b/EagerLoader.php index e2adde80..d639cdee 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -440,7 +440,7 @@ public function attachAssociations(Query $query, Table $repository, bool $includ * * @param \Cake\ORM\Table $repository The table containing the associations to be * attached - * @return array + * @return \Cake\ORM\EagerLoadable[] */ public function attachableAssociations(Table $repository): array { @@ -576,9 +576,9 @@ protected function _correctStrategy(EagerLoadable $loadable): void * Helper function used to compile a list of all associations that can be * joined in the query. * - * @param array $associations list of associations from which to obtain joins. - * @param array $matching list of associations that should be forcibly joined. - * @return array + * @param \Cake\ORM\EagerLoadable[] $associations list of associations from which to obtain joins. + * @param \Cake\ORM\EagerLoadable[] $matching list of associations that should be forcibly joined. + * @return \Cake\ORM\EagerLoadable[] */ protected function _resolveJoins(array $associations, array $matching = []): array { @@ -614,6 +614,7 @@ protected function _resolveJoins(array $associations, array $matching = []): arr * associations * @param \Cake\Database\StatementInterface $statement The statement created after executing the $query * @return \Cake\Database\StatementInterface statement modified statement with extra loaders + * @throws \RuntimeException */ public function loadExternal(Query $query, StatementInterface $statement): StatementInterface { @@ -630,6 +631,9 @@ public function loadExternal(Query $query, StatementInterface $statement): State foreach ($external as $meta) { $contain = $meta->associations(); $instance = $meta->instance(); + if (!$instance) { + throw new \RuntimeException('No instance available.'); + } $config = $meta->getConfig(); $alias = $instance->getSource()->getAlias(); $path = $meta->aliasPath(); @@ -689,16 +693,19 @@ public function associationsMap(Table $table): array * associationsMap() method. * * @param array $map An initial array for the map. - * @param array $level An array of EagerLoadable instances. + * @param \Cake\ORM\EagerLoadable[] $level An array of EagerLoadable instances. * @param bool $matching Whether or not it is an association loaded through `matching()`. * @return array + * @throws \RuntimeException */ protected function _buildAssociationsMap(array $map, array $level, bool $matching = false): array { - /** @var \Cake\ORM\EagerLoadable $meta */ foreach ($level as $assoc => $meta) { $canBeJoined = $meta->canBeJoined(); $instance = $meta->instance(); + if (!$instance) { + throw new \RuntimeException('No instance available.'); + } $associations = $meta->associations(); $forMatching = $meta->forMatching(); $map[] = [ diff --git a/Marshaller.php b/Marshaller.php index 79f1a789..5e254882 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -302,10 +302,11 @@ protected function _marshalAssociation(Association $assoc, $value, array $option $targetTable = $assoc->getTarget(); $marshaller = $targetTable->marshaller(); $types = [Association::ONE_TO_ONE, Association::MANY_TO_ONE]; - if (in_array($assoc->type(), $types)) { + $type = $assoc->type(); + if (in_array($type, $types, true)) { return $marshaller->one($value, (array)$options); } - if ($assoc->type() === Association::ONE_TO_MANY || $assoc->type() === Association::MANY_TO_MANY) { + if ($type === Association::ONE_TO_MANY || $type === Association::MANY_TO_MANY) { $hasIds = array_key_exists('_ids', $value); $onlyIds = array_key_exists('onlyIds', $options) && $options['onlyIds']; @@ -316,7 +317,7 @@ protected function _marshalAssociation(Association $assoc, $value, array $option return []; } } - if ($assoc->type() === Association::MANY_TO_MANY) { + if ($type === Association::MANY_TO_MANY) { return $marshaller->_belongsToMany($assoc, $value, (array)$options); } @@ -621,7 +622,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) * the accessible fields list in the entity will be used. * - accessibleFields: A list of fields to allow or deny in entity accessible fields. * - * @param \Cake\Datasource\EntityInterface[]|\Traversable $entities the entities that will get the + * @param \Cake\Datasource\EntityInterface[] $entities the entities that will get the * data merged in * @param array $data list of arrays to be merged into the entities * @param array $options List of options. @@ -720,14 +721,15 @@ protected function _mergeAssociation($original, Association $assoc, $value, arra $targetTable = $assoc->getTarget(); $marshaller = $targetTable->marshaller(); $types = [Association::ONE_TO_ONE, Association::MANY_TO_ONE]; - if (in_array($assoc->type(), $types)) { + $type = $assoc->type(); + if (in_array($type, $types, true)) { return $marshaller->merge($original, $value, (array)$options); } - if ($assoc->type() === Association::MANY_TO_MANY) { + if ($type === Association::MANY_TO_MANY) { return $marshaller->_mergeBelongsToMany($original, $assoc, $value, (array)$options); } - if ($assoc->type() === Association::ONE_TO_MANY) { + if ($type === Association::ONE_TO_MANY) { $hasIds = array_key_exists('_ids', $value); $onlyIds = array_key_exists('onlyIds', $options) && $options['onlyIds']; if ($hasIds && is_array($value['_ids'])) { @@ -746,7 +748,7 @@ protected function _mergeAssociation($original, Association $assoc, $value, arra * association. * * @param \Cake\Datasource\EntityInterface[] $original The original entities list. - * @param \Cake\ORM\Association $assoc The association to marshall + * @param \Cake\ORM\Association\BelongsToMany $assoc The association to marshall * @param array $value The data to hydrate * @param array $options List of options. * @return \Cake\Datasource\EntityInterface[] From 871403792e3da1eff12beafc037edd8a7c4a7248 Mon Sep 17 00:00:00 2001 From: mscherer Date: Fri, 17 May 2019 22:08:59 +0200 Subject: [PATCH 1375/2059] Make instance() non nullable as it is used everywhere. --- EagerLoadable.php | 11 ++++++++--- EagerLoader.php | 11 ----------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/EagerLoadable.php b/EagerLoadable.php index 6a01eb81..5e05753f 100644 --- a/EagerLoadable.php +++ b/EagerLoadable.php @@ -43,7 +43,7 @@ class EagerLoadable /** * The Association class instance to use for loading the records. * - * @var \Cake\ORM\Association + * @var \Cake\ORM\Association|null */ protected $_instance; @@ -167,10 +167,15 @@ public function associations(): array /** * Gets the Association class instance to use for loading the records. * - * @return \Cake\ORM\Association|null + * @return \Cake\ORM\Association + * @throws \RuntimeException */ - public function instance(): ?Association + public function instance(): Association { + if ($this->_instance === null) { + throw new \RuntimeException('No instance set.'); + } + return $this->_instance; } diff --git a/EagerLoader.php b/EagerLoader.php index d639cdee..db903304 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -631,9 +631,6 @@ public function loadExternal(Query $query, StatementInterface $statement): State foreach ($external as $meta) { $contain = $meta->associations(); $instance = $meta->instance(); - if (!$instance) { - throw new \RuntimeException('No instance available.'); - } $config = $meta->getConfig(); $alias = $instance->getSource()->getAlias(); $path = $meta->aliasPath(); @@ -696,16 +693,12 @@ public function associationsMap(Table $table): array * @param \Cake\ORM\EagerLoadable[] $level An array of EagerLoadable instances. * @param bool $matching Whether or not it is an association loaded through `matching()`. * @return array - * @throws \RuntimeException */ protected function _buildAssociationsMap(array $map, array $level, bool $matching = false): array { foreach ($level as $assoc => $meta) { $canBeJoined = $meta->canBeJoined(); $instance = $meta->instance(); - if (!$instance) { - throw new \RuntimeException('No instance available.'); - } $associations = $meta->associations(); $forMatching = $meta->forMatching(); $map[] = [ @@ -762,16 +755,12 @@ public function addToJoinsMap( * @param \Cake\ORM\Query $query The query from which the results where generated * @param \Cake\Database\StatementInterface $statement The statement to work on * @return array - * @throws \RuntimeException */ protected function _collectKeys(array $external, Query $query, $statement): array { $collectKeys = []; foreach ($external as $meta) { $instance = $meta->instance(); - if (!$instance) { - throw new \RuntimeException('No instance available.'); - } if (!$instance->requiresKeys($meta->getConfig())) { continue; } From 6e8c4f609eaefb3ec7bb1b7342ed6fd4ad692ea0 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Sat, 18 May 2019 01:02:37 -0500 Subject: [PATCH 1376/2059] Removed broken @link in TableRegistry --- TableRegistry.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/TableRegistry.php b/TableRegistry.php index d4a52309..ee505688 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -41,8 +41,7 @@ * * ### Getting instances * - * You can fetch instances out of the registry through the `TableLocator`. - * {@link TableLocator::get()} + * You can fetch instances out of the registry through `TableLocator::get()`. * One instance is stored per alias. Once an alias is populated the same * instance will always be returned. This reduces the ORM memory cost and * helps make cyclic references easier to solve. From d50b2fb57561ac79db4ccfa985ebda8daadce3e3 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 19 May 2019 17:51:54 +0530 Subject: [PATCH 1377/2059] Update param type in docblock --- Marshaller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index 5e254882..3d61de7b 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -622,7 +622,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) * the accessible fields list in the entity will be used. * - accessibleFields: A list of fields to allow or deny in entity accessible fields. * - * @param \Cake\Datasource\EntityInterface[] $entities the entities that will get the + * @param \Cake\Datasource\EntityInterface[]|\Traversable<\Cake\Datasource\EntityInterface> $entities the entities that will get the * data merged in * @param array $data list of arrays to be merged into the entities * @param array $options List of options. From e68cf76ccea158e12e2b65d69c265875b9859b5f Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 19 May 2019 17:55:53 +0530 Subject: [PATCH 1378/2059] Update typehint --- Marshaller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index 3d61de7b..b39e579a 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -753,7 +753,7 @@ protected function _mergeAssociation($original, Association $assoc, $value, arra * @param array $options List of options. * @return \Cake\Datasource\EntityInterface[] */ - protected function _mergeBelongsToMany(array $original, Association $assoc, $value, array $options): array + protected function _mergeBelongsToMany(array $original, BelongsToMany $assoc, array $value, array $options): array { $associated = $options['associated'] ?? []; From e79cc5ded7fad35c7404feef9748e74dcfa62782 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 19 May 2019 20:04:36 +0530 Subject: [PATCH 1379/2059] Use TableSchemaInterface for typehints instead of TableSchema. It's only TestFixture which requires the methods provided by SqlGeneratorInterface. --- Table.php | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Table.php b/Table.php index e3926d0e..9231cc8c 100644 --- a/Table.php +++ b/Table.php @@ -21,6 +21,7 @@ use Cake\Core\App; use Cake\Database\Connection; use Cake\Database\Schema\TableSchema; +use Cake\Database\Schema\TableSchemaInterface; use Cake\Database\TypeFactory; use Cake\Datasource\ConnectionInterface; use Cake\Datasource\ConnectionManager; @@ -188,7 +189,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc /** * The schema object containing a description of this table fields * - * @var \Cake\Database\Schema\TableSchema + * @var \Cake\Database\Schema\TableSchemaInterface */ protected $_schema; @@ -244,7 +245,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc * - connection: The connection instance to use * - entityClass: The fully namespaced class name of the entity class that will * represent rows in this table. - * - schema: A \Cake\Database\Schema\TableSchema object or an array that can be + * - schema: A \Cake\Database\Schema\TableSchemaInterface object or an array that can be * passed to it. * - eventManager: An instance of an event manager to use for internal events * - behaviors: A BehaviorRegistry. Generally not used outside of tests. @@ -482,9 +483,9 @@ public function getConnection(): Connection /** * Returns the schema table object describing this table's properties. * - * @return \Cake\Database\Schema\TableSchema + * @return \Cake\Database\Schema\TableSchemaInterface */ - public function getSchema(): TableSchema + public function getSchema(): TableSchemaInterface { if ($this->_schema === null) { $this->_schema = $this->_initializeSchema( @@ -500,10 +501,10 @@ public function getSchema(): TableSchema /** * Sets the schema table object describing this table's properties. * - * If an array is passed, a new TableSchema will be constructed + * If an array is passed, a new TableSchemaInterface will be constructed * out of it and used as the schema for this table. * - * @param array|\Cake\Database\Schema\TableSchema $schema Schema to be used for this table + * @param array|\Cake\Database\Schema\TableSchemaInterface $schema Schema to be used for this table * @return $this */ public function setSchema($schema) @@ -538,16 +539,16 @@ public function setSchema($schema) * ### Example: * * ``` - * protected function _initializeSchema(\Cake\Database\Schema\TableSchema $schema) { + * protected function _initializeSchema(\Cake\Database\Schema\TableSchemaInterface $schema) { * $schema->setColumnType('preferences', 'json'); * return $schema; * } * ``` * - * @param \Cake\Database\Schema\TableSchema $schema The table definition fetched from database. - * @return \Cake\Database\Schema\TableSchema the altered schema + * @param \Cake\Database\Schema\TableSchemaInterface $schema The table definition fetched from database. + * @return \Cake\Database\Schema\TableSchemaInterface the altered schema */ - protected function _initializeSchema(TableSchema $schema): TableSchema + protected function _initializeSchema(TableSchemaInterface $schema): TableSchemaInterface { return $schema; } From ed38325c5cb85a70a76021ab3d0d17b2fcb27955 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 19 May 2019 23:04:41 +0530 Subject: [PATCH 1380/2059] Typehint using DriverInterface instead of Driver. --- ResultSet.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ResultSet.php b/ResultSet.php index 9cd603da..8be05697 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -156,7 +156,7 @@ class ResultSet implements ResultSetInterface * * Cached in a property to avoid multiple calls to the same function. * - * @var \Cake\Database\Driver + * @var \Cake\Database\DriverInterface */ protected $_driver; From 72d7e8e16dd2fb2198e8beb0abff6eb9a8dd583b Mon Sep 17 00:00:00 2001 From: mscherer Date: Mon, 20 May 2019 01:29:18 +0200 Subject: [PATCH 1381/2059] More phpstan/psalm fixes --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 9231cc8c..43113bfa 100644 --- a/Table.php +++ b/Table.php @@ -189,7 +189,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc /** * The schema object containing a description of this table fields * - * @var \Cake\Database\Schema\TableSchemaInterface + * @var \Cake\Database\Schema\TableSchemaInterface|null */ protected $_schema; From 595dcca5d6db2ee9feadae416e94476484021c30 Mon Sep 17 00:00:00 2001 From: mscherer Date: Mon, 20 May 2019 02:08:05 +0200 Subject: [PATCH 1382/2059] More phpstan/psalm fixes --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 43113bfa..cd8da784 100644 --- a/Table.php +++ b/Table.php @@ -231,7 +231,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc /** * Registry key used to create this table object * - * @var string + * @var string|null */ protected $_registryAlias; From d9ffeac8c597e93b547bea7acdf51c2f623947dd Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 19 May 2019 22:45:43 -0400 Subject: [PATCH 1383/2059] Improve error message for missing associations. Try to make the missing association error message clearer. Indicate that the property is undefined and that it was interpreted as an association. --- Table.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index e9a44799..7ac160ed 100644 --- a/Table.php +++ b/Table.php @@ -2531,9 +2531,11 @@ public function __get($property) $association = $this->_associations->get($property); if (!$association) { throw new RuntimeException(sprintf( - 'Table "%s" is not associated with "%s"', + 'Undefined property `%s`. ' . + 'You have tried to use the `%s` property on %s, but that association does not exist.', + $property, + $property, get_class($this), - $property )); } From f5bf6ed6409685cca8c019a6bee61c925e7514e3 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 20 May 2019 11:52:32 +0530 Subject: [PATCH 1384/2059] Cleanup registry classes. _resolveClassName() now only deals with strings. --- BehaviorRegistry.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 996d2bd2..f531ea95 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -104,7 +104,7 @@ public static function className(string $class): ?string * @param string $class Partial classname to resolve. * @return string|null Either the correct class name or null. */ - protected function _resolveClassName($class) + protected function _resolveClassName(string $class): ?string { return static::className($class); } From cab43c1a62bb065bf15b827afac843990b075378 Mon Sep 17 00:00:00 2001 From: mscherer Date: Mon, 20 May 2019 10:52:33 +0200 Subject: [PATCH 1385/2059] More phpstan/psalm fixes --- Behavior/TreeBehavior.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index f575ac12..14486f9a 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -605,7 +605,7 @@ protected function _removeFromTree(EntityInterface $node) * @param \Cake\Datasource\EntityInterface $node The node to move * @param int|true $number How many places to move the node, or true to move to first position * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found - * @return \Cake\Datasource\EntityInterface|bool $node The node after being moved or false on failure + * @return \Cake\Datasource\EntityInterface|false $node The node after being moved or false on failure */ public function moveUp(EntityInterface $node, $number = 1) { @@ -626,7 +626,7 @@ public function moveUp(EntityInterface $node, $number = 1) * @param \Cake\Datasource\EntityInterface $node The node to move * @param int|true $number How many places to move the node, or true to move to first position * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found - * @return \Cake\Datasource\EntityInterface|bool $node The node after being moved or false on failure + * @return \Cake\Datasource\EntityInterface|false $node The node after being moved or false on failure */ protected function _moveUp(EntityInterface $node, $number) { @@ -695,7 +695,7 @@ protected function _moveUp(EntityInterface $node, $number) * @param \Cake\Datasource\EntityInterface $node The node to move * @param int|true $number How many places to move the node or true to move to last position * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found - * @return \Cake\Datasource\EntityInterface|bool the entity after being moved or false on failure + * @return \Cake\Datasource\EntityInterface|false the entity after being moved or false on failure */ public function moveDown(EntityInterface $node, $number = 1) { @@ -716,7 +716,7 @@ public function moveDown(EntityInterface $node, $number = 1) * @param \Cake\Datasource\EntityInterface $node The node to move * @param int|true $number How many places to move the node, or true to move to last position * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found - * @return \Cake\Datasource\EntityInterface|bool $node The node after being moved or false on failure + * @return \Cake\Datasource\EntityInterface|false $node The node after being moved or false on failure */ protected function _moveDown(EntityInterface $node, $number) { From 79479a7303f3d6ac4ef97460565c434356b4a36a Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 21 May 2019 10:18:35 +0530 Subject: [PATCH 1386/2059] Fix psalm errors --- Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Query.php b/Query.php index db1b05f9..41ad5c83 100644 --- a/Query.php +++ b/Query.php @@ -1204,7 +1204,7 @@ protected function _dirty(): void * This changes the query type to be 'update'. * Can be combined with set() and where() methods to create update queries. * - * @param string|null $table Unused parameter. + * @param string|\Cake\Database\ExpressionInterface|null $table Unused parameter. * @return $this */ public function update($table = null) From 70c1f207093547347267c118de998e28761a8409 Mon Sep 17 00:00:00 2001 From: mscherer Date: Tue, 21 May 2019 10:05:43 +0200 Subject: [PATCH 1387/2059] Cleanup ignore list. Add missing inline annotation for IDE. --- Association/BelongsToMany.php | 2 +- Table.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 400d1208..5924e237 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1169,7 +1169,7 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { $inserted = array_combine( array_keys($inserts), (array)$sourceEntity->get($property) - ); + ) ?: []; $targetEntities = $inserted + $targetEntities; } diff --git a/Table.php b/Table.php index 3c0849cb..81bf1c47 100644 --- a/Table.php +++ b/Table.php @@ -1921,7 +1921,7 @@ protected function _insert(EntityInterface $entity, array $data) $id = (array)$this->_newId($primary) + $keys; // Generate primary keys preferring values in $data. - $primary = array_combine($primary, $id); + $primary = array_combine($primary, $id) ?: []; $primary = array_intersect_key($data, $primary) + $primary; $filteredKeys = array_filter($primary, function ($v) { From bdb90375b169c8826388a8c7f6416b75f0967af2 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 20 May 2019 21:30:11 -0400 Subject: [PATCH 1388/2059] Improve wording --- Table.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index 7ac160ed..93f83f01 100644 --- a/Table.php +++ b/Table.php @@ -2532,10 +2532,10 @@ public function __get($property) if (!$association) { throw new RuntimeException(sprintf( 'Undefined property `%s`. ' . - 'You have tried to use the `%s` property on %s, but that association does not exist.', + 'You have not defined the `%s` association on `%s`.', $property, $property, - get_class($this), + static::class )); } From 215eb266326f5f83e860c6244f1044d27e6ca3dc Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 27 May 2019 04:16:18 +0530 Subject: [PATCH 1389/2059] Fix return typehint. --- Table.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index 2efe8730..8a08c63e 100644 --- a/Table.php +++ b/Table.php @@ -889,9 +889,9 @@ protected function findAssociation(string $name): ?Association /** * Get the associations collection for this table. * - * @return \Cake\ORM\AssociationCollection|\Cake\ORM\Association[] The collection of association objects. + * @return \Cake\ORM\AssociationCollection The collection of association objects. */ - public function associations(): iterable + public function associations(): AssociationCollection { return $this->_associations; } From 2c27a3709449879ca29fdafb4d85518911ab9635 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 29 May 2019 15:03:22 +0530 Subject: [PATCH 1390/2059] Fix errors reported by psalm. --- Marshaller.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Marshaller.php b/Marshaller.php index b39e579a..00577a3d 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -628,6 +628,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) * @param array $options List of options. * @return \Cake\Datasource\EntityInterface[] * @see \Cake\ORM\Entity::$_accessible + * @psalm-suppress NullArrayOffset */ public function mergeMany(iterable $entities, array $data, array $options = []): array { From f3ef79c6f47fb44847ebe04640008c38b2646abc Mon Sep 17 00:00:00 2001 From: mscherer Date: Fri, 31 May 2019 16:31:13 +0200 Subject: [PATCH 1391/2059] Fix up string[] doc blocks and annotations. --- Association.php | 12 ++++++------ Association/BelongsTo.php | 2 +- Association/BelongsToMany.php | 8 ++++---- Association/HasMany.php | 2 +- Association/HasOne.php | 2 +- Association/Loader/SelectLoader.php | 7 ++++--- Association/Loader/SelectWithPivotLoader.php | 2 +- Query.php | 4 ++-- Table.php | 16 ++++++++-------- 9 files changed, 28 insertions(+), 27 deletions(-) diff --git a/Association.php b/Association.php index 73d8f725..ee0c2e27 100644 --- a/Association.php +++ b/Association.php @@ -107,14 +107,14 @@ abstract class Association /** * The field name in the owning side table that is used to match with the foreignKey * - * @var string|array + * @var string|string[] */ protected $_bindingKey; /** * The name of the field representing the foreign key to the table to load * - * @var string|array + * @var string|string[] */ protected $_foreignKey; @@ -449,7 +449,7 @@ public function getConditions() * Sets the name of the field representing the binding field with the target table. * When not manually specified the primary key of the owning side table is used. * - * @param string|array $key the table field or fields to be used to link both tables together + * @param string|string[] $key the table field or fields to be used to link both tables together * @return $this */ public function setBindingKey($key) @@ -463,7 +463,7 @@ public function setBindingKey($key) * Gets the name of the field representing the binding field with the target table. * When not manually specified the primary key of the owning side table is used. * - * @return string|array + * @return string|string[] */ public function getBindingKey() { @@ -479,7 +479,7 @@ public function getBindingKey() /** * Gets the name of the field representing the foreign key to the target table. * - * @return string|array + * @return string|string[] */ public function getForeignKey() { @@ -489,7 +489,7 @@ public function getForeignKey() /** * Sets the name of the field representing the foreign key to the target table. * - * @param string|array $key the key or keys to be used to link both tables together + * @param string|string[] $key the key or keys to be used to link both tables together * @return $this */ public function setForeignKey($key) diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 00b70ec2..eab771aa 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -46,7 +46,7 @@ class BelongsTo extends Association /** * Gets the name of the field representing the foreign key to the target table. * - * @return string|array + * @return string|string[] */ public function getForeignKey() { diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 5924e237..835bd440 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -106,7 +106,7 @@ class BelongsToMany extends Association /** * The name of the field representing the foreign key to the target table * - * @var string|array + * @var string|string[] */ protected $_targetForeignKey; @@ -161,7 +161,7 @@ class BelongsToMany extends Association /** * Sets the name of the field representing the foreign key to the target table. * - * @param string|array $key the key to be used to link both tables together + * @param string|string[] $key the key to be used to link both tables together * @return $this */ public function setTargetForeignKey($key) @@ -174,7 +174,7 @@ public function setTargetForeignKey($key) /** * Gets the name of the field representing the foreign key to the target table. * - * @return string|array + * @return string|string[] */ public function getTargetForeignKey() { @@ -200,7 +200,7 @@ public function canBeJoined(array $options = []): bool /** * Gets the name of the field representing the foreign key to the source table. * - * @return string|array + * @return string|string[] */ public function getForeignKey() { diff --git a/Association/HasMany.php b/Association/HasMany.php index ea24b1d7..0ffb9629 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -575,7 +575,7 @@ public function canBeJoined(array $options = []): bool /** * Gets the name of the field representing the foreign key to the source table. * - * @return string|array + * @return string|string[] */ public function getForeignKey() { diff --git a/Association/HasOne.php b/Association/HasOne.php index 1a4e23eb..060fa5ff 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -44,7 +44,7 @@ class HasOne extends Association /** * Gets the name of the field representing the foreign key to the target table. * - * @return string|array + * @return string|string[] */ public function getForeignKey() { diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index b12ec174..b46f1698 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -279,7 +279,7 @@ protected function _assertFieldsPresent(Query $fetchQuery, array $key): void * filtering needs to be done using a subquery. * * @param \Cake\ORM\Query $query Target table's query - * @param string|array $key the fields that should be used for filtering + * @param string|string[] $key the fields that should be used for filtering * @param \Cake\ORM\Query $subquery The Subquery to use for filtering * @return \Cake\ORM\Query */ @@ -336,7 +336,7 @@ protected function _addFilteringCondition(Query $query, $key, $filter): Query * from $keys with the tuple values in $filter using the provided operator. * * @param \Cake\ORM\Query $query Target table's query - * @param array $keys the fields that should be used for filtering + * @param string[] $keys the fields that should be used for filtering * @param mixed $filter the value that should be used to match for $key * @param string $operator The operator for comparing the tuples * @return \Cake\Database\Expression\TupleComparison @@ -359,7 +359,8 @@ protected function _createTupleCondition(Query $query, array $keys, $filter, $op * which the filter should be applied * * @param array $options The options for getting the link field. - * @return string|array + * @return string|string[] + * @throws \RuntimeException */ protected function _linkField(array $options) { diff --git a/Association/Loader/SelectWithPivotLoader.php b/Association/Loader/SelectWithPivotLoader.php index 0816ec6f..3498540a 100644 --- a/Association/Loader/SelectWithPivotLoader.php +++ b/Association/Loader/SelectWithPivotLoader.php @@ -135,7 +135,7 @@ protected function _buildQuery(array $options): Query * which the filter should be applied * * @param array $options the options to use for getting the link field. - * @return array|string + * @return string|string[] */ protected function _linkField(array $options) { diff --git a/Query.php b/Query.php index 41ad5c83..d3ca5c0c 100644 --- a/Query.php +++ b/Query.php @@ -215,7 +215,7 @@ public function __construct(Connection $connection, Table $table) * @param bool $overwrite whether to reset fields with passed list or not * @return $this */ - public function select($fields = [], $overwrite = false) + public function select($fields = [], bool $overwrite = false) { if ($fields instanceof Association) { $fields = $fields->getTarget(); @@ -243,7 +243,7 @@ public function select($fields = [], $overwrite = false) * @return $this * @throws \InvalidArgumentException If Association|Table is not passed in first argument */ - public function selectAllExcept($table, array $excludedFields, $overwrite = false) + public function selectAllExcept($table, array $excludedFields, bool $overwrite = false) { if ($table instanceof Association) { $table = $table->getTarget(); diff --git a/Table.php b/Table.php index 8a08c63e..84a63c06 100644 --- a/Table.php +++ b/Table.php @@ -196,14 +196,14 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc /** * The name of the field that represents the primary key in the table * - * @var string|array + * @var string|string[] */ protected $_primaryKey; /** * The name of the field that represents a human readable representation of a row * - * @var string|array + * @var string|string[] */ protected $_displayField; @@ -572,7 +572,7 @@ public function hasField(string $field): bool /** * Sets the primary key field name. * - * @param string|array $key Sets a new name to be used as primary key + * @param string|string[] $key Sets a new name to be used as primary key * @return $this */ public function setPrimaryKey($key) @@ -585,7 +585,7 @@ public function setPrimaryKey($key) /** * Returns the primary key field name. * - * @return string|array + * @return string|string[] */ public function getPrimaryKey() { @@ -603,7 +603,7 @@ public function getPrimaryKey() /** * Sets the display field. * - * @param string|array $field Name to be used as display field. + * @param string|string[] $field Name to be used as display field. * @return $this */ public function setDisplayField($field) @@ -616,7 +616,7 @@ public function setDisplayField($field) /** * Returns the display field. * - * @return string|array + * @return string|string[] */ public function getDisplayField() { @@ -1982,8 +1982,8 @@ protected function _insert(EntityInterface $entity, array $data) * Note: The ORM will not generate primary key values for composite primary keys. * You can overwrite _newId() in your table class. * - * @param array $primary The primary key columns to get a new ID for. - * @return null|string|array Either null or the primary key value or a list of primary key values. + * @param string[] $primary The primary key columns to get a new ID for. + * @return string|null Either null or the primary key value or a list of primary key values. */ protected function _newId(array $primary) { From 325e693a79349b898c9c62578b7e29491d2eb797 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 11 Jun 2019 12:20:26 +0530 Subject: [PATCH 1392/2059] Add QueryInterface::select(). --- Query.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Query.php b/Query.php index d3ca5c0c..9b1deea4 100644 --- a/Query.php +++ b/Query.php @@ -214,6 +214,7 @@ public function __construct(Connection $connection, Table $table) * to be added to the list. * @param bool $overwrite whether to reset fields with passed list or not * @return $this + * @psalm-suppress MoreSpecificImplementedParamType */ public function select($fields = [], bool $overwrite = false) { From a99abbe9973b767d9b2afbc10d4b355e2664076d Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 11 Jun 2019 12:32:15 +0530 Subject: [PATCH 1393/2059] Move "join" constants from Datsource\Queryinterface to Database\Query. Joins are specific to relational databases. --- Association.php | 3 +-- Association/BelongsToMany.php | 5 ++--- Association/HasMany.php | 4 ++-- Behavior/Translate/EavStrategy.php | 7 +++---- EagerLoader.php | 3 +-- Query.php | 6 +++--- 6 files changed, 12 insertions(+), 16 deletions(-) diff --git a/Association.php b/Association.php index ee0c2e27..df5ff41c 100644 --- a/Association.php +++ b/Association.php @@ -21,7 +21,6 @@ use Cake\Core\ConventionsTrait; use Cake\Database\Expression\IdentifierExpression; use Cake\Datasource\EntityInterface; -use Cake\Datasource\QueryInterface; use Cake\Datasource\ResultSetDecorator; use Cake\ORM\Locator\LocatorAwareTrait; use Cake\Utility\Inflector; @@ -161,7 +160,7 @@ abstract class Association * * @var string */ - protected $_joinType = QueryInterface::JOIN_TYPE_LEFT; + protected $_joinType = Query::JOIN_TYPE_LEFT; /** * The property name that should be filled with data from the target table diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 835bd440..79eb1200 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -20,7 +20,6 @@ use Cake\Database\Expression\IdentifierExpression; use Cake\Database\ExpressionInterface; use Cake\Datasource\EntityInterface; -use Cake\Datasource\QueryInterface; use Cake\ORM\Association; use Cake\ORM\Association\Loader\SelectWithPivotLoader; use Cake\ORM\Query; @@ -57,7 +56,7 @@ class BelongsToMany extends Association * * @var string */ - protected $_joinType = QueryInterface::JOIN_TYPE_INNER; + protected $_joinType = Query::JOIN_TYPE_INNER; /** * The strategy name to be used to fetch associated records. @@ -1063,7 +1062,7 @@ protected function _appendJunctionJoin(Query $query, ?array $conditions = null): $name => [ 'table' => $this->junction()->getTable(), 'conditions' => $conditions, - 'type' => QueryInterface::JOIN_TYPE_INNER, + 'type' => Query::JOIN_TYPE_INNER, ], ]; diff --git a/Association/HasMany.php b/Association/HasMany.php index 0ffb9629..07c2368b 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -21,9 +21,9 @@ use Cake\Database\Expression\FieldInterface; use Cake\Database\Expression\QueryExpression; use Cake\Datasource\EntityInterface; -use Cake\Datasource\QueryInterface; use Cake\ORM\Association; use Cake\ORM\Association\Loader\SelectLoader; +use Cake\ORM\Query; use Cake\ORM\Table; use Closure; use InvalidArgumentException; @@ -48,7 +48,7 @@ class HasMany extends Association * * @var string */ - protected $_joinType = QueryInterface::JOIN_TYPE_INNER; + protected $_joinType = Query::JOIN_TYPE_INNER; /** * The strategy name to be used to fetch associated records. diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index 4de08b07..aa8f5eec 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -21,7 +21,6 @@ use Cake\Collection\CollectionInterface; use Cake\Core\InstanceConfigTrait; use Cake\Datasource\EntityInterface; -use Cake\Datasource\QueryInterface; use Cake\Event\EventInterface; use Cake\ORM\Entity; use Cake\ORM\Locator\LocatorAwareTrait; @@ -130,7 +129,7 @@ protected function setupAssociations() $this->table->hasOne($name, [ 'targetTable' => $fieldTable, 'foreignKey' => 'foreign_key', - 'joinType' => $filter ? QueryInterface::JOIN_TYPE_INNER : QueryInterface::JOIN_TYPE_LEFT, + 'joinType' => $filter ? Query::JOIN_TYPE_INNER : Query::JOIN_TYPE_LEFT, 'conditions' => $conditions, 'propertyName' => $field . '_translation', ]); @@ -204,8 +203,8 @@ public function beforeFind(EventInterface $event, Query $query, ArrayObject $opt if ($changeFilter) { $filter = $options['filterByCurrentLocale'] - ? QueryInterface::JOIN_TYPE_INNER - : QueryInterface::JOIN_TYPE_LEFT; + ? Query::JOIN_TYPE_INNER + : Query::JOIN_TYPE_LEFT; $contain[$name]['joinType'] = $filter; } } diff --git a/EagerLoader.php b/EagerLoader.php index db903304..3bb499a3 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -19,7 +19,6 @@ use Cake\Database\Statement\BufferedStatement; use Cake\Database\Statement\CallbackStatement; use Cake\Database\StatementInterface; -use Cake\Datasource\QueryInterface; use Closure; use InvalidArgumentException; @@ -246,7 +245,7 @@ public function setMatching(string $assoc, ?callable $builder = null, array $opt } if (!isset($options['joinType'])) { - $options['joinType'] = QueryInterface::JOIN_TYPE_INNER; + $options['joinType'] = Query::JOIN_TYPE_INNER; } $assocs = explode('.', $assoc); diff --git a/Query.php b/Query.php index 9b1deea4..1268d84a 100644 --- a/Query.php +++ b/Query.php @@ -627,7 +627,7 @@ public function leftJoinWith(string $assoc, ?callable $builder = null) { $result = $this->getEagerLoader() ->setMatching($assoc, $builder, [ - 'joinType' => QueryInterface::JOIN_TYPE_LEFT, + 'joinType' => Query::JOIN_TYPE_LEFT, 'fields' => false, ]) ->getMatching(); @@ -676,7 +676,7 @@ public function innerJoinWith(string $assoc, ?callable $builder = null) { $result = $this->getEagerLoader() ->setMatching($assoc, $builder, [ - 'joinType' => QueryInterface::JOIN_TYPE_INNER, + 'joinType' => Query::JOIN_TYPE_INNER, 'fields' => false, ]) ->getMatching(); @@ -740,7 +740,7 @@ public function notMatching(string $assoc, ?callable $builder = null) { $result = $this->getEagerLoader() ->setMatching($assoc, $builder, [ - 'joinType' => QueryInterface::JOIN_TYPE_LEFT, + 'joinType' => Query::JOIN_TYPE_LEFT, 'fields' => false, 'negateMatch' => true, ]) From 1e99654d9dee7c5ab78d318bc4be9a0914f078b3 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 19 Jun 2019 00:06:33 +0530 Subject: [PATCH 1394/2059] Mark unused method and property as deprecated. --- ResultSet.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ResultSet.php b/ResultSet.php index 615e3e72..5d155431 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -155,6 +155,7 @@ class ResultSet implements ResultSetInterface * Converters are indexed by alias and column name. * * @var array + * @deprecated 3.2.0 Not used anymore. Type casting is done at the statement level */ protected $_types = []; @@ -443,6 +444,7 @@ protected function _calculateTypeMap() * @param \Cake\ORM\Table $table The table from which to get the schema * @param array $fields The fields whitelist to use for fields in the schema. * @return array + * @deprecated 3.2.0 Not used anymore. Type casting is done at the statement level */ protected function _getTypes($table, $fields) { From c7a69f139ec9cb09d4c5ef8199261bc1c35f1858 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Thu, 11 Jul 2019 14:44:43 +0200 Subject: [PATCH 1395/2059] Enable autoFields in order to resolve lazy loading translated entities. Refs #13181 --- LazyEagerLoader.php | 1 + 1 file changed, 1 insertion(+) diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 337b8d02..2880dbff 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -94,6 +94,7 @@ protected function _getQuery($objects, $contain, $source) return new TupleComparison($primaryKey, $keys->toList(), $types, 'IN'); }) + ->enableAutoFields() ->contain($contain); foreach ($query->getEagerLoader()->attachableAssociations($source) as $loadable) { From 1c9aa22aaa69ec3142affe1315120f0d94851d3a Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 12 Jul 2019 11:13:27 +0530 Subject: [PATCH 1396/2059] Update typehints. --- Association.php | 4 ++-- Association/BelongsTo.php | 2 +- AssociationCollection.php | 2 +- Behavior/TimestampBehavior.php | 2 +- Behavior/Translate/ShadowTableStrategy.php | 4 ++-- EagerLoadable.php | 8 ++++---- LazyEagerLoader.php | 2 +- ResultSet.php | 2 +- RulesChecker.php | 19 ++++++++++++------- Table.php | 2 +- 10 files changed, 26 insertions(+), 21 deletions(-) diff --git a/Association.php b/Association.php index df5ff41c..0f37adc6 100644 --- a/Association.php +++ b/Association.php @@ -327,9 +327,9 @@ public function setClassName(string $className) /** * Gets the class name of the target table object. * - * @return string|null + * @return string */ - public function getClassName(): ?string + public function getClassName(): string { return $this->_className; } diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index eab771aa..a132a944 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -145,7 +145,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) * clause for getting the results on the target table. * * @param array $options list of options passed to attachTo method - * @return array + * @return \Cake\Database\Expression\IdentifierExpression[] * @throws \RuntimeException if the number of columns in the foreignKey do not * match the number of columns in the target table primaryKey */ diff --git a/AssociationCollection.php b/AssociationCollection.php index 2bb69a04..a932d21e 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -160,7 +160,7 @@ public function keys(): array * * @param string|array $class The type of associations you want. * For example 'BelongsTo' or array like ['BelongsTo', 'HasOne'] - * @return array An array of Association objects. + * @return \Cake\ORM\Association[] An array of Association objects. * @since 3.5.3 */ public function getByType($class): array diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 216ab4df..8636fed1 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -90,7 +90,7 @@ public function initialize(array $config): void * @param \Cake\Event\EventInterface $event Event instance. * @param \Cake\Datasource\EntityInterface $entity Entity instance. * @throws \UnexpectedValueException if a field's when value is misdefined - * @return bool Returns true irrespective of the behavior logic, the save will not be prevented. + * @return true Returns true irrespective of the behavior logic, the save will not be prevented. * @throws \UnexpectedValueException When the value for an event is not 'always', 'new' or 'existing' */ public function handleEvent(EventInterface $event, EntityInterface $entity): bool diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 3df93be6..1ee4006b 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -213,7 +213,7 @@ protected function addFieldsToQuery($query, array $config) * @param array $config The config to use for adding fields. * @return bool Whether a join to the translation table is required. */ - protected function iterateClause($query, $name = '', $config = []) + protected function iterateClause($query, $name = '', $config = []): bool { $clause = $query->clause($name); if (!$clause || !$clause->count()) { @@ -258,7 +258,7 @@ function ($c, &$field) use ($fields, $alias, $mainTableAlias, $mainTableFields, * @param array $config The config to use for adding fields. * @return bool Whether a join to the translation table is required. */ - protected function traverseClause($query, $name = '', $config = []) + protected function traverseClause($query, $name = '', $config = []): bool { $clause = $query->clause($name); if (!$clause || !$clause->count()) { diff --git a/EagerLoadable.php b/EagerLoadable.php index 5e05753f..503d6992 100644 --- a/EagerLoadable.php +++ b/EagerLoadable.php @@ -59,7 +59,7 @@ class EagerLoadable * A dotted separated string representing the path of associations * that should be followed to fetch this level. * - * @var string + * @var string|null */ protected $_aliasPath; @@ -75,7 +75,7 @@ class EagerLoadable * * The property path of `country` will be `author.company` * - * @var string + * @var string|null */ protected $_propertyPath; @@ -90,7 +90,7 @@ class EagerLoadable * Whether or not this level was meant for a "matching" fetch * operation * - * @var bool + * @var bool|null */ protected $_forMatching; @@ -106,7 +106,7 @@ class EagerLoadable * * The target property of `country` will be just `country` * - * @var string + * @var string|null */ protected $_targetProperty; diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index cf1d82f2..052b5316 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -113,7 +113,7 @@ protected function _getQuery(CollectionInterface $objects, array $contain, Table * * @param \Cake\ORM\Table $source The table having the top level associations * @param string[] $associations The name of the top level associations - * @return array + * @return string[] */ protected function _getPropertyMap(Table $source, array $associations): array { diff --git a/ResultSet.php b/ResultSet.php index 4d17bb9c..b49277aa 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -51,7 +51,7 @@ class ResultSet implements ResultSetInterface /** * Last record fetched from the statement * - * @var array + * @var array|object */ protected $_current; diff --git a/RulesChecker.php b/RulesChecker.php index 9503a88f..c8628d36 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -16,6 +16,7 @@ */ namespace Cake\ORM; +use Cake\Datasource\RuleInvoker; use Cake\Datasource\RulesChecker as BaseRulesChecker; use Cake\ORM\Rule\ExistsIn; use Cake\ORM\Rule\IsUnique; @@ -43,9 +44,9 @@ class RulesChecker extends BaseRulesChecker * @param array $fields The list of fields to check for uniqueness. * @param string|array|null $message The error message to show in case the rule does not pass. Can * also be an array of options. When an array, the 'message' key can be used to provide a message. - * @return callable + * @return \Cake\Datasource\RuleInvoker */ - public function isUnique(array $fields, $message = null): callable + public function isUnique(array $fields, $message = null): RuleInvoker { $options = []; if (is_array($message)) { @@ -89,9 +90,9 @@ public function isUnique(array $fields, $message = null): callable * @param object|string $table The table name where the fields existence will be checked. * @param string|array|null $message The error message to show in case the rule does not pass. Can * also be an array of options. When an array, the 'message' key can be used to provide a message. - * @return callable + * @return \Cake\Datasource\RuleInvoker */ - public function existsIn($field, $table, $message = null): callable + public function existsIn($field, $table, $message = null): RuleInvoker { $options = []; if (is_array($message)) { @@ -120,10 +121,14 @@ public function existsIn($field, $table, $message = null): callable * @param int $count The expected count. * @param string $operator The operator for the count comparison. * @param string|null $message The error message to show in case the rule does not pass. - * @return callable + * @return \Cake\Datasource\RuleInvoker */ - public function validCount(string $field, int $count = 0, string $operator = '>', ?string $message = null): callable - { + public function validCount( + string $field, + int $count = 0, + string $operator = '>', + ?string $message = null + ): RuleInvoker { if (!$message) { if ($this->_useI18n) { $message = __d('cake', 'The count does not match {0}{1}', [$operator, $count]); diff --git a/Table.php b/Table.php index 84a63c06..3e50e0e6 100644 --- a/Table.php +++ b/Table.php @@ -2266,7 +2266,7 @@ protected function _deleteMany(iterable $entities, $options = []): ?EntityInterf * * @param \Cake\Datasource\EntityInterface $entity The entity to remove. * @param array|\ArrayAccess $options The options for the delete. - * @return bool success + * @return true * @throws \Cake\ORM\Exception\PersistenceFailedException * @see \Cake\ORM\Table::delete() */ From bbfaac15e65443a2d03b0c8109fffd2c5db3c6b0 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 16 Jul 2019 13:18:31 +0530 Subject: [PATCH 1397/2059] Code cleanup. --- BehaviorRegistry.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index f531ea95..56c87348 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -88,12 +88,8 @@ public function setTable(Table $table): void */ public static function className(string $class): ?string { - $result = App::className($class, 'Model/Behavior', 'Behavior'); - if (!$result) { - $result = App::className($class, 'ORM/Behavior', 'Behavior'); - } - - return $result; + return App::className($class, 'Model/Behavior', 'Behavior') + ?: App::className($class, 'ORM/Behavior', 'Behavior'); } /** From 2ddc7749eaad8000723ce0b55b77c705759078f2 Mon Sep 17 00:00:00 2001 From: ndm2 Date: Tue, 16 Jul 2019 20:17:50 +0200 Subject: [PATCH 1398/2059] Fix wrong association and _locale properties set when using matching. When using matching, the translate behavior sets wrong association and `_locale` properties, because the eager loadables hold wrong property paths that point to the properties that are used for regular containments, instead of the `_matchingData` property where matching records are set. refs #12954 --- EagerLoader.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/EagerLoader.php b/EagerLoader.php index 510f2ebe..83f0d4bf 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -557,7 +557,14 @@ protected function _normalizeContain(Table $parent, $alias, $options, $paths) $paths += ['aliasPath' => '', 'propertyPath' => '', 'root' => $alias]; $paths['aliasPath'] .= '.' . $alias; - $paths['propertyPath'] .= '.' . $instance->getProperty(); + + if (isset($options['matching']) && + $options['matching'] === true + ) { + $paths['propertyPath'] = '_matchingData.' . $alias; + } else { + $paths['propertyPath'] .= '.' . $instance->getProperty(); + } $table = $instance->getTarget(); From b7485f920abf5692b1e4197561c96974aa19de64 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 24 Jul 2019 12:40:17 +0530 Subject: [PATCH 1399/2059] Ensure Association::$_className is always set. --- Association.php | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/Association.php b/Association.php index 0f37adc6..d390824b 100644 --- a/Association.php +++ b/Association.php @@ -226,7 +226,7 @@ public function __construct(string $alias, array $options = []) } } - if (empty($this->_className) && strpos($alias, '.')) { + if (empty($this->_className)) { $this->_className = $alias; } @@ -378,7 +378,7 @@ public function setTarget(Table $table) public function getTarget(): Table { if ($this->_targetTable === null) { - if (strpos((string)$this->_className, '.')) { + if (strpos($this->_className, '.')) { [$plugin] = pluginSplit($this->_className, true); $registryAlias = $plugin . $this->_name; } else { @@ -395,7 +395,7 @@ public function getTarget(): Table $this->_targetTable = $tableLocator->get($registryAlias, $config); if ($exists) { - $className = $this->_getClassName($registryAlias, ['className' => $this->_className]); + $className = App::className($this->_className, 'Model/Table', 'Table') ?: Table::class; if (!$this->_targetTable instanceof $className) { $errorMessage = '%s association "%s" of type "%s" to "%s" doesn\'t match the expected class "%s". '; @@ -1125,24 +1125,6 @@ protected function _extractFinder($finderData): array return [key($finderData), current($finderData)]; } - /** - * Gets the table class name. - * - * @param string $alias The alias name you want to get. - * @param array $options Table options array. - * @return string - */ - protected function _getClassName(string $alias, array $options = []): string - { - if (empty($options['className'])) { - $options['className'] = Inflector::camelize($alias); - } - - $className = App::className($options['className'], 'Model/Table', 'Table') ?: Table::class; - - return ltrim($className, '\\'); - } - /** * Proxies property retrieval to the target table. This is handy for getting this * association's associations From 4bcfe3397b6ff84e5fc0857e32c88eda17747d1d Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 27 Jul 2019 18:49:20 +0530 Subject: [PATCH 1400/2059] Update docblock. If table doesn't have field named "title" or "name" and no primary key then Table::$_displayField property would be null. --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 3e50e0e6..b148cae6 100644 --- a/Table.php +++ b/Table.php @@ -616,7 +616,7 @@ public function setDisplayField($field) /** * Returns the display field. * - * @return string|string[] + * @return string|string[]|null */ public function getDisplayField() { From 06f4c1970e762f188503b79229c075e68ec0f9cb Mon Sep 17 00:00:00 2001 From: Hideki Kinjyo Date: Wed, 31 Jul 2019 06:44:45 +0900 Subject: [PATCH 1401/2059] Add throws tag to findOrCreate ref: d840670 / #12914 --- Table.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Table.php b/Table.php index 581d21ca..9287fa90 100644 --- a/Table.php +++ b/Table.php @@ -1687,6 +1687,7 @@ protected function _transactionCommitted($atomic, $primary) * is persisted. * @param array $options The options to use when saving. * @return \Cake\Datasource\EntityInterface An entity. + * @throws \Cake\ORM\Exception\PersistenceFailedException When the entity couldn't be saved */ public function findOrCreate($search, callable $callback = null, $options = []) { @@ -1716,6 +1717,7 @@ public function findOrCreate($search, callable $callback = null, $options = []) * is persisted. * @param array $options The options to use when saving. * @return \Cake\Datasource\EntityInterface An entity. + * @throws \Cake\ORM\Exception\PersistenceFailedException When the entity couldn't be saved */ protected function _processFindOrCreate($search, callable $callback = null, $options = []) { From eb98a03c61f9aba80d2c5fa23f32130c650e189f Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 2 Aug 2019 11:13:53 +0530 Subject: [PATCH 1402/2059] Split combined getter/setter EntityInterface::isNew(). isNew() now only acts as getter and new method setNew() has been added as getter. --- Association/BelongsToMany.php | 2 +- Behavior/Translate/EavStrategy.php | 4 ++-- Entity.php | 2 +- Table.php | 10 +++++----- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 79eb1200..11f7016c 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -769,7 +769,7 @@ protected function _saveLinks(EntityInterface $sourceEntity, array $targetEntiti // as new, we let save() sort out whether or not we have a new link // or if we are updating an existing link. if ($changedKeys) { - $joint->isNew(true); + $joint->setNew(true); $joint->unset($junction->getPrimaryKey()) ->set(array_merge($sourceKeys, $targetKeys), ['guard' => false]); } diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index aa8f5eec..a7a9b496 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -462,11 +462,11 @@ protected function bundleTranslatedFields($entity) foreach ($find as $i => $translation) { if (!empty($results[$i])) { $contents[$i]->set('id', $results[$i], ['setter' => false]); - $contents[$i]->isNew(false); + $contents[$i]->setNew(false); } else { $translation['model'] = $this->_config['referenceName']; $contents[$i]->set($translation, ['setter' => false, 'guard' => false]); - $contents[$i]->isNew(true); + $contents[$i]->setNew(true); } } diff --git a/Entity.php b/Entity.php index 60179d6e..a7167875 100644 --- a/Entity.php +++ b/Entity.php @@ -62,7 +62,7 @@ public function __construct(array $properties = [], array $options = []) } if ($options['markNew'] !== null) { - $this->isNew($options['markNew']); + $this->setNew($options['markNew']); } if (!empty($properties) && $options['markClean'] && !$options['useSetters']) { diff --git a/Table.php b/Table.php index b148cae6..da62a0b9 100644 --- a/Table.php +++ b/Table.php @@ -1765,7 +1765,7 @@ public function save(EntityInterface $entity, $options = []) } if ($options['atomic'] || $options['_primary']) { $entity->clean(); - $entity->isNew(false); + $entity->setNew(false); $entity->setSource($this->getRegistryAlias()); } } @@ -1813,7 +1813,7 @@ protected function _processSave(EntityInterface $entity, ArrayObject $options) foreach ($entity->extract($primaryColumns) as $k => $v) { $conditions["$alias.$k"] = $v; } - $entity->isNew(!$this->exists($conditions)); + $entity->setNew(!$this->exists($conditions)); } $mode = $entity->isNew() ? RulesChecker::CREATE : RulesChecker::UPDATE; @@ -1854,7 +1854,7 @@ protected function _processSave(EntityInterface $entity, ArrayObject $options) if (!$success && $isNew) { $entity->unset($this->getPrimaryKey()); - $entity->isNew(true); + $entity->setNew(true); } return $success ? $entity : false; @@ -1891,7 +1891,7 @@ protected function _onSaveSuccess(EntityInterface $entity, ArrayObject $options) if (!$options['atomic'] && !$options['_primary']) { $entity->clean(); - $entity->isNew(false); + $entity->setNew(false); $entity->setSource($this->getRegistryAlias()); } @@ -2096,7 +2096,7 @@ protected function _saveMany(iterable $entities, $options = []): iterable foreach ($entities as $key => $entity) { if (isset($isNew[$key]) && $isNew[$key]) { $entity->unset($this->getPrimaryKey()); - $entity->isNew(true); + $entity->setNew(true); } } }; From d15ec6407a50bcb70df3b8969f0d00f0e7927ab6 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 6 Aug 2019 01:07:43 +0530 Subject: [PATCH 1403/2059] Add / fix property docblocks. --- Exception/MissingBehaviorException.php | 3 +++ Exception/MissingEntityException.php | 3 +++ Exception/MissingTableClassException.php | 3 +++ Exception/RolledbackTransactionException.php | 3 +++ 4 files changed, 12 insertions(+) diff --git a/Exception/MissingBehaviorException.php b/Exception/MissingBehaviorException.php index 18af1f98..eaaf511a 100644 --- a/Exception/MissingBehaviorException.php +++ b/Exception/MissingBehaviorException.php @@ -21,5 +21,8 @@ */ class MissingBehaviorException extends Exception { + /** + * @var string + */ protected $_messageTemplate = 'Behavior class %s could not be found.'; } diff --git a/Exception/MissingEntityException.php b/Exception/MissingEntityException.php index cecec8a9..50001358 100644 --- a/Exception/MissingEntityException.php +++ b/Exception/MissingEntityException.php @@ -25,5 +25,8 @@ */ class MissingEntityException extends Exception { + /** + * @var string + */ protected $_messageTemplate = 'Entity class %s could not be found.'; } diff --git a/Exception/MissingTableClassException.php b/Exception/MissingTableClassException.php index 08ded71b..699a0f29 100644 --- a/Exception/MissingTableClassException.php +++ b/Exception/MissingTableClassException.php @@ -23,5 +23,8 @@ */ class MissingTableClassException extends Exception { + /** + * @var string + */ protected $_messageTemplate = 'Table class %s could not be found.'; } diff --git a/Exception/RolledbackTransactionException.php b/Exception/RolledbackTransactionException.php index 64baee99..019abcad 100644 --- a/Exception/RolledbackTransactionException.php +++ b/Exception/RolledbackTransactionException.php @@ -21,6 +21,9 @@ */ class RolledbackTransactionException extends Exception { + /** + * @var string + */ // phpcs:ignore Generic.Files.LineLength protected $_messageTemplate = 'The afterSave event in "%s" is aborting the transaction before the save process is done.'; } From 150b1fd17897ceb0d693beece5aed9d1f6659f93 Mon Sep 17 00:00:00 2001 From: mscherer Date: Tue, 6 Aug 2019 17:43:00 +0200 Subject: [PATCH 1404/2059] Use less inflection on aliasing. --- Locator/TableLocator.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 9ebab6a8..9df3666e 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -157,7 +157,7 @@ public function getConfig(?string $alias = null): array * key in the registry. This means that if two plugins, or a plugin and app provide * the same alias, the registry will only store the first instance. * - * @param string $alias The alias name you want to get. + * @param string $alias The alias name you want to get. Must be in CamelCase format. * @param array $options The options you want to build the table with. * If a table has already been loaded the options will be ignored. * @return \Cake\ORM\Table @@ -189,7 +189,7 @@ public function get(string $alias, array $options = []): Table $options['className'] = $className; } else { if (empty($options['className'])) { - $options['className'] = Inflector::camelize($alias); + $options['className'] = $alias; } if (!isset($options['table']) && strpos($options['className'], '\\') === false) { [, $table] = pluginSplit($options['className']); @@ -226,14 +226,14 @@ public function get(string $alias, array $options = []): Table /** * Gets the table class name. * - * @param string $alias The alias name you want to get. + * @param string $alias The alias name you want to get. Must be in CamelCase format. * @param array $options Table options array. * @return string|null */ protected function _getClassName(string $alias, array $options = []): ?string { if (empty($options['className'])) { - $options['className'] = Inflector::camelize($alias); + $options['className'] = $alias; } if (strpos($options['className'], '\\') !== false && class_exists($options['className'])) { From d229069ebf85997a6bd6e58e41e10d4bcb591ccd Mon Sep 17 00:00:00 2001 From: mscherer Date: Wed, 7 Aug 2019 11:41:53 +0200 Subject: [PATCH 1405/2059] Use less inflection on aliasing. Add tests. --- Locator/TableLocator.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 9df3666e..46adcf37 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -157,7 +157,7 @@ public function getConfig(?string $alias = null): array * key in the registry. This means that if two plugins, or a plugin and app provide * the same alias, the registry will only store the first instance. * - * @param string $alias The alias name you want to get. Must be in CamelCase format. + * @param string $alias The alias name you want to get. Should be in CamelCase format. * @param array $options The options you want to build the table with. * If a table has already been loaded the options will be ignored. * @return \Cake\ORM\Table @@ -226,7 +226,7 @@ public function get(string $alias, array $options = []): Table /** * Gets the table class name. * - * @param string $alias The alias name you want to get. Must be in CamelCase format. + * @param string $alias The alias name you want to get. Should be in CamelCase format. * @param array $options Table options array. * @return string|null */ From cd2e20777df69eda5dcccf862b5fe230893e8005 Mon Sep 17 00:00:00 2001 From: mscherer Date: Wed, 7 Aug 2019 11:57:14 +0200 Subject: [PATCH 1406/2059] Add more concrete array doc. --- Rule/IsUnique.php | 4 ++-- RulesChecker.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index 73c78c6a..6265ccbb 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -26,7 +26,7 @@ class IsUnique /** * The list of fields to check * - * @var array + * @var string[] */ protected $_fields; @@ -46,7 +46,7 @@ class IsUnique * multi-column unique rules. By default this is `true` to emulate how SQL UNIQUE * keys work. * - * @param array $fields The list of fields to check uniqueness for + * @param string[] $fields The list of fields to check uniqueness for * @param array $options The additional options for this rule. */ public function __construct(array $fields, array $options = []) diff --git a/RulesChecker.php b/RulesChecker.php index c8628d36..31b4b29f 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -41,7 +41,7 @@ class RulesChecker extends BaseRulesChecker * $rules->add($rules->isUnique(['email'], 'The email should be unique')); * ``` * - * @param array $fields The list of fields to check for uniqueness. + * @param string[] $fields The list of fields to check for uniqueness. * @param string|array|null $message The error message to show in case the rule does not pass. Can * also be an array of options. When an array, the 'message' key can be used to provide a message. * @return \Cake\Datasource\RuleInvoker From b574772a43df2b5a82232fa6a38b6c953bebf834 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 7 Aug 2019 19:36:51 +0530 Subject: [PATCH 1407/2059] Throw exception for invalid comparison with NULL in WHERE clause conditions. --- Association/BelongsToMany.php | 13 +++++++++++-- Behavior/Translate/EavStrategy.php | 8 +++++++- Rule/ExistsIn.php | 6 ++++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 11f7016c..e1c5ff40 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1329,9 +1329,18 @@ protected function _collectJointEntities(EntityInterface $sourceEntity, array $t $unions = []; foreach ($missing as $key) { + $conditions = array_merge( + array_combine($foreignKey, $sourceKey), + array_combine($assocForeignKey, $key) + ); + foreach ($conditions as $k => $v) { + if ($v === null) { + $conditions[$k . ' IS'] = $v; + unset($conditions[$k]); + } + } $unions[] = $hasMany->find() - ->where(array_combine($foreignKey, $sourceKey)) - ->where(array_combine($assocForeignKey, $key)); + ->where($conditions); } $query = array_shift($unions); diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index a7a9b496..21f37625 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -446,7 +446,13 @@ protected function bundleTranslatedFields($entity) if (!$translation->isDirty($field)) { continue; } - $find[] = ['locale' => $lang, 'field' => $field, 'foreign_key' => $key]; + $c = ['locale' => $lang, 'field' => $field]; + if ($key === null) { + $c['foreign_key IS'] = $key; + } else { + $c['foreign_key'] = $key; + } + $find[] = $c; $contents[] = new Entity(['content' => $translation->get($field)], [ 'useSetters' => false, ]); diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 611398d2..34ae9e1c 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -140,6 +140,12 @@ public function __invoke(EntityInterface $entity, array $options): bool $primary, $entity->extract($fields) ); + foreach ($conditions as $k => $v) { + if ($v === null) { + $conditions[$k . ' IS'] = $v; + unset($conditions[$k]); + } + } return $target->exists($conditions); } From c70ca5736f2e58b3df13026482c603810fdd01b0 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 7 Aug 2019 22:02:37 +0530 Subject: [PATCH 1408/2059] Simplify generation of conditions array with potentially null values. --- Association/BelongsToMany.php | 19 ++++++++----------- Behavior/Translate/EavStrategy.php | 8 +------- Rule/ExistsIn.php | 10 +++------- 3 files changed, 12 insertions(+), 25 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index e1c5ff40..745e6f61 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1324,23 +1324,20 @@ protected function _collectJointEntities(EntityInterface $sourceEntity, array $t $belongsTo = $junction->getAssociation($target->getAlias()); $hasMany = $source->getAssociation($junction->getAlias()); $foreignKey = (array)$this->getForeignKey(); + $foreignKey = array_map(function ($key) { + return $key . ' IS'; + }, $foreignKey); $assocForeignKey = (array)$belongsTo->getForeignKey(); + $assocForeignKey = array_map(function ($key) { + return $key . ' IS'; + }, $assocForeignKey); $sourceKey = $sourceEntity->extract((array)$source->getPrimaryKey()); $unions = []; foreach ($missing as $key) { - $conditions = array_merge( - array_combine($foreignKey, $sourceKey), - array_combine($assocForeignKey, $key) - ); - foreach ($conditions as $k => $v) { - if ($v === null) { - $conditions[$k . ' IS'] = $v; - unset($conditions[$k]); - } - } $unions[] = $hasMany->find() - ->where($conditions); + ->where(array_combine($foreignKey, $sourceKey)) + ->where(array_combine($assocForeignKey, $key)); } $query = array_shift($unions); diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index 21f37625..aceb4ac5 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -446,13 +446,7 @@ protected function bundleTranslatedFields($entity) if (!$translation->isDirty($field)) { continue; } - $c = ['locale' => $lang, 'field' => $field]; - if ($key === null) { - $c['foreign_key IS'] = $key; - } else { - $c['foreign_key'] = $key; - } - $find[] = $c; + $find[] = ['locale' => $lang, 'field' => $field, 'foreign_key IS' => $key]; $contents[] = new Entity(['content' => $translation->get($field)], [ 'useSetters' => false, ]); diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 34ae9e1c..6494b2a5 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -133,19 +133,15 @@ public function __invoke(EntityInterface $entity, array $options): bool } $primary = array_map( - [$target, 'aliasField'], + function ($key) use ($target) { + return $target->aliasField($key) . ' IS'; + }, $bindingKey ); $conditions = array_combine( $primary, $entity->extract($fields) ); - foreach ($conditions as $k => $v) { - if ($v === null) { - $conditions[$k . ' IS'] = $v; - unset($conditions[$k]); - } - } return $target->exists($conditions); } From f658fd3a78c324d180f31b138c4974e86924476c Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 8 Aug 2019 01:05:28 +0530 Subject: [PATCH 1409/2059] Remove unnecessary casting. --- EagerLoader.php | 2 +- Query.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index dcaf7639..12ee5b27 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -194,7 +194,7 @@ public function clearContain(): void */ public function enableAutoFields(bool $enable = true) { - $this->_autoFields = (bool)$enable; + $this->_autoFields = $enable; return $this; } diff --git a/Query.php b/Query.php index 1268d84a..c5483ab5 100644 --- a/Query.php +++ b/Query.php @@ -976,7 +976,7 @@ public function counter(?callable $counter) public function enableHydration(bool $enable = true) { $this->_dirty(); - $this->_hydrate = (bool)$enable; + $this->_hydrate = $enable; return $this; } @@ -1319,7 +1319,7 @@ public function jsonSerialize(): ResultSetInterface */ public function enableAutoFields(bool $value = true) { - $this->_autoFields = (bool)$value; + $this->_autoFields = $value; return $this; } From 656b8d8cb7de4734e7c5ff11726fec57fc75eb6a Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 8 Aug 2019 03:00:20 +0530 Subject: [PATCH 1410/2059] Remove useless option from IsUnique rule. --- Rule/IsUnique.php | 27 +++++---------------------- RulesChecker.php | 11 ++--------- 2 files changed, 7 insertions(+), 31 deletions(-) diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index 6265ccbb..76ea4a01 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -40,19 +40,11 @@ class IsUnique /** * Constructor. * - * ### Options - * - * - `allowMultipleNulls` Set to false to disallow multiple null values in - * multi-column unique rules. By default this is `true` to emulate how SQL UNIQUE - * keys work. - * * @param string[] $fields The list of fields to check uniqueness for - * @param array $options The additional options for this rule. */ - public function __construct(array $fields, array $options = []) + public function __construct(array $fields) { $this->_fields = $fields; - $this->_options = $options + ['allowMultipleNulls' => true]; } /** @@ -68,13 +60,12 @@ public function __invoke(EntityInterface $entity, array $options): bool if (!$entity->extract($this->_fields, true)) { return true; } - $allowMultipleNulls = $this->_options['allowMultipleNulls']; $alias = $options['repository']->getAlias(); - $conditions = $this->_alias($alias, $entity->extract($this->_fields), $allowMultipleNulls); + $conditions = $this->_alias($alias, $entity->extract($this->_fields)); if ($entity->isNew() === false) { $keys = (array)$options['repository']->getPrimaryKey(); - $keys = $this->_alias($alias, $entity->extract($keys), $allowMultipleNulls); + $keys = $this->_alias($alias, $entity->extract($keys)); if (array_filter($keys, 'strlen')) { $conditions['NOT'] = $keys; } @@ -86,23 +77,15 @@ public function __invoke(EntityInterface $entity, array $options): bool /** * Add a model alias to all the keys in a set of conditions. * - * Null values will be omitted from the generated conditions, - * as SQL UNIQUE indexes treat `NULL != NULL` - * * @param string $alias The alias to add. * @param array $conditions The conditions to alias. - * @param bool $multipleNulls Whether or not to allow multiple nulls. * @return array */ - protected function _alias(string $alias, array $conditions, bool $multipleNulls): array + protected function _alias(string $alias, array $conditions): array { $aliased = []; foreach ($conditions as $key => $value) { - if ($multipleNulls) { - $aliased["$alias.$key"] = $value; - } else { - $aliased["$alias.$key IS"] = $value; - } + $aliased["$alias.$key IS"] = $value; } return $aliased; diff --git a/RulesChecker.php b/RulesChecker.php index 31b4b29f..c8fbe1b3 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -42,18 +42,11 @@ class RulesChecker extends BaseRulesChecker * ``` * * @param string[] $fields The list of fields to check for uniqueness. - * @param string|array|null $message The error message to show in case the rule does not pass. Can - * also be an array of options. When an array, the 'message' key can be used to provide a message. + * @param string|null $message The error message to show in case the rule does not pass. * @return \Cake\Datasource\RuleInvoker */ public function isUnique(array $fields, $message = null): RuleInvoker { - $options = []; - if (is_array($message)) { - $options = $message + ['message' => null]; - $message = $options['message']; - unset($options['message']); - } if (!$message) { if ($this->_useI18n) { $message = __d('cake', 'This value is already in use'); @@ -64,7 +57,7 @@ public function isUnique(array $fields, $message = null): RuleInvoker $errorField = current($fields); - return $this->_addError(new IsUnique($fields, $options), '_isUnique', compact('errorField', 'message')); + return $this->_addError(new IsUnique($fields), '_isUnique', compact('errorField', 'message')); } /** From fa00a6f6bc1bb4f2924d3fd7e3ea5c3decefdab3 Mon Sep 17 00:00:00 2001 From: mscherer Date: Thu, 8 Aug 2019 11:05:06 +0200 Subject: [PATCH 1411/2059] Fix array typehints to be more precise, string array check strictness --- Association/HasMany.php | 2 +- Association/Loader/SelectLoader.php | 4 ++-- Behavior.php | 2 +- Marshaller.php | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index 12b376ce..cf159d6a 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -110,7 +110,7 @@ public function isOwningSide(Table $side) */ public function setSaveStrategy($strategy) { - if (!in_array($strategy, [self::SAVE_APPEND, self::SAVE_REPLACE])) { + if (!in_array($strategy, [self::SAVE_APPEND, self::SAVE_REPLACE], true)) { $msg = sprintf('Invalid save strategy "%s"', $strategy); throw new InvalidArgumentException($msg); } diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 5bd2dd2c..fa6d38af 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -459,8 +459,8 @@ protected function _subqueryFields($query) protected function _buildResultMap($fetchQuery, $options) { $resultMap = []; - $singleResult = in_array($this->associationType, [Association::MANY_TO_ONE, Association::ONE_TO_ONE]); - $keys = in_array($this->associationType, [Association::ONE_TO_ONE, Association::ONE_TO_MANY]) ? + $singleResult = in_array($this->associationType, [Association::MANY_TO_ONE, Association::ONE_TO_ONE], true); + $keys = in_array($this->associationType, [Association::ONE_TO_ONE, Association::ONE_TO_MANY], true) ? $this->foreignKey : $this->bindingKey; $key = (array)$keys; diff --git a/Behavior.php b/Behavior.php index 00e927dd..fce15777 100644 --- a/Behavior.php +++ b/Behavior.php @@ -403,7 +403,7 @@ protected function _reflectionCache() foreach ($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { $methodName = $method->getName(); - if (in_array($methodName, $baseMethods) || + if (in_array($methodName, $baseMethods, true) || isset($eventMethods[$methodName]) ) { continue; diff --git a/Marshaller.php b/Marshaller.php index 11253427..a54160d8 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -309,7 +309,7 @@ protected function _marshalAssociation($assoc, $value, $options) $targetTable = $assoc->getTarget(); $marshaller = $targetTable->marshaller(); $types = [Association::ONE_TO_ONE, Association::MANY_TO_ONE]; - if (in_array($assoc->type(), $types)) { + if (in_array($assoc->type(), $types, true)) { return $marshaller->one($value, (array)$options); } if ($assoc->type() === Association::ONE_TO_MANY || $assoc->type() === Association::MANY_TO_MANY) { @@ -747,7 +747,7 @@ protected function _mergeAssociation($original, $assoc, $value, $options) $targetTable = $assoc->getTarget(); $marshaller = $targetTable->marshaller(); $types = [Association::ONE_TO_ONE, Association::MANY_TO_ONE]; - if (in_array($assoc->type(), $types)) { + if (in_array($assoc->type(), $types, true)) { return $marshaller->merge($original, $value, (array)$options); } if ($assoc->type() === Association::MANY_TO_MANY) { @@ -792,7 +792,7 @@ protected function _mergeBelongsToMany($original, $assoc, $value, $options) return []; } - if (!empty($associated) && !in_array('_joinData', $associated) && !isset($associated['_joinData'])) { + if (!empty($associated) && !in_array('_joinData', $associated, true) && !isset($associated['_joinData'])) { return $this->mergeMany($original, $value, $options); } From f29ea71944aac6af300f1cea9eb65782449ae1d8 Mon Sep 17 00:00:00 2001 From: mscherer Date: Thu, 8 Aug 2019 22:22:54 +0200 Subject: [PATCH 1412/2059] Backport saveManyOrFail(), deleteMany(), deleteManyOrFail() --- Table.php | 132 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 127 insertions(+), 5 deletions(-) diff --git a/Table.php b/Table.php index 9287fa90..a0f12205 100644 --- a/Table.php +++ b/Table.php @@ -39,6 +39,7 @@ use Cake\Utility\Inflector; use Cake\Validation\ValidatorAwareInterface; use Cake\Validation\ValidatorAwareTrait; +use Exception; use InvalidArgumentException; use RuntimeException; @@ -2231,8 +2232,43 @@ protected function _update($entity, $data) */ public function saveMany($entities, $options = []) { + try { + return $this->_saveMany($entities, $options); + } catch (PersistenceFailedException $exception) { + return false; + } + } + + /** + * Persists multiple entities of a table. + * + * The records will be saved in a transaction which will be rolled back if + * any one of the records fails to save due to failed validation or database + * error. + * + * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to save. + * @param array|\ArrayAccess $options Options used when calling Table::save() for each entity. + * @return \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface Entities list. + * @throws \Exception + * @throws \Cake\ORM\Exception\PersistenceFailedException If an entity couldn't be saved. + */ + public function saveManyOrFail($entities, $options = []) + { + return $this->_saveMany($entities, $options); + } + + /** + * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to save. + * @param array|\ArrayAccess $options Options used when calling Table::save() for each entity. + * @return \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface Entities list. + * @throws \Cake\ORM\Exception\PersistenceFailedException If an entity couldn't be saved. + */ + protected function _saveMany($entities, $options = []) + { + /** @var bool[] $isNew */ $isNew = []; $cleanup = function ($entities) use (&$isNew) { + /** @var \Cake\Datasource\EntityInterface[] $entities */ foreach ($entities as $key => $entity) { if (isset($isNew[$key]) && $isNew[$key]) { $entity->unsetProperty($this->getPrimaryKey()); @@ -2242,25 +2278,25 @@ public function saveMany($entities, $options = []) }; try { - $return = $this->getConnection() + $failed = $this->getConnection() ->transactional(function () use ($entities, $options, &$isNew) { foreach ($entities as $key => $entity) { $isNew[$key] = $entity->isNew(); if ($this->save($entity, $options) === false) { - return false; + return $entity; } } }); - } catch (\Exception $e) { + } catch (Exception $e) { $cleanup($entities); throw $e; } - if ($return === false) { + if ($failed !== null) { $cleanup($entities); - return false; + throw new PersistenceFailedException($failed, ['saveMany']); } return $entities; @@ -2335,6 +2371,92 @@ public function deleteOrFail(EntityInterface $entity, $options = []) return $deleted; } + + /** + * Deletes multiple entities of a table. + * + * The records will be deleted in a transaction which will be rolled back if + * any one of the records fails to delete due to failed validation or database + * error. + * + * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to delete. + * @param array|\ArrayAccess $options Options used when calling Table::save() for each entity. + * @return bool|\Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface + * False on failure, entities list on success. + * @throws \Exception + * @see \Cake\ORM\Table::delete() for options and events related to this method. + */ + public function deleteMany($entities, $options = []) + { + $failed = $this->_deleteMany($entities, $options); + + if ($failed !== null) { + return false; + } + + return $entities; + } + + /** + * Deletes multiple entities of a table. + * + * The records will be deleted in a transaction which will be rolled back if + * any one of the records fails to delete due to failed validation or database + * error. + * + * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to delete. + * @param array|\ArrayAccess $options Options used when calling Table::save() for each entity. + * @return \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface Entities list. + * @throws \Exception + * @throws \Cake\ORM\Exception\PersistenceFailedException + * @see \Cake\ORM\Table::delete() for options and events related to this method. + */ + public function deleteManyOrFail($entities, $options = []) + { + $failed = $this->_deleteMany($entities, $options); + + if ($failed !== null) { + throw new PersistenceFailedException($failed, ['deleteMany']); + } + + return $entities; + } + + /** + * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to delete. + * @param array|\ArrayAccess $options Options used. + * @return \Cake\Datasource\EntityInterface|null + */ + protected function _deleteMany($entities, $options = []) + { + $options = new ArrayObject((array)$options + [ + 'atomic' => true, + 'checkRules' => true, + '_primary' => true, + ]); + + $failed = $this->_executeTransaction(function () use ($entities, $options) { + foreach ($entities as $entity) { + if (!$this->_processDelete($entity, $options)) { + return $entity; + } + } + + return null; + }, $options['atomic']); + + if ($failed === null && $this->_transactionCommitted($options['atomic'], $options['_primary'])) { + foreach ($entities as $entity) { + $this->dispatchEvent('Model.afterDeleteCommit', [ + 'entity' => $entity, + 'options' => $options, + ]); + } + } + + return $failed; + } + /** * Perform the delete operation. * From cd67e595e771e062167632d411cd4f28ed58fccd Mon Sep 17 00:00:00 2001 From: stickler-ci Date: Thu, 8 Aug 2019 20:24:02 +0000 Subject: [PATCH 1413/2059] Fixing style errors. --- Table.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Table.php b/Table.php index a0f12205..6acfb813 100644 --- a/Table.php +++ b/Table.php @@ -2371,7 +2371,6 @@ public function deleteOrFail(EntityInterface $entity, $options = []) return $deleted; } - /** * Deletes multiple entities of a table. * From 9de1bf8dadbc2173aca0aeb06c119389ce27e15f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Fri, 2 Aug 2019 11:13:53 +0530 Subject: [PATCH 1414/2059] Split combined getter/setter EntityInterface::isNew(). isNew() now only acts as getter and new method setNew() has been added as setter. --- Association/BelongsToMany.php | 2 +- Entity.php | 2 +- Table.php | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 6f7f207d..afd42f17 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -844,7 +844,7 @@ protected function _saveLinks(EntityInterface $sourceEntity, $targetEntities, $o // as new, we let save() sort out whether or not we have a new link // or if we are updating an existing link. if ($changedKeys) { - $joint->isNew(true); + $joint->setNew(true); $joint->unsetProperty($junction->getPrimaryKey()) ->set(array_merge($sourceKeys, $targetKeys), ['guard' => false]); } diff --git a/Entity.php b/Entity.php index a3771491..5c9f270e 100644 --- a/Entity.php +++ b/Entity.php @@ -60,7 +60,7 @@ public function __construct(array $properties = [], array $options = []) } if ($options['markNew'] !== null) { - $this->isNew($options['markNew']); + $this->setNew($options['markNew']); } if (!empty($properties) && $options['markClean'] && !$options['useSetters']) { diff --git a/Table.php b/Table.php index 9287fa90..6a28d18d 100644 --- a/Table.php +++ b/Table.php @@ -1940,7 +1940,7 @@ public function save(EntityInterface $entity, $options = []) } if ($options['atomic'] || $options['_primary']) { $entity->clean(); - $entity->isNew(false); + $entity->setNew(false); $entity->setSource($this->getRegistryAlias()); } } @@ -1988,7 +1988,7 @@ protected function _processSave($entity, $options) foreach ($entity->extract($primaryColumns) as $k => $v) { $conditions["$alias.$k"] = $v; } - $entity->isNew(!$this->exists($conditions)); + $entity->setNew(!$this->exists($conditions)); } $mode = $entity->isNew() ? RulesChecker::CREATE : RulesChecker::UPDATE; @@ -2029,7 +2029,7 @@ protected function _processSave($entity, $options) if (!$success && $isNew) { $entity->unsetProperty($this->getPrimaryKey()); - $entity->isNew(true); + $entity->setNew(true); } return $success ? $entity : false; @@ -2066,7 +2066,7 @@ protected function _onSaveSuccess($entity, $options) if (!$options['atomic'] && !$options['_primary']) { $entity->clean(); - $entity->isNew(false); + $entity->setNew(false); $entity->setSource($this->getRegistryAlias()); } @@ -2236,7 +2236,7 @@ public function saveMany($entities, $options = []) foreach ($entities as $key => $entity) { if (isset($isNew[$key]) && $isNew[$key]) { $entity->unsetProperty($this->getPrimaryKey()); - $entity->isNew(true); + $entity->setNew(true); } } }; From 0a977f3de3e42994ed233b325c99401a6e4d75e9 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 9 Aug 2019 22:17:08 +0530 Subject: [PATCH 1415/2059] Remove unused vars and assigments. --- Association/Loader/SelectLoader.php | 1 - Marshaller.php | 1 - 2 files changed, 2 deletions(-) diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index b46f1698..01729d17 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -297,7 +297,6 @@ protected function _addFilteringJoin(Query $query, $key, $subquery): Query } $subquery->select($filter, true); - $conditions = null; if (is_array($key)) { $conditions = $this->_createTupleCondition($query, $key, $filter, '='); } else { diff --git a/Marshaller.php b/Marshaller.php index 00577a3d..21404e38 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -383,7 +383,6 @@ protected function _belongsToMany(BelongsToMany $assoc, array $data, array $opti $primaryKey = array_flip((array)$target->getPrimaryKey()); $records = $conditions = []; $primaryCount = count($primaryKey); - $conditions = []; foreach ($data as $i => $row) { if (!is_array($row)) { From 9a464d6002d2f6e335cf5a4de9c035ace70ed287 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 13 Aug 2019 01:48:13 +0530 Subject: [PATCH 1416/2059] Update doblock / typehints. --- Behavior/TreeBehavior.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 14486f9a..18a0b8b1 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -625,10 +625,10 @@ public function moveUp(EntityInterface $node, $number = 1) * * @param \Cake\Datasource\EntityInterface $node The node to move * @param int|true $number How many places to move the node, or true to move to first position + * @return \Cake\Datasource\EntityInterface $node The node after being moved or false on failure * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found - * @return \Cake\Datasource\EntityInterface|false $node The node after being moved or false on failure */ - protected function _moveUp(EntityInterface $node, $number) + protected function _moveUp(EntityInterface $node, $number): EntityInterface { $config = $this->getConfig(); [$parent, $left, $right] = [$config['parent'], $config['left'], $config['right']]; @@ -715,10 +715,10 @@ public function moveDown(EntityInterface $node, $number = 1) * * @param \Cake\Datasource\EntityInterface $node The node to move * @param int|true $number How many places to move the node, or true to move to last position + * @return \Cake\Datasource\EntityInterface $node The node after being moved or false on failure * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found - * @return \Cake\Datasource\EntityInterface|false $node The node after being moved or false on failure */ - protected function _moveDown(EntityInterface $node, $number) + protected function _moveDown(EntityInterface $node, $number): EntityInterface { $config = $this->getConfig(); [$parent, $left, $right] = [$config['parent'], $config['left'], $config['right']]; From 26cd1c7d36ac5443bc319be9ebda6c021e64e260 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 25 Aug 2019 03:39:57 +0530 Subject: [PATCH 1417/2059] Use template / generic type for annotating registries object types. --- BehaviorRegistry.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 56c87348..5acd2048 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -29,6 +29,8 @@ * and constructing behavior objects. * * This class also provides method for checking and dispatching behavior methods. + * + * @extends \Cake\Core\ObjectRegistry<\Cake\ORM\Behavior> */ class BehaviorRegistry extends ObjectRegistry implements EventDispatcherInterface { From 4ab297c5f1bf908994538f55dff6982cc19e1a24 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 27 Aug 2019 02:05:36 +0530 Subject: [PATCH 1418/2059] Use psalm specific annotations. This prevents errors in IDEs/tools which don't yet support generic types and object-like array types. --- Association.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association.php b/Association.php index d390824b..c6e390c3 100644 --- a/Association.php +++ b/Association.php @@ -380,7 +380,7 @@ public function getTarget(): Table if ($this->_targetTable === null) { if (strpos($this->_className, '.')) { [$plugin] = pluginSplit($this->_className, true); - $registryAlias = $plugin . $this->_name; + $registryAlias = (string)$plugin . $this->_name; } else { $registryAlias = $this->_name; } From c48238f8cbb65cf6f38fab3f60b9315cae70c2c4 Mon Sep 17 00:00:00 2001 From: mscherer Date: Tue, 27 Aug 2019 00:02:05 +0200 Subject: [PATCH 1419/2059] More concrete array doc for string value arrays. --- Association/BelongsToMany.php | 12 ++++++------ Association/HasMany.php | 2 ++ Association/Loader/SelectLoader.php | 2 +- LazyEagerLoader.php | 2 ++ Query.php | 2 +- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 745e6f61..176f9d2e 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -734,7 +734,7 @@ protected function _saveTarget(EntityInterface $parentEntity, array $entities, $ * * @param \Cake\Datasource\EntityInterface $sourceEntity the entity from source table in this * association - * @param array $targetEntities list of entities to link to link to the source entity using the + * @param \Cake\Datasource\EntityInterface[] $targetEntities list of entities to link to link to the source entity using the * junction table * @param array $options list of options accepted by `Table::save()` * @return bool success @@ -808,7 +808,7 @@ protected function _saveLinks(EntityInterface $sourceEntity, array $targetEntiti * * @param \Cake\Datasource\EntityInterface $sourceEntity the row belonging to the `source` side * of this association - * @param array $targetEntities list of entities belonging to the `target` side + * @param \Cake\Datasource\EntityInterface[] $targetEntities list of entities belonging to the `target` side * of this association * @param array $options list of options to be passed to the internal `save` call * @throws \InvalidArgumentException when any of the values in $targetEntities is @@ -858,7 +858,7 @@ function () use ($sourceEntity, $targetEntities, $options) { * * @param \Cake\Datasource\EntityInterface $sourceEntity An entity persisted in the source table for * this association. - * @param array $targetEntities List of entities persisted in the target table for + * @param \Cake\Datasource\EntityInterface[] $targetEntities List of entities persisted in the target table for * this association. * @param array|bool $options List of options to be passed to the internal `delete` call, * or a `boolean` as `cleanProperty` key shortcut. @@ -1187,7 +1187,7 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { * `$targetEntities` that were not deleted from calculating the difference. * * @param \Cake\ORM\Query $existing a query for getting existing links - * @param array $jointEntities link entities that should be persisted + * @param \Cake\Datasource\EntityInterface[] $jointEntities link entities that should be persisted * @param array $targetEntities entities in target table that are related to * the `$jointEntities` * @param array $options list of options accepted by `Table::delete()` @@ -1258,7 +1258,7 @@ protected function _diffLinks( * * @param \Cake\Datasource\EntityInterface $sourceEntity the row belonging to the `source` side * of this association - * @param array $targetEntities list of entities belonging to the `target` side + * @param \Cake\Datasource\EntityInterface[] $targetEntities list of entities belonging to the `target` side * of this association * @return bool * @throws \InvalidArgumentException @@ -1290,7 +1290,7 @@ protected function _checkPersistenceStatus(EntityInterface $sourceEntity, array * association. * @throws \InvalidArgumentException if any of the entities is lacking a primary * key value - * @return array + * @return \Cake\Datasource\EntityInterface[] */ protected function _collectJointEntities(EntityInterface $sourceEntity, array $targetEntities): array { diff --git a/Association/HasMany.php b/Association/HasMany.php index bf75c1b2..522b5176 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -355,6 +355,7 @@ public function unlink(EntityInterface $sourceEntity, array $targetEntities, $op $conditions = [ 'OR' => (new Collection($targetEntities)) ->map(function ($entity) use ($targetPrimaryKey) { + /** @var \Cake\Datasource\EntityInterface $entity */ return $entity->extract($targetPrimaryKey); }) ->toList(), @@ -462,6 +463,7 @@ protected function _unlinkAssociated( $exclusions = new Collection($remainingEntities); $exclusions = $exclusions->map( function ($ent) use ($primaryKey) { + /** @var \Cake\Datasource\EntityInterface $ent */ return $ent->extract($primaryKey); } ) diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 01729d17..fae0bab2 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -526,7 +526,7 @@ protected function _resultInjector(Query $fetchQuery, array $resultMap, array $o * be done with multiple foreign keys * * @param array $resultMap A keyed arrays containing the target table - * @param array $sourceKeys An array with aliased keys to match + * @param string[] $sourceKeys An array with aliased keys to match * @param string $nestKey The key under which results should be nested * @return \Closure */ diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 04fde21b..4e79ad9c 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -88,9 +88,11 @@ protected function _getQuery(CollectionInterface $objects, array $contain, Table } if (is_string($primaryKey)) { + /** @var \Cake\Database\Expression\QueryExpression $exp */ return $exp->in($source->aliasField($primaryKey), $keys->toList()); } + /** @var \Cake\ORM\Query $q */ $types = array_intersect_key($q->getDefaultTypes(), array_flip($primaryKey)); $primaryKey = array_map([$source, 'aliasField'], $primaryKey); diff --git a/Query.php b/Query.php index c5483ab5..f1229457 100644 --- a/Query.php +++ b/Query.php @@ -239,7 +239,7 @@ public function select($fields = [], bool $overwrite = false) * * * @param \Cake\ORM\Table|\Cake\ORM\Association $table The table to use to get an array of columns - * @param array $excludedFields The un-aliased column names you do not want selected from $table + * @param string[] $excludedFields The un-aliased column names you do not want selected from $table * @param bool $overwrite Whether to reset/remove previous selected fields * @return $this * @throws \InvalidArgumentException If Association|Table is not passed in first argument From 4e7250ce17f4af2c96ae64b65e63b62c5e91857a Mon Sep 17 00:00:00 2001 From: Val Bancer Date: Tue, 27 Aug 2019 16:16:49 +0200 Subject: [PATCH 1420/2059] Clear uncleared property in TableLocator::clear method --- Locator/TableLocator.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 215d1f6e..96d04184 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -318,6 +318,7 @@ public function clear() $this->_instances = []; $this->_config = []; $this->_fallbacked = []; + $this->_options = []; } /** From cba6b47b80bc213ea6e14c0e5f879b00d1e1423c Mon Sep 17 00:00:00 2001 From: Val Bancer Date: Wed, 28 Aug 2019 13:04:52 +0200 Subject: [PATCH 1421/2059] Improve initialization of table locator object --- Locator/LocatorAwareTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index 2d26e519..0d77b9d4 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -70,7 +70,7 @@ public function setTableLocator(LocatorInterface $tableLocator) */ public function getTableLocator() { - if (!$this->_tableLocator) { + if (!isset($this->_tableLocator)) { $this->_tableLocator = TableRegistry::getTableLocator(); } From 6c533bad6fa0fda48e29f5b2d14871dc9d63bb35 Mon Sep 17 00:00:00 2001 From: Val Bancer Date: Wed, 28 Aug 2019 14:35:48 +0200 Subject: [PATCH 1422/2059] Use === instead isset --- Locator/LocatorAwareTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index 0d77b9d4..d07d93cd 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -70,7 +70,7 @@ public function setTableLocator(LocatorInterface $tableLocator) */ public function getTableLocator() { - if (!isset($this->_tableLocator)) { + if ($this->_tableLocator === null) { $this->_tableLocator = TableRegistry::getTableLocator(); } From e4760f00f1ef5f2966259e5d04cdcf90a3dbd51b Mon Sep 17 00:00:00 2001 From: mscherer Date: Thu, 29 Aug 2019 15:17:59 +0200 Subject: [PATCH 1423/2059] Docblock fixes: Backport PR 13563 --- Association/BelongsToMany.php | 12 ++++++------ Association/HasMany.php | 2 ++ Association/Loader/SelectLoader.php | 2 +- LazyEagerLoader.php | 2 ++ Query.php | 2 +- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 6f7f207d..deab7faa 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -809,7 +809,7 @@ protected function _saveTarget(EntityInterface $parentEntity, $entities, $option * * @param \Cake\Datasource\EntityInterface $sourceEntity the entity from source table in this * association - * @param array $targetEntities list of entities to link to link to the source entity using the + * @param \Cake\Datasource\EntityInterface[] $targetEntities list of entities to link to link to the source entity using the * junction table * @param array $options list of options accepted by `Table::save()` * @return bool success @@ -883,7 +883,7 @@ protected function _saveLinks(EntityInterface $sourceEntity, $targetEntities, $o * * @param \Cake\Datasource\EntityInterface $sourceEntity the row belonging to the `source` side * of this association - * @param array $targetEntities list of entities belonging to the `target` side + * @param \Cake\Datasource\EntityInterface[] $targetEntities list of entities belonging to the `target` side * of this association * @param array $options list of options to be passed to the internal `save` call * @throws \InvalidArgumentException when any of the values in $targetEntities is @@ -933,7 +933,7 @@ function () use ($sourceEntity, $targetEntities, $options) { * * @param \Cake\Datasource\EntityInterface $sourceEntity An entity persisted in the source table for * this association. - * @param array $targetEntities List of entities persisted in the target table for + * @param \Cake\Datasource\EntityInterface[] $targetEntities List of entities persisted in the target table for * this association. * @param array|bool $options List of options to be passed to the internal `delete` call, * or a `boolean` as `cleanProperty` key shortcut. @@ -1251,7 +1251,7 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { * `$targetEntities` that were not deleted from calculating the difference. * * @param \Cake\ORM\Query $existing a query for getting existing links - * @param array $jointEntities link entities that should be persisted + * @param \Cake\Datasource\EntityInterface[] $jointEntities link entities that should be persisted * @param array $targetEntities entities in target table that are related to * the `$jointEntities` * @param array $options list of options accepted by `Table::delete()` @@ -1318,7 +1318,7 @@ protected function _diffLinks($existing, $jointEntities, $targetEntities, $optio * * @param \Cake\Datasource\EntityInterface $sourceEntity the row belonging to the `source` side * of this association - * @param array $targetEntities list of entities belonging to the `target` side + * @param \Cake\Datasource\EntityInterface[] $targetEntities list of entities belonging to the `target` side * of this association * @return bool * @throws \InvalidArgumentException @@ -1350,7 +1350,7 @@ protected function _checkPersistenceStatus($sourceEntity, array $targetEntities) * association. * @throws \InvalidArgumentException if any of the entities is lacking a primary * key value - * @return array + * @return \Cake\Datasource\EntityInterface[] */ protected function _collectJointEntities($sourceEntity, $targetEntities) { diff --git a/Association/HasMany.php b/Association/HasMany.php index cf159d6a..3d39cbe7 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -374,6 +374,7 @@ public function unlink(EntityInterface $sourceEntity, array $targetEntities, $op $conditions = [ 'OR' => (new Collection($targetEntities)) ->map(function ($entity) use ($targetPrimaryKey) { + /** @var \Cake\Datasource\EntityInterface $entity */ return $entity->extract($targetPrimaryKey); }) ->toList() @@ -476,6 +477,7 @@ protected function _unlinkAssociated(array $foreignKeyReference, EntityInterface $exclusions = new Collection($remainingEntities); $exclusions = $exclusions->map( function ($ent) use ($primaryKey) { + /** @var \Cake\Datasource\EntityInterface $ent */ return $ent->extract($primaryKey); } ) diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index fa6d38af..708844a7 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -524,7 +524,7 @@ protected function _resultInjector($fetchQuery, $resultMap, $options) * be done with multiple foreign keys * * @param array $resultMap A keyed arrays containing the target table - * @param array $sourceKeys An array with aliased keys to match + * @param string[] $sourceKeys An array with aliased keys to match * @param string $nestKey The key under which results should be nested * @return \Closure */ diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 2880dbff..0fedcffc 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -86,9 +86,11 @@ protected function _getQuery($objects, $contain, $source) } if (is_string($primaryKey)) { + /** @var \Cake\Database\Expression\QueryExpression $exp */ return $exp->in($source->aliasField($primaryKey), $keys->toList()); } + /** @var \Cake\ORM\Query $q */ $types = array_intersect_key($q->getDefaultTypes(), array_flip($primaryKey)); $primaryKey = array_map([$source, 'aliasField'], $primaryKey); diff --git a/Query.php b/Query.php index f14e7515..4107290f 100644 --- a/Query.php +++ b/Query.php @@ -231,7 +231,7 @@ public function select($fields = [], $overwrite = false) * * * @param \Cake\ORM\Table|\Cake\ORM\Association $table The table to use to get an array of columns - * @param array $excludedFields The un-aliased column names you do not want selected from $table + * @param string[] $excludedFields The un-aliased column names you do not want selected from $table * @param bool $overwrite Whether to reset/remove previous selected fields * @return Query * @throws \InvalidArgumentException If Association|Table is not passed in first argument From 021aeace9f9ee9cc753eb18baddb90fb40d0886b Mon Sep 17 00:00:00 2001 From: mscherer Date: Thu, 29 Aug 2019 15:35:39 +0200 Subject: [PATCH 1424/2059] Docblock fixes: Backport PR 13485 --- Rule/IsUnique.php | 4 ++-- RulesChecker.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index 2d024ed1..53713b1b 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -25,7 +25,7 @@ class IsUnique /** * The list of fields to check * - * @var array + * @var string[] */ protected $_fields; @@ -45,7 +45,7 @@ class IsUnique * multi-column unique rules. By default this is `true` to emulate how SQL UNIQUE * keys work. * - * @param array $fields The list of fields to check uniqueness for + * @param string[] $fields The list of fields to check uniqueness for * @param array $options The additional options for this rule. */ public function __construct(array $fields, array $options = []) diff --git a/RulesChecker.php b/RulesChecker.php index feea8c89..c9e1d8a9 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -39,7 +39,7 @@ class RulesChecker extends BaseRulesChecker * $rules->add($rules->isUnique(['email'], 'The email should be unique')); * ``` * - * @param array $fields The list of fields to check for uniqueness. + * @param string[] $fields The list of fields to check for uniqueness. * @param string|array|null $message The error message to show in case the rule does not pass. Can * also be an array of options. When an array, the 'message' key can be used to provide a message. * @return callable From 5be594c83fab19a131d7d1791a9bdb02b73909b4 Mon Sep 17 00:00:00 2001 From: mscherer Date: Thu, 29 Aug 2019 15:43:27 +0200 Subject: [PATCH 1425/2059] Docblock fixes: Backport PR 13308 --- Association.php | 12 ++++++------ Association/BelongsToMany.php | 6 +++--- Association/Loader/SelectLoader.php | 7 ++++--- Association/Loader/SelectWithPivotLoader.php | 2 +- Table.php | 14 +++++++------- 5 files changed, 21 insertions(+), 20 deletions(-) diff --git a/Association.php b/Association.php index dc6fdb08..a408c833 100644 --- a/Association.php +++ b/Association.php @@ -105,14 +105,14 @@ abstract class Association /** * The field name in the owning side table that is used to match with the foreignKey * - * @var string|array + * @var string|string[] */ protected $_bindingKey; /** * The name of the field representing the foreign key to the table to load * - * @var string|array + * @var string|string[] */ protected $_foreignKey; @@ -561,7 +561,7 @@ public function conditions($conditions = null) * Sets the name of the field representing the binding field with the target table. * When not manually specified the primary key of the owning side table is used. * - * @param string|array $key the table field or fields to be used to link both tables together + * @param string|string[] $key the table field or fields to be used to link both tables together * @return $this */ public function setBindingKey($key) @@ -575,7 +575,7 @@ public function setBindingKey($key) * Gets the name of the field representing the binding field with the target table. * When not manually specified the primary key of the owning side table is used. * - * @return string|array + * @return string|string[] */ public function getBindingKey() { @@ -614,7 +614,7 @@ public function bindingKey($key = null) /** * Gets the name of the field representing the foreign key to the target table. * - * @return string|array + * @return string|string[] */ public function getForeignKey() { @@ -624,7 +624,7 @@ public function getForeignKey() /** * Sets the name of the field representing the foreign key to the target table. * - * @param string|array $key the key or keys to be used to link both tables together + * @param string|string[] $key the key or keys to be used to link both tables together * @return $this */ public function setForeignKey($key) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index deab7faa..9020fd83 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -105,7 +105,7 @@ class BelongsToMany extends Association /** * The name of the field representing the foreign key to the target table * - * @var string|array + * @var string|string[] */ protected $_targetForeignKey; @@ -160,7 +160,7 @@ class BelongsToMany extends Association /** * Sets the name of the field representing the foreign key to the target table. * - * @param string $key the key to be used to link both tables together + * @param string|string[] $key the key to be used to link both tables together * @return $this */ public function setTargetForeignKey($key) @@ -173,7 +173,7 @@ public function setTargetForeignKey($key) /** * Gets the name of the field representing the foreign key to the target table. * - * @return string + * @return string|string[] */ public function getTargetForeignKey() { diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 708844a7..14e89637 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -276,7 +276,7 @@ protected function _assertFieldsPresent($fetchQuery, $key) * filtering needs to be done using a subquery. * * @param \Cake\ORM\Query $query Target table's query - * @param string|array $key the fields that should be used for filtering + * @param string|string[] $key the fields that should be used for filtering * @param \Cake\ORM\Query $subquery The Subquery to use for filtering * @return \Cake\ORM\Query */ @@ -334,7 +334,7 @@ protected function _addFilteringCondition($query, $key, $filter) * from $keys with the tuple values in $filter using the provided operator. * * @param \Cake\ORM\Query $query Target table's query - * @param array $keys the fields that should be used for filtering + * @param string[] $keys the fields that should be used for filtering * @param mixed $filter the value that should be used to match for $key * @param string $operator The operator for comparing the tuples * @return \Cake\Database\Expression\TupleComparison @@ -357,7 +357,8 @@ protected function _createTupleCondition($query, $keys, $filter, $operator) * which the filter should be applied * * @param array $options The options for getting the link field. - * @return string|array + * @return string|string[] + * @throws \RuntimeException */ protected function _linkField($options) { diff --git a/Association/Loader/SelectWithPivotLoader.php b/Association/Loader/SelectWithPivotLoader.php index 733a8c7f..521a2ace 100644 --- a/Association/Loader/SelectWithPivotLoader.php +++ b/Association/Loader/SelectWithPivotLoader.php @@ -133,7 +133,7 @@ protected function _buildQuery($options) * which the filter should be applied * * @param array $options the options to use for getting the link field. - * @return array|string + * @return string|string[] */ protected function _linkField($options) { diff --git a/Table.php b/Table.php index 9287fa90..4084a3dc 100644 --- a/Table.php +++ b/Table.php @@ -191,7 +191,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc /** * The name of the field that represents the primary key in the table * - * @var string|array + * @var string|string[] */ protected $_primaryKey; @@ -664,7 +664,7 @@ public function hasField($field) /** * Sets the primary key field name. * - * @param string|array $key Sets a new name to be used as primary key + * @param string|string[] $key Sets a new name to be used as primary key * @return $this */ public function setPrimaryKey($key) @@ -677,7 +677,7 @@ public function setPrimaryKey($key) /** * Returns the primary key field name. * - * @return string|array + * @return string|string[] */ public function getPrimaryKey() { @@ -696,8 +696,8 @@ public function getPrimaryKey() * Returns the primary key field name or sets a new one * * @deprecated 3.4.0 Use setPrimaryKey()/getPrimaryKey() instead. - * @param string|array|null $key Sets a new name to be used as primary key - * @return string|array + * @param string|string[]|null $key Sets a new name to be used as primary key + * @return string|string[] */ public function primaryKey($key = null) { @@ -2157,8 +2157,8 @@ protected function _insert($entity, $data) * Note: The ORM will not generate primary key values for composite primary keys. * You can overwrite _newId() in your table class. * - * @param array $primary The primary key columns to get a new ID for. - * @return null|string|array Either null or the primary key value or a list of primary key values. + * @param string[] $primary The primary key columns to get a new ID for. + * @return string|null Either null or the primary key value or a list of primary key values. */ protected function _newId($primary) { From 26f4ff0324b63259cf58a6e9926e68b40ae56f66 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 1 Sep 2019 21:50:31 +0530 Subject: [PATCH 1426/2059] Fix type errors reported by psalm. --- Association/DependentDeleteHelper.php | 1 + Association/HasMany.php | 12 +++++++++--- Association/Loader/SelectLoader.php | 4 ++-- Behavior/Translate/EavStrategy.php | 1 + Behavior/Translate/ShadowTableStrategy.php | 5 +++++ Behavior/TreeBehavior.php | 6 +++++- BehaviorRegistry.php | 4 ++-- EagerLoadable.php | 6 +++--- EagerLoader.php | 1 + LazyEagerLoader.php | 3 ++- Marshaller.php | 3 +++ Query.php | 19 +++++++++++++++---- ResultSet.php | 4 ++-- Table.php | 12 +++++++----- 14 files changed, 58 insertions(+), 23 deletions(-) diff --git a/Association/DependentDeleteHelper.php b/Association/DependentDeleteHelper.php index 0b4bb18c..9b6c5161 100644 --- a/Association/DependentDeleteHelper.php +++ b/Association/DependentDeleteHelper.php @@ -42,6 +42,7 @@ public function cascadeDelete(Association $association, EntityInterface $entity, return true; } $table = $association->getTarget(); + /** @psalm-suppress InvalidArgument */ $foreignKey = array_map([$association, 'aliasField'], (array)$association->getForeignKey()); $bindingKey = (array)$association->getBindingKey(); $conditions = array_combine($foreignKey, $entity->extract($bindingKey)); diff --git a/Association/HasMany.php b/Association/HasMany.php index 522b5176..e02e1d3e 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -177,6 +177,9 @@ public function saveAssociated(EntityInterface $entity, array $options = []) return false; } + if (!is_array($targetEntities)) { + $targetEntities = iterator_to_array($targetEntities); + } if (!$this->_saveTarget($foreignKeyReference, $entity, $targetEntities, $options)) { return false; } @@ -448,7 +451,7 @@ public function replace(EntityInterface $sourceEntity, array $targetEntities, ar * target entity, and the parent entity. * @param \Cake\Datasource\EntityInterface $entity the entity which should have its associated entities unassigned * @param \Cake\ORM\Table $target The associated table - * @param array $remainingEntities Entities that should not be deleted + * @param iterable $remainingEntities Entities that should not be deleted * @param array $options list of options accepted by `Table::delete()` * @return bool success */ @@ -456,7 +459,7 @@ protected function _unlinkAssociated( array $foreignKeyReference, EntityInterface $entity, Table $target, - array $remainingEntities = [], + iterable $remainingEntities = [], array $options = [] ): bool { $primaryKey = (array)$target->getPrimaryKey(); @@ -509,7 +512,10 @@ protected function _unlink(array $foreignKey, Table $target, array $conditions = $conditions = new QueryExpression($conditions); $conditions->traverse(function ($entry) use ($target): void { if ($entry instanceof FieldInterface) { - $entry->setField($target->aliasField($entry->getField())); + $field = $entry->getField(); + if (is_string($field)) { + $entry->setField($target->aliasField($field)); + } } }); $query = $this->find()->where($conditions); diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index fae0bab2..421baa08 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -323,10 +323,10 @@ protected function _addFilteringCondition(Query $query, $key, $filter): Query { if (is_array($key)) { $conditions = $this->_createTupleCondition($query, $key, $filter, 'IN'); + } else { + $conditions = [$key . ' IN' => $filter]; } - $conditions = $conditions ?? [$key . ' IN' => $filter]; - return $query->andWhere($conditions); } diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index aceb4ac5..b30e3ab5 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -277,6 +277,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array } $model = $this->_config['referenceName']; + /** @psalm-suppress UndefinedClass */ $preexistent = $this->translationTable->find() ->select(['id', 'field']) ->where([ diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 1ee4006b..120a9581 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -360,6 +360,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array $where = ['id' => $id, 'locale' => $locale]; + /** @var \Cake\Datasource\EntityInterface|null $translation */ $translation = $this->translationTable->find() ->select(array_merge(['id', 'locale'], $fields)) ->where($where) @@ -455,6 +456,10 @@ protected function rowMapper($results, $locale) /** @var \Cake\ORM\Entity|array $translation */ $translation = $row['translation']; + /** + * @psalm-suppress PossiblyInvalidMethodCall + * @psalm-suppress PossiblyInvalidArgument + */ $keys = $hydrated ? $translation->getVisible() : array_keys($translation); foreach ($keys as $field) { diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 18a0b8b1..8c9ac3f6 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -636,6 +636,7 @@ protected function _moveUp(EntityInterface $node, $number): EntityInterface $targetNode = null; if ($number !== true) { + /** @var \Cake\Datasource\EntityInterface|null $targetNode */ $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) ->where(["$parent IS" => $nodeParent]) @@ -649,6 +650,7 @@ protected function _moveUp(EntityInterface $node, $number): EntityInterface ->first(); } if (!$targetNode) { + /** @var \Cake\Datasource\EntityInterface|null $targetNode */ $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) ->where(["$parent IS" => $nodeParent]) @@ -726,6 +728,7 @@ protected function _moveDown(EntityInterface $node, $number): EntityInterface $targetNode = null; if ($number !== true) { + /** @var \Cake\Datasource\EntityInterface|null $targetNode */ $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) ->where(["$parent IS" => $nodeParent]) @@ -739,6 +742,7 @@ protected function _moveDown(EntityInterface $node, $number): EntityInterface ->first(); } if (!$targetNode) { + /** @var \Cake\Datasource\EntityInterface|null $targetNode */ $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) ->where(["$parent IS" => $nodeParent]) @@ -880,7 +884,7 @@ protected function _getMax(): int ->orderDesc($rightField) ->first(); - if (empty($edge[$field])) { + if ($edge === null || empty($edge[$field])) { return 0; } diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 5acd2048..93f75fb6 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -260,10 +260,10 @@ public function call(string $method, array $args = []) * * @param string $type The finder type to invoke. * @param array $args The arguments you want to invoke the method with. - * @return mixed The return value depends on the underlying behavior method. + * @return \Cake\ORM\Query The return value depends on the underlying behavior method. * @throws \BadMethodCallException When the method is unknown. */ - public function callFinder(string $type, array $args = []) + public function callFinder(string $type, array $args = []): Query { $type = strtolower($type); diff --git a/EagerLoadable.php b/EagerLoadable.php index 503d6992..820f9fcb 100644 --- a/EagerLoadable.php +++ b/EagerLoadable.php @@ -59,7 +59,7 @@ class EagerLoadable * A dotted separated string representing the path of associations * that should be followed to fetch this level. * - * @var string|null + * @var string */ protected $_aliasPath; @@ -183,9 +183,9 @@ public function instance(): Association * Gets a dot separated string representing the path of associations * that should be followed to fetch this level. * - * @return string|null + * @return string */ - public function aliasPath(): ?string + public function aliasPath(): string { return $this->_aliasPath; } diff --git a/EagerLoader.php b/EagerLoader.php index 12ee5b27..d71c2126 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -684,6 +684,7 @@ public function associationsMap(Table $table): array return $map; } + /** @psalm-suppress PossiblyNullReference */ $map = $this->_buildAssociationsMap($map, $this->_matching->normalized($table), true); $map = $this->_buildAssociationsMap($map, $this->normalized($table)); $map = $this->_buildAssociationsMap($map, $this->_joinsMap); diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 4e79ad9c..0b72a07b 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -123,6 +123,7 @@ protected function _getPropertyMap(Table $source, array $associations): array $map = []; $container = $source->associations(); foreach ($associations as $assoc) { + /** @psalm-suppress PossiblyNullReference */ $map[$assoc] = $container->get($assoc)->getProperty(); } @@ -134,7 +135,7 @@ protected function _getPropertyMap(Table $source, array $associations): array * entities. * * @param \Cake\Datasource\EntityInterface[]|\Traversable $objects The original list of entities - * @param \Cake\Collection\CollectionInterface|\Cake\Database\Query $results The loaded results + * @param \Cake\Collection\CollectionInterface|\Cake\ORM\Query $results The loaded results * @param string[] $associations The top level associations that were loaded * @param \Cake\ORM\Table $source The table where the entities came from * @return array diff --git a/Marshaller.php b/Marshaller.php index 8fbd955a..5d5467eb 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -723,9 +723,11 @@ protected function _mergeAssociation($original, Association $assoc, $value, arra $types = [Association::ONE_TO_ONE, Association::MANY_TO_ONE]; $type = $assoc->type(); if (in_array($type, $types, true)) { + /** @var \Cake\Datasource\EntityInterface $original */ return $marshaller->merge($original, $value, (array)$options); } if ($type === Association::MANY_TO_MANY) { + /** @var \Cake\Datasource\EntityInterface[] $original */ return $marshaller->_mergeBelongsToMany($original, $assoc, $value, (array)$options); } @@ -740,6 +742,7 @@ protected function _mergeAssociation($original, Association $assoc, $value, arra } } + /** @var \Cake\Datasource\EntityInterface[] $original */ return $marshaller->mergeMany($original, $value, (array)$options); } diff --git a/Query.php b/Query.php index f1229457..cd58c350 100644 --- a/Query.php +++ b/Query.php @@ -51,7 +51,7 @@ * @method mixed max($field, int $type = SORT_NUMERIC) Returns the maximum value for a single column in all the results. * @method mixed min($field, int $type = SORT_NUMERIC) Returns the minimum value for a single column in all the results. * @method \Cake\Collection\CollectionInterface groupBy(string|callable $field) In-memory group all results by the value of a column. - * @method \Cake\Collection\CollectionInterface indexBy(string|callable $field) Returns the results indexed by the value of a column. + * @method \Cake\Collection\CollectionInterface indexBy(string|callable $callback) Returns the results indexed by the value of a column. * @method \Cake\Collection\CollectionInterface countBy(string|callable $field) Returns the number of unique values for a column * @method float sumOf(string|callable $field) Returns the sum of all values for a single column * @method \Cake\Collection\CollectionInterface shuffle() In-memory randomize the order the results are returned @@ -934,10 +934,14 @@ protected function _performCount(): int ->execute(); } - $result = $statement->fetch('assoc')['count']; + $result = $statement->fetch('assoc'); $statement->closeCursor(); - return (int)$result; + if ($result === false) { + return 0; + } + + return (int)$result['count']; } /** @@ -1010,10 +1014,14 @@ public function isHydrationEnabled(): bool /** * {@inheritDoc} * + * @param false|string|\Closure $key Either the cache key or a function to generate the cache key. + * When using a function, this query instance will be supplied as an argument. + * @param string|\Cake\Cache\CacheEngine $config Either the name of the cache config to use, or + * a cache config instance. * @return $this * @throws \RuntimeException When you attempt to cache a non-select query. */ - public function cache(string $key, $config = 'default') + public function cache($key, $config = 'default') { if ($this->_type !== 'select' && $this->_type !== null) { throw new RuntimeException('You cannot cache the results of non-select queries.'); @@ -1264,6 +1272,9 @@ public function insert(array $columns, array $types = []) /** * {@inheritDoc} * + * @param string $method the method to call + * @param array $arguments list of arguments for the method to call + * @return mixed * @throws \BadMethodCallException if the method is called for a non-select query */ public function __call($method, $arguments) diff --git a/ResultSet.php b/ResultSet.php index b49277aa..1de42cb7 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -103,7 +103,7 @@ class ResultSet implements ResultSetInterface /** * Results that have been fetched or hydrated into the results. * - * @var array|\ArrayAccess + * @var array|\SplFixedArray */ protected $_results = []; @@ -117,7 +117,7 @@ class ResultSet implements ResultSetInterface /** * Tracks value of $_autoFields property of $query passed to constructor. * - * @var bool + * @var bool|null */ protected $_autoFields; diff --git a/Table.php b/Table.php index a93c41a5..a56ea79a 100644 --- a/Table.php +++ b/Table.php @@ -1185,10 +1185,10 @@ public function belongsToMany(string $associated, array $options = []): BelongsT * Would invoke the `findPublished` method. * * @param string $type the type of query to perform - * @param array|\ArrayAccess $options An array that will be passed to Query::applyOptions() + * @param array $options An array that will be passed to Query::applyOptions() * @return \Cake\ORM\Query The query builder */ - public function find(string $type = 'all', $options = []): Query + public function find(string $type = 'all', array $options = []): Query { $query = $this->query(); $query->select(); @@ -1730,7 +1730,7 @@ public function exists($conditions): bool * ``` * * @param \Cake\Datasource\EntityInterface $entity - * @param array $options + * @param array|\ArrayAccess|\Cake\ORM\SaveOptionsBuilder $options * @return \Cake\Datasource\EntityInterface|false * @throws \Cake\ORM\Exception\RolledbackTransactionException If the transaction is aborted in the afterSave event. */ @@ -1962,6 +1962,7 @@ protected function _insert(EntityInterface $entity, array $data) foreach ($primary as $key => $v) { if (!isset($data[$key])) { $id = $statement->lastInsertId($this->getTable(), $key); + /** @var string $type */ $type = $schema->getColumnType($key); $entity->set($key, TypeFactory::build($type)->toPHP($id, $driver)); break; @@ -1991,6 +1992,7 @@ protected function _newId(array $primary) if (!$primary || count((array)$primary) > 1) { return null; } + /** @var string $typeName */ $typeName = $this->getSchema()->getColumnType($primary[0]); $type = TypeFactory::build($typeName); @@ -2051,7 +2053,7 @@ protected function _update(EntityInterface $entity, array $data) * error. * * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to save. - * @param array|\ArrayAccess $options Options used when calling Table::save() for each entity. + * @param array|\ArrayAccess|\Cake\ORM\SaveOptionsBuilder $options Options used when calling Table::save() for each entity. * @return \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface|false False on failure, entities list on success. * @throws \Exception */ @@ -2084,7 +2086,7 @@ public function saveManyOrFail(iterable $entities, $options = []): iterable /** * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to save. - * @param array|\ArrayAccess $options Options used when calling Table::save() for each entity. + * @param array|\ArrayAccess|\Cake\ORM\SaveOptionsBuilder $options Options used when calling Table::save() for each entity. * @return \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface Entities list. * @throws \Cake\ORM\Exception\PersistenceFailedException If an entity couldn't be saved. */ From 2ae1462e64efeef7d327c89cfb9fec880efb6c13 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 2 Sep 2019 01:36:32 +0530 Subject: [PATCH 1427/2059] Skip merging if association conditions are not array. --- Association/BelongsToMany.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 176f9d2e..8512ea8f 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -568,7 +568,12 @@ public function cascadeDelete(EntityInterface $entity, array $options = []): boo return true; } - $conditions = array_merge($conditions, $hasMany->getConditions()); + $assocConditions = $hasMany->getConditions(); + if (is_array($assocConditions)) { + $conditions = array_merge($conditions, $assocConditions); + } else { + $conditions[] = $assocConditions; + } $table->deleteAll($conditions); From a78c2ae0c9b8e3035ea40ebffde529c4841b0c64 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 3 Sep 2019 23:02:54 +0530 Subject: [PATCH 1428/2059] Fix psalm reported error under ORM namespace. --- BehaviorRegistry.php | 1 + Query.php | 1 + 2 files changed, 2 insertions(+) diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 93f75fb6..6c4849b6 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -136,6 +136,7 @@ protected function _throwMissingClassError(string $class, ?string $plugin): void * @param string $alias The alias of the object. * @param array $config An array of config to use for the behavior. * @return \Cake\ORM\Behavior The constructed behavior class. + * @psalm-suppress MoreSpecificImplementedParamType */ protected function _create($class, string $alias, array $config): Behavior { diff --git a/Query.php b/Query.php index cd58c350..ecfe830b 100644 --- a/Query.php +++ b/Query.php @@ -789,6 +789,7 @@ public function notMatching(string $assoc, ?callable $builder = null) * * @param array $options the options to be applied * @return $this + * @psalm-suppress ImplementedReturnTypeMismatch */ public function applyOptions(array $options) { From 47bce82e3017304910ca95314ca18c9bc38fa4d3 Mon Sep 17 00:00:00 2001 From: mscherer Date: Sat, 7 Sep 2019 10:49:30 +0200 Subject: [PATCH 1429/2059] Fix some docblocks. --- Query.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Query.php b/Query.php index 4107290f..dc8e31dd 100644 --- a/Query.php +++ b/Query.php @@ -101,7 +101,7 @@ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface * Whether the user select any fields before being executed, this is used * to determined if any fields should be automatically be selected. * - * @var bool + * @var bool|null */ protected $_hasFields; @@ -109,7 +109,7 @@ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface * Tracks whether or not the original query should include * fields from the top level table. * - * @var bool + * @var bool|null */ protected $_autoFields; @@ -124,7 +124,7 @@ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface * A callable function that can be used to calculate the total amount of * records this query will match when not using `limit` * - * @var callable + * @var callable|null */ protected $_counter; @@ -132,7 +132,7 @@ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface * Instance of a class responsible for storing association containments and * for eager loading them when this query is executed * - * @var \Cake\ORM\EagerLoader + * @var \Cake\ORM\EagerLoader|null */ protected $_eagerLoader; @@ -1391,7 +1391,7 @@ public function disableAutoFields() * By default calling select() will disable auto-fields. You can re-enable * auto-fields with enableAutoFields(). * - * @return bool The current value. + * @return bool|null The current value. Returns null if neither enabled or disabled yet. */ public function isAutoFieldsEnabled() { @@ -1406,7 +1406,7 @@ public function isAutoFieldsEnabled() * * @deprecated 3.4.0 Use enableAutoFields()/isAutoFieldsEnabled() instead. * @param bool|null $value The value to set or null to read the current value. - * @return bool|$this Either the current value or the query object. + * @return bool|null|$this Either the current value or the query object. */ public function autoFields($value = null) { From 429653532767c3c156656efadec9a53f2700e542 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 7 Sep 2019 16:53:45 +0530 Subject: [PATCH 1430/2059] Update ExpressInterface::traverse() to accept only Closure instead of callable. --- Association.php | 8 ++++---- Association/Loader/SelectWithPivotLoader.php | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Association.php b/Association.php index c6e390c3..d6152273 100644 --- a/Association.php +++ b/Association.php @@ -121,7 +121,7 @@ abstract class Association * A list of conditions to be always included when fetching records from * the target association * - * @var array|callable + * @var array|\Closure */ protected $_conditions = []; @@ -421,7 +421,7 @@ public function getTarget(): Table * Sets a list of conditions to be always included when fetching records from * the target association. * - * @param array|callable $conditions list of conditions to be used + * @param array|\Closure $conditions list of conditions to be used * @see \Cake\Database\Query::where() for examples on the format of the array * @return \Cake\ORM\Association */ @@ -437,7 +437,7 @@ public function setConditions($conditions) * the target association. * * @see \Cake\Database\Query::where() for examples on the format of the array - * @return array|callable + * @return array|\Closure */ public function getConditions() { @@ -855,7 +855,7 @@ public function find($type = null, array $options = []): Query * Proxies the operation to the target table's exists method after * appending the default conditions for this association * - * @param array|callable|\Cake\Database\ExpressionInterface $conditions The conditions to use + * @param array|\Closure|\Cake\Database\ExpressionInterface $conditions The conditions to use * for checking if any record matches. * @see \Cake\ORM\Table::exists() * @return bool diff --git a/Association/Loader/SelectWithPivotLoader.php b/Association/Loader/SelectWithPivotLoader.php index 3498540a..0f9c1fb9 100644 --- a/Association/Loader/SelectWithPivotLoader.php +++ b/Association/Loader/SelectWithPivotLoader.php @@ -50,7 +50,7 @@ class SelectWithPivotLoader extends SelectLoader /** * Custom conditions for the junction association * - * @var string|array|\Cake\Database\ExpressionInterface|callable|null + * @var string|array|\Cake\Database\ExpressionInterface|\Closure|null */ protected $junctionConditions; From aa865c026ed3d74b0772401cc883947d5f282503 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 8 Sep 2019 20:57:43 +0530 Subject: [PATCH 1431/2059] Allow validation constants to be overridden in child classes. --- Table.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Table.php b/Table.php index a56ea79a..3a9a0b0d 100644 --- a/Table.php +++ b/Table.php @@ -136,6 +136,13 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc use RulesAwareTrait; use ValidatorAwareTrait; + /** + * Name of default validation set. + * + * @var string + */ + public const DEFAULT_VALIDATOR = 'default'; + /** * The alias this object is assigned to validators as. * From bedb83b8a46c413906c9e5e12bc5aedea9cc89fc Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 9 Sep 2019 06:22:04 +0530 Subject: [PATCH 1432/2059] Fix errors reported by psalm --- Query.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Query.php b/Query.php index ecfe830b..cd58c350 100644 --- a/Query.php +++ b/Query.php @@ -789,7 +789,6 @@ public function notMatching(string $assoc, ?callable $builder = null) * * @param array $options the options to be applied * @return $this - * @psalm-suppress ImplementedReturnTypeMismatch */ public function applyOptions(array $options) { From 25a8b5e5664adb557e9d69a1d28cd6105de6a4f5 Mon Sep 17 00:00:00 2001 From: mscherer Date: Mon, 9 Sep 2019 15:08:53 +0200 Subject: [PATCH 1433/2059] Fix newlines around psr2 --- Association.php | 1 - Association/BelongsTo.php | 1 - Association/BelongsToMany.php | 1 - Association/DependentDeleteHelper.php | 1 - Association/DependentDeleteTrait.php | 1 - Association/HasMany.php | 1 - Association/Loader/SelectLoader.php | 1 - Association/Loader/SelectWithPivotLoader.php | 1 - AssociationCollection.php | 1 - AssociationsNormalizerTrait.php | 1 - Behavior.php | 1 - Behavior/CounterCacheBehavior.php | 1 - Behavior/TimestampBehavior.php | 1 - Behavior/Translate/TranslateTrait.php | 1 - Behavior/TranslateBehavior.php | 1 - Behavior/TreeBehavior.php | 1 - BehaviorRegistry.php | 1 - EagerLoadable.php | 1 - EagerLoader.php | 1 - Exception/MissingBehaviorException.php | 1 - Exception/MissingEntityException.php | 1 - Exception/MissingTableClassException.php | 1 - Exception/PersistenceFailedException.php | 1 - Exception/RolledbackTransactionException.php | 1 - LazyEagerLoader.php | 1 - Locator/LocatorAwareTrait.php | 1 - Locator/LocatorInterface.php | 1 - Locator/TableLocator.php | 1 - Marshaller.php | 1 - ResultSet.php | 1 - Rule/ExistsIn.php | 1 - Rule/IsUnique.php | 1 - Rule/ValidCount.php | 1 - RulesChecker.php | 1 - SaveOptionsBuilder.php | 1 - Table.php | 1 - TableRegistry.php | 1 - 37 files changed, 37 deletions(-) diff --git a/Association.php b/Association.php index a408c833..2180b64f 100644 --- a/Association.php +++ b/Association.php @@ -34,7 +34,6 @@ */ abstract class Association { - use ConventionsTrait; use LocatorAwareTrait; diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 651f81e3..76bea951 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -30,7 +30,6 @@ */ class BelongsTo extends Association { - /** * Valid strategies for this type of association * diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 9020fd83..df8687e4 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -36,7 +36,6 @@ */ class BelongsToMany extends Association { - /** * Saving strategy that will only append to the links set * diff --git a/Association/DependentDeleteHelper.php b/Association/DependentDeleteHelper.php index 5bd74aff..7e395c56 100644 --- a/Association/DependentDeleteHelper.php +++ b/Association/DependentDeleteHelper.php @@ -24,7 +24,6 @@ */ class DependentDeleteHelper { - /** * Cascade a delete to remove dependent records. * diff --git a/Association/DependentDeleteTrait.php b/Association/DependentDeleteTrait.php index aab6048a..16d30d74 100644 --- a/Association/DependentDeleteTrait.php +++ b/Association/DependentDeleteTrait.php @@ -26,7 +26,6 @@ */ trait DependentDeleteTrait { - /** * Cascade a delete to remove dependent records. * diff --git a/Association/HasMany.php b/Association/HasMany.php index 3d39cbe7..4528ab29 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -35,7 +35,6 @@ */ class HasMany extends Association { - /** * Order in which target records should be returned * diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 14e89637..be59f5a8 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -28,7 +28,6 @@ */ class SelectLoader { - /** * The alias of the association loading the results * diff --git a/Association/Loader/SelectWithPivotLoader.php b/Association/Loader/SelectWithPivotLoader.php index 521a2ace..c1413991 100644 --- a/Association/Loader/SelectWithPivotLoader.php +++ b/Association/Loader/SelectWithPivotLoader.php @@ -23,7 +23,6 @@ */ class SelectWithPivotLoader extends SelectLoader { - /** * The name of the junction association * diff --git a/AssociationCollection.php b/AssociationCollection.php index a6ccca21..65a35ad9 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -29,7 +29,6 @@ */ class AssociationCollection implements IteratorAggregate { - use AssociationsNormalizerTrait; use LocatorAwareTrait; diff --git a/AssociationsNormalizerTrait.php b/AssociationsNormalizerTrait.php index 335804b4..a7d03e39 100644 --- a/AssociationsNormalizerTrait.php +++ b/AssociationsNormalizerTrait.php @@ -20,7 +20,6 @@ */ trait AssociationsNormalizerTrait { - /** * Returns an array out of the original passed associations list where dot notation * is transformed into nested arrays so that they can be parsed by other routines diff --git a/Behavior.php b/Behavior.php index fce15777..963a3e12 100644 --- a/Behavior.php +++ b/Behavior.php @@ -112,7 +112,6 @@ */ class Behavior implements EventListenerInterface { - use InstanceConfigTrait; /** diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index b89a42d3..18d437eb 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -100,7 +100,6 @@ */ class CounterCacheBehavior extends Behavior { - /** * Store the fields which should be ignored * diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 6d2d54fe..144dfcec 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -27,7 +27,6 @@ */ class TimestampBehavior extends Behavior { - /** * Default config * diff --git a/Behavior/Translate/TranslateTrait.php b/Behavior/Translate/TranslateTrait.php index 343740e2..9ce6126e 100644 --- a/Behavior/Translate/TranslateTrait.php +++ b/Behavior/Translate/TranslateTrait.php @@ -22,7 +22,6 @@ */ trait TranslateTrait { - /** * Returns the entity containing the translated fields for this object and for * the specified language. If the translation for the passed language is not diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 9d3219ad..013d408f 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -42,7 +42,6 @@ */ class TranslateBehavior extends Behavior implements PropertyMarshalInterface { - use LocatorAwareTrait; /** diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 44b32daa..b5ebc5c1 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -37,7 +37,6 @@ */ class TreeBehavior extends Behavior { - /** * Cached copy of the first column in a table's primary key. * diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index a72389e6..58c40a22 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -30,7 +30,6 @@ */ class BehaviorRegistry extends ObjectRegistry implements EventDispatcherInterface { - use EventDispatcherTrait; /** diff --git a/EagerLoadable.php b/EagerLoadable.php index 9d93f5b1..c18eba2e 100644 --- a/EagerLoadable.php +++ b/EagerLoadable.php @@ -24,7 +24,6 @@ */ class EagerLoadable { - /** * The name of the association to load. * diff --git a/EagerLoader.php b/EagerLoader.php index 83f0d4bf..40a045cc 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -28,7 +28,6 @@ */ class EagerLoader { - /** * Nested array describing the association to be fetched * and the options to apply for each of them, if any diff --git a/Exception/MissingBehaviorException.php b/Exception/MissingBehaviorException.php index 725e5e8a..c6c34cf6 100644 --- a/Exception/MissingBehaviorException.php +++ b/Exception/MissingBehaviorException.php @@ -19,6 +19,5 @@ */ class MissingBehaviorException extends Exception { - protected $_messageTemplate = 'Behavior class %s could not be found.'; } diff --git a/Exception/MissingEntityException.php b/Exception/MissingEntityException.php index 6f355279..d4771e3c 100644 --- a/Exception/MissingEntityException.php +++ b/Exception/MissingEntityException.php @@ -23,6 +23,5 @@ */ class MissingEntityException extends Exception { - protected $_messageTemplate = 'Entity class %s could not be found.'; } diff --git a/Exception/MissingTableClassException.php b/Exception/MissingTableClassException.php index 544a968d..83c96059 100644 --- a/Exception/MissingTableClassException.php +++ b/Exception/MissingTableClassException.php @@ -21,6 +21,5 @@ */ class MissingTableClassException extends Exception { - protected $_messageTemplate = 'Table class %s could not be found.'; } diff --git a/Exception/PersistenceFailedException.php b/Exception/PersistenceFailedException.php index 8596f1e9..4673cb32 100644 --- a/Exception/PersistenceFailedException.php +++ b/Exception/PersistenceFailedException.php @@ -21,7 +21,6 @@ */ class PersistenceFailedException extends Exception { - /** * The entity on which the persistence operation failed * diff --git a/Exception/RolledbackTransactionException.php b/Exception/RolledbackTransactionException.php index ac31a9d0..7cdd2fe5 100644 --- a/Exception/RolledbackTransactionException.php +++ b/Exception/RolledbackTransactionException.php @@ -19,6 +19,5 @@ */ class RolledbackTransactionException extends Exception { - protected $_messageTemplate = 'The afterSave event in "%s" is aborting the transaction before the save process is done.'; } diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 0fedcffc..a4e8cfce 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -26,7 +26,6 @@ */ class LazyEagerLoader { - /** * Loads the specified associations in the passed entity or list of entities * by executing extra queries in the database and merging the results in the diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index 2d26e519..1f837028 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -21,7 +21,6 @@ */ trait LocatorAwareTrait { - /** * Table locator instance * diff --git a/Locator/LocatorInterface.php b/Locator/LocatorInterface.php index 08cddd6a..3bd07589 100644 --- a/Locator/LocatorInterface.php +++ b/Locator/LocatorInterface.php @@ -24,7 +24,6 @@ */ interface LocatorInterface { - /** * Stores a list of options to be used when instantiating an object * with a matching alias. diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 215d1f6e..74a30660 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -26,7 +26,6 @@ */ class TableLocator implements LocatorInterface { - /** * Contains a list of locations where table classes should be looked for. * diff --git a/Marshaller.php b/Marshaller.php index a54160d8..cb66a5d6 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -35,7 +35,6 @@ */ class Marshaller { - use AssociationsNormalizerTrait; /** diff --git a/ResultSet.php b/ResultSet.php index 5d155431..882b91eb 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -30,7 +30,6 @@ */ class ResultSet implements ResultSetInterface { - use CollectionTrait; /** diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index d583120b..104f9a86 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -24,7 +24,6 @@ */ class ExistsIn { - /** * The list of fields to check * diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index 53713b1b..9d1524b1 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -21,7 +21,6 @@ */ class IsUnique { - /** * The list of fields to check * diff --git a/Rule/ValidCount.php b/Rule/ValidCount.php index 33a98081..33696ad7 100644 --- a/Rule/ValidCount.php +++ b/Rule/ValidCount.php @@ -23,7 +23,6 @@ */ class ValidCount { - /** * The field to check * diff --git a/RulesChecker.php b/RulesChecker.php index c9e1d8a9..9def175d 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -28,7 +28,6 @@ */ class RulesChecker extends BaseRulesChecker { - /** * Returns a callable that can be used as a rule for checking the uniqueness of a value * in the table. diff --git a/SaveOptionsBuilder.php b/SaveOptionsBuilder.php index f3ea0b95..470ed502 100644 --- a/SaveOptionsBuilder.php +++ b/SaveOptionsBuilder.php @@ -27,7 +27,6 @@ */ class SaveOptionsBuilder extends ArrayObject { - use AssociationsNormalizerTrait; /** diff --git a/Table.php b/Table.php index 4084a3dc..56771965 100644 --- a/Table.php +++ b/Table.php @@ -126,7 +126,6 @@ */ class Table implements RepositoryInterface, EventListenerInterface, EventDispatcherInterface, ValidatorAwareInterface { - use EventDispatcherTrait; use RulesAwareTrait; use ValidatorAwareTrait; diff --git a/TableRegistry.php b/TableRegistry.php index ee505688..1cb465df 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -55,7 +55,6 @@ */ class TableRegistry { - /** * LocatorInterface implementation instance. * From 5d17c28bec2a7037b66e133839a873579be0b443 Mon Sep 17 00:00:00 2001 From: mscherer Date: Mon, 9 Sep 2019 21:37:39 +0200 Subject: [PATCH 1434/2059] Fix CS - empty values last in property docblocks --- Association/BelongsToMany.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index df8687e4..aaf1d5b1 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -138,14 +138,14 @@ class BelongsToMany extends Association /** * Filtered conditions that reference the target table. * - * @var null|array + * @var array|null */ protected $_targetConditions; /** * Filtered conditions that reference the junction table. * - * @var null|array + * @var array|null */ protected $_junctionConditions; From f2111db7c26b9268d3acbc5ea73f8c7aae1b8af3 Mon Sep 17 00:00:00 2001 From: mscherer Date: Mon, 9 Sep 2019 21:44:29 +0200 Subject: [PATCH 1435/2059] Fix CS - PSR12 class inst --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 56771965..d878a1d2 100644 --- a/Table.php +++ b/Table.php @@ -2933,7 +2933,7 @@ public function getSaveOptionsBuilder(array $options = []) */ public function loadInto($entities, array $contain) { - return (new LazyEagerLoader)->loadInto($entities, $contain, $this); + return (new LazyEagerLoader())->loadInto($entities, $contain, $this); } /** From 1cdfd04a4182920e35abc329627e312e24a56f5c Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 11 Sep 2019 21:34:42 +0530 Subject: [PATCH 1436/2059] Fix CS - empty values last in docblocks --- Association/BelongsToMany.php | 4 ++-- Query.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 8512ea8f..a3a2b31f 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -139,14 +139,14 @@ class BelongsToMany extends Association /** * Filtered conditions that reference the target table. * - * @var null|array + * @var array|null */ protected $_targetConditions; /** * Filtered conditions that reference the junction table. * - * @var null|array + * @var array|null */ protected $_junctionConditions; diff --git a/Query.php b/Query.php index cd58c350..fc812c9f 100644 --- a/Query.php +++ b/Query.php @@ -1014,7 +1014,7 @@ public function isHydrationEnabled(): bool /** * {@inheritDoc} * - * @param false|string|\Closure $key Either the cache key or a function to generate the cache key. + * @param \Closure|string|false $key Either the cache key or a function to generate the cache key. * When using a function, this query instance will be supplied as an argument. * @param string|\Cake\Cache\CacheEngine $config Either the name of the cache config to use, or * a cache config instance. From 0a0be03e62835ddb8be9b0765a2ee2d56c279459 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 26 Sep 2019 01:48:23 +0530 Subject: [PATCH 1437/2059] Fix errors reported by pslam --- Marshaller.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Marshaller.php b/Marshaller.php index 5d5467eb..b03cb7ed 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -648,6 +648,7 @@ public function mergeMany(iterable $entities, array $data, array $options = []): ->toArray(); $new = $indexed[null] ?? []; + /** @psalm-suppress PossiblyNullArrayOffset */ unset($indexed[null]); $output = []; From 43bfa71040c989f848042243aa9c5024583ec19c Mon Sep 17 00:00:00 2001 From: Val Bancer Date: Mon, 30 Sep 2019 22:03:14 +0200 Subject: [PATCH 1438/2059] Refactor TableSchema construction to a method in Driver class, #13689 --- Table.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 50847e04..3938ddfe 100644 --- a/Table.php +++ b/Table.php @@ -20,6 +20,7 @@ use Cake\Database\Schema\TableSchema; use Cake\Database\Type; use Cake\Datasource\ConnectionInterface; +use Cake\Datasource\ConnectionManager; use Cake\Datasource\EntityInterface; use Cake\Datasource\Exception\InvalidPrimaryKeyException; use Cake\Datasource\RepositoryInterface; @@ -583,7 +584,11 @@ public function setSchema($schema) unset($schema['_constraints']); } - $schema = new TableSchema($this->getTable(), $schema); + $connection = $this->getConnection(); + if ($connection === null) { + $connection = ConnectionManager::get(static::defaultConnectionName()); + } + $schema = $connection->getDriver()->newTableSchema($this->getTable(), $schema); foreach ($constraints as $name => $value) { $schema->addConstraint($name, $value); From e7bc7f732eed266ebecff52770f7b8722f36def2 Mon Sep 17 00:00:00 2001 From: Val Bancer Date: Mon, 30 Sep 2019 23:04:54 +0200 Subject: [PATCH 1439/2059] Optimize according to the code review --- Table.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Table.php b/Table.php index 3938ddfe..26c69992 100644 --- a/Table.php +++ b/Table.php @@ -524,6 +524,9 @@ public function setConnection(ConnectionInterface $connection) */ public function getConnection() { + if (!$this->_connection) { + $this->_connection = ConnectionManager::get(static::defaultConnectionName()); + } return $this->_connection; } @@ -584,11 +587,7 @@ public function setSchema($schema) unset($schema['_constraints']); } - $connection = $this->getConnection(); - if ($connection === null) { - $connection = ConnectionManager::get(static::defaultConnectionName()); - } - $schema = $connection->getDriver()->newTableSchema($this->getTable(), $schema); + $schema = $this->getConnection()->getDriver()->newTableSchema($this->getTable(), $schema); foreach ($constraints as $name => $value) { $schema->addConstraint($name, $value); From dcf288e6c3959718d8f9505621e5084d8ece155f Mon Sep 17 00:00:00 2001 From: Val Bancer Date: Mon, 30 Sep 2019 23:26:17 +0200 Subject: [PATCH 1440/2059] Improve code style and documentation, add 'tableSchema' config check --- Table.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Table.php b/Table.php index 26c69992..7897e92e 100644 --- a/Table.php +++ b/Table.php @@ -527,6 +527,7 @@ public function getConnection() if (!$this->_connection) { $this->_connection = ConnectionManager::get(static::defaultConnectionName()); } + return $this->_connection; } From 3d7077cbdfa955fe908d50e7cc576194345c5e2d Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 29 Sep 2019 11:00:33 +0530 Subject: [PATCH 1441/2059] Fix CS errors --- Association.php | 3 ++- Association/HasMany.php | 6 ++++-- Behavior.php | 3 ++- Behavior/CounterCacheBehavior.php | 6 ++++-- Behavior/TimestampBehavior.php | 13 ++++++++++--- Behavior/Translate/EavStrategy.php | 3 ++- EagerLoader.php | 5 +++-- Marshaller.php | 17 ++++++++++++++--- Table.php | 3 ++- 9 files changed, 43 insertions(+), 16 deletions(-) diff --git a/Association.php b/Association.php index c6e390c3..402336d0 100644 --- a/Association.php +++ b/Association.php @@ -309,7 +309,8 @@ public function getCascadeCallbacks(): bool */ public function setClassName(string $className) { - if ($this->_targetTable !== null && + if ( + $this->_targetTable !== null && get_class($this->_targetTable) !== App::className($className, 'Model/Table', 'Table') ) { throw new InvalidArgumentException(sprintf( diff --git a/Association/HasMany.php b/Association/HasMany.php index e02e1d3e..1a765a24 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -149,7 +149,8 @@ public function saveAssociated(EntityInterface $entity, array $options = []) $isEmpty = in_array($targetEntities, [null, [], '', false], true); if ($isEmpty) { - if ($entity->isNew() || + if ( + $entity->isNew() || $this->getSaveStrategy() !== self::SAVE_REPLACE ) { return $entity; @@ -171,7 +172,8 @@ public function saveAssociated(EntityInterface $entity, array $options = []) $options['_sourceTable'] = $this->getSource(); - if ($this->_saveStrategy === self::SAVE_REPLACE && + if ( + $this->_saveStrategy === self::SAVE_REPLACE && !$this->_unlinkAssociated($foreignKeyReference, $entity, $this->getTarget(), $targetEntities, $options) ) { return false; diff --git a/Behavior.php b/Behavior.php index 4b1d2c49..ac5c0d8f 100644 --- a/Behavior.php +++ b/Behavior.php @@ -408,7 +408,8 @@ protected function _reflectionCache(): array foreach ($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { $methodName = $method->getName(); - if (in_array($methodName, $baseMethods, true) || + if ( + in_array($methodName, $baseMethods, true) || isset($eventMethods[$methodName]) ) { continue; diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index b1b2738b..8b40655f 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -136,7 +136,8 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array $registryAlias = $assoc->getTarget()->getRegistryAlias(); $entityAlias = $assoc->getProperty(); - if (!is_callable($config) && + if ( + !is_callable($config) && isset($config['ignoreDirty']) && $config['ignoreDirty'] === true && $entity->$entityAlias->isDirty($field) @@ -233,7 +234,8 @@ protected function _processAssociation( $config = []; } - if (isset($this->_ignoreDirty[$assoc->getTarget()->getRegistryAlias()][$field]) && + if ( + isset($this->_ignoreDirty[$assoc->getTarget()->getRegistryAlias()][$field]) && $this->_ignoreDirty[$assoc->getTarget()->getRegistryAlias()][$field] === true ) { continue; diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 8636fed1..dfeb2913 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -108,9 +108,16 @@ public function handleEvent(EventInterface $event, EntityInterface $entity): boo $when )); } - if ($when === 'always' || - ($when === 'new' && $new) || - ($when === 'existing' && !$new) + if ( + $when === 'always' || + ( + $when === 'new' && + $new + ) || + ( + $when === 'existing' && + !$new + ) ) { $this->_updateField($entity, $field, $refresh); } diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index b30e3ab5..5dd078be 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -172,7 +172,8 @@ public function beforeFind(EventInterface $event, Query $query, ArrayObject $opt return function ($q) use ($field, $locale, $query, $select) { $q->where([$q->getRepository()->aliasField('locale') => $locale]); - if ($query->isAutoFieldsEnabled() || + if ( + $query->isAutoFieldsEnabled() || in_array($field, $select, true) || in_array($this->table->aliasField($field), $select, true) ) { diff --git a/EagerLoader.php b/EagerLoader.php index d71c2126..c4a6a67c 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -492,8 +492,9 @@ protected function _normalizeContain(Table $parent, string $alias, array $option $paths += ['aliasPath' => '', 'propertyPath' => '', 'root' => $alias]; $paths['aliasPath'] .= '.' . $alias; - if (isset($options['matching']) && - $options['matching'] === true + if ( + isset($options['matching']) && + $options['matching'] === true ) { $paths['propertyPath'] = '_matchingData.' . $alias; } else { diff --git a/Marshaller.php b/Marshaller.php index b03cb7ed..f0e85a99 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -561,9 +561,20 @@ public function merge(EntityInterface $entity, array $data, array $options = []) // change. Arrays will always be marked as dirty because // the original/updated list could contain references to the // same objects, even though those objects may have changed internally. - if ((is_scalar($value) && $original === $value) || - ($value === null && $original === $value) || - (is_object($value) && !($value instanceof EntityInterface) && $original === $value) + if ( + ( + is_scalar($value) + && $original === $value + ) + || ( + $value === null + && $original === $value + ) + || ( + is_object($value) + && !($value instanceof EntityInterface) + && $original === $value + ) ) { continue; } diff --git a/Table.php b/Table.php index 3a9a0b0d..7f00c7b9 100644 --- a/Table.php +++ b/Table.php @@ -1283,7 +1283,8 @@ public function findList(Query $query, array $options): Query 'groupField' => null, ]; - if (!$query->clause('select') && + if ( + !$query->clause('select') && !is_object($options['keyField']) && !is_object($options['valueField']) && !is_object($options['groupField']) From f4422173c62d8ee9564d3fc7e40a2da5146a8a95 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 5 Oct 2019 22:26:30 -0400 Subject: [PATCH 1442/2059] Fix BelongsToMany::find() applying joins The association find proxy method should only apply joins if the association has junction conditions. If a custom finder is used then that finder method should apply the required joins. Fixes #13712 --- Association/BelongsToMany.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index a3a2b31f..3a019544 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1036,11 +1036,11 @@ public function find($type = null, array $options = []): Query ->where($this->targetConditions()) ->addDefaultTypes($this->getTarget()); - if (!($this->junctionConditions() || $this->getFinder())) { - return $query; + if ($this->junctionConditions()) { + return $this->_appendJunctionJoin($query); } - return $this->_appendJunctionJoin($query); + return $query; } /** @@ -1159,6 +1159,7 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { $existing = $this->find() ->select($keys) ->where(array_combine($prefixedForeignKey, $primaryValue)); + $existing = $this->_appendJunctionJoin($existing); $jointEntities = $this->_collectJointEntities($sourceEntity, $targetEntities); $inserts = $this->_diffLinks($existing, $jointEntities, $targetEntities, $options); From 30c3040d210460fb1649164c82bbe9ec0e0377a2 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 6 Oct 2019 00:13:45 -0400 Subject: [PATCH 1443/2059] Fix up types from merge. --- Table.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Table.php b/Table.php index d4583e19..ed9c74fa 100644 --- a/Table.php +++ b/Table.php @@ -20,7 +20,6 @@ use BadMethodCallException; use Cake\Core\App; use Cake\Database\Connection; -use Cake\Database\Schema\TableSchema; use Cake\Database\Schema\TableSchemaInterface; use Cake\Database\TypeFactory; use Cake\Datasource\ConnectionInterface; @@ -464,7 +463,7 @@ public function getRegistryAlias(): string * @param \Cake\Database\Connection $connection The connection instance * @return $this */ - public function setConnection(ConnectionInterface $connection) + public function setConnection(Connection $connection) { $this->_connection = $connection; @@ -479,7 +478,9 @@ public function setConnection(ConnectionInterface $connection) public function getConnection(): Connection { if (!$this->_connection) { - $this->_connection = ConnectionManager::get(static::defaultConnectionName()); + /** @var \Cake\Database\Connection $connection */ + $connection = ConnectionManager::get(static::defaultConnectionName()); + $this->_connection = $connection; } return $this->_connection; From b22c83a1ce98f634007a12d36790f78041666c8c Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 7 Oct 2019 08:42:05 +0530 Subject: [PATCH 1444/2059] Fix CS errors --- Table.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Table.php b/Table.php index ed9c74fa..3aaa12b7 100644 --- a/Table.php +++ b/Table.php @@ -22,7 +22,6 @@ use Cake\Database\Connection; use Cake\Database\Schema\TableSchemaInterface; use Cake\Database\TypeFactory; -use Cake\Datasource\ConnectionInterface; use Cake\Datasource\ConnectionManager; use Cake\Datasource\EntityInterface; use Cake\Datasource\Exception\InvalidPrimaryKeyException; From b3fa4e7a6780a13cedbab2199258f5b94374b032 Mon Sep 17 00:00:00 2001 From: mirko-pagliai Date: Mon, 7 Oct 2019 15:02:05 +0200 Subject: [PATCH 1445/2059] `Query::__call()` should be compatible with `QueryTrait::__call()` --- Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Query.php b/Query.php index ca6217f6..14464383 100644 --- a/Query.php +++ b/Query.php @@ -1277,7 +1277,7 @@ public function insert(array $columns, array $types = []) * @return mixed * @throws \BadMethodCallException if the method is called for a non-select query */ - public function __call($method, $arguments) + public function __call(string $method, array $arguments) { if ($this->type() === 'select') { return $this->_call($method, $arguments); From 00fbd1564e0a4ad2f54c4da6599d246add923021 Mon Sep 17 00:00:00 2001 From: mscherer Date: Mon, 14 Oct 2019 21:06:30 +0200 Subject: [PATCH 1446/2059] Remove unneeded inline annotations. Fix self to static. --- LazyEagerLoader.php | 1 - 1 file changed, 1 deletion(-) diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 0b72a07b..daf56bee 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -78,7 +78,6 @@ protected function _getQuery(CollectionInterface $objects, array $contain, Table return $entity->{$method}($primaryKey); }); - /** @var \Cake\ORM\Query $query */ $query = $source ->find() ->select((array)$primaryKey) From b3a45feb8a2f06ed0776522073dc373741bd0fb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgaras=20Janu=C5=A1auskas?= Date: Sun, 20 Oct 2019 12:43:32 +0300 Subject: [PATCH 1447/2059] Add and/or methods on QueryExpression directly --- Marshaller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index f0e85a99..54a5c645 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -411,7 +411,7 @@ protected function _belongsToMany(BelongsToMany $assoc, array $data, array $opti $query = $target->find(); $query->andWhere(function ($exp) use ($conditions) { /** @var \Cake\Database\Expression\QueryExpression $exp */ - return $exp->or_($conditions); + return $exp->or($conditions); }); $keyFields = array_keys($primaryKey); From 07a444e978635164fbe8450129a1e1911f0471a0 Mon Sep 17 00:00:00 2001 From: mscherer Date: Mon, 21 Oct 2019 00:21:20 +0200 Subject: [PATCH 1448/2059] Fix array docblocks. --- AssociationCollection.php | 2 +- RulesChecker.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/AssociationCollection.php b/AssociationCollection.php index 65a35ad9..3384ebfb 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -170,7 +170,7 @@ public function type($class) /** * Get an array of associations matching a specific type. * - * @param string|array $class The type of associations you want. + * @param string|string[] $class The type of associations you want. * For example 'BelongsTo' or array like ['BelongsTo', 'HasOne'] * @return array An array of Association objects. * @since 3.5.3 diff --git a/RulesChecker.php b/RulesChecker.php index 9def175d..1457f676 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -82,7 +82,7 @@ public function isUnique(array $fields, $message = null) * 'message' sets a custom error message. * Set 'allowNullableNulls' to true to accept composite foreign keys where one or more nullable columns are null. * - * @param string|array $field The field or list of fields to check for existence by + * @param string|string[] $field The field or list of fields to check for existence by * primary key lookup in the other table. * @param object|string $table The table name where the fields existence will be checked. * @param string|array|null $message The error message to show in case the rule does not pass. Can From bcf74ce2fc8a146c7cca9bb3511f8fc1ee1d565c Mon Sep 17 00:00:00 2001 From: ndm2 Date: Tue, 30 Jul 2019 15:50:24 +0200 Subject: [PATCH 1449/2059] Add support for link constraint checks. This rule is supposed to help with validating constraints on application level, in order to avoid triggering hard exceptions in the database layer when violating foreign key constraints. --- Rule/LinkConstraint.php | 201 ++++++++++++++++++++++++++++++++++++++++ RulesChecker.php | 133 ++++++++++++++++++++++++++ 2 files changed, 334 insertions(+) create mode 100644 Rule/LinkConstraint.php diff --git a/Rule/LinkConstraint.php b/Rule/LinkConstraint.php new file mode 100644 index 00000000..d38188da --- /dev/null +++ b/Rule/LinkConstraint.php @@ -0,0 +1,201 @@ +_association = $association; + $this->_requiredLinkState = $requiredLinkStatus; + } + + /** + * Callable handler. + * + * Performs the actual link check. + * + * @param \Cake\Datasource\EntityInterface $entity The entity involved in the operation. + * @param array $options Options passed from the rules checker. + * @return bool Whether the check was successful. + */ + public function __invoke(EntityInterface $entity, array $options): bool + { + if ( + !isset($options['repository']) || + !($options['repository'] instanceof Table) + ) { + throw new \InvalidArgumentException( + 'Argument 2 is expected to have a `repository` key that holds an instance of `\Cake\ORM\Table`.' + ); + } + + /** @var \Cake\ORM\Table $table */ + $table = $options['repository']; + + $association = $this->_association; + if (!$association instanceof Association) { + $association = $table->getAssociation($association); + } + + $count = $this->_countLinks($association, $entity); + + if ( + ( + $this->_requiredLinkState === static::STATUS_LINKED && + $count < 1 + ) || + ( + $this->_requiredLinkState === static::STATUS_NOT_LINKED && + $count !== 0 + ) + ) { + return false; + } + + return true; + } + + /** + * Alias fields. + * + * @param array $fields The fields that should be aliased. + * @param \Cake\ORM\Table $source The object to use for aliasing. + * @return array The aliased fields + */ + protected function _aliasFields(array $fields, Table $source): array + { + foreach ($fields as $key => $value) { + $fields[$key] = $source->aliasField($value); + } + + return $fields; + } + + /** + * Build conditions. + * + * @param array $fields The condition fields. + * @param array $values The condition values. + * @return array A conditions array combined from the passed fields and values. + */ + protected function _buildConditions(array $fields, array $values): array + { + if (count($fields) !== count($values)) { + throw new \InvalidArgumentException(sprintf( + 'The number of fields is expected to match the number of values, got %d field(s) and %d value(s).', + count($fields), + count($values) + )); + } + + return array_combine($fields, $values); + } + + /** + * Count links. + * + * @param \Cake\ORM\Association $association The association for which to count links. + * @param \Cake\Datasource\EntityInterface $entity The entity involved in the operation. + * @return int The number of links. + */ + protected function _countLinks(Association $association, EntityInterface $entity): int + { + $source = $association->getSource(); + + $primaryKey = (array)$source->getPrimaryKey(); + if (!$entity->has($primaryKey)) { + throw new \RuntimeException(sprintf( + 'LinkConstraint rule on `%s` requires all primary key values for building the counting ' . + 'conditions, expected values for `(%s)`, got `(%s)`.', + $source->getAlias(), + implode(', ', $primaryKey), + implode(', ', $entity->extract($primaryKey)) + )); + } + + $aliasedPrimaryKey = $this->_aliasFields($primaryKey, $source); + $conditions = $this->_buildConditions( + $aliasedPrimaryKey, + $entity->extract($primaryKey) + ); + + return $source + ->find() + ->matching($association->getName()) + ->where($conditions) + ->count(); + } +} diff --git a/RulesChecker.php b/RulesChecker.php index 444ee469..23a27643 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -20,7 +20,9 @@ use Cake\Datasource\RulesChecker as BaseRulesChecker; use Cake\ORM\Rule\ExistsIn; use Cake\ORM\Rule\IsUnique; +use Cake\ORM\Rule\LinkConstraint; use Cake\ORM\Rule\ValidCount; +use Cake\Utility\Inflector; /** * ORM flavoured rules checker. @@ -108,6 +110,137 @@ public function existsIn($field, $table, $message = null): RuleInvoker return $this->_addError(new ExistsIn($field, $table, $options), '_existsIn', compact('errorField', 'message')); } + /** + * Validates whether links to the given association exist. + * + * ### Example: + * + * ``` + * $rules->update($rules->isLinkedTo('Articles', 'article')); + * ``` + * + * On a `Comments` table that has a `belongsTo Articles` association, this check would ensure that comments + * can only be edited as long as they are associated to an existing article. + * + * @param \Cake\ORM\Association|string $association The association to check for links. + * @param string|null $field The name of the association property. When supplied, this is the name used to set + * possible errors. When absent, the name is inferred from `$association`. + * @param string|null $message The error message to show in case the rule does not pass. + * @return \Cake\Datasource\RuleInvoker + * + * @since 4.0.0 + */ + public function isLinkedTo($association, ?string $field = null, ?string $message = null): RuleInvoker + { + return $this->_addLinkConstraintRule( + $association, + $field, + $message, + LinkConstraint::STATUS_LINKED, + '_isLinkedTo' + ); + } + + /** + * Validates whether links to the given association do not exist. + * + * ### Example: + * + * ``` + * $rules->delete($rules->isNotLinkedTo('Comments', 'comments')); + * ``` + * + * On a `Articles` table that has a `hasMany Comments` association, this check would ensure that articles + * can only be deleted when no associated comments exist. + * + * @param \Cake\ORM\Association|string $association The association to check for links. + * @param string|null $field The name of the association property. When supplied, this is the name used to set + * possible errors. When absent, the name is inferred from `$association`. + * @param string|null $message The error message to show in case the rule does not pass. + * @return \Cake\Datasource\RuleInvoker + * + * @since 4.0.0 + */ + public function isNotLinkedTo($association, ?string $field = null, ?string $message = null): RuleInvoker + { + return $this->_addLinkConstraintRule( + $association, + $field, + $message, + LinkConstraint::STATUS_NOT_LINKED, + '_isNotLinkedTo' + ); + } + + /** + * Adds a link constraint rule. + * + * @param \Cake\ORM\Association|string $association The association to check for links. + * @param string|null $errorField The name of the property to use for setting possible errors. When absent, + * the name is inferred from `$association`. + * @param string|null $message The error message to show in case the rule does not pass. + * @param string $linkStatus The ink status required for the check to pass. + * @param string $ruleName The alias/name of the rule. + * @return \Cake\Datasource\RuleInvoker + * + * @throws \InvalidArgumentException In case the `$association` argument is of an invalid type. + * + * @since 4.0.0 + * + * @see \Cake\ORM\RulesChecker::isLinkedTo() + * @see \Cake\ORM\RulesChecker::isNotLinkedTo() + * @see \Cake\ORM\Rule\LinkConstraint::STATUS_LINKED + * @see \Cake\ORM\Rule\LinkConstraint::STATUS_NOT_LINKED + */ + protected function _addLinkConstraintRule( + $association, + ?string $errorField, + ?string $message, + string $linkStatus, + string $ruleName + ): RuleInvoker { + if ($association instanceof Association) { + $associationAlias = $association->getName(); + + if ($errorField === null) { + $errorField = $association->getProperty(); + } + } elseif (is_string($association)) { + $associationAlias = $association; + + if ($errorField === null) { + $errorField = Inflector::underscore($association); + } + } else { + throw new \InvalidArgumentException(sprintf( + 'Argument 1 is expected to be of type `\Cake\ORM\Association|string`, `%s` given.', + getTypeName($association) + )); + } + + if (!$message) { + if ($this->_useI18n) { + $message = __d( + 'cake', + 'Cannot modify row: a constraint for the `{0}` association fails.', + $associationAlias + ); + } else { + $message = sprintf( + 'Cannot modify row: a constraint for the `%s` association fails.', + $associationAlias + ); + } + } + + $rule = new LinkConstraint( + $association, + $linkStatus + ); + + return $this->_addError($rule, $ruleName, compact('errorField', 'message')); + } + /** * Validates the count of associated records. * From 975d96c7ab336d84435fccfc2eb8c53eca1236f0 Mon Sep 17 00:00:00 2001 From: ndm2 Date: Sat, 26 Oct 2019 17:04:14 +0200 Subject: [PATCH 1450/2059] Improve inferring of error fields for association aliases. --- RulesChecker.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/RulesChecker.php b/RulesChecker.php index 23a27643..b82bcd85 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -209,7 +209,13 @@ protected function _addLinkConstraintRule( $associationAlias = $association; if ($errorField === null) { - $errorField = Inflector::underscore($association); + $repository = $this->_options['repository'] ?? null; + if ($repository instanceof Table) { + $association = $repository->getAssociation($association); + $errorField = $association->getProperty(); + } else { + $errorField = Inflector::underscore($association); + } } } else { throw new \InvalidArgumentException(sprintf( From defeec8c03d1352ba0b463eae280ede019f0012e Mon Sep 17 00:00:00 2001 From: ndm2 Date: Sat, 26 Oct 2019 17:06:16 +0200 Subject: [PATCH 1451/2059] Simplify repository check. --- Rule/LinkConstraint.php | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Rule/LinkConstraint.php b/Rule/LinkConstraint.php index d38188da..6a25ba23 100644 --- a/Rule/LinkConstraint.php +++ b/Rule/LinkConstraint.php @@ -93,18 +93,13 @@ public function __construct($association, string $requiredLinkStatus) */ public function __invoke(EntityInterface $entity, array $options): bool { - if ( - !isset($options['repository']) || - !($options['repository'] instanceof Table) - ) { + $table = $options['repository'] ?? null; + if (!($table instanceof Table)) { throw new \InvalidArgumentException( 'Argument 2 is expected to have a `repository` key that holds an instance of `\Cake\ORM\Table`.' ); } - /** @var \Cake\ORM\Table $table */ - $table = $options['repository']; - $association = $this->_association; if (!$association instanceof Association) { $association = $table->getAssociation($association); From 754a1a553cd0cdf5e61533179b9e6cda5ddde6fa Mon Sep 17 00:00:00 2001 From: ndm2 Date: Mon, 28 Oct 2019 14:30:44 +0100 Subject: [PATCH 1452/2059] Fix docblock code snippets. --- RulesChecker.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RulesChecker.php b/RulesChecker.php index b82bcd85..b46348be 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -116,7 +116,7 @@ public function existsIn($field, $table, $message = null): RuleInvoker * ### Example: * * ``` - * $rules->update($rules->isLinkedTo('Articles', 'article')); + * $rules->addUpdate($rules->isLinkedTo('Articles', 'article')); * ``` * * On a `Comments` table that has a `belongsTo Articles` association, this check would ensure that comments @@ -147,7 +147,7 @@ public function isLinkedTo($association, ?string $field = null, ?string $message * ### Example: * * ``` - * $rules->delete($rules->isNotLinkedTo('Comments', 'comments')); + * $rules->addDelete($rules->isNotLinkedTo('Comments', 'comments')); * ``` * * On a `Articles` table that has a `hasMany Comments` association, this check would ensure that articles From 899e0338b0e9e6f5fc84e7d3a3df185f4aa14a12 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 28 Oct 2019 09:50:50 -0400 Subject: [PATCH 1453/2059] Fix examples --- RulesChecker.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RulesChecker.php b/RulesChecker.php index b82bcd85..b46348be 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -116,7 +116,7 @@ public function existsIn($field, $table, $message = null): RuleInvoker * ### Example: * * ``` - * $rules->update($rules->isLinkedTo('Articles', 'article')); + * $rules->addUpdate($rules->isLinkedTo('Articles', 'article')); * ``` * * On a `Comments` table that has a `belongsTo Articles` association, this check would ensure that comments @@ -147,7 +147,7 @@ public function isLinkedTo($association, ?string $field = null, ?string $message * ### Example: * * ``` - * $rules->delete($rules->isNotLinkedTo('Comments', 'comments')); + * $rules->addDelete($rules->isNotLinkedTo('Comments', 'comments')); * ``` * * On a `Articles` table that has a `hasMany Comments` association, this check would ensure that articles From 6f9dfe3edd499796279b4bbf2c0ebf611ab5628b Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 30 Oct 2019 00:10:41 +0530 Subject: [PATCH 1454/2059] Use constants where possible. --- Query.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Query.php b/Query.php index 14464383..afc08582 100644 --- a/Query.php +++ b/Query.php @@ -839,12 +839,12 @@ public function cleanCopy() $clone = clone $this; $clone->setEagerLoader(clone $this->getEagerLoader()); $clone->triggerBeforeFind(); - $clone->enableAutoFields(false); + $clone->disableAutoFields(); $clone->limit(null); $clone->order([], true); $clone->offset(null); $clone->mapReduce(null, null, true); - $clone->formatResults(null, true); + $clone->formatResults(null, self::OVERWRITE); $clone->setSelectTypeMap(new TypeMap()); $clone->decorateResults(null, true); From e027a897dd126c157340350d1656c3bf87bfa3e1 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 30 Oct 2019 00:58:24 +0530 Subject: [PATCH 1455/2059] Ensure Table::save() always returns documented type. --- Table.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 3aaa12b7..b39bf962 100644 --- a/Table.php +++ b/Table.php @@ -1832,7 +1832,19 @@ protected function _processSave(EntityInterface $entity, ArrayObject $options) $event = $this->dispatchEvent('Model.beforeSave', compact('entity', 'options')); if ($event->isStopped()) { - return $event->getResult(); + $result = $event->getResult(); + if ($result === null) { + return false; + } + + if ($result !== false && !($result instanceof EntityInterface)) { + throw new RuntimeException(sprintf( + 'beforeSave callback must return `false` or `EntityInterface` instance. Got `%s` instead.', + getTypeName($result) + )); + } + + return $result; } $saved = $this->_associations->saveParents( From 31163c9ecea5c85e70532ce2e4520cc34a3a28e0 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 30 Oct 2019 09:32:20 +0530 Subject: [PATCH 1456/2059] Update exception message --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index b39bf962..f6910218 100644 --- a/Table.php +++ b/Table.php @@ -1839,7 +1839,7 @@ protected function _processSave(EntityInterface $entity, ArrayObject $options) if ($result !== false && !($result instanceof EntityInterface)) { throw new RuntimeException(sprintf( - 'beforeSave callback must return `false` or `EntityInterface` instance. Got `%s` instead.', + 'The beforeSave callback must return `false` or `EntityInterface` instance. Got `%s` instead.', getTypeName($result) )); } From 3d34232172644ad66e7eb7978d5c77bf94b2f73a Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Tue, 29 Oct 2019 23:06:05 -0500 Subject: [PATCH 1457/2059] Updated links from book.cakephp.org/3.0/ to book.cakephp.org/3/ --- Behavior/TranslateBehavior.php | 4 ++-- README.md | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 013d408f..d4d880b4 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -431,8 +431,8 @@ public function buildMarshalMap($marshaller, $map, $options) * globally configured locale. * @return $this * @see \Cake\ORM\Behavior\TranslateBehavior::getLocale() - * @link https://book.cakephp.org/3.0/en/orm/behaviors/translate.html#retrieving-one-language-without-using-i18n-locale - * @link https://book.cakephp.org/3.0/en/orm/behaviors/translate.html#saving-in-another-language + * @link https://book.cakephp.org/3/en/orm/behaviors/translate.html#retrieving-one-language-without-using-i18n-locale + * @link https://book.cakephp.org/3/en/orm/behaviors/translate.html#saving-in-another-language */ public function setLocale($locale) { diff --git a/README.md b/README.md index 19ac4a00..3990b502 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ supports 4 association types out of the box: * belongsToMany - E.g. An article belongsToMany tags. You define associations in your table's `initialize()` method. See the -[documentation](https://book.cakephp.org/3.0/en/orm/associations.html) for +[documentation](https://book.cakephp.org/3/en/orm/associations.html) for complete examples. ## Reading Data @@ -99,8 +99,8 @@ foreach ($articles->find() as $article) { } ``` -You can use the [query builder](https://book.cakephp.org/3.0/en/orm/query-builder.html) to create -complex queries, and a [variety of methods](https://book.cakephp.org/3.0/en/orm/retrieving-data-and-resultsets.html) +You can use the [query builder](https://book.cakephp.org/3/en/orm/query-builder.html) to create +complex queries, and a [variety of methods](https://book.cakephp.org/3/en/orm/retrieving-data-and-resultsets.html) to access your data. ## Saving Data @@ -134,7 +134,7 @@ $articles->save($article, [ ``` The above shows how you can easily marshal and save an entity and its -associations in a simple & powerful way. Consult the [ORM documentation](https://book.cakephp.org/3.0/en/orm/saving-data.html) +associations in a simple & powerful way. Consult the [ORM documentation](https://book.cakephp.org/3/en/orm/saving-data.html) for more in-depth examples. ## Deleting Data @@ -234,5 +234,5 @@ Configure::write('App.namespace', 'My\Log\SubNamespace'); ## Additional Documentation -Consult [the CakePHP ORM documentation](https://book.cakephp.org/3.0/en/orm.html) +Consult [the CakePHP ORM documentation](https://book.cakephp.org/3/en/orm.html) for more in-depth documentation. From a21c2495c6789335e88e2fd548e9d494b49261f7 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 30 Oct 2019 13:36:51 +0530 Subject: [PATCH 1458/2059] Document and fix the ability to disable default locale feature. --- Behavior/Translate/EavStrategy.php | 28 ++++++++++++---------- Behavior/Translate/ShadowTableStrategy.php | 20 +++++++++------- Behavior/TranslateBehavior.php | 21 +++++++++++++++- 3 files changed, 48 insertions(+), 21 deletions(-) diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index 5dd078be..13a39958 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -278,18 +278,22 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array } $model = $this->_config['referenceName']; - /** @psalm-suppress UndefinedClass */ - $preexistent = $this->translationTable->find() - ->select(['id', 'field']) - ->where([ - 'field IN' => $fields, - 'locale' => $locale, - 'foreign_key' => $key, - 'model' => $model, - ]) - ->disableBufferedResults() - ->all() - ->indexBy('field'); + + $preexistent = []; + if ($key) { + /** @psalm-suppress UndefinedClass */ + $preexistent = $this->translationTable->find() + ->select(['id', 'field']) + ->where([ + 'field IN' => $fields, + 'locale' => $locale, + 'foreign_key' => $key, + 'model' => $model, + ]) + ->disableBufferedResults() + ->all() + ->indexBy('field'); + } $modified = []; foreach ($preexistent as $field => $translation) { diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 120a9581..7cd0624c 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -358,14 +358,18 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array return; } - $where = ['id' => $id, 'locale' => $locale]; - - /** @var \Cake\Datasource\EntityInterface|null $translation */ - $translation = $this->translationTable->find() - ->select(array_merge(['id', 'locale'], $fields)) - ->where($where) - ->disableBufferedResults() - ->first(); + $where = ['locale' => $locale]; + $translation = null; + if ($id) { + $where['id'] = $id; + + /** @var \Cake\Datasource\EntityInterface|null $translation */ + $translation = $this->translationTable->find() + ->select(array_merge(['id', 'locale'], $fields)) + ->where($where) + ->disableBufferedResults() + ->first(); + } if ($translation) { $translation->set($values); diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 70efb305..b73b3ad7 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -55,7 +55,7 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface 'translationField' => 'translationField', ], 'fields' => [], - 'defaultLocale' => '', + 'defaultLocale' => null, 'referenceName' => '', 'allowEmptyTranslations' => true, 'onlyTranslated' => false, @@ -81,6 +81,25 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface /** * Constructor * + * ### Options + * - `fields`: List of fields which need to be translated. Providing this fields + * list is mandatory when using `EavStrategy`. If the fields list is empty when + * using `ShadowTableStrategy` then the list will be auto generated based on + * shadow table schema. + * - `defaultLocale`: The locale which is treated as default by the behavior. + * Fields values for defaut locale will be stored in the primary table itself + * and the rest in translation table. If not explicitly set the value of + * `I18n::getDefaultLocale()` will be used to get default locale. + * If you do not want any default locale and want translated fields + * for all locales to be stored in translation table then set this config + * to empty string `''`. + * - `allowEmptyTranslations`: By default if a record has been translated and + * stored as an empty string the translate behavior will take and use this + * value to overwrite the original field value. If you don't want this behavior + * then set this option to `false`. + * - `validator`: The validator that should be used when translation records + * are created/modified. Default `null`. + * * @param \Cake\ORM\Table $table The table this behavior is attached to. * @param array $config The config for this behavior. */ From 59faa52df0b1520a05127ba36357953e71a404d7 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Thu, 31 Oct 2019 16:57:08 -0500 Subject: [PATCH 1459/2059] Updated all book.cakephp.org links to /4/ --- Behavior/TranslateBehavior.php | 4 ++-- README.md | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 3ae0b08c..6a09ce47 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -238,8 +238,8 @@ public function buildMarshalMap(Marshaller $marshaller, array $map, array $optio * globally configured locale. * @return $this * @see \Cake\ORM\Behavior\TranslateBehavior::getLocale() - * @link https://book.cakephp.org/3/en/orm/behaviors/translate.html#retrieving-one-language-without-using-i18n-locale - * @link https://book.cakephp.org/3/en/orm/behaviors/translate.html#saving-in-another-language + * @link https://book.cakephp.org/4/en/orm/behaviors/translate.html#retrieving-one-language-without-using-i18n-locale + * @link https://book.cakephp.org/4/en/orm/behaviors/translate.html#saving-in-another-language */ public function setLocale(?string $locale) { diff --git a/README.md b/README.md index 2b872318..0329241c 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ supports 4 association types out of the box: * belongsToMany - E.g. An article belongsToMany tags. You define associations in your table's `initialize()` method. See the -[documentation](https://book.cakephp.org/3/en/orm/associations.html) for +[documentation](https://book.cakephp.org/4/en/orm/associations.html) for complete examples. ## Reading Data @@ -99,8 +99,8 @@ foreach ($articles->find() as $article) { } ``` -You can use the [query builder](https://book.cakephp.org/3/en/orm/query-builder.html) to create -complex queries, and a [variety of methods](https://book.cakephp.org/3/en/orm/retrieving-data-and-resultsets.html) +You can use the [query builder](https://book.cakephp.org/4/en/orm/query-builder.html) to create +complex queries, and a [variety of methods](https://book.cakephp.org/4/en/orm/retrieving-data-and-resultsets.html) to access your data. ## Saving Data @@ -134,7 +134,7 @@ $articles->save($article, [ ``` The above shows how you can easily marshal and save an entity and its -associations in a simple & powerful way. Consult the [ORM documentation](https://book.cakephp.org/3/en/orm/saving-data.html) +associations in a simple & powerful way. Consult the [ORM documentation](https://book.cakephp.org/4/en/orm/saving-data.html) for more in-depth examples. ## Deleting Data @@ -234,5 +234,5 @@ Configure::write('App.namespace', 'My\Log\SubNamespace'); ## Additional Documentation -Consult [the CakePHP ORM documentation](https://book.cakephp.org/3/en/orm.html) +Consult [the CakePHP ORM documentation](https://book.cakephp.org/4/en/orm.html) for more in-depth documentation. From e113351b55857a90ea5dc3acb5aa1147ae72857b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgaras=20Janu=C5=A1auskas?= Date: Thu, 7 Nov 2019 21:58:57 +0200 Subject: [PATCH 1460/2059] Promote cleaner names of QueryExpression::and()/or() without _ --- Association/BelongsToMany.php | 5 +++-- Marshaller.php | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index aaf1d5b1..bcbc7ecf 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -17,6 +17,7 @@ use Cake\Core\App; use Cake\Database\ExpressionInterface; use Cake\Database\Expression\IdentifierExpression; +use Cake\Database\Expression\QueryExpression; use Cake\Datasource\EntityInterface; use Cake\Datasource\QueryInterface; use Cake\ORM\Association; @@ -518,7 +519,7 @@ protected function _appendNotMatching($query, $options) $subquery = $this->_appendJunctionJoin($subquery, $conditions); $query - ->andWhere(function ($exp) use ($subquery, $conds) { + ->andWhere(function (QueryExpression $exp) use ($subquery, $conds) { $identifiers = []; foreach (array_keys($conds) as $field) { $identifiers[] = new IdentifierExpression($field); @@ -527,7 +528,7 @@ protected function _appendNotMatching($query, $options) $nullExp = clone $exp; return $exp - ->or_([ + ->or([ $exp->notIn($identifiers, $subquery), $nullExp->and(array_map([$nullExp, 'isNull'], array_keys($conds))), ]); diff --git a/Marshaller.php b/Marshaller.php index cb66a5d6..1d69f776 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -418,7 +418,7 @@ protected function _belongsToMany(BelongsToMany $assoc, array $data, $options = $query = $target->find(); $query->andWhere(function ($exp) use ($conditions) { /** @var \Cake\Database\Expression\QueryExpression $exp */ - return $exp->or_($conditions); + return $exp->or($conditions); }); $keyFields = array_keys($primaryKey); From 78858387e96576dd592e812255519b2aac5f9ccf Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 10 Nov 2019 02:40:35 +0530 Subject: [PATCH 1461/2059] Update psalm's annotations --- BehaviorRegistry.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 6c4849b6..479a277c 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -87,6 +87,7 @@ public function setTable(Table $table): void * @param string $class Partial classname to resolve. * @return string|null Either the correct classname or null. * @since 3.5.7 + * @psalm-return class-string */ public static function className(string $class): ?string { @@ -101,6 +102,7 @@ public static function className(string $class): ?string * * @param string $class Partial classname to resolve. * @return string|null Either the correct class name or null. + * @psalm-return class-string */ protected function _resolveClassName(string $class): ?string { From 6a8f720663ccce1fbbc429d818e7d90e532b9074 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 14 Nov 2019 02:14:45 +0530 Subject: [PATCH 1462/2059] Remove unneeded variable assignments. --- Behavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior.php b/Behavior.php index ac5c0d8f..d1562d0b 100644 --- a/Behavior.php +++ b/Behavior.php @@ -382,7 +382,7 @@ protected function _reflectionCache(): array $events = $this->implementedEvents(); $eventMethods = []; - foreach ($events as $e => $binding) { + foreach ($events as $binding) { if (is_array($binding) && isset($binding['callable'])) { /** @var string $callable */ $callable = $binding['callable']; From b0f4df168260917c5af79a94e0d843b73e66feb9 Mon Sep 17 00:00:00 2001 From: mscherer Date: Tue, 19 Nov 2019 02:17:33 +0100 Subject: [PATCH 1463/2059] Backport 4.x docblock fixes. --- Association.php | 6 +++--- Association/BelongsTo.php | 4 ++-- Association/BelongsToMany.php | 4 ++-- Association/HasMany.php | 4 ++-- Association/HasOne.php | 4 ++-- Association/Loader/SelectLoader.php | 2 +- Behavior.php | 4 ++-- Behavior/TranslateBehavior.php | 10 +++++----- Behavior/TreeBehavior.php | 18 +++++++++--------- EagerLoader.php | 6 +++--- Locator/TableLocator.php | 2 +- Query.php | 6 +++--- ResultSet.php | 6 +++--- 13 files changed, 38 insertions(+), 38 deletions(-) diff --git a/Association.php b/Association.php index 2180b64f..c48d12cb 100644 --- a/Association.php +++ b/Association.php @@ -187,7 +187,7 @@ abstract class Association /** * Valid strategies for this association. Subclasses can narrow this down. * - * @var array + * @var string[] */ protected $_validStrategies = [ self::STRATEGY_JOIN, @@ -1280,7 +1280,7 @@ protected function _formatAssociationResults($query, $surrogate, $options) $extracted = new ResultSetDecorator($callable($extracted)); } - /* @var \Cake\Collection\CollectionInterface $results */ + /** @var \Cake\Collection\CollectionInterface $results */ return $results->insert($property, $extracted); }, Query::PREPEND); } @@ -1522,7 +1522,7 @@ abstract public function isOwningSide(Table $side); * * @param \Cake\Datasource\EntityInterface $entity the data to be saved * @param array $options The options for saving associated data. - * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns + * @return \Cake\Datasource\EntityInterface|false False if $entity could not be saved, otherwise it returns * the saved entity * @see \Cake\ORM\Table::save() */ diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 76bea951..3e915a66 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -33,7 +33,7 @@ class BelongsTo extends Association /** * Valid strategies for this type of association * - * @var array + * @var string[] */ protected $_validStrategies = [ self::STRATEGY_JOIN, @@ -111,7 +111,7 @@ public function type() * * @param \Cake\Datasource\EntityInterface $entity an entity from the source table * @param array $options options to be passed to the save method in the target table - * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns + * @return \Cake\Datasource\EntityInterface|false False if $entity could not be saved, otherwise it returns * the saved entity * @see \Cake\ORM\Table::save() */ diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index bcbc7ecf..16cebbb5 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -119,7 +119,7 @@ class BelongsToMany extends Association /** * Valid strategies for this type of association * - * @var array + * @var string[] */ protected $_validStrategies = [ self::STRATEGY_SELECT, @@ -704,7 +704,7 @@ public function saveStrategy($strategy = null) * @param array $options options to be passed to the save method in the target table * @throws \InvalidArgumentException if the property representing the association * in the parent entity cannot be traversed - * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns + * @return \Cake\Datasource\EntityInterface|false False if $entity could not be saved, otherwise it returns * the saved entity * @see \Cake\ORM\Table::save() * @see \Cake\ORM\Association\BelongsToMany::replaceLinks() diff --git a/Association/HasMany.php b/Association/HasMany.php index 4528ab29..dd7a7dc3 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -59,7 +59,7 @@ class HasMany extends Association /** * Valid strategies for this type of association * - * @var array + * @var string[] */ protected $_validStrategies = [ self::STRATEGY_SELECT, @@ -159,7 +159,7 @@ public function saveStrategy($strategy = null) * * @param \Cake\Datasource\EntityInterface $entity an entity from the source table * @param array $options options to be passed to the save method in the target table - * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns + * @return \Cake\Datasource\EntityInterface|false False if $entity could not be saved, otherwise it returns * the saved entity * @see \Cake\ORM\Table::save() * @throws \InvalidArgumentException when the association data cannot be traversed. diff --git a/Association/HasOne.php b/Association/HasOne.php index 88e6b49f..279502e4 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -32,7 +32,7 @@ class HasOne extends Association /** * Valid strategies for this type of association * - * @var array + * @var string[] */ protected $_validStrategies = [ self::STRATEGY_JOIN, @@ -96,7 +96,7 @@ public function type() * * @param \Cake\Datasource\EntityInterface $entity an entity from the source table * @param array $options options to be passed to the save method in the target table - * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns + * @return \Cake\Datasource\EntityInterface|false False if $entity could not be saved, otherwise it returns * the saved entity * @see \Cake\ORM\Table::save() */ diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index be59f5a8..45357ce0 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -162,7 +162,7 @@ protected function _buildQuery($options) $options['fields'] = []; } - /* @var \Cake\ORM\Query $query */ + /** @var \Cake\ORM\Query $query */ $query = $finder(); if (isset($options['finder'])) { list($finderName, $opts) = $this->_extractFinder($options['finder']); diff --git a/Behavior.php b/Behavior.php index 963a3e12..ee09ca21 100644 --- a/Behavior.php +++ b/Behavior.php @@ -378,14 +378,14 @@ protected function _reflectionCache() $eventMethods = []; foreach ($events as $e => $binding) { if (is_array($binding) && isset($binding['callable'])) { - /* @var string $callable */ + /** @var string $callable */ $callable = $binding['callable']; $binding = $callable; } $eventMethods[$binding] = true; } - $baseClass = 'Cake\ORM\Behavior'; + $baseClass = self::class; if (isset(self::$_reflectionCache[$baseClass])) { $baseMethods = self::$_reflectionCache[$baseClass]; } else { diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index d4d880b4..c0af4d47 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -218,10 +218,10 @@ public function beforeFind(Event $event, Query $query, $options) $conditions = function ($field, $locale, $query, $select) { return function ($q) use ($field, $locale, $query, $select) { - /* @var \Cake\Datasource\QueryInterface $q */ + /** @var \Cake\Datasource\QueryInterface $q */ $q->where([$q->getRepository()->aliasField('locale') => $locale]); - /* @var \Cake\ORM\Query $query */ + /** @var \Cake\ORM\Query $query */ if ($query->isAutoFieldsEnabled() || in_array($field, $select, true) || in_array($this->_table->aliasField($field), $select, true) @@ -387,7 +387,7 @@ public function buildMarshalMap($marshaller, $map, $options) return [ '_translations' => function ($value, $entity) use ($marshaller, $options) { - /* @var \Cake\Datasource\EntityInterface $entity */ + /** @var \Cake\Datasource\EntityInterface $entity */ $translations = $entity->get('_translations'); foreach ($this->_config['fields'] as $field) { $options['validate'] = $this->_config['validator']; @@ -534,7 +534,7 @@ public function findTranslations(Query $query, array $options) return $query ->contain([$targetAlias => function ($query) use ($locales, $targetAlias) { if ($locales) { - /* @var \Cake\Datasource\QueryInterface $query */ + /** @var \Cake\Datasource\QueryInterface $query */ $query->where(["$targetAlias.locale IN" => $locales]); } @@ -601,7 +601,7 @@ protected function _rowMapper($results, $locale) $row['_locale'] = $locale; if ($hydrated) { - /* @var \Cake\Datasource\EntityInterface $row */ + /** @var \Cake\Datasource\EntityInterface $row */ $row->clean(); } diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index b5ebc5c1..e31b3f68 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -194,7 +194,7 @@ protected function _setChildrenLevel($entity) 'order' => $config['left'], ]); - /* @var \Cake\Datasource\EntityInterface $node */ + /** @var \Cake\Datasource\EntityInterface $node */ foreach ($children as $node) { $parentIdValue = $node->get($config['parent']); $depth = $depths[$parentIdValue] + 1; @@ -226,7 +226,7 @@ public function beforeDelete(Event $event, EntityInterface $entity) $query = $this->_scope($this->_table->query()) ->delete() ->where(function ($exp) use ($config, $left, $right) { - /* @var \Cake\Database\Expression\QueryExpression $exp */ + /** @var \Cake\Database\Expression\QueryExpression $exp */ return $exp ->gte($config['leftField'], $left + 1) ->lte($config['leftField'], $right - 1); @@ -346,7 +346,7 @@ protected function _unmarkInternalTree() $config = $this->getConfig(); $this->_table->updateAll( function ($exp) use ($config) { - /* @var \Cake\Database\Expression\QueryExpression $exp */ + /** @var \Cake\Database\Expression\QueryExpression $exp */ $leftInverse = clone $exp; $leftInverse->setConjunction('*')->add('-1'); $rightInverse = clone $leftInverse; @@ -356,7 +356,7 @@ function ($exp) use ($config) { ->eq($config['rightField'], $rightInverse->add($config['rightField'])); }, function ($exp) use ($config) { - /* @var \Cake\Database\Expression\QueryExpression $exp */ + /** @var \Cake\Database\Expression\QueryExpression $exp */ return $exp->lt($config['leftField'], 0); } ); @@ -520,7 +520,7 @@ public function findTreeList(Query $query, array $options) public function formatTreeList(Query $query, array $options = []) { return $query->formatResults(function ($results) use ($options) { - /* @var \Cake\Collection\CollectionTrait $results */ + /** @var \Cake\Collection\CollectionTrait $results */ $options += [ 'keyPath' => $this->_getPrimaryKey(), 'valuePath' => $this->_table->getDisplayField(), @@ -638,7 +638,7 @@ protected function _moveUp($node, $number) ->select([$left, $right]) ->where(["$parent IS" => $nodeParent]) ->where(function ($exp) use ($config, $nodeLeft) { - /* @var \Cake\Database\Expression\QueryExpression $exp */ + /** @var \Cake\Database\Expression\QueryExpression $exp */ return $exp->lt($config['rightField'], $nodeLeft); }) ->orderDesc($config['leftField']) @@ -651,7 +651,7 @@ protected function _moveUp($node, $number) ->select([$left, $right]) ->where(["$parent IS" => $nodeParent]) ->where(function ($exp) use ($config, $nodeLeft) { - /* @var \Cake\Database\Expression\QueryExpression $exp */ + /** @var \Cake\Database\Expression\QueryExpression $exp */ return $exp->lt($config['rightField'], $nodeLeft); }) ->orderAsc($config['leftField']) @@ -728,7 +728,7 @@ protected function _moveDown($node, $number) ->select([$left, $right]) ->where(["$parent IS" => $nodeParent]) ->where(function ($exp) use ($config, $nodeRight) { - /* @var \Cake\Database\Expression\QueryExpression $exp */ + /** @var \Cake\Database\Expression\QueryExpression $exp */ return $exp->gt($config['leftField'], $nodeRight); }) ->orderAsc($config['leftField']) @@ -741,7 +741,7 @@ protected function _moveDown($node, $number) ->select([$left, $right]) ->where(["$parent IS" => $nodeParent]) ->where(function ($exp) use ($config, $nodeRight) { - /* @var \Cake\Database\Expression\QueryExpression $exp */ + /** @var \Cake\Database\Expression\QueryExpression $exp */ return $exp->gt($config['leftField'], $nodeRight); }) ->orderDesc($config['leftField']) diff --git a/EagerLoader.php b/EagerLoader.php index 40a045cc..52a9c083 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -611,7 +611,7 @@ protected function _fixStrategies() if (count($configs) < 2) { continue; } - /* @var \Cake\ORM\EagerLoadable $loadable */ + /** @var \Cake\ORM\EagerLoadable $loadable */ foreach ($configs as $loadable) { if (strpos($loadable->aliasPath(), '.')) { $this->_correctStrategy($loadable); @@ -765,7 +765,7 @@ public function associationsMap($table) */ protected function _buildAssociationsMap($map, $level, $matching = false) { - /* @var \Cake\ORM\EagerLoadable $meta */ + /** @var \Cake\ORM\EagerLoadable $meta */ foreach ($level as $assoc => $meta) { $canBeJoined = $meta->canBeJoined(); $instance = $meta->instance(); @@ -825,7 +825,7 @@ public function addToJoinsMap($alias, Association $assoc, $asMatching = false, $ protected function _collectKeys($external, $query, $statement) { $collectKeys = []; - /* @var \Cake\ORM\EagerLoadable $meta */ + /** @var \Cake\ORM\EagerLoadable $meta */ foreach ($external as $meta) { $instance = $meta->instance(); if (!$instance->requiresKeys($meta->getConfig())) { diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 74a30660..e6f7fa9a 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -234,7 +234,7 @@ public function get($alias, array $options = []) if (!empty($options['connectionName'])) { $connectionName = $options['connectionName']; } else { - /* @var \Cake\ORM\Table $className */ + /** @var \Cake\ORM\Table $className */ $className = $options['className']; $connectionName = $className::defaultConnectionName(); } diff --git a/Query.php b/Query.php index dc8e31dd..6e1623a8 100644 --- a/Query.php +++ b/Query.php @@ -43,9 +43,9 @@ * @method \Cake\Collection\CollectionInterface extract($field) Extracts a single column from each row * @method mixed max($field, $type = SORT_NUMERIC) Returns the maximum value for a single column in all the results. * @method mixed min($field, $type = SORT_NUMERIC) Returns the minimum value for a single column in all the results. - * @method \Cake\Collection\CollectionInterface groupBy(string|callable $field) In-memory group all results by the value of a column. - * @method \Cake\Collection\CollectionInterface indexBy(string|callable $field) Returns the results indexed by the value of a column. - * @method \Cake\Collection\CollectionInterface countBy(string|callable $field) Returns the number of unique values for a column + * @method \Cake\Collection\CollectionInterface groupBy(string|callable $callable) In-memory group all results by the value of a column. + * @method \Cake\Collection\CollectionInterface indexBy(string|callable $callable) Returns the results indexed by the value of a column. + * @method \Cake\Collection\CollectionInterface countBy(string|callable $callable) Returns the number of unique values for a column * @method float sumOf(string|callable $field) Returns the sum of all values for a single column * @method \Cake\Collection\CollectionInterface shuffle() In-memory randomize the order the results are returned * @method \Cake\Collection\CollectionInterface sample($size = 10) In-memory shuffle the results and return a subset of them. diff --git a/ResultSet.php b/ResultSet.php index 882b91eb..ff3e1345 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -515,10 +515,10 @@ protected function _groupResult($row) array_intersect_key($row, $keys) ); if ($this->_hydrate) { - /* @var \Cake\ORM\Table $table */ + /** @var \Cake\ORM\Table $table */ $table = $matching['instance']; $options['source'] = $table->getRegistryAlias(); - /* @var \Cake\Datasource\EntityInterface $entity */ + /** @var \Cake\Datasource\EntityInterface $entity */ $entity = new $matching['entityClass']($results['_matchingData'][$alias], $options); $results['_matchingData'][$alias] = $entity; } @@ -545,7 +545,7 @@ protected function _groupResult($row) continue; } - /* @var \Cake\ORM\Association $instance */ + /** @var \Cake\ORM\Association $instance */ $instance = $assoc['instance']; if (!$assoc['canBeJoined'] && !isset($row[$alias])) { From 8e48adf4fe32e0f5d8cd225a144fc551f92a4ac4 Mon Sep 17 00:00:00 2001 From: mscherer Date: Tue, 19 Nov 2019 02:35:22 +0100 Subject: [PATCH 1464/2059] Forward port 3.next into 4.x. --- Association/BelongsToMany.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 3a019544..d7dc193f 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -19,6 +19,7 @@ use Cake\Core\App; use Cake\Database\Expression\IdentifierExpression; use Cake\Database\ExpressionInterface; +use Cake\Database\Expression\QueryExpression; use Cake\Datasource\EntityInterface; use Cake\ORM\Association; use Cake\ORM\Association\Loader\SelectWithPivotLoader; @@ -472,7 +473,7 @@ protected function _appendNotMatching(Query $query, array $options): void $subquery = $this->_appendJunctionJoin($subquery); $query - ->andWhere(function ($exp) use ($subquery, $conds) { + ->andWhere(function (QueryExpression $exp) use ($subquery, $conds) { $identifiers = []; foreach (array_keys($conds) as $field) { $identifiers[] = new IdentifierExpression($field); @@ -481,7 +482,7 @@ protected function _appendNotMatching(Query $query, array $options): void $nullExp = clone $exp; return $exp - ->or_([ + ->or([ $exp->notIn($identifiers, $subquery), $nullExp->and(array_map([$nullExp, 'isNull'], array_keys($conds))), ]); From b27c440cf8fd26b5447a1ccb3e0e6c891bdbe115 Mon Sep 17 00:00:00 2001 From: mscherer Date: Tue, 19 Nov 2019 14:39:32 +0100 Subject: [PATCH 1465/2059] Fix CS for easier 3.x/4.x comparison/merge. --- Association.php | 6 +++--- Association/BelongsTo.php | 4 ++-- Association/BelongsToMany.php | 20 ++++++++++---------- Association/HasMany.php | 12 ++++++------ Association/HasOne.php | 4 ++-- Association/Loader/SelectLoader.php | 2 +- AssociationCollection.php | 2 +- Behavior.php | 4 ++-- Behavior/TimestampBehavior.php | 8 ++++---- Behavior/TranslateBehavior.php | 22 +++++++++++----------- BehaviorRegistry.php | 2 +- EagerLoadable.php | 6 +++--- EagerLoader.php | 14 +++++++------- Entity.php | 4 ++-- Query.php | 10 +++++----- ResultSet.php | 2 +- Table.php | 18 +++++++++--------- 17 files changed, 70 insertions(+), 70 deletions(-) diff --git a/Association.php b/Association.php index c48d12cb..a7eec733 100644 --- a/Association.php +++ b/Association.php @@ -192,7 +192,7 @@ abstract class Association protected $_validStrategies = [ self::STRATEGY_JOIN, self::STRATEGY_SELECT, - self::STRATEGY_SUBQUERY + self::STRATEGY_SUBQUERY, ]; /** @@ -216,7 +216,7 @@ public function __construct($alias, array $options = []) 'tableLocator', 'propertyName', 'sourceTable', - 'targetTable' + 'targetTable', ]; foreach ($defaults as $property) { if (isset($options[$property])) { @@ -993,7 +993,7 @@ public function attachTo(Query $query, array $options = []) 'fields' => [], 'type' => $joinType, 'table' => $table, - 'finder' => $this->getFinder() + 'finder' => $this->getFinder(), ]; if (!empty($options['foreignKey'])) { diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 3e915a66..86098832 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -37,7 +37,7 @@ class BelongsTo extends Association */ protected $_validStrategies = [ self::STRATEGY_JOIN, - self::STRATEGY_SELECT + self::STRATEGY_SELECT, ]; /** @@ -193,7 +193,7 @@ public function eagerLoader(array $options) 'bindingKey' => $this->getBindingKey(), 'strategy' => $this->getStrategy(), 'associationType' => $this->type(), - 'finder' => [$this, 'find'] + 'finder' => [$this, 'find'], ]); return $loader->buildEagerLoader($options); diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 16cebbb5..4c33a0aa 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -123,7 +123,7 @@ class BelongsToMany extends Association */ protected $_validStrategies = [ self::STRATEGY_SELECT, - self::STRATEGY_SUBQUERY + self::STRATEGY_SUBQUERY, ]; /** @@ -424,13 +424,13 @@ protected function _generateJunctionAssociations($junction, $source, $target) if (!$junction->hasAssociation($tAlias)) { $junction->belongsTo($tAlias, [ 'foreignKey' => $this->getTargetForeignKey(), - 'targetTable' => $target + 'targetTable' => $target, ]); } if (!$junction->hasAssociation($sAlias)) { $junction->belongsTo($sAlias, [ 'foreignKey' => $this->getForeignKey(), - 'targetTable' => $source + 'targetTable' => $source, ]); } } @@ -514,7 +514,7 @@ protected function _appendNotMatching($query, $options) $assoc = $junction->getAssociation($this->getTarget()->getAlias()); $conditions = $assoc->_joinCondition([ - 'foreignKey' => $this->getTargetForeignKey() + 'foreignKey' => $this->getTargetForeignKey(), ]); $subquery = $this->_appendJunctionJoin($subquery, $conditions); @@ -579,7 +579,7 @@ public function eagerLoader(array $options) 'junctionConditions' => $this->junctionConditions(), 'finder' => function () { return $this->_appendJunctionJoin($this->find(), []); - } + }, ]); return $loader->buildEagerLoader($options); @@ -945,7 +945,7 @@ public function unlink(EntityInterface $sourceEntity, array $targetEntities, $op { if (is_bool($options)) { $options = [ - 'cleanProperty' => $options + 'cleanProperty' => $options, ]; } else { $options += ['cleanProperty' => true]; @@ -1112,7 +1112,7 @@ public function find($type = null, array $options = []) $belongsTo = $this->junction()->getAssociation($this->getTarget()->getAlias()); $conditions = $belongsTo->_joinCondition([ - 'foreignKey' => $this->getTargetForeignKey() + 'foreignKey' => $this->getTargetForeignKey(), ]); $conditions += $this->junctionConditions(); @@ -1135,8 +1135,8 @@ protected function _appendJunctionJoin($query, $conditions) $name => [ 'table' => $this->junction()->getTable(), 'conditions' => $conditions, - 'type' => QueryInterface::JOIN_TYPE_INNER - ] + 'type' => QueryInterface::JOIN_TYPE_INNER, + ], ]; $assoc = $this->getTarget()->getAssociation($name); @@ -1434,7 +1434,7 @@ protected function _junctionTableName($name = null) if (empty($this->_junctionTableName)) { $tablesNames = array_map('Cake\Utility\Inflector::underscore', [ $this->getSource()->getTable(), - $this->getTarget()->getTable() + $this->getTarget()->getTable(), ]); sort($tablesNames); $this->_junctionTableName = implode('_', $tablesNames); diff --git a/Association/HasMany.php b/Association/HasMany.php index dd7a7dc3..1b0cc94a 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -63,7 +63,7 @@ class HasMany extends Association */ protected $_validStrategies = [ self::STRATEGY_SELECT, - self::STRATEGY_SUBQUERY + self::STRATEGY_SUBQUERY, ]; /** @@ -356,7 +356,7 @@ public function unlink(EntityInterface $sourceEntity, array $targetEntities, $op { if (is_bool($options)) { $options = [ - 'cleanProperty' => $options + 'cleanProperty' => $options, ]; } else { $options += ['cleanProperty' => true]; @@ -376,7 +376,7 @@ public function unlink(EntityInterface $sourceEntity, array $targetEntities, $op /** @var \Cake\Datasource\EntityInterface $entity */ return $entity->extract($targetPrimaryKey); }) - ->toList() + ->toList(), ]; $this->_unlink($foreignKey, $target, $conditions, $options); @@ -492,9 +492,9 @@ function ($v) { if (count($exclusions) > 0) { $conditions = [ 'NOT' => [ - 'OR' => $exclusions + 'OR' => $exclusions, ], - $foreignKeyReference + $foreignKeyReference, ]; } @@ -690,7 +690,7 @@ public function eagerLoader(array $options) 'strategy' => $this->getStrategy(), 'associationType' => $this->type(), 'sort' => $this->getSort(), - 'finder' => [$this, 'find'] + 'finder' => [$this, 'find'], ]); return $loader->buildEagerLoader($options); diff --git a/Association/HasOne.php b/Association/HasOne.php index 279502e4..14efa63e 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -36,7 +36,7 @@ class HasOne extends Association */ protected $_validStrategies = [ self::STRATEGY_JOIN, - self::STRATEGY_SELECT + self::STRATEGY_SELECT, ]; /** @@ -137,7 +137,7 @@ public function eagerLoader(array $options) 'bindingKey' => $this->getBindingKey(), 'strategy' => $this->getStrategy(), 'associationType' => $this->type(), - 'finder' => [$this, 'find'] + 'finder' => [$this, 'find'], ]); return $loader->buildEagerLoader($options); diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 45357ce0..dabcd497 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -138,7 +138,7 @@ protected function _defaultOptions() 'conditions' => [], 'strategy' => $this->strategy, 'nestKey' => $this->alias, - 'sort' => $this->sort + 'sort' => $this->sort, ]; } diff --git a/AssociationCollection.php b/AssociationCollection.php index 3384ebfb..7ac3f7b8 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -83,7 +83,7 @@ public function add($alias, Association $association) public function load($className, $associated, array $options = []) { $options += [ - 'tableLocator' => $this->getTableLocator() + 'tableLocator' => $this->getTableLocator(), ]; $association = new $className($associated, $options); diff --git a/Behavior.php b/Behavior.php index ee09ca21..a304a522 100644 --- a/Behavior.php +++ b/Behavior.php @@ -285,7 +285,7 @@ public function implementedEvents() } else { $events[$event] = [ 'callable' => $method, - 'priority' => $priority + 'priority' => $priority, ]; } } @@ -395,7 +395,7 @@ protected function _reflectionCache() $return = [ 'finders' => [], - 'methods' => [] + 'methods' => [], ]; $reflection = new ReflectionClass($class); diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 144dfcec..90daaf14 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -46,15 +46,15 @@ class TimestampBehavior extends Behavior 'implementedFinders' => [], 'implementedMethods' => [ 'timestamp' => 'timestamp', - 'touch' => 'touch' + 'touch' => 'touch', ], 'events' => [ 'Model.beforeSave' => [ 'created' => 'new', - 'modified' => 'always' - ] + 'modified' => 'always', + ], ], - 'refreshTimestamp' => true + 'refreshTimestamp' => true, ]; /** diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index c0af4d47..a745e164 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -79,7 +79,7 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface 'setLocale' => 'setLocale', 'getLocale' => 'getLocale', 'locale' => 'locale', - 'translationField' => 'translationField' + 'translationField' => 'translationField', ], 'fields' => [], 'translationTable' => 'I18n', @@ -89,7 +89,7 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface 'onlyTranslated' => false, 'strategy' => 'subquery', 'tableLocator' => null, - 'validator' => false + 'validator' => false, ]; /** @@ -102,7 +102,7 @@ public function __construct(Table $table, array $config = []) { $config += [ 'defaultLocale' => I18n::getDefaultLocale(), - 'referenceName' => $this->_referenceName($table) + 'referenceName' => $this->_referenceName($table), ]; if (isset($config['tableLocator'])) { @@ -160,7 +160,7 @@ public function setupFieldAssociations($fields, $table, $model, $strategy) $fieldTable = $tableLocator->get($name, [ 'className' => $table, 'alias' => $name, - 'table' => $this->_translationTable->getTable() + 'table' => $this->_translationTable->getTable(), ]); } else { $fieldTable = $tableLocator->get($name); @@ -179,7 +179,7 @@ public function setupFieldAssociations($fields, $table, $model, $strategy) 'foreignKey' => 'foreign_key', 'joinType' => $filter ? QueryInterface::JOIN_TYPE_INNER : QueryInterface::JOIN_TYPE_LEFT, 'conditions' => $conditions, - 'propertyName' => $field . '_translation' + 'propertyName' => $field . '_translation', ]); } @@ -194,7 +194,7 @@ public function setupFieldAssociations($fields, $table, $model, $strategy) 'strategy' => $strategy, 'conditions' => $conditions, 'propertyName' => '_i18n', - 'dependent' => true + 'dependent' => true, ]); } @@ -331,7 +331,7 @@ public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $o 'field IN' => $fields, 'locale' => $locale, 'foreign_key' => $key, - 'model' => $model + 'model' => $model, ]) ->disableBufferedResults() ->all() @@ -347,7 +347,7 @@ public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $o foreach ($new as $field => $content) { $new[$field] = new Entity(compact('locale', 'field', 'content', 'model'), [ 'useSetters' => false, - 'markNew' => true + 'markNew' => true, ]); } @@ -410,7 +410,7 @@ public function buildMarshalMap($marshaller, $map, $options) } return $translations; - } + }, ]; } @@ -634,7 +634,7 @@ public function groupTranslations($results) $translation = new $entityClass($keys + ['locale' => $locale], [ 'markNew' => false, 'useSetters' => false, - 'markClean' => true + 'markClean' => true, ]); $result[$locale] = $translation; } @@ -677,7 +677,7 @@ protected function _bundleTranslatedFields($entity) } $find[] = ['locale' => $lang, 'field' => $field, 'foreign_key' => $key]; $contents[] = new Entity(['content' => $translation->get($field)], [ - 'useSetters' => false + 'useSetters' => false, ]); } } diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 58c40a22..afd60c4d 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -125,7 +125,7 @@ protected function _throwMissingClassError($class, $plugin) { throw new MissingBehaviorException([ 'class' => $class . 'Behavior', - 'plugin' => $plugin + 'plugin' => $plugin, ]); } diff --git a/EagerLoadable.php b/EagerLoadable.php index c18eba2e..b01e6809 100644 --- a/EagerLoadable.php +++ b/EagerLoadable.php @@ -131,7 +131,7 @@ public function __construct($name, array $config = []) $this->_name = $name; $allowed = [ 'associations', 'instance', 'config', 'canBeJoined', - 'aliasPath', 'propertyPath', 'forMatching', 'targetProperty' + 'aliasPath', 'propertyPath', 'forMatching', 'targetProperty', ]; foreach ($allowed as $property) { if (isset($config[$property])) { @@ -336,8 +336,8 @@ public function asContainArray() return [ $this->_name => [ 'associations' => $associations, - 'config' => $config - ] + 'config' => $config, + ], ]; } } diff --git a/EagerLoader.php b/EagerLoader.php index 52a9c083..2adf6d15 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -61,7 +61,7 @@ class EagerLoader 'finder' => 1, 'joinType' => 1, 'strategy' => 1, - 'negateMatch' => 1 + 'negateMatch' => 1, ]; /** @@ -152,8 +152,8 @@ public function contain($associations = [], callable $queryBuilder = null) $associations = [ $associations => [ - 'queryBuilder' => $queryBuilder - ] + 'queryBuilder' => $queryBuilder, + ], ]; } @@ -574,7 +574,7 @@ protected function _normalizeContain(Table $parent, $alias, $options, $paths) 'config' => array_diff_key($options, $extra), 'aliasPath' => trim($paths['aliasPath'], '.'), 'propertyPath' => trim($paths['propertyPath'], '.'), - 'targetProperty' => $instance->getProperty() + 'targetProperty' => $instance->getProperty(), ]; $config['canBeJoined'] = $instance->canBeJoined($config['config']); $eagerLoadable = new EagerLoadable($alias, $config); @@ -715,7 +715,7 @@ public function loadExternal($query, $statement) 'query' => $query, 'contain' => $contain, 'keys' => $keys, - 'nestKey' => $meta->aliasPath() + 'nestKey' => $meta->aliasPath(), ] ); $statement = new CallbackStatement($statement, $driver, $f); @@ -778,7 +778,7 @@ protected function _buildAssociationsMap($map, $level, $matching = false) 'entityClass' => $instance->getTarget()->getEntityClass(), 'nestKey' => $canBeJoined ? $assoc : $meta->aliasPath(), 'matching' => $forMatching !== null ? $forMatching : $matching, - 'targetProperty' => $meta->targetProperty() + 'targetProperty' => $meta->targetProperty(), ]; if ($canBeJoined && $associations) { $map = $this->_buildAssociationsMap($map, $associations, $matching); @@ -809,7 +809,7 @@ public function addToJoinsMap($alias, Association $assoc, $asMatching = false, $ 'instance' => $assoc, 'canBeJoined' => true, 'forMatching' => $asMatching, - 'targetProperty' => $targetProperty ?: $assoc->getProperty() + 'targetProperty' => $targetProperty ?: $assoc->getProperty(), ]); } diff --git a/Entity.php b/Entity.php index a3771491..add57bc7 100644 --- a/Entity.php +++ b/Entity.php @@ -52,7 +52,7 @@ public function __construct(array $properties = [], array $options = []) 'markClean' => false, 'markNew' => null, 'guard' => false, - 'source' => null + 'source' => null, ]; if (!empty($options['source'])) { @@ -72,7 +72,7 @@ public function __construct(array $properties = [], array $options = []) if (!empty($properties)) { $this->set($properties, [ 'setter' => $options['useSetters'], - 'guard' => $options['guard'] + 'guard' => $options['guard'], ]); } diff --git a/Query.php b/Query.php index 6e1623a8..e2df3eb4 100644 --- a/Query.php +++ b/Query.php @@ -656,7 +656,7 @@ public function leftJoinWith($assoc, callable $builder = null) $result = $this->getEagerLoader() ->setMatching($assoc, $builder, [ 'joinType' => QueryInterface::JOIN_TYPE_LEFT, - 'fields' => false + 'fields' => false, ]) ->getMatching(); $this->_addAssociationsToTypeMap($this->getRepository(), $this->getTypeMap(), $result); @@ -705,7 +705,7 @@ public function innerJoinWith($assoc, callable $builder = null) $result = $this->getEagerLoader() ->setMatching($assoc, $builder, [ 'joinType' => QueryInterface::JOIN_TYPE_INNER, - 'fields' => false + 'fields' => false, ]) ->getMatching(); $this->_addAssociationsToTypeMap($this->getRepository(), $this->getTypeMap(), $result); @@ -770,7 +770,7 @@ public function notMatching($assoc, callable $builder = null) ->setMatching($assoc, $builder, [ 'joinType' => QueryInterface::JOIN_TYPE_LEFT, 'fields' => false, - 'negateMatch' => true + 'negateMatch' => true, ]) ->getMatching(); $this->_addAssociationsToTypeMap($this->getRepository(), $this->getTypeMap(), $result); @@ -1105,7 +1105,7 @@ public function triggerBeforeFind() $repository->dispatchEvent('Model.beforeFind', [ $this, new ArrayObject($this->_options), - !$this->isEagerLoaded() + !$this->isEagerLoaded(), ]); } } @@ -1341,7 +1341,7 @@ public function __debugInfo() 'contain' => $eagerLoader ? $eagerLoader->getContain() : [], 'matching' => $eagerLoader ? $eagerLoader->getMatching() : [], 'extraOptions' => $this->_options, - 'repository' => $this->_repository + 'repository' => $this->_repository, ]; } diff --git a/ResultSet.php b/ResultSet.php index ff3e1345..87fc09e4 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -505,7 +505,7 @@ protected function _groupResult($row) 'useSetters' => false, 'markClean' => true, 'markNew' => false, - 'guard' => false + 'guard' => false, ]; foreach ($this->_matchingMapColumns as $alias => $keys) { diff --git a/Table.php b/Table.php index d878a1d2..496d163e 100644 --- a/Table.php +++ b/Table.php @@ -1441,7 +1441,7 @@ public function findList(Query $query, array $options) $options += [ 'keyField' => $this->getPrimaryKey(), 'valueField' => $this->getDisplayField(), - 'groupField' => null + 'groupField' => null, ]; if (isset($options['idField'])) { @@ -1510,7 +1510,7 @@ public function findThreaded(Query $query, array $options) $options += [ 'keyField' => $this->getPrimaryKey(), 'parentField' => 'parent_id', - 'nestingKey' => 'children' + 'nestingKey' => 'children', ]; if (isset($options['idField'])) { @@ -1918,7 +1918,7 @@ public function save(EntityInterface $entity, $options = []) 'associated' => true, 'checkRules' => true, 'checkExisting' => true, - '_primary' => true + '_primary' => true, ]); if ($entity->hasErrors($options['associated'])) { @@ -2307,7 +2307,7 @@ public function delete(EntityInterface $entity, $options = []) if ($success && $this->_transactionCommitted($options['atomic'], $options['_primary'])) { $this->dispatchEvent('Model.afterDeleteCommit', [ 'entity' => $entity, - 'options' => $options + 'options' => $options, ]); } @@ -2364,7 +2364,7 @@ protected function _processDelete($entity, $options) $event = $this->dispatchEvent('Model.beforeDelete', [ 'entity' => $entity, - 'options' => $options + 'options' => $options, ]); if ($event->isStopped()) { @@ -2389,7 +2389,7 @@ protected function _processDelete($entity, $options) $this->dispatchEvent('Model.afterDelete', [ 'entity' => $entity, - 'options' => $options + 'options' => $options, ]); return $success; @@ -2489,7 +2489,7 @@ protected function _dynamicFinder($method, $args) } elseif ($hasOr !== false) { $fields = explode('_or_', $fields); $conditions = [ - 'OR' => $makeConditions($fields, $args) + 'OR' => $makeConditions($fields, $args), ]; } elseif ($hasAnd !== false) { $fields = explode('_and_', $fields); @@ -2808,7 +2808,7 @@ public function validateUnique($value, array $options, array $context = null) [ 'useSetters' => false, 'markNew' => $context['newRecord'], - 'source' => $this->getRegistryAlias() + 'source' => $this->getRegistryAlias(), ] ); $fields = array_merge( @@ -2964,7 +2964,7 @@ public function __debugInfo() 'associations' => $associations ? $associations->keys() : false, 'behaviors' => $behaviors ? $behaviors->loaded() : false, 'defaultConnection' => static::defaultConnectionName(), - 'connectionName' => $conn ? $conn->configName() : null + 'connectionName' => $conn ? $conn->configName() : null, ]; } } From e2108a573d372d0d85db22f19d9265ef9f1e38b1 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 20 Nov 2019 00:04:35 +0530 Subject: [PATCH 1466/2059] Fix CS error --- Association/BelongsToMany.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index d7dc193f..50177844 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -18,8 +18,8 @@ use Cake\Core\App; use Cake\Database\Expression\IdentifierExpression; -use Cake\Database\ExpressionInterface; use Cake\Database\Expression\QueryExpression; +use Cake\Database\ExpressionInterface; use Cake\Datasource\EntityInterface; use Cake\ORM\Association; use Cake\ORM\Association\Loader\SelectWithPivotLoader; From 75f3c4ec1b105ec19ba19d80bed3535466943f7b Mon Sep 17 00:00:00 2001 From: mscherer Date: Wed, 20 Nov 2019 01:00:46 +0100 Subject: [PATCH 1467/2059] Fix CS as per PSR-12. --- Association.php | 3 ++- Association/HasMany.php | 9 ++++++--- Behavior.php | 3 ++- Behavior/CounterCacheBehavior.php | 6 ++++-- Behavior/TimestampBehavior.php | 3 ++- Behavior/TranslateBehavior.php | 3 ++- EagerLoader.php | 5 +++-- Marshaller.php | 3 ++- Table.php | 3 ++- 9 files changed, 25 insertions(+), 13 deletions(-) diff --git a/Association.php b/Association.php index a7eec733..8d20cbd6 100644 --- a/Association.php +++ b/Association.php @@ -344,7 +344,8 @@ public function cascadeCallbacks($cascadeCallbacks = null) */ public function setClassName($className) { - if ($this->_targetTable !== null && + if ( + $this->_targetTable !== null && get_class($this->_targetTable) !== App::className($className, 'Model/Table', 'Table') ) { throw new InvalidArgumentException( diff --git a/Association/HasMany.php b/Association/HasMany.php index 1b0cc94a..3b2e5e81 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -170,7 +170,8 @@ public function saveAssociated(EntityInterface $entity, array $options = []) $isEmpty = in_array($targetEntities, [null, [], '', false], true); if ($isEmpty) { - if ($entity->isNew() || + if ( + $entity->isNew() || $this->getSaveStrategy() !== self::SAVE_REPLACE ) { return $entity; @@ -179,7 +180,8 @@ public function saveAssociated(EntityInterface $entity, array $options = []) $targetEntities = []; } - if (!is_array($targetEntities) && + if ( + !is_array($targetEntities) && !($targetEntities instanceof Traversable) ) { $name = $this->getProperty(); @@ -194,7 +196,8 @@ public function saveAssociated(EntityInterface $entity, array $options = []) $options['_sourceTable'] = $this->getSource(); - if ($this->_saveStrategy === self::SAVE_REPLACE && + if ( + $this->_saveStrategy === self::SAVE_REPLACE && !$this->_unlinkAssociated($foreignKeyReference, $entity, $this->getTarget(), $targetEntities, $options) ) { return false; diff --git a/Behavior.php b/Behavior.php index a304a522..ff595232 100644 --- a/Behavior.php +++ b/Behavior.php @@ -402,7 +402,8 @@ protected function _reflectionCache() foreach ($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { $methodName = $method->getName(); - if (in_array($methodName, $baseMethods, true) || + if ( + in_array($methodName, $baseMethods, true) || isset($eventMethods[$methodName]) ) { continue; diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 18d437eb..f9b63de3 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -133,7 +133,8 @@ public function beforeSave(Event $event, EntityInterface $entity, $options) $registryAlias = $assoc->getTarget()->getRegistryAlias(); $entityAlias = $assoc->getProperty(); - if (!is_callable($config) && + if ( + !is_callable($config) && isset($config['ignoreDirty']) && $config['ignoreDirty'] === true && $entity->$entityAlias->isDirty($field) @@ -226,7 +227,8 @@ protected function _processAssociation(Event $event, EntityInterface $entity, As $config = []; } - if (isset($this->_ignoreDirty[$assoc->getTarget()->getRegistryAlias()][$field]) && + if ( + isset($this->_ignoreDirty[$assoc->getTarget()->getRegistryAlias()][$field]) && $this->_ignoreDirty[$assoc->getTarget()->getRegistryAlias()][$field] === true ) { continue; diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 90daaf14..415cbba8 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -103,7 +103,8 @@ public function handleEvent(Event $event, EntityInterface $entity) sprintf('When should be one of "always", "new" or "existing". The passed value "%s" is invalid', $when) ); } - if ($when === 'always' || + if ( + $when === 'always' || ($when === 'new' && $new) || ($when === 'existing' && !$new) ) { diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index a745e164..7bfb5017 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -222,7 +222,8 @@ public function beforeFind(Event $event, Query $query, $options) $q->where([$q->getRepository()->aliasField('locale') => $locale]); /** @var \Cake\ORM\Query $query */ - if ($query->isAutoFieldsEnabled() || + if ( + $query->isAutoFieldsEnabled() || in_array($field, $select, true) || in_array($this->_table->aliasField($field), $select, true) ) { diff --git a/EagerLoader.php b/EagerLoader.php index 2adf6d15..55eb4a08 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -557,8 +557,9 @@ protected function _normalizeContain(Table $parent, $alias, $options, $paths) $paths += ['aliasPath' => '', 'propertyPath' => '', 'root' => $alias]; $paths['aliasPath'] .= '.' . $alias; - if (isset($options['matching']) && - $options['matching'] === true + if ( + isset($options['matching']) && + $options['matching'] === true ) { $paths['propertyPath'] = '_matchingData.' . $alias; } else { diff --git a/Marshaller.php b/Marshaller.php index 1d69f776..7abf0c61 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -586,7 +586,8 @@ public function merge(EntityInterface $entity, array $data, array $options = []) // change. Arrays will always be marked as dirty because // the original/updated list could contain references to the // same objects, even though those objects may have changed internally. - if ((is_scalar($value) && $original === $value) || + if ( + (is_scalar($value) && $original === $value) || ($value === null && $original === $value) || (is_object($value) && !($value instanceof EntityInterface) && $original == $value) ) { diff --git a/Table.php b/Table.php index 496d163e..7f834e59 100644 --- a/Table.php +++ b/Table.php @@ -1450,7 +1450,8 @@ public function findList(Query $query, array $options) deprecationWarning('Option "idField" is deprecated, use "keyField" instead.'); } - if (!$query->clause('select') && + if ( + !$query->clause('select') && !is_object($options['keyField']) && !is_object($options['valueField']) && !is_object($options['groupField']) From 27d0aec5b4d972cfa0ff5ce5b893e2cb9f766309 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Wed, 27 Nov 2019 21:19:55 -0600 Subject: [PATCH 1468/2059] Renamed Validator::errors() to Validator::validate() --- Marshaller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index 7abf0c61..be586ca9 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -257,7 +257,7 @@ protected function _validate($data, $options, $isNew) ); } - return $validator->errors($data, $isNew); + return $validator->validate($data, $isNew); } /** From 13f6e18ff26701e0aa987fd5dfe7785bf6b6bf6f Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Sat, 30 Nov 2019 21:54:13 -0600 Subject: [PATCH 1469/2059] Suppressed new psalm errors --- Table.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Table.php b/Table.php index f6910218..234433c8 100644 --- a/Table.php +++ b/Table.php @@ -882,6 +882,7 @@ protected function findAssociation(string $name): ?Association } [$name, $next] = array_pad(explode('.', $name, 2), 2, null); + /** @psalm-suppress PossiblyNullArgument */ $result = $this->_associations->get($name); if ($result !== null && $next !== null) { From db8ca31ddfe259e2257bbadc309e462916a55069 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Fri, 6 Dec 2019 00:35:47 -0600 Subject: [PATCH 1470/2059] Added cakephp/cache to ORM suggest list and mention needing the package if adding a cache config. --- README.md | 3 +++ composer.json | 1 + 2 files changed, 4 insertions(+) diff --git a/README.md b/README.md index 0329241c..22dacc00 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,7 @@ $articles->delete($article); It is recommended to enable meta data cache for production systems to avoid performance issues. For e.g. file system strategy your bootstrap file could look like this: + ```php use Cake\Cache\Engine\FileEngine; @@ -163,6 +164,8 @@ $cacheConfig = [ Cache::setConfig('_cake_model_', $cacheConfig); ``` +Cache configs are optional so you must require ``cachephp/cache`` to add one. + ## Creating Custom Table and Entity Classes By default, the Cake ORM uses the `\Cake\ORM\Table` and `\Cake\ORM\Entity` classes to diff --git a/composer.json b/composer.json index ad574a71..70f4ccee 100644 --- a/composer.json +++ b/composer.json @@ -33,6 +33,7 @@ "cakephp/validation": "^4.0" }, "suggest": { + "cakephp/cache": "If you decide to use Query caching.", "cakephp/i18n": "If you are using Translate / Timestamp Behavior." }, "autoload": { From d4317cc2c3806df27400c577394882def42840b6 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 9 Dec 2019 12:19:04 +0530 Subject: [PATCH 1471/2059] Add return typehint --- AssociationCollection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AssociationCollection.php b/AssociationCollection.php index a932d21e..9dbb2091 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -340,7 +340,7 @@ public function cascadeDelete(EntityInterface $entity, array $options): void * @param array $options The options used in the delete operation. * @return \Cake\ORM\Association[] */ - protected function _getNoCascadeItems(EntityInterface $entity, array $options) + protected function _getNoCascadeItems(EntityInterface $entity, array $options): array { $noCascade = []; foreach ($this->_items as $assoc) { From cd2925c23f4b667bd7510135b7f80d36238d5827 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Tue, 10 Dec 2019 02:06:53 -0600 Subject: [PATCH 1472/2059] Added cakephp/i18n to database suggest list and mention locale-aware use and Chronos types. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 70f4ccee..c0f2675d 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,7 @@ }, "suggest": { "cakephp/cache": "If you decide to use Query caching.", - "cakephp/i18n": "If you are using Translate / Timestamp Behavior." + "cakephp/i18n": "If you are using Translate/TimestampBehavior or Chronos types." }, "autoload": { "psr-4": { From 7a0869e9cc9e9eadffa5d7486489db68645718ff Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Thu, 12 Dec 2019 12:31:05 -0600 Subject: [PATCH 1473/2059] Fixed markdown lists in docblocks --- Behavior/TranslateBehavior.php | 1 + EagerLoader.php | 1 + 2 files changed, 2 insertions(+) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 6a09ce47..707a1a6f 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -82,6 +82,7 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface * Constructor * * ### Options + * * - `fields`: List of fields which need to be translated. Providing this fields * list is mandatory when using `EavStrategy`. If the fields list is empty when * using `ShadowTableStrategy` then the list will be auto generated based on diff --git a/EagerLoader.php b/EagerLoader.php index c4a6a67c..37b3f550 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -229,6 +229,7 @@ public function isAutoFieldsEnabled(): bool * `matching` option. * * ### Options + * * - 'joinType': INNER, OUTER, ... * - 'fields': Fields to contain * From 66a7ede0c6185d9d707db99d22ee17c413d9e55c Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Fri, 13 Dec 2019 09:01:23 -0600 Subject: [PATCH 1474/2059] Added TableSchemaInterface::getPrimaryKey() to use instead of primaryKey() --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 1080055b..c653a96a 100644 --- a/Table.php +++ b/Table.php @@ -595,7 +595,7 @@ public function setPrimaryKey($key) public function getPrimaryKey() { if ($this->_primaryKey === null) { - $key = (array)$this->getSchema()->primaryKey(); + $key = (array)$this->getSchema()->getPrimaryKey(); if (count($key) === 1) { $key = $key[0]; } From f32c8f3fdf3381d939a50e6ef416112a527adca4 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 19 Dec 2019 14:41:10 -0500 Subject: [PATCH 1475/2059] Fix silent association failures When selecting specific fields if foreign key columns are omitted, associations would silently fail to load. When we know that the source query returned results but that no foreign keys could be collected the ORM raises an error. This should help prevent missing associations. We do not raise an error for nested association loads as the nested association could be dependent on an optional association. Fixes #13467 --- EagerLoader.php | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index 37b3f550..30234ee0 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -636,6 +636,11 @@ public function loadExternal(Query $query, StatementInterface $statement): State $driver = $query->getConnection()->getDriver(); [$collected, $statement] = $this->_collectKeys($external, $query, $statement); + // No records found, skip trying to attach associations. + if (empty($collected) && $statement->count() === 0) { + return $statement; + } + foreach ($external as $meta) { $contain = $meta->associations(); $instance = $meta->instance(); @@ -644,8 +649,22 @@ public function loadExternal(Query $query, StatementInterface $statement): State $path = $meta->aliasPath(); $requiresKeys = $instance->requiresKeys($config); - if ($requiresKeys && empty($collected[$path][$alias])) { - continue; + if ($requiresKeys) { + // If the path or alias has no key the required assoication load will fail. + // Nested paths are not subject to this condition because they could + // be attached to joined associations. + if (strpos($path, '.') === false && + (!array_key_exists($path, $collected) || !array_key_exists($alias, $collected[$path])) + ) { + $message = "Unable to load `{$path}` association. Ensure foreign key on `{$alias}` is selected."; + throw new InvalidArgumentException($message); + } + + // If the association foreign keys are missing skip loading + // as the association could be optional. + if (empty($collected[$path][$alias])) { + continue; + } } $keys = $collected[$path][$alias] ?? null; @@ -786,7 +805,6 @@ protected function _collectKeys(array $external, Query $query, $statement): arra } $collectKeys[$meta->aliasPath()] = [$alias, $pkFields, count($pkFields) === 1]; } - if (empty($collectKeys)) { return [[], $statement]; } From 9e4449f304546d6e3feb0c7007f3febfae6fac5d Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 19 Dec 2019 14:48:50 -0500 Subject: [PATCH 1476/2059] Improve performance in eagerloader When loading external associations we need to enumerate all the records in the statement. Using fetchAll() results in fewer lines of code running and fewer allocations as the buffered results are not resized multiple times. --- EagerLoader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EagerLoader.php b/EagerLoader.php index 37b3f550..4e7c7049 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -809,7 +809,7 @@ protected function _collectKeys(array $external, Query $query, $statement): arra protected function _groupKeys(BufferedStatement $statement, array $collectKeys): array { $keys = []; - while ($result = $statement->fetch('assoc')) { + foreach ($statement->fetchAll('assoc') as $result) { foreach ($collectKeys as $nestKey => $parts) { // Missed joins will have null in the results. if ($parts[2] === true && !isset($result[$parts[1][0]])) { From 737fdae9e5aca11df01fccea5f4611936481a61d Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 19 Dec 2019 14:51:25 -0500 Subject: [PATCH 1477/2059] Fix formatting. --- EagerLoader.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/EagerLoader.php b/EagerLoader.php index 30234ee0..2add34c0 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -653,7 +653,8 @@ public function loadExternal(Query $query, StatementInterface $statement): State // If the path or alias has no key the required assoication load will fail. // Nested paths are not subject to this condition because they could // be attached to joined associations. - if (strpos($path, '.') === false && + if ( + strpos($path, '.') === false && (!array_key_exists($path, $collected) || !array_key_exists($alias, $collected[$path])) ) { $message = "Unable to load `{$path}` association. Ensure foreign key on `{$alias}` is selected."; From ca16c674edd9c5d994c5901d42802a3c9783f4cc Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 19 Dec 2019 20:55:40 -0500 Subject: [PATCH 1478/2059] Apply suggestions from code review Co-Authored-By: othercorey --- EagerLoader.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index 2add34c0..b1231d90 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -650,14 +650,14 @@ public function loadExternal(Query $query, StatementInterface $statement): State $requiresKeys = $instance->requiresKeys($config); if ($requiresKeys) { - // If the path or alias has no key the required assoication load will fail. + // If the path or alias has no key the required association load will fail. // Nested paths are not subject to this condition because they could // be attached to joined associations. if ( strpos($path, '.') === false && (!array_key_exists($path, $collected) || !array_key_exists($alias, $collected[$path])) ) { - $message = "Unable to load `{$path}` association. Ensure foreign key on `{$alias}` is selected."; + $message = "Unable to load `{$path}` association. Ensure foreign key in `{$alias}` is selected."; throw new InvalidArgumentException($message); } From 5ea5050e86e7d91aa3c35928e5dd8cb0f64d7bdf Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 19 Dec 2019 20:57:16 -0500 Subject: [PATCH 1479/2059] Apply suggestions from code review Co-Authored-By: othercorey --- EagerLoader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EagerLoader.php b/EagerLoader.php index 4e7c7049..59d2a0e1 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -809,7 +809,7 @@ protected function _collectKeys(array $external, Query $query, $statement): arra protected function _groupKeys(BufferedStatement $statement, array $collectKeys): array { $keys = []; - foreach ($statement->fetchAll('assoc') as $result) { + foreach (($statement->fetchAll('assoc') ?: []) as $result) { foreach ($collectKeys as $nestKey => $parts) { // Missed joins will have null in the results. if ($parts[2] === true && !isset($result[$parts[1][0]])) { From 529c8af0a65996e8cc6a2e8b463a461a0c4514f9 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 28 Dec 2019 10:59:56 +0530 Subject: [PATCH 1480/2059] Fix errors reported by psalm --- Behavior/CounterCacheBehavior.php | 12 +++--------- TableRegistry.php | 1 + 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 8b40655f..9b719328 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -21,7 +21,7 @@ use Cake\Event\EventInterface; use Cake\ORM\Association; use Cake\ORM\Behavior; -use RuntimeException; +use Closure; /** * CounterCache behavior @@ -241,10 +241,7 @@ protected function _processAssociation( continue; } - if (is_callable($config)) { - if (is_string($config)) { - throw new RuntimeException('You must not use a string as callable.'); - } + if ($config instanceof Closure) { $count = $config($event, $entity, $this->_table, false); } else { $count = $this->_getCount($config, $countConditions); @@ -254,10 +251,7 @@ protected function _processAssociation( } if (isset($updateOriginalConditions)) { - if (is_callable($config)) { - if (is_string($config)) { - throw new RuntimeException('You must not use a string as callable.'); - } + if ($config instanceof Closure) { $count = $config($event, $entity, $this->_table, true); } else { $count = $this->_getCount($config, $countOriginalConditions); diff --git a/TableRegistry.php b/TableRegistry.php index faeebf2b..800a9ae0 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -68,6 +68,7 @@ class TableRegistry * Default LocatorInterface implementation class. * * @var string + * @psalm-var class-string<\Cake\ORM\Locator\TableLocator> */ protected static $_defaultLocatorClass = Locator\TableLocator::class; From a5cf6918004bfedf217936751b9a560a44fc1eeb Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Sat, 28 Dec 2019 02:59:39 -0600 Subject: [PATCH 1481/2059] Check table and column name combinations against max alias lengths for Table. --- Table.php | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/Table.php b/Table.php index c653a96a..08c49e08 100644 --- a/Table.php +++ b/Table.php @@ -19,6 +19,7 @@ use ArrayObject; use BadMethodCallException; use Cake\Core\App; +use Cake\Core\Configure; use Cake\Database\Connection; use Cake\Database\Schema\TableSchemaInterface; use Cake\Database\TypeFactory; @@ -498,6 +499,9 @@ public function getSchema(): TableSchemaInterface ->getSchemaCollection() ->describe($this->getTable()) ); + if (Configure::read('debug')) { + $this->checkAliasLengths(); + } } return $this->_schema; @@ -530,10 +534,48 @@ public function setSchema($schema) } $this->_schema = $schema; + if (Configure::read('debug')) { + $this->checkAliasLengths(); + } return $this; } + /** + * Checks if all table name + column name combinations used for + * queries fit into the max length allowed by database driver. + * + * @return void + * @throws \RuntimeException When an alias combination is too long + */ + protected function checkAliasLengths(): void + { + if ($this->_schema === null) { + throw new RuntimeException("Unable to check max alias lengths for `{$this->getAlias()}` without schema."); + } + + $maxLength = null; + if (method_exists($this->getConnection()->getDriver(), "getMaxAliasLength")) { + $maxLength = $this->getConnection()->getDriver()->getMaxAliasLength(); + } + if ($maxLength === null) { + return; + } + + $table = $this->getAlias(); + foreach ($this->_schema->columns() as $name) { + if (strlen($table . '__' . $name) > $maxLength) { + $nameLength = $maxLength - 2; + throw new RuntimeException( + "ORM queries generate field aliases using the table name/alias and column name. " . + "The table alias `{$table}` and column `{$name}` create an alias longer than ({$nameLength}). " . + "You must change the table schema in the database and shorten either the table or column " . + "identifier so they fit within the database alias limits." + ); + } + } + } + /** * Override this function in order to alter the schema used by this table. * This function is only called after fetching the schema out of the database. From 2f1903c901b4612dc9c962da3550a7158cbbd0ac Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 31 Dec 2019 18:44:49 +0530 Subject: [PATCH 1482/2059] Fix example code --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 22dacc00..077b5fd5 100644 --- a/README.md +++ b/README.md @@ -160,7 +160,7 @@ $cacheConfig = [ 'duration' => '+1 year', 'serialize' => true, 'prefix' => 'orm_', -], +]; Cache::setConfig('_cake_model_', $cacheConfig); ``` From 8b9aabda05e2a87053fa4b95cceb259dab45ffd1 Mon Sep 17 00:00:00 2001 From: gregs Date: Sat, 21 Dec 2019 16:46:26 -0500 Subject: [PATCH 1483/2059] Fix dirty associations that use formatResults When a joined association uses formatResults the parent entity should not be marked dirty if the result formatter modifieds the association entity. While this may not be the optimal place to do this performance wise it does solve the problem and I'd like to revise the dirty/clean flagging that happens multiple times during association construction anyways. Refs #14062 Refs #13666 Cherry pick of changes in #14107 --- Association.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Association.php b/Association.php index a1af231f..5084b788 100644 --- a/Association.php +++ b/Association.php @@ -1008,7 +1008,13 @@ protected function _formatAssociationResults(Query $query, Query $surrogate, arr } /** @var \Cake\Collection\CollectionInterface $results */ - return $results->insert($property, $extracted); + return $results + ->insert($property, $extracted) + ->map(function ($result) { + $result->clean(); + + return $result; + }); }, Query::PREPEND); } From f5baf8e8f28fab52a04ccf7bbd5e5b66aafef827 Mon Sep 17 00:00:00 2001 From: gregs Date: Mon, 6 Jan 2020 10:21:07 -0500 Subject: [PATCH 1484/2059] Only try to clean entities --- Association.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Association.php b/Association.php index 5084b788..a1efb548 100644 --- a/Association.php +++ b/Association.php @@ -1011,7 +1011,9 @@ protected function _formatAssociationResults(Query $query, Query $surrogate, arr return $results ->insert($property, $extracted) ->map(function ($result) { - $result->clean(); + if ($result instanceof EntityInterface) { + $result->clean(); + } return $result; }); From 3fe2e9b92f237efb4f8c0fbd006d45c1c17f3c95 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 6 Jan 2020 14:39:00 -0500 Subject: [PATCH 1485/2059] Don't map() when we don't need to. If query hydration is disabled we don't need to map + clean the results. Refs #14133 --- Association.php | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Association.php b/Association.php index a1efb548..04643970 100644 --- a/Association.php +++ b/Association.php @@ -990,7 +990,7 @@ protected function _formatAssociationResults(Query $query, Query $surrogate, arr $property = $options['propertyPath']; $propertyPath = explode('.', $property); - $query->formatResults(function ($results) use ($formatters, $property, $propertyPath) { + $query->formatResults(function ($results) use ($formatters, $property, $propertyPath, $query) { $extracted = []; foreach ($results as $result) { foreach ($propertyPath as $propertyPathItem) { @@ -1008,15 +1008,16 @@ protected function _formatAssociationResults(Query $query, Query $surrogate, arr } /** @var \Cake\Collection\CollectionInterface $results */ - return $results - ->insert($property, $extracted) - ->map(function ($result) { - if ($result instanceof EntityInterface) { - $result->clean(); - } + $results = $results->insert($property, $extracted); + if ($query->isHydrationEnabled()) { + $results = $results->map(function ($result) { + $result->clean(); return $result; }); + } + + return $results; }, Query::PREPEND); } From 08eeb8e7f6dd4d04f1e7adc43c1576d6da42a6e5 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Tue, 7 Jan 2020 13:18:24 -0600 Subject: [PATCH 1486/2059] Fixed new psalm warnings --- Marshaller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index 5d532e8a..22c8e376 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -96,7 +96,7 @@ protected function _buildPropertyMap(array $data, array $options): array if (substr($key, 0, 1) !== '_') { throw new InvalidArgumentException(sprintf( 'Cannot marshal data for "%s" association. It is not associated with "%s".', - $key, + (string)$key, $this->_table->getAlias() )); } From cb7c5e96187f0780dad8ecab5e453d942d2b69bd Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Tue, 7 Jan 2020 17:20:58 -0600 Subject: [PATCH 1487/2059] Cleaned up enableAutoFields() calls --- Query.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Query.php b/Query.php index afc08582..fb974cc7 100644 --- a/Query.php +++ b/Query.php @@ -575,7 +575,7 @@ public function matching(string $assoc, ?callable $builder = null) * ->select(['total_articles' => $query->func()->count('Articles.id')]) * ->leftJoinWith('Articles') * ->group(['Users.id']) - * ->enableAutoFields(true); + * ->enableAutoFields(); * ``` * * You can also customize the conditions passed to the LEFT JOIN: @@ -588,7 +588,7 @@ public function matching(string $assoc, ?callable $builder = null) * return $q->where(['Articles.votes >=' => 5]); * }) * ->group(['Users.id']) - * ->enableAutoFields(true); + * ->enableAutoFields(); * ``` * * This will create the following SQL: @@ -922,10 +922,10 @@ protected function _performCount(): int $count = ['count' => $query->func()->count('*')]; if (!$complex) { - $query->getEagerLoader()->enableAutoFields(false); + $query->getEagerLoader()->disableAutoFields(); $statement = $query ->select($count, true) - ->enableAutoFields(false) + ->disableAutoFields() ->execute(); } else { $statement = $this->getConnection()->newQuery() From e2d1e3c172b31fdfe6f680106edbdffb86b0e331 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Wed, 8 Jan 2020 05:10:32 -0600 Subject: [PATCH 1488/2059] Fixed contain() BelongsToMany association restricting fields with 'fields' option --- Association/Loader/SelectWithPivotLoader.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Association/Loader/SelectWithPivotLoader.php b/Association/Loader/SelectWithPivotLoader.php index 0f9c1fb9..fe2f886c 100644 --- a/Association/Loader/SelectWithPivotLoader.php +++ b/Association/Loader/SelectWithPivotLoader.php @@ -130,6 +130,14 @@ protected function _buildQuery(array $options): Query return $query; } + /** + * @inheritDoc + */ + protected function _assertFieldsPresent(Query $fetchQuery, array $key): void + { + // _buildQuery() manually adds in required fields from junction table + } + /** * Generates a string used as a table field that contains the values upon * which the filter should be applied From 51c20a057e6f09a55ea15d129c9b4fc4cd78ccc3 Mon Sep 17 00:00:00 2001 From: mscherer Date: Fri, 10 Jan 2020 02:40:08 +0100 Subject: [PATCH 1489/2059] Fix docblocks around bool false. --- Table.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index 08c49e08..319fae60 100644 --- a/Table.php +++ b/Table.php @@ -2250,8 +2250,8 @@ public function delete(EntityInterface $entity, $options = []): bool * * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to delete. * @param array|\ArrayAccess $options Options used when calling Table::save() for each entity. - * @return bool|\Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface - * False on failure, entities list on success. + * @return \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface|false Entities list + * on success, false on failure. * @throws \Exception * @see \Cake\ORM\Table::delete() for options and events related to this method. */ From ea13d067f302e2bc3899b9cc8d93bb1fee639dae Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 19 Jan 2020 01:28:56 +0530 Subject: [PATCH 1490/2059] Fix CS errors and issues reported by static analyzers --- Behavior/Translate/EavStrategy.php | 2 +- Behavior/TranslateBehavior.php | 6 +++--- Behavior/TreeBehavior.php | 2 +- BehaviorRegistry.php | 6 ++---- LazyEagerLoader.php | 6 ++++-- Locator/TableLocator.php | 1 + Marshaller.php | 8 ++++---- Query.php | 7 +++---- Table.php | 7 ++++--- TableRegistry.php | 3 +-- 10 files changed, 24 insertions(+), 24 deletions(-) diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index 13a39958..5c33edfb 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -354,6 +354,7 @@ public function translationField(string $field): string protected function rowMapper($results, $locale) { return $results->map(function ($row) use ($locale) { + /** @var \Cake\Datasource\EntityInterface|array|null $row */ if ($row === null) { return $row; } @@ -378,7 +379,6 @@ protected function rowMapper($results, $locale) $row['_locale'] = $locale; if ($hydrated) { - /** @var \Cake\Datasource\EntityInterface $row */ $row->clean(); } diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 707a1a6f..5b67ea8f 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -67,7 +67,7 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface /** * Default strategy class name. * - * @var string + * @var class-string<\Cake\ORM\Behavior\Translate\TranslateStrategyInterface> */ protected static $defaultStrategyClass = EavStrategy::class; @@ -176,9 +176,9 @@ protected function createStrategy() $this->_config, ['implementedFinders', 'implementedMethods', 'strategyClass'] ); + /** @var class-string<\Cake\ORM\Behavior\Translate\TranslateStrategyInterface> $className */ $className = $this->getConfig('strategyClass', static::$defaultStrategyClass); - /** @var \Cake\ORM\Behavior\Translate\TranslateStrategyInterface */ return new $className($this->_table, $config); } @@ -308,8 +308,8 @@ public function findTranslations(Query $query, array $options): Query return $query ->contain([$targetAlias => function ($query) use ($locales, $targetAlias) { + /** @var \Cake\Datasource\QueryInterface $query */ if ($locales) { - /** @var \Cake\Datasource\QueryInterface $query */ $query->where(["$targetAlias.locale IN" => $locales]); } diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 8c9ac3f6..e2db3b5a 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -680,7 +680,7 @@ protected function _moveUp(EntityInterface $node, $number): EntityInterface $this->_sync($nodeToHole, '-', "> {$edge}"); $node->set($left, $targetLeft); - $node->set($right, $targetLeft + ($nodeRight - $nodeLeft)); + $node->set($right, $targetLeft + $nodeRight - $nodeLeft); $node->setDirty($left, false); $node->setDirty($right, false); diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 479a277c..fa139c55 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -85,9 +85,8 @@ public function setTable(Table $table): void * Resolve a behavior classname. * * @param string $class Partial classname to resolve. - * @return string|null Either the correct classname or null. + * @return class-string|null Either the correct classname or null. * @since 3.5.7 - * @psalm-return class-string */ public static function className(string $class): ?string { @@ -101,8 +100,7 @@ public static function className(string $class): ?string * Part of the template method for Cake\Core\ObjectRegistry::load() * * @param string $class Partial classname to resolve. - * @return string|null Either the correct class name or null. - * @psalm-return class-string + * @return class-string|null Either the correct class name or null. */ protected function _resolveClassName(string $class): ?string { diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index daf56bee..cf2ddb55 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -82,16 +82,18 @@ protected function _getQuery(CollectionInterface $objects, array $contain, Table ->find() ->select((array)$primaryKey) ->where(function ($exp, $q) use ($primaryKey, $keys, $source) { + /** + * @var \Cake\Database\Expression\QueryExpression $exp + * @var \Cake\ORM\Query $q + */ if (is_array($primaryKey) && count($primaryKey) === 1) { $primaryKey = current($primaryKey); } if (is_string($primaryKey)) { - /** @var \Cake\Database\Expression\QueryExpression $exp */ return $exp->in($source->aliasField($primaryKey), $keys->toList()); } - /** @var \Cake\ORM\Query $q */ $types = array_intersect_key($q->getDefaultTypes(), array_flip($primaryKey)); $primaryKey = array_map([$source, 'aliasField'], $primaryKey); diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index f2e6b4f9..7dbee112 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -258,6 +258,7 @@ protected function _getClassName(string $alias, array $options = []): ?string */ protected function _create(array $options): Table { + // phpcs:ignore SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration.InvalidFormat /** @var \Cake\ORM\Table */ return new $options['className']($options); } diff --git a/Marshaller.php b/Marshaller.php index 22c8e376..15134677 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -632,7 +632,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) * the accessible fields list in the entity will be used. * - accessibleFields: A list of fields to allow or deny in entity accessible fields. * - * @param \Cake\Datasource\EntityInterface[]|\Traversable<\Cake\Datasource\EntityInterface> $entities the entities that will get the + * @param iterable<\Cake\Datasource\EntityInterface> $entities the entities that will get the * data merged in * @param array $data list of arrays to be merged into the entities * @param array $options List of options. @@ -735,11 +735,11 @@ protected function _mergeAssociation($original, Association $assoc, $value, arra $types = [Association::ONE_TO_ONE, Association::MANY_TO_ONE]; $type = $assoc->type(); if (in_array($type, $types, true)) { - /** @var \Cake\Datasource\EntityInterface $original */ + /** @psalm-suppress PossiblyInvalidArgument */ return $marshaller->merge($original, $value, (array)$options); } if ($type === Association::MANY_TO_MANY) { - /** @var \Cake\Datasource\EntityInterface[] $original */ + /** @psalm-suppress PossiblyInvalidArgument */ return $marshaller->_mergeBelongsToMany($original, $assoc, $value, (array)$options); } @@ -754,7 +754,7 @@ protected function _mergeAssociation($original, Association $assoc, $value, arra } } - /** @var \Cake\Datasource\EntityInterface[] $original */ + /** @psalm-suppress PossiblyInvalidArgument */ return $marshaller->mergeMany($original, $value, (array)$options); } diff --git a/Query.php b/Query.php index fb974cc7..2d1e6116 100644 --- a/Query.php +++ b/Query.php @@ -40,7 +40,7 @@ * * @see \Cake\Collection\CollectionInterface For a full description of the collection methods supported by this class * @method \Cake\Collection\CollectionInterface each(callable $c) Passes each of the query results to the callable - * @method \Cake\Collection\CollectionInterface sortBy($callback, int $dir = SORT_DESC, int $type = \SORT_NUMERIC) Sorts the query with the callback + * @method \Cake\Collection\CollectionInterface sortBy($callback, int $dir, int $type) Sorts the query with the callback * @method \Cake\Collection\CollectionInterface filter(callable $c = null) Keeps the results using passing the callable test * @method \Cake\Collection\CollectionInterface reject(callable $c) Removes the results passing the callable test * @method bool every(callable $c) Returns true if all the results pass the callable test @@ -48,8 +48,8 @@ * @method \Cake\Collection\CollectionInterface map(callable $c) Modifies each of the results using the callable * @method mixed reduce(callable $c, $zero = null) Folds all the results into a single value using the callable. * @method \Cake\Collection\CollectionInterface extract($field) Extracts a single column from each row - * @method mixed max($field, int $type = SORT_NUMERIC) Returns the maximum value for a single column in all the results. - * @method mixed min($field, int $type = SORT_NUMERIC) Returns the minimum value for a single column in all the results. + * @method mixed max($field, int $type) Returns the maximum value for a single column in all the results. + * @method mixed min($field, int $type) Returns the minimum value for a single column in all the results. * @method \Cake\Collection\CollectionInterface groupBy(string|callable $field) In-memory group all results by the value of a column. * @method \Cake\Collection\CollectionInterface indexBy(string|callable $callback) Returns the results indexed by the value of a column. * @method \Cake\Collection\CollectionInterface countBy(string|callable $field) Returns the number of unique values for a column @@ -1093,7 +1093,6 @@ protected function _execute(): ResultSetInterface if ($this->_results) { $decorator = $this->_decoratorClass(); - /** @var \Cake\Datasource\ResultSetInterface */ return new $decorator($this->_results); } diff --git a/Table.php b/Table.php index 319fae60..a40cbacf 100644 --- a/Table.php +++ b/Table.php @@ -230,7 +230,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc /** * The name of the class that represent a single row for this table * - * @var string + * @var class-string<\Cake\Datasource\EntityInterface> */ protected $_entityClass; @@ -685,7 +685,8 @@ public function getDisplayField() /** * Returns the class used to hydrate rows for this table. * - * @return string + * @return class-string<\Cake\Datasource\EntityInterface> + * @psalm-suppress MoreSpecificReturnType */ public function getEntityClass(): string { @@ -704,6 +705,7 @@ public function getEntityClass(): string return $this->_entityClass = $default; } + /** @var class-string<\Cake\Datasource\EntityInterface>|null $class */ $class = App::className($name, 'Model/Entity'); if (!$class) { throw new MissingEntityException([$name]); @@ -2599,7 +2601,6 @@ public function newEmptyEntity(): EntityInterface { $class = $this->getEntityClass(); - /** @var \Cake\Datasource\EntityInterface */ return new $class([], ['source' => $this->getRegistryAlias()]); } diff --git a/TableRegistry.php b/TableRegistry.php index 800a9ae0..c042916b 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -67,8 +67,7 @@ class TableRegistry /** * Default LocatorInterface implementation class. * - * @var string - * @psalm-var class-string<\Cake\ORM\Locator\TableLocator> + * @var class-string<\Cake\ORM\Locator\TableLocator> */ protected static $_defaultLocatorClass = Locator\TableLocator::class; From 91ad2a23c94ce9156ea0c590a691d1daea752b15 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Mon, 20 Jan 2020 02:48:51 -0600 Subject: [PATCH 1491/2059] Moved class-string back to psalm specific annotations --- BehaviorRegistry.php | 7 ++++--- Table.php | 6 ++++-- TableRegistry.php | 3 ++- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index fa139c55..772145a0 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -85,8 +85,8 @@ public function setTable(Table $table): void * Resolve a behavior classname. * * @param string $class Partial classname to resolve. - * @return class-string|null Either the correct classname or null. - * @since 3.5.7 + * @return string|null Either the correct classname or null. + * @psalm-return class-string|null */ public static function className(string $class): ?string { @@ -100,7 +100,8 @@ public static function className(string $class): ?string * Part of the template method for Cake\Core\ObjectRegistry::load() * * @param string $class Partial classname to resolve. - * @return class-string|null Either the correct class name or null. + * @return string|null Either the correct class name or null. + * @psalm-return class-string|null */ protected function _resolveClassName(string $class): ?string { diff --git a/Table.php b/Table.php index a40cbacf..35769b59 100644 --- a/Table.php +++ b/Table.php @@ -230,7 +230,8 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc /** * The name of the class that represent a single row for this table * - * @var class-string<\Cake\Datasource\EntityInterface> + * @var string + * @psalm-var class-string<\Cake\Datasource\EntityInterface> */ protected $_entityClass; @@ -685,8 +686,9 @@ public function getDisplayField() /** * Returns the class used to hydrate rows for this table. * - * @return class-string<\Cake\Datasource\EntityInterface> + * @return string * @psalm-suppress MoreSpecificReturnType + * @psalm-return class-string<\Cake\Datasource\EntityInterface> */ public function getEntityClass(): string { diff --git a/TableRegistry.php b/TableRegistry.php index c042916b..800a9ae0 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -67,7 +67,8 @@ class TableRegistry /** * Default LocatorInterface implementation class. * - * @var class-string<\Cake\ORM\Locator\TableLocator> + * @var string + * @psalm-var class-string<\Cake\ORM\Locator\TableLocator> */ protected static $_defaultLocatorClass = Locator\TableLocator::class; From 3486726dbb41af835d5a7820f3971984cf2c67b5 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 21 Jan 2020 02:09:35 +0530 Subject: [PATCH 1492/2059] Update phpstan's config --- ResultSet.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ResultSet.php b/ResultSet.php index 1de42cb7..b5b0333b 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -276,8 +276,7 @@ public function valid(): bool * * This method will also close the underlying statement cursor. * - * @return array|object - * @psalm-suppress InvalidNullableReturnType + * @return array|object|null */ public function first() { @@ -288,6 +287,8 @@ public function first() return $result; } + + return null; } /** From a3000815318f54a6d9758b2c3d3243f86ce81599 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 6 Feb 2020 19:28:25 -0500 Subject: [PATCH 1493/2059] Update table docs. The buildRules() method is slightly different than other hooks as it doesn't use an event on the Table class. Refs #14250 --- Table.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index 35769b59..7e5b065d 100644 --- a/Table.php +++ b/Table.php @@ -96,8 +96,10 @@ * - `buildValidator(EventInterface $event, Validator $validator, string $name)` * Allows listeners to modify validation rules for the provided named validator. * - * - `buildRules(EventInterface $event, RulesChecker $rules)` - * Allows listeners to modify the rules checker by adding more rules. + * - `buildRules(RulesChecker $rules)` + * Allows table classes to modify the rules checker by adding more rules. The + * `Model.buildRules` event will also include an `$event` parameter before the + * `$rules` parameter. * * - `beforeRules(EventInterface $event, EntityInterface $entity, ArrayObject $options, string $operation)` * Fired before an entity is validated using the rules checker. By stopping this event, From 994f8a8d198d53a609ddb7cd3b0bc3f700a53cbb Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 8 Feb 2020 02:20:22 +0530 Subject: [PATCH 1494/2059] Remove unneeded psalm error suppression annotations --- Association/DependentDeleteHelper.php | 1 - Table.php | 1 - 2 files changed, 2 deletions(-) diff --git a/Association/DependentDeleteHelper.php b/Association/DependentDeleteHelper.php index 9b6c5161..0b4bb18c 100644 --- a/Association/DependentDeleteHelper.php +++ b/Association/DependentDeleteHelper.php @@ -42,7 +42,6 @@ public function cascadeDelete(Association $association, EntityInterface $entity, return true; } $table = $association->getTarget(); - /** @psalm-suppress InvalidArgument */ $foreignKey = array_map([$association, 'aliasField'], (array)$association->getForeignKey()); $bindingKey = (array)$association->getBindingKey(); $conditions = array_combine($foreignKey, $entity->extract($bindingKey)); diff --git a/Table.php b/Table.php index 35769b59..d13caabb 100644 --- a/Table.php +++ b/Table.php @@ -687,7 +687,6 @@ public function getDisplayField() * Returns the class used to hydrate rows for this table. * * @return string - * @psalm-suppress MoreSpecificReturnType * @psalm-return class-string<\Cake\Datasource\EntityInterface> */ public function getEntityClass(): string From ab8bfe85f094e7731832b17233de09fbf1b31ce4 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 7 Feb 2020 21:57:34 -0500 Subject: [PATCH 1495/2059] Separate events and callbacks. --- Table.php | 76 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 41 insertions(+), 35 deletions(-) diff --git a/Table.php b/Table.php index 7e5b065d..7231fa87 100644 --- a/Table.php +++ b/Table.php @@ -81,53 +81,59 @@ * You can use Table::updateAll() and Table::deleteAll() to do bulk updates/deletes. * You should be aware that events will *not* be fired for bulk updates/deletes. * - * ### Callbacks/events + * ### Events * - * Table objects provide a few callbacks/events you can hook into to augment/replace - * find operations. Each event uses the standard event subsystem in CakePHP + * Table objects emit several events during as life-cycle hooks during find, delete and save + * operations. All events use the CakePHP event package: * - * - `beforeFind(EventInterface $event, Query $query, ArrayObject $options, boolean $primary)` - * Fired before each find operation. By stopping the event and supplying a - * return value you can bypass the find operation entirely. Any changes done - * to the $query instance will be retained for the rest of the find. The - * $primary parameter indicates whether or not this is the root query, - * or an associated query. + * - `Model.beforeFind` Fired before each find operation. By stopping the event and + * supplying a return value you can bypass the find operation entirely. Any + * changes done to the $query instance will be retained for the rest of the find. The + * `$primary` parameter indicates whether or not this is the root query, or an + * associated query. * - * - `buildValidator(EventInterface $event, Validator $validator, string $name)` - * Allows listeners to modify validation rules for the provided named validator. + * - `Model.buildValidator` Allows listeners to modify validation rules + * for the provided named validator. * - * - `buildRules(RulesChecker $rules)` - * Allows table classes to modify the rules checker by adding more rules. The - * `Model.buildRules` event will also include an `$event` parameter before the - * `$rules` parameter. + * - `Model.buildRules` Allows listeners to modify the rules checker by adding more rules. * - * - `beforeRules(EventInterface $event, EntityInterface $entity, ArrayObject $options, string $operation)` - * Fired before an entity is validated using the rules checker. By stopping this event, - * you can return the final value of the rules checking operation. + * - `Model.beforeRules` Fired before an entity is validated using the rules checker. + * By stopping this event, you can return the final value of the rules checking operation. * - * - `afterRules(EventInterface $event, EntityInterface $entity, ArrayObject $options, bool $result, string $operation)` - * Fired after the rules have been checked on the entity. By stopping this event, - * you can return the final value of the rules checking operation. + * - `Model.afterRules` Fired after the rules have been checked on the entity. By + * stopping this event, you can return the final value of the rules checking operation. * - * - `beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options)` - * Fired before each entity is saved. Stopping this event will abort the save - * operation. When the event is stopped the result of the event will be returned. + * - `Model.beforeSave` Fired before each entity is saved. Stopping this event will + * abort the save operation. When the event is stopped the result of the event will be returned. * - * - `afterSave(EventInterface $event, EntityInterface $entity, ArrayObject $options)` - * Fired after an entity is saved. + * - `Model.afterSave` Fired after an entity is saved. * - * - `afterSaveCommit(EventInterface $event, EntityInterface $entity, ArrayObject $options)` - * Fired after the transaction in which the save operation is wrapped has been committed. - * It’s also triggered for non atomic saves where database operations are implicitly committed. - * The event is triggered only for the primary table on which save() is directly called. - * The event is not triggered if a transaction is started before calling save. + * - `Model.afterSaveCommit` Fired after the transaction in which the save operation is + * wrapped has been committed. It’s also triggered for non atomic saves where database + * operations are implicitly committed. The event is triggered only for the primary + * table on which save() is directly called. The event is not triggered if a + * transaction is started before calling save. * - * - `beforeDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options)` - * Fired before an entity is deleted. By stopping this event you will abort - * the delete operation. + * - `Model.beforeDelete` Fired before an entity is deleted. By stopping this + * event you will abort the delete operation. + * + * - `Model.afterDelete` Fired after an entity has been deleted. + * + * ### Callbacks * + * You can subscribe to the events listed above in your table classes by implementing the + * lifecycle methods below: + * + * - `beforeFind(EventInterface $event, Query $query, ArrayObject $options, boolean $primary)` + * - `buildValidator(EventInterface $event, Validator $validator, string $name)` + * - `buildRules(RulesChecker $rules)` + * - `beforeRules(EventInterface $event, EntityInterface $entity, ArrayObject $options, string $operation)` + * - `afterRules(EventInterface $event, EntityInterface $entity, ArrayObject $options, bool $result, string $operation)` + * - `beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options)` + * - `afterSave(EventInterface $event, EntityInterface $entity, ArrayObject $options)` + * - `afterSaveCommit(EventInterface $event, EntityInterface $entity, ArrayObject $options)` + * - `beforeDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options)` * - `afterDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options)` - * Fired after an entity has been deleted. * * @see \Cake\Event\EventManager for reference on the events system. */ From b254e6f287fc71976f3bd7ec779c3d4179c34677 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 7 Feb 2020 23:05:24 -0500 Subject: [PATCH 1496/2059] Fix phpcs --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 7231fa87..ec14e2af 100644 --- a/Table.php +++ b/Table.php @@ -92,7 +92,7 @@ * `$primary` parameter indicates whether or not this is the root query, or an * associated query. * - * - `Model.buildValidator` Allows listeners to modify validation rules + * - `Model.buildValidator` Allows listeners to modify validation rules * for the provided named validator. * * - `Model.buildRules` Allows listeners to modify the rules checker by adding more rules. From 336f48732aa7136340a579af186b892bc4776376 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 13 Feb 2020 14:29:43 +0530 Subject: [PATCH 1497/2059] Don't try to update counter cache when foreign key value is null. Closes #14276 --- Behavior/CounterCacheBehavior.php | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 9b719328..0813e42c 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -219,11 +219,24 @@ protected function _processAssociation( array $settings ): void { $foreignKeys = (array)$assoc->getForeignKey(); - $primaryKeys = (array)$assoc->getBindingKey(); $countConditions = $entity->extract($foreignKeys); + $allNulls = true; + foreach ($countConditions as $field => $value) { + if ($value === null) { + $countConditions[$field . ' IS'] = $value; + unset($countConditions[$field]); + } else { + $allNulls = false; + } + } + if ($allNulls) { + return; + } + + $primaryKeys = (array)$assoc->getBindingKey(); $updateConditions = array_combine($primaryKeys, $countConditions); - $countOriginalConditions = $entity->extractOriginalChanged($foreignKeys); + $countOriginalConditions = $entity->extractOriginalChanged($foreignKeys); if ($countOriginalConditions !== []) { $updateOriginalConditions = array_combine($primaryKeys, $countOriginalConditions); } From 6ac9b0e5279fc3e8cad2588fde5b02f6b06cdd8c Mon Sep 17 00:00:00 2001 From: Victor Eduardo de Assis Date: Thu, 13 Feb 2020 14:11:14 -0300 Subject: [PATCH 1498/2059] Fix EagerLoader to avoid exception on loading optional association with leftJoinWith and contain --- EagerLoader.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index 8e5413a2..cc3adb82 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -830,13 +830,15 @@ protected function _groupKeys(BufferedStatement $statement, array $collectKeys): $keys = []; foreach (($statement->fetchAll('assoc') ?: []) as $result) { foreach ($collectKeys as $nestKey => $parts) { - // Missed joins will have null in the results. - if ($parts[2] === true && !isset($result[$parts[1][0]])) { - continue; - } if ($parts[2] === true) { - $value = $result[$parts[1][0]]; - $keys[$nestKey][$parts[0]][$value] = $value; + // Missed joins will have null in the results. + // Assign empty array to avoid not found association when optional. + if (empty($keys[$nestKey][$parts[0]]) && !isset($result[$parts[1][0]])) { + $keys[$nestKey][$parts[0]] = []; + } else { + $value = $result[$parts[1][0]]; + $keys[$nestKey][$parts[0]][$value] = $value; + } continue; } From 111f1a1951fed9d29e4dfc84d7664b0486073985 Mon Sep 17 00:00:00 2001 From: Victor Eduardo de Assis Date: Thu, 13 Feb 2020 15:05:42 -0300 Subject: [PATCH 1499/2059] Fix EagerLoader to still be able to throw the no association exception if the needed field was not selected on the query --- EagerLoader.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/EagerLoader.php b/EagerLoader.php index cc3adb82..62f1aaa4 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -832,6 +832,9 @@ protected function _groupKeys(BufferedStatement $statement, array $collectKeys): foreach ($collectKeys as $nestKey => $parts) { if ($parts[2] === true) { // Missed joins will have null in the results. + if (!array_key_exists($parts[1][0], $result)) { + continue; + } // Assign empty array to avoid not found association when optional. if (empty($keys[$nestKey][$parts[0]]) && !isset($result[$parts[1][0]])) { $keys[$nestKey][$parts[0]] = []; @@ -850,7 +853,6 @@ protected function _groupKeys(BufferedStatement $statement, array $collectKeys): $keys[$nestKey][$parts[0]][implode(';', $collected)] = $collected; } } - $statement->rewind(); return $keys; From 7cc249cf3596a22e4f38e908171d308bf5e8af38 Mon Sep 17 00:00:00 2001 From: Victor Eduardo de Assis Date: Fri, 14 Feb 2020 09:40:21 -0300 Subject: [PATCH 1500/2059] Change empty to !isset call to check if there are any keys present on _groupKeys method Add condition to avoid indexed array with empty string when key is null but the result keys set is already set. --- EagerLoader.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index 62f1aaa4..a72b30a3 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -836,8 +836,10 @@ protected function _groupKeys(BufferedStatement $statement, array $collectKeys): continue; } // Assign empty array to avoid not found association when optional. - if (empty($keys[$nestKey][$parts[0]]) && !isset($result[$parts[1][0]])) { - $keys[$nestKey][$parts[0]] = []; + if (!isset($result[$parts[1][0]])) { + if (!isset($keys[$nestKey][$parts[0]])) { + $keys[$nestKey][$parts[0]] = []; + } } else { $value = $result[$parts[1][0]]; $keys[$nestKey][$parts[0]][$value] = $value; From 6c6b4e7dae99717eae51ddb6fed5857c0d0a0ee0 Mon Sep 17 00:00:00 2001 From: Victor Eduardo de Assis Date: Thu, 20 Feb 2020 09:26:52 -0300 Subject: [PATCH 1501/2059] Remove unnecessary set of original entities on entity when there's a rule error on an atomic call to HasMany::_saveTarget method --- Association/HasMany.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index 1a765a24..df547b6a 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -232,8 +232,6 @@ protected function _saveTarget( if (!empty($options['atomic'])) { $original[$k]->setErrors($entity->getErrors()); - $entity->set($this->getProperty(), $original); - return false; } } From 3b4f2732043f7f4301c2fd5e2bd5b12b64f0b168 Mon Sep 17 00:00:00 2001 From: Victor Eduardo de Assis Date: Thu, 20 Feb 2020 09:33:52 -0300 Subject: [PATCH 1502/2059] Set _invalid property of original entity when there's a rule error on an atomic call to HasMany::_saveTarget keeping the entities content consistent --- Association/HasMany.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Association/HasMany.php b/Association/HasMany.php index df547b6a..8137a629 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -232,6 +232,7 @@ protected function _saveTarget( if (!empty($options['atomic'])) { $original[$k]->setErrors($entity->getErrors()); + $original[$k]->setInvalid($entity->getInvalid()); return false; } } From 65c2d0bb011ab3a7f96d7590909bde69eb3c7351 Mon Sep 17 00:00:00 2001 From: Victor Eduardo de Assis Date: Thu, 20 Feb 2020 09:52:08 -0300 Subject: [PATCH 1503/2059] Fix coding style --- Association/HasMany.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Association/HasMany.php b/Association/HasMany.php index 8137a629..c130e06c 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -233,6 +233,7 @@ protected function _saveTarget( if (!empty($options['atomic'])) { $original[$k]->setErrors($entity->getErrors()); $original[$k]->setInvalid($entity->getInvalid()); + return false; } } From 8c4214e49f627a5bb308639479e1bfc07f9ffc53 Mon Sep 17 00:00:00 2001 From: Victor Eduardo de Assis Date: Thu, 20 Feb 2020 10:55:22 -0300 Subject: [PATCH 1504/2059] Change type of $entities param on HasMany::_saveTarget method docblock to accept both EntityInterface and InvalidPropertyInterface --- Association/HasMany.php | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index c130e06c..3dcfc90a 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -21,6 +21,7 @@ use Cake\Database\Expression\FieldInterface; use Cake\Database\Expression\QueryExpression; use Cake\Datasource\EntityInterface; +use Cake\Datasource\InvalidPropertyInterface; use Cake\ORM\Association; use Cake\ORM\Association\Loader\SelectLoader; use Cake\ORM\Query; @@ -197,9 +198,10 @@ public function saveAssociated(EntityInterface $entity, array $options = []) * target entity, and the parent entity. * @param \Cake\Datasource\EntityInterface $parentEntity The source entity containing the target * entities to be saved. - * @param array $entities list of entities to persist in target table and to - * link to the parent entity + * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\InvalidPropertyInterface[] $entities list of entities + * to persist in target table and to link to the parent entity * @param array $options list of options accepted by `Table::save()`. + * * @return bool `true` on success, `false` otherwise. */ protected function _saveTarget( @@ -231,8 +233,12 @@ protected function _saveTarget( } if (!empty($options['atomic'])) { - $original[$k]->setErrors($entity->getErrors()); - $original[$k]->setInvalid($entity->getInvalid()); + if ($original[$k] instanceof EntityInterface && $entity instanceof EntityInterface) { + $original[$k]->setErrors($entity->getErrors()); + } + if ($original[$k] instanceof InvalidPropertyInterface && $entity instanceof InvalidPropertyInterface) { + $original[$k]->setInvalid($entity->getInvalid()); + } return false; } From 307e51100ba80d5f52ee7c47e60f1f3fa0570040 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 21 Feb 2020 11:18:06 +0530 Subject: [PATCH 1505/2059] Remove redundant instanceof check. Fix type in docblock. Refs #14306 --- Association/HasMany.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index 3dcfc90a..2e4e8b61 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -198,7 +198,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) * target entity, and the parent entity. * @param \Cake\Datasource\EntityInterface $parentEntity The source entity containing the target * entities to be saved. - * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\InvalidPropertyInterface[] $entities list of entities + * @param array $entities list of entities * to persist in target table and to link to the parent entity * @param array $options list of options accepted by `Table::save()`. * @@ -233,10 +233,8 @@ protected function _saveTarget( } if (!empty($options['atomic'])) { - if ($original[$k] instanceof EntityInterface && $entity instanceof EntityInterface) { - $original[$k]->setErrors($entity->getErrors()); - } - if ($original[$k] instanceof InvalidPropertyInterface && $entity instanceof InvalidPropertyInterface) { + $original[$k]->setErrors($entity->getErrors()); + if ($entity instanceof InvalidPropertyInterface) { $original[$k]->setInvalid($entity->getInvalid()); } From 9ba6e45a14f145b7e781f963f6bb8dab6c07077d Mon Sep 17 00:00:00 2001 From: nook24 Date: Thu, 27 Feb 2020 16:01:17 +0100 Subject: [PATCH 1506/2059] Adding two examples of how to use the accessibleFields option --- Marshaller.php | 8 ++++++++ Table.php | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/Marshaller.php b/Marshaller.php index 15134677..5586b4b1 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -160,6 +160,14 @@ protected function _buildPropertyMap(array $data, array $options): array * ]); * ``` * + * ``` + * $result = $marshaller->one($data, [ + * 'associated' => [ + * 'Tags' => ['accessibleFields' => ['*' => true]] + * ] + * ]); + * ``` + * * @param array $data The data to hydrate. * @param array $options List of options * @return \Cake\Datasource\EntityInterface diff --git a/Table.php b/Table.php index 538cb0e5..3ba16cf5 100644 --- a/Table.php +++ b/Table.php @@ -2733,6 +2733,14 @@ public function newEntities(array $data, array $options = []): array * ); * ``` * + * ``` + * $article = $this->Articles->patchEntity($article, $this->request->getData(), [ + * 'associated' => [ + * 'Tags' => ['accessibleFields' => ['*' => true]] + * ] + * ]); + * ``` + * * By default, the data is validated before being passed to the entity. In * the case of invalid fields, those will not be assigned to the entity. * The `validate` option can be used to disable validation on the passed data: From 598d6b4e73585aac1973d39c20c685c4159ebd9f Mon Sep 17 00:00:00 2001 From: Victor Eduardo de Assis Date: Mon, 2 Mar 2020 15:46:15 -0300 Subject: [PATCH 1507/2059] Fix Table::_saveMany() to properly rollback when one entity fail to save --- Table.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Table.php b/Table.php index 3ba16cf5..77e726e8 100644 --- a/Table.php +++ b/Table.php @@ -2178,13 +2178,16 @@ protected function _saveMany(iterable $entities, $options = []): iterable } }; + $failed = null; try { - $failed = $this->getConnection() - ->transactional(function () use ($entities, $options, &$isNew) { + $this->getConnection() + ->transactional(function () use ($entities, $options, &$isNew, &$failed) { foreach ($entities as $key => $entity) { $isNew[$key] = $entity->isNew(); if ($this->save($entity, $options) === false) { - return $entity; + $failed = $entity; + + return false; } } }); From f3d771b5bb76dcc8a23b296041325cab112760fd Mon Sep 17 00:00:00 2001 From: Victor Eduardo de Assis Date: Tue, 3 Mar 2020 05:20:16 -0300 Subject: [PATCH 1508/2059] Add inline annotation for $failed variable --- Table.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Table.php b/Table.php index 77e726e8..7e2caabd 100644 --- a/Table.php +++ b/Table.php @@ -2178,6 +2178,7 @@ protected function _saveMany(iterable $entities, $options = []): iterable } }; + /** @var \Cake\Datasource\EntityInterface|null $failed */ $failed = null; try { $this->getConnection() From 9d644bed2899774b0179f7e059dc631a10a646fb Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 8 Mar 2020 00:54:49 +0530 Subject: [PATCH 1509/2059] Fix errors reported by psalm --- EagerLoader.php | 1 + 1 file changed, 1 insertion(+) diff --git a/EagerLoader.php b/EagerLoader.php index a72b30a3..a4b8625c 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -254,6 +254,7 @@ public function setMatching(string $assoc, ?callable $builder = null, array $opt $containments = []; $pointer =& $containments; $opts = ['matching' => true] + $options; + /** @psalm-suppress InvalidArrayOffset */ unset($opts['negateMatch']); foreach ($assocs as $name) { From 2a1125dcf1fa6c86cdf02d60fdd6f1ccec1b2e9f Mon Sep 17 00:00:00 2001 From: Jad Bitar Date: Wed, 11 Mar 2020 09:30:57 -0400 Subject: [PATCH 1510/2059] Fix argument type declaration --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 7e2caabd..4553d588 100644 --- a/Table.php +++ b/Table.php @@ -1463,7 +1463,7 @@ protected function _setFieldMatchers(array $options, array $keys): array * incorrect number of elements. * @psalm-suppress InvalidReturnType */ - public function get($primaryKey, $options = []): EntityInterface + public function get($primaryKey, array $options = []): EntityInterface { $key = (array)$this->getPrimaryKey(); $alias = $this->getAlias(); From 756e0e4b88f32ddfc228427198481571f0278216 Mon Sep 17 00:00:00 2001 From: victoreassis Date: Sat, 14 Mar 2020 23:14:47 -0300 Subject: [PATCH 1511/2059] Fix CounterCacheBehavior::_processAssociation() to properly update count on null association (#14363) Fix CounterCacheBehavior::_processAssociation() to properly update count on null association When new count were null the original count was not updated, and when the original count was null an exception was thrown because of the null conditions invalid build. * Add test case on CounterCacheBehavior to cover the presence of null conditions on new and original count * Fix coding style --- Behavior/CounterCacheBehavior.php | 39 ++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 0813e42c..f91e0146 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -220,18 +220,13 @@ protected function _processAssociation( ): void { $foreignKeys = (array)$assoc->getForeignKey(); $countConditions = $entity->extract($foreignKeys); - $allNulls = true; + foreach ($countConditions as $field => $value) { if ($value === null) { $countConditions[$field . ' IS'] = $value; unset($countConditions[$field]); - } else { - $allNulls = false; } } - if ($allNulls) { - return; - } $primaryKeys = (array)$assoc->getBindingKey(); $updateConditions = array_combine($primaryKeys, $countConditions); @@ -254,16 +249,18 @@ protected function _processAssociation( continue; } - if ($config instanceof Closure) { - $count = $config($event, $entity, $this->_table, false); - } else { - $count = $this->_getCount($config, $countConditions); - } - if ($count !== false) { - $assoc->getTarget()->updateAll([$field => $count], $updateConditions); + if ($this->_shouldUpdateCount($updateConditions)) { + if ($config instanceof Closure) { + $count = $config($event, $entity, $this->_table, false); + } else { + $count = $this->_getCount($config, $countConditions); + } + if ($count !== false) { + $assoc->getTarget()->updateAll([$field => $count], $updateConditions); + } } - if (isset($updateOriginalConditions)) { + if (isset($updateOriginalConditions) && $this->_shouldUpdateCount($updateOriginalConditions)) { if ($config instanceof Closure) { $count = $config($event, $entity, $this->_table, true); } else { @@ -276,6 +273,20 @@ protected function _processAssociation( } } + /** + * Checks if the count should be updated given a set of conditions. + * + * @param array $conditions Conditions to update count. + * + * @return bool True if the count update should happen, false otherwise. + */ + protected function _shouldUpdateCount(array $conditions) + { + return !empty(array_filter($conditions, function ($value) { + return $value !== null; + })); + } + /** * Fetches and returns the count for a single field in an association * From 99686140d9a14dd2cd6c3ea48f4cd23ffab5dcef Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Tue, 17 Mar 2020 13:07:21 -0500 Subject: [PATCH 1512/2059] Fixed type for formatResults callback --- Behavior/TreeBehavior.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index e2db3b5a..97cc3095 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -16,6 +16,7 @@ */ namespace Cake\ORM\Behavior; +use Cake\Collection\CollectionInterface; use Cake\Database\Expression\IdentifierExpression; use Cake\Datasource\EntityInterface; use Cake\Datasource\Exception\RecordNotFoundException; @@ -521,8 +522,7 @@ public function findTreeList(Query $query, array $options): Query */ public function formatTreeList(Query $query, array $options = []): Query { - return $query->formatResults(function ($results) use ($options) { - /** @var \Cake\Collection\CollectionTrait $results */ + return $query->formatResults(function (CollectionInterface $results) use ($options) { $options += [ 'keyPath' => $this->_getPrimaryKey(), 'valuePath' => $this->_table->getDisplayField(), From 6342a0c898ae7e40fcc2e930625108a9513a28ac Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Tue, 17 Mar 2020 13:22:12 -0500 Subject: [PATCH 1513/2059] Set typehint for listNested() return type --- Behavior/TreeBehavior.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 97cc3095..66971c4a 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -529,9 +529,10 @@ public function formatTreeList(Query $query, array $options = []): Query 'spacer' => '_', ]; - return $results - ->listNested() - ->printer($options['valuePath'], $options['keyPath'], $options['spacer']); + /** @var \Cake\Collection\Iterator\TreeIterator $nested */ + $nested = $results->listNested(); + + return $nested->printer($options['valuePath'], $options['keyPath'], $options['spacer']); }); } From 39e984435905eda71ae0bafc5e8632819d47aaeb Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 18 Mar 2020 23:49:21 +0530 Subject: [PATCH 1514/2059] Fix typehint for iterables. Improve docblock types to help static analyzers and IDEs detect the type of item returned by iterables. https://blog.jetbrains.com/phpstorm/2016/06/phpstorm-2016-2-eap-162-844/ --- AssociationCollection.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/AssociationCollection.php b/AssociationCollection.php index 9dbb2091..8bfe8a38 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -22,6 +22,7 @@ use Cake\ORM\Locator\LocatorInterface; use InvalidArgumentException; use IteratorAggregate; +use Traversable; /** * A container/collection for association classes. @@ -378,9 +379,10 @@ public function normalizeKeys($keys): array /** * Allow looping through the associations * - * @return \ArrayIterator + * @return \Cake\ORM\Association[] + * @psalm-return \Traversable */ - public function getIterator(): ArrayIterator + public function getIterator(): Traversable { return new ArrayIterator($this->_items); } From 9bdbe951b6cf72b199269bc6670bccb9b5ec6e9b Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Thu, 19 Mar 2020 11:29:10 -0500 Subject: [PATCH 1515/2059] Use bindingKey from junction table association instead of target table primary key directly for BelongsToMany --- Association/BelongsToMany.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 50177844..7c2a2761 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -753,7 +753,7 @@ protected function _saveLinks(EntityInterface $sourceEntity, array $targetEntiti $belongsTo = $junction->getAssociation($target->getAlias()); $foreignKey = (array)$this->getForeignKey(); $assocForeignKey = (array)$belongsTo->getForeignKey(); - $targetPrimaryKey = (array)$target->getPrimaryKey(); + $targetBindingKey = (array)$belongsTo->getBindingKey(); $bindingKey = (array)$this->getBindingKey(); $jointProperty = $this->_junctionProperty; $junctionRegistryAlias = $junction->getRegistryAlias(); @@ -764,7 +764,7 @@ protected function _saveLinks(EntityInterface $sourceEntity, array $targetEntiti $joint = new $entityClass([], ['markNew' => true, 'source' => $junctionRegistryAlias]); } $sourceKeys = array_combine($foreignKey, $sourceEntity->extract($bindingKey)); - $targetKeys = array_combine($assocForeignKey, $e->extract($targetPrimaryKey)); + $targetKeys = array_combine($assocForeignKey, $e->extract($targetBindingKey)); $changedKeys = ( $sourceKeys !== $joint->extract($foreignKey) || From 407534f0f583175233225fb128e0dd86b8d8f26a Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Thu, 19 Mar 2020 17:46:29 -0500 Subject: [PATCH 1516/2059] Throw exception when adding BelongsToMany association that's incompatible with existing associations with the same junction table --- Association/BelongsToMany.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 50177844..5215d012 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -253,6 +253,7 @@ public function defaultRowValue(array $row, bool $joined): array * * @param string|\Cake\ORM\Table|null $table Name or instance for the join table * @return \Cake\ORM\Table + * @throws \InvalidArgumentException If the expected associations are incompatible with existing associations. */ public function junction($table = null): Table { @@ -374,6 +375,7 @@ protected function _generateSourceAssociations(Table $junction, Table $source): * @param \Cake\ORM\Table $source The source table. * @param \Cake\ORM\Table $target The target table. * @return void + * @throws \InvalidArgumentException If the expected associations are incompatible with existing associations. */ protected function _generateJunctionAssociations(Table $junction, Table $source, Table $target): void { @@ -385,7 +387,19 @@ protected function _generateJunctionAssociations(Table $junction, Table $source, 'foreignKey' => $this->getTargetForeignKey(), 'targetTable' => $target, ]); + } else { + $belongsTo = $junction->getAssociation($tAlias); + if ( + $this->getTargetForeignKey() !== $belongsTo->getForeignKey() || + $target !== $belongsTo->getTarget() + ) { + throw new InvalidArgumentException( + "The existing `{$tAlias}` association on `{$junction->getAlias()} " . + "is incompatible with the `{$this->getName()}` association on `{$source->getAlias()}`" + ); + } } + if (!$junction->hasAssociation($sAlias)) { $junction->belongsTo($sAlias, [ 'foreignKey' => $this->getForeignKey(), From b87248e72ee54152c1cdd5733b5d650dc2267e95 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Sat, 21 Mar 2020 01:14:13 -0500 Subject: [PATCH 1517/2059] Set binding key on generated HasMany associations for junction tables --- Association/BelongsToMany.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 7c2a2761..9828ffca 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -312,10 +312,17 @@ protected function _generateTargetAssociations(Table $junction, Table $source, T { $junctionAlias = $junction->getAlias(); $sAlias = $source->getAlias(); + $tAlias = $target->getAlias(); + + $targetBindingKey = null; + if ($junction->hasAssociation($tAlias)) { + $targetBindingKey = $junction->getAssociation($tAlias)->getBindingKey(); + } if (!$target->hasAssociation($junctionAlias)) { $target->hasMany($junctionAlias, [ 'targetTable' => $junction, + 'bindingKey' => $targetBindingKey, 'foreignKey' => $this->getTargetForeignKey(), 'strategy' => $this->_strategy, ]); @@ -350,9 +357,17 @@ protected function _generateTargetAssociations(Table $junction, Table $source, T protected function _generateSourceAssociations(Table $junction, Table $source): void { $junctionAlias = $junction->getAlias(); + $sAlias = $source->getAlias(); + + $sourceBindingKey = null; + if ($junction->hasAssociation($sAlias)) { + $sourceBindingKey = $junction->getAssociation($sAlias)->getBindingKey(); + } + if (!$source->hasAssociation($junctionAlias)) { $source->hasMany($junctionAlias, [ 'targetTable' => $junction, + 'bindingKey' => $sourceBindingKey, 'foreignKey' => $this->getForeignKey(), 'strategy' => $this->_strategy, ]); From 5629691dfbb326c03e560e9481eb1e5853ddc401 Mon Sep 17 00:00:00 2001 From: saeid Date: Sun, 22 Mar 2020 19:19:27 +0430 Subject: [PATCH 1518/2059] add afterMarshal event --- Marshaller.php | 2 ++ Table.php | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Marshaller.php b/Marshaller.php index 5586b4b1..7eb318c4 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -232,6 +232,7 @@ public function one(array $data, array $options = []): EntityInterface } $entity->setErrors($errors); + $this->_table->dispatchEvent('Model.afterMarshal', compact('entity')); return $entity; } @@ -612,6 +613,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) $entity->setDirty($field, $properties[$field]->isDirty()); } } + $this->_table->dispatchEvent('Model.afterMarshal', compact('entity')); return $entity; } diff --git a/Table.php b/Table.php index 4553d588..801ac61f 100644 --- a/Table.php +++ b/Table.php @@ -2884,6 +2884,7 @@ public function validateUnique($value, array $options, ?array $context = null): * The conventional method map is: * * - Model.beforeMarshal => beforeMarshal + * - Model.afterMarshal => afterMarshal * - Model.buildValidator => buildValidator * - Model.beforeFind => beforeFind * - Model.beforeSave => beforeSave @@ -2901,6 +2902,7 @@ public function implementedEvents(): array { $eventMap = [ 'Model.beforeMarshal' => 'beforeMarshal', + 'Model.afterMarshal' => 'afterMarshal', 'Model.buildValidator' => 'buildValidator', 'Model.beforeFind' => 'beforeFind', 'Model.beforeSave' => 'beforeSave', From 6c3246b45bc7d84461b00526ddcfa90ceb1431f3 Mon Sep 17 00:00:00 2001 From: saeid Date: Mon, 23 Mar 2020 17:08:32 +0430 Subject: [PATCH 1519/2059] apply afterMarshal on behavior --- Behavior.php | 1 + Marshaller.php | 1 + 2 files changed, 2 insertions(+) diff --git a/Behavior.php b/Behavior.php index d1562d0b..fcd69e6f 100644 --- a/Behavior.php +++ b/Behavior.php @@ -266,6 +266,7 @@ public function implementedEvents(): array { $eventMap = [ 'Model.beforeMarshal' => 'beforeMarshal', + 'Model.afterMarshal' => 'afterMarshal', 'Model.beforeFind' => 'beforeFind', 'Model.beforeSave' => 'beforeSave', 'Model.afterSave' => 'afterSave', diff --git a/Marshaller.php b/Marshaller.php index 7eb318c4..321c541b 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -600,6 +600,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) $entity->setDirty($field, $value->isDirty()); } } + $this->_table->dispatchEvent('Model.afterMarshal', compact('entity')); return $entity; } From 9d7b978c33726a1c48d3038b1dd02945aa594a70 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 29 Mar 2020 15:10:45 +0530 Subject: [PATCH 1520/2059] Improve psalm annotations --- Behavior/TranslateBehavior.php | 5 ++++- RulesChecker.php | 2 +- Table.php | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 5b67ea8f..8dd02e72 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -67,7 +67,8 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface /** * Default strategy class name. * - * @var class-string<\Cake\ORM\Behavior\Translate\TranslateStrategyInterface> + * @var string + * @psalm-var class-string<\Cake\ORM\Behavior\Translate\TranslateStrategyInterface> */ protected static $defaultStrategyClass = EavStrategy::class; @@ -132,6 +133,7 @@ public function initialize(array $config): void * @param string $class Class name. * @return void * @since 4.0.0 + * @psalm-var class-string<\Cake\ORM\Behavior\Translate\TranslateStrategyInterface> */ public static function setDefaultStrategyClass(string $class) { @@ -143,6 +145,7 @@ public static function setDefaultStrategyClass(string $class) * * @return string * @since 4.0.0 + * @psalm-return class-string<\Cake\ORM\Behavior\Translate\TranslateStrategyInterface> */ public static function getDefaultStrategyClass(): string { diff --git a/RulesChecker.php b/RulesChecker.php index b46348be..4cb2eca1 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -83,7 +83,7 @@ public function isUnique(array $fields, $message = null): RuleInvoker * * @param string|string[] $field The field or list of fields to check for existence by * primary key lookup in the other table. - * @param object|string $table The table name where the fields existence will be checked. + * @param \Cake\ORM\Table|\Cake\ORM\Association|string $table The table name where the fields existence will be checked. * @param string|array|null $message The error message to show in case the rule does not pass. Can * also be an array of options. When an array, the 'message' key can be used to provide a message. * @return \Cake\Datasource\RuleInvoker diff --git a/Table.php b/Table.php index 4553d588..f969893a 100644 --- a/Table.php +++ b/Table.php @@ -735,8 +735,9 @@ public function getEntityClass(): string */ public function setEntityClass(string $name) { + /** @psalm-var class-string<\Cake\Datasource\EntityInterface>|null */ $class = App::className($name, 'Model/Entity'); - if (!$class) { + if ($class === null) { throw new MissingEntityException([$name]); } From f8ed1aab8a831c196a9a413a0c066e89de2c0dd5 Mon Sep 17 00:00:00 2001 From: mscherer Date: Sun, 29 Mar 2020 18:19:39 +0200 Subject: [PATCH 1521/2059] Add json ext as dependency as the core needs this. --- Table.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Table.php b/Table.php index f969893a..d86f9b99 100644 --- a/Table.php +++ b/Table.php @@ -2162,8 +2162,9 @@ public function saveManyOrFail(iterable $entities, $options = []): iterable /** * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to save. * @param array|\ArrayAccess|\Cake\ORM\SaveOptionsBuilder $options Options used when calling Table::save() for each entity. - * @return \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface Entities list. * @throws \Cake\ORM\Exception\PersistenceFailedException If an entity couldn't be saved. + * @throws \Exception If an entity couldn't be saved. + * @return \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface Entities list. */ protected function _saveMany(iterable $entities, $options = []): iterable { @@ -2268,7 +2269,6 @@ public function delete(EntityInterface $entity, $options = []): bool * @param array|\ArrayAccess $options Options used when calling Table::save() for each entity. * @return \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface|false Entities list * on success, false on failure. - * @throws \Exception * @see \Cake\ORM\Table::delete() for options and events related to this method. */ public function deleteMany(iterable $entities, $options = []) @@ -2292,7 +2292,6 @@ public function deleteMany(iterable $entities, $options = []) * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to delete. * @param array|\ArrayAccess $options Options used when calling Table::save() for each entity. * @return \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface Entities list. - * @throws \Exception * @throws \Cake\ORM\Exception\PersistenceFailedException * @see \Cake\ORM\Table::delete() for options and events related to this method. */ From 016f8ae560b8241bb354303069e0a49ad61384e2 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 31 Mar 2020 00:24:58 +0530 Subject: [PATCH 1522/2059] Fix error reported by phpstan --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index d86f9b99..351e0377 100644 --- a/Table.php +++ b/Table.php @@ -1536,7 +1536,7 @@ protected function _executeTransaction(callable $worker, bool $atomic = true) */ protected function _transactionCommitted(bool $atomic, bool $primary): bool { - return !$this->getConnection()->inTransaction() && ($atomic || ($primary && !$atomic)); + return !$this->getConnection()->inTransaction() && ($atomic || $primary); } /** From f399509a4fa395f62ea022567536961ac5cf696c Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Tue, 31 Mar 2020 14:09:41 -0500 Subject: [PATCH 1523/2059] Updated inheritDoc comments to follow formatting standard. --- Association/BelongsTo.php | 4 +-- Association/BelongsToMany.php | 4 +-- Association/HasMany.php | 4 +-- Association/HasOne.php | 4 +-- Association/Loader/SelectWithPivotLoader.php | 2 +- Behavior/Translate/ShadowTableStrategy.php | 2 +- Behavior/TranslateBehavior.php | 7 ++++- Query.php | 7 +++-- Table.php | 33 ++++++++++++++++++-- 9 files changed, 47 insertions(+), 20 deletions(-) diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index a132a944..fb354148 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -182,9 +182,7 @@ protected function _joinCondition(array $options): array } /** - * {@inheritDoc} - * - * @return \Closure + * @inheritDoc */ public function eagerLoader(array $options): Closure { diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 50177844..9423783c 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -511,9 +511,7 @@ protected function _joinCondition(array $options): array } /** - * {@inheritDoc} - * - * @return \Closure + * @inheritDoc */ public function eagerLoader(array $options): Closure { diff --git a/Association/HasMany.php b/Association/HasMany.php index 2e4e8b61..79966f8c 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -653,9 +653,7 @@ protected function _options(array $opts): void } /** - * {@inheritDoc} - * - * @return \Closure + * @inheritDoc */ public function eagerLoader(array $options): Closure { diff --git a/Association/HasOne.php b/Association/HasOne.php index 060fa5ff..bd10694b 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -125,9 +125,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) } /** - * {@inheritDoc} - * - * @return \Closure + * @inheritDoc */ public function eagerLoader(array $options): Closure { diff --git a/Association/Loader/SelectWithPivotLoader.php b/Association/Loader/SelectWithPivotLoader.php index fe2f886c..33005b24 100644 --- a/Association/Loader/SelectWithPivotLoader.php +++ b/Association/Loader/SelectWithPivotLoader.php @@ -55,7 +55,7 @@ class SelectWithPivotLoader extends SelectLoader protected $junctionConditions; /** - * {@inheritDoc} + * @inheritDoc * */ public function __construct(array $options) diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 7cd0624c..4365a7f7 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -393,7 +393,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array } /** - * {@inheritDoc} + * @inheritDoc */ public function buildMarshalMap(Marshaller $marshaller, array $map, array $options): array { diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 8dd02e72..5c5aad27 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -214,11 +214,16 @@ public function implementedEvents(): array } /** + * {@inheritDoc} + * * Add in `_translations` marshalling handlers. You can disable marshalling * of translations by setting `'translations' => false` in the options * provided to `Table::newEntity()` or `Table::patchEntity()`. * - * {@inheritDoc} + * @param \Cake\ORM\Marshaller $marshaller The marhshaller of the table the behavior is attached to. + * @param array $map The property map being built. + * @param array $options The options array used in the marshalling call. + * @return array A map of `[property => callable]` of additional properties to marshal. */ public function buildMarshalMap(Marshaller $marshaller, array $map, array $options): array { diff --git a/Query.php b/Query.php index 2d1e6116..a50d0ce3 100644 --- a/Query.php +++ b/Query.php @@ -872,6 +872,8 @@ public function __clone() * Returns the COUNT(*) for the query. If the query has not been * modified, and the count has already been performed the cached * value is returned + * + * @return int */ public function count(): int { @@ -1033,6 +1035,7 @@ public function cache($key, $config = 'default') /** * {@inheritDoc} * + * @return \Cake\Datasource\ResultSetInterface * @throws \RuntimeException if this method is called on a non-select Query. */ public function all(): ResultSetInterface @@ -1181,9 +1184,7 @@ protected function _addDefaultSelectTypes(): void } /** - * {@inheritDoc} - * - * @see \Cake\ORM\Table::find() + * @inheritDoc */ public function find(string $finder, array $options = []) { diff --git a/Table.php b/Table.php index 351e0377..d58db4cb 100644 --- a/Table.php +++ b/Table.php @@ -1460,8 +1460,14 @@ protected function _setFieldMatchers(array $options, array $keys): array * $article = $articles->get(1, ['contain' => ['Users', 'Comments']]); * ``` * + * @param mixed $primaryKey primary key value to find + * @param array $options options accepted by `Table::find()` + * @return \Cake\Datasource\EntityInterface + * @throws \Cake\Datasource\Exception\RecordNotFoundException if the record with such id + * could not be found * @throws \Cake\Datasource\Exception\InvalidPrimaryKeyException When $primaryKey has an * incorrect number of elements. + * @see \Cake\Datasource\RepositoryInterface::find() * @psalm-suppress InvalidReturnType */ public function get($primaryKey, array $options = []): EntityInterface @@ -1792,8 +1798,8 @@ public function exists($conditions): bool * $articles->save($entity, ['associated' => false]); * ``` * - * @param \Cake\Datasource\EntityInterface $entity - * @param array|\ArrayAccess|\Cake\ORM\SaveOptionsBuilder $options + * @param \Cake\Datasource\EntityInterface $entity the entity to be saved + * @param array|\ArrayAccess|\Cake\ORM\SaveOptionsBuilder $options The options to use when saving. * @return \Cake\Datasource\EntityInterface|false * @throws \Cake\ORM\Exception\RolledbackTransactionException If the transaction is aborted in the afterSave event. */ @@ -2235,6 +2241,9 @@ protected function _saveMany(iterable $entities, $options = []): iterable * for the duration of the callbacks, this allows listeners to modify * the options used in the delete operation. * + * @param \Cake\Datasource\EntityInterface $entity The entity to remove. + * @param array|\ArrayAccess $options The options for the delete. + * @return bool success */ public function delete(EntityInterface $entity, $options = []): bool { @@ -2670,6 +2679,10 @@ public function newEmptyEntity(): EntityInterface * * You can use the `Model.beforeMarshal` event to modify request data * before it is converted into entities. + * + * @param array $data The data to build an entity with. + * @param array $options A list of options for the object hydration. + * @return \Cake\Datasource\EntityInterface */ public function newEntity(array $data, array $options = []): EntityInterface { @@ -2708,6 +2721,10 @@ public function newEntity(array $data, array $options = []): EntityInterface * * You can use the `Model.beforeMarshal` event to modify request data * before it is converted into entities. + * + * @param array $data The data to build an entity with. + * @param array $options A list of options for the objects hydration. + * @return \Cake\Datasource\EntityInterface[] An array of hydrated records. */ public function newEntities(array $data, array $options = []): array { @@ -2762,6 +2779,12 @@ public function newEntities(array $data, array $options = []): array * presently has an identical value, the setter will not be called, and the * property will not be marked as dirty. This is an optimization to prevent unnecessary field * updates when persisting entities. + * + * @param \Cake\Datasource\EntityInterface $entity the entity that will get the + * data merged in + * @param array $data key value list of fields to be merged into the entity + * @param array $options A list of options for the object hydration. + * @return \Cake\Datasource\EntityInterface */ public function patchEntity(EntityInterface $entity, array $data, array $options = []): EntityInterface { @@ -2797,6 +2820,12 @@ public function patchEntity(EntityInterface $entity, array $data, array $options * * You can use the `Model.beforeMarshal` event to modify request data * before it is converted into entities. + * + * @param \Cake\Datasource\EntityInterface[]|\Traversable $entities the entities that will get the + * data merged in + * @param array $data list of arrays to be merged into the entities + * @param array $options A list of options for the objects hydration. + * @return \Cake\Datasource\EntityInterface[] */ public function patchEntities(iterable $entities, array $data, array $options = []): array { From ac72e94db3657c52a989b8746f1b393e7b1464af Mon Sep 17 00:00:00 2001 From: saeid Date: Wed, 1 Apr 2020 19:44:24 +0430 Subject: [PATCH 1524/2059] add data + options into afterMarshal --- Marshaller.php | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 321c541b..0d7ed4fe 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -232,7 +232,7 @@ public function one(array $data, array $options = []): EntityInterface } $entity->setErrors($errors); - $this->_table->dispatchEvent('Model.afterMarshal', compact('entity')); + $this->dispatchAfterMarshal($entity, $data, $options); return $entity; } @@ -600,7 +600,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) $entity->setDirty($field, $value->isDirty()); } } - $this->_table->dispatchEvent('Model.afterMarshal', compact('entity')); + $this->dispatchAfterMarshal($entity, $data, $options); return $entity; } @@ -614,7 +614,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) $entity->setDirty($field, $properties[$field]->isDirty()); } } - $this->_table->dispatchEvent('Model.afterMarshal', compact('entity')); + $this->dispatchAfterMarshal($entity, $data, $options); return $entity; } @@ -860,4 +860,19 @@ protected function _mergeJoinData(array $original, BelongsToMany $assoc, array $ return $records; } + + /** + * dispatch Model.afterMarshal event. + * + * @param \Cake\Datasource\EntityInterface $entity The entity that was marshaled. + * @param array $data readOnly $data to use. + * @param array $options List of options that are readOnly. + * @return void + */ + protected function dispatchAfterMarshal(EntityInterface $entity, array $data, array $options = []): void + { + $data = new ArrayObject($data); + $options = new ArrayObject($options); + $this->_table->dispatchEvent('Model.afterMarshal', compact('entity', 'data', 'options')); + } } From 33a7e3d4cdd96a2efc515b19c8aa6a51bf58ac5a Mon Sep 17 00:00:00 2001 From: saeid Date: Wed, 1 Apr 2020 19:57:15 +0430 Subject: [PATCH 1525/2059] fix cs --- Marshaller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index 0d7ed4fe..be71bb6c 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -861,7 +861,7 @@ protected function _mergeJoinData(array $original, BelongsToMany $assoc, array $ return $records; } - /** + /** * dispatch Model.afterMarshal event. * * @param \Cake\Datasource\EntityInterface $entity The entity that was marshaled. From cc33545e5060babd437edb3120462f4f512feadd Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Thu, 2 Apr 2020 12:45:53 -0500 Subject: [PATCH 1526/2059] Fixed spacing for reference assignment --- AssociationsNormalizerTrait.php | 6 +++--- EagerLoader.php | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/AssociationsNormalizerTrait.php b/AssociationsNormalizerTrait.php index 21eb1e05..6d6ba450 100644 --- a/AssociationsNormalizerTrait.php +++ b/AssociationsNormalizerTrait.php @@ -33,7 +33,7 @@ protected function _normalizeAssociations($associations): array { $result = []; foreach ((array)$associations as $table => $options) { - $pointer =& $result; + $pointer = &$result; if (is_int($table)) { $table = $options; @@ -49,14 +49,14 @@ protected function _normalizeAssociations($associations): array $table = array_pop($path); $first = array_shift($path); $pointer += [$first => []]; - $pointer =& $pointer[$first]; + $pointer = &$pointer[$first]; $pointer += ['associated' => []]; foreach ($path as $t) { $pointer += ['associated' => []]; $pointer['associated'] += [$t => []]; $pointer['associated'][$t] += ['associated' => []]; - $pointer =& $pointer['associated'][$t]; + $pointer = &$pointer['associated'][$t]; } $pointer['associated'] += [$table => []]; diff --git a/EagerLoader.php b/EagerLoader.php index a4b8625c..25267312 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -252,14 +252,14 @@ public function setMatching(string $assoc, ?callable $builder = null, array $opt $assocs = explode('.', $assoc); $last = array_pop($assocs); $containments = []; - $pointer =& $containments; + $pointer = &$containments; $opts = ['matching' => true] + $options; /** @psalm-suppress InvalidArrayOffset */ unset($opts['negateMatch']); foreach ($assocs as $name) { $pointer[$name] = $opts; - $pointer =& $pointer[$name]; + $pointer = &$pointer[$name]; } $pointer[$last] = ['queryBuilder' => $builder, 'matching' => true] + $options; @@ -337,7 +337,7 @@ protected function _reformatContain(array $associations, array $original): array $result = $original; foreach ((array)$associations as $table => $options) { - $pointer =& $result; + $pointer = &$result; if (is_int($table)) { $table = $options; $options = []; @@ -359,7 +359,7 @@ protected function _reformatContain(array $associations, array $original): array $table = array_pop($path); foreach ($path as $t) { $pointer += [$t => []]; - $pointer =& $pointer[$t]; + $pointer = &$pointer[$t]; } } From 525e85eecca785a0c12eca2bbe1eab5958978efa Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 5 Apr 2020 23:28:21 +0530 Subject: [PATCH 1527/2059] Maintain new/empty line consistency in docblocks. --- Association/HasMany.php | 2 -- Association/Loader/SelectWithPivotLoader.php | 1 - Behavior/CounterCacheBehavior.php | 1 - Locator/TableLocator.php | 1 - Query.php | 2 -- RulesChecker.php | 5 ----- Table.php | 1 - 7 files changed, 13 deletions(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index 79966f8c..235017b7 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -2,7 +2,6 @@ declare(strict_types=1); /** - * * CakePHP(tm) : Rapid Development Framework (https://cakephp.org) * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @@ -201,7 +200,6 @@ public function saveAssociated(EntityInterface $entity, array $options = []) * @param array $entities list of entities * to persist in target table and to link to the parent entity * @param array $options list of options accepted by `Table::save()`. - * * @return bool `true` on success, `false` otherwise. */ protected function _saveTarget( diff --git a/Association/Loader/SelectWithPivotLoader.php b/Association/Loader/SelectWithPivotLoader.php index 33005b24..128f52f5 100644 --- a/Association/Loader/SelectWithPivotLoader.php +++ b/Association/Loader/SelectWithPivotLoader.php @@ -56,7 +56,6 @@ class SelectWithPivotLoader extends SelectLoader /** * @inheritDoc - * */ public function __construct(array $options) { diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index f91e0146..68746fb0 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -277,7 +277,6 @@ protected function _processAssociation( * Checks if the count should be updated given a set of conditions. * * @param array $conditions Conditions to update count. - * * @return bool True if the count update should happen, false otherwise. */ protected function _shouldUpdateCount(array $conditions) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 7dbee112..2f9c42fe 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -320,7 +320,6 @@ public function remove(string $alias): void * * @param string $location Location to add. * @return $this - * * @since 3.8.0 */ public function addLocation(string $location) diff --git a/Query.php b/Query.php index a50d0ce3..82e7b164 100644 --- a/Query.php +++ b/Query.php @@ -236,8 +236,6 @@ public function select($fields = [], bool $overwrite = false) * been added to the query by the first. If you need to change the list after the first call, * pass overwrite boolean true which will reset the select clause removing all previous additions. * - * - * * @param \Cake\ORM\Table|\Cake\ORM\Association $table The table to use to get an array of columns * @param string[] $excludedFields The un-aliased column names you do not want selected from $table * @param bool $overwrite Whether to reset/remove previous selected fields diff --git a/RulesChecker.php b/RulesChecker.php index 4cb2eca1..bb12b95d 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -127,7 +127,6 @@ public function existsIn($field, $table, $message = null): RuleInvoker * possible errors. When absent, the name is inferred from `$association`. * @param string|null $message The error message to show in case the rule does not pass. * @return \Cake\Datasource\RuleInvoker - * * @since 4.0.0 */ public function isLinkedTo($association, ?string $field = null, ?string $message = null): RuleInvoker @@ -158,7 +157,6 @@ public function isLinkedTo($association, ?string $field = null, ?string $message * possible errors. When absent, the name is inferred from `$association`. * @param string|null $message The error message to show in case the rule does not pass. * @return \Cake\Datasource\RuleInvoker - * * @since 4.0.0 */ public function isNotLinkedTo($association, ?string $field = null, ?string $message = null): RuleInvoker @@ -182,11 +180,8 @@ public function isNotLinkedTo($association, ?string $field = null, ?string $mess * @param string $linkStatus The ink status required for the check to pass. * @param string $ruleName The alias/name of the rule. * @return \Cake\Datasource\RuleInvoker - * * @throws \InvalidArgumentException In case the `$association` argument is of an invalid type. - * * @since 4.0.0 - * * @see \Cake\ORM\RulesChecker::isLinkedTo() * @see \Cake\ORM\RulesChecker::isNotLinkedTo() * @see \Cake\ORM\Rule\LinkConstraint::STATUS_LINKED diff --git a/Table.php b/Table.php index d58db4cb..202457f0 100644 --- a/Table.php +++ b/Table.php @@ -2435,7 +2435,6 @@ protected function _processDelete(EntityInterface $entity, ArrayObject $options) * Returns true if the finder exists for the table * * @param string $type name of finder to check - * * @return bool */ public function hasFinder(string $type): bool From 449920aa410a92adec2ee8043c59b14966c1df9c Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 16 Apr 2020 23:41:00 +0530 Subject: [PATCH 1528/2059] Fix errors reported by psalm --- Query.php | 8 +++++++- Table.php | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Query.php b/Query.php index 82e7b164..29d044be 100644 --- a/Query.php +++ b/Query.php @@ -1182,13 +1182,19 @@ protected function _addDefaultSelectTypes(): void } /** - * @inheritDoc + * {@inheritDoc} + * + * @param string $finder The finder method to use. + * @param array $options The options for the finder. + * @return static Returns a modified query. + * @psalm-suppress MoreSpecificReturnType */ public function find(string $finder, array $options = []) { /** @var \Cake\ORM\Table $table */ $table = $this->getRepository(); + /** @psalm-suppress LessSpecificReturnStatement */ return $table->callFinder($finder, $this, $options); } diff --git a/Table.php b/Table.php index 202457f0..4e0f81cd 100644 --- a/Table.php +++ b/Table.php @@ -2526,9 +2526,9 @@ protected function _dynamicFinder(string $method, array $args) } elseif ($hasOr !== false) { $fields = explode('_or_', $fields); $conditions = [ - 'OR' => $makeConditions($fields, $args), + 'OR' => $makeConditions($fields, $args), ]; - } elseif ($hasAnd !== false) { + } else { $fields = explode('_and_', $fields); $conditions = $makeConditions($fields, $args); } From d0e1f0dade3240246f1349ccdd2b0d9318b04a38 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 4 May 2020 20:20:32 +0530 Subject: [PATCH 1529/2059] Enable psalm's type coercion check. --- Behavior/TranslateBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 5c5aad27..d8c0d049 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -133,7 +133,7 @@ public function initialize(array $config): void * @param string $class Class name. * @return void * @since 4.0.0 - * @psalm-var class-string<\Cake\ORM\Behavior\Translate\TranslateStrategyInterface> + * @psalm-param class-string<\Cake\ORM\Behavior\Translate\TranslateStrategyInterface> $class */ public static function setDefaultStrategyClass(string $class) { From b47351a9ac9f94d08e7c0e1b17b00e05413a9621 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 6 May 2020 17:09:45 +0530 Subject: [PATCH 1530/2059] Remove lower casing of assocation names in AssociationCollection. It causes undeterministic behavior in the ORM. For e.g. using different casing in contain() calls than the one used when declaring association caused related records to be under different entity property in the resultset entities. Also related record remained as an array instead of being hydrated as an entity. Fixes #14554. --- AssociationCollection.php | 7 +++---- Table.php | 8 +++++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/AssociationCollection.php b/AssociationCollection.php index 8bfe8a38..9b91720d 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -71,7 +71,7 @@ public function add(string $alias, Association $association): Association { [, $alias] = pluginSplit($alias); - return $this->_items[strtolower($alias)] = $association; + return $this->_items[$alias] = $association; } /** @@ -110,7 +110,6 @@ public function load(string $className, string $associated, array $options = []) */ public function get(string $alias): ?Association { - $alias = strtolower($alias); if (isset($this->_items[$alias])) { return $this->_items[$alias]; } @@ -143,7 +142,7 @@ public function getByProperty(string $prop): ?Association */ public function has(string $alias): bool { - return isset($this->_items[strtolower($alias)]); + return isset($this->_items[$alias]); } /** @@ -187,7 +186,7 @@ public function getByType($class): array */ public function remove(string $alias): void { - unset($this->_items[strtolower($alias)]); + unset($this->_items[$alias]); } /** diff --git a/Table.php b/Table.php index 490327ef..a1e7f456 100644 --- a/Table.php +++ b/Table.php @@ -894,7 +894,13 @@ public function getAssociation(string $name): Association { $association = $this->findAssociation($name); if (!$association) { - throw new InvalidArgumentException("The {$name} association is not defined on {$this->getAlias()}."); + $assocations = $this->associations()->keys(); + + $message = "The `{$name}` association is not defined on `{$this->getAlias()}`."; + if ($assocations) { + $message .= "\nValid associations are: " . implode(', ', $assocations); + } + throw new InvalidArgumentException($message); } return $association; From 6c685a5f0d251c75b248e2e5769ecf5cbaf759f7 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Wed, 6 May 2020 04:24:19 -0500 Subject: [PATCH 1531/2059] Throw exception when contain() in query builder for matching/innerJoinWith --- Association.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Association.php b/Association.php index 04643970..41bcf486 100644 --- a/Association.php +++ b/Association.php @@ -751,6 +751,16 @@ public function attachTo(Query $query, array $options = []): void } } + if ( + !empty($options['matching']) && + $this->_strategy === static::STRATEGY_JOIN && + $dummy->getContain() + ) { + throw new RuntimeException( + "`{$this->getName()}` association cannot contain() associations when using JOIN strategy." + ); + } + $dummy->where($options['conditions']); $this->_dispatchBeforeFind($dummy); From b1013526892f8e69fa475325bbddf57ee4593f71 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Thu, 7 May 2020 18:16:08 -0500 Subject: [PATCH 1532/2059] Clean up cloning of Query and EagerLoader --- EagerLoadable.php | 12 ++++++++++++ EagerLoader.php | 4 +--- Query.php | 7 ++----- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/EagerLoadable.php b/EagerLoadable.php index 820f9fcb..b782571f 100644 --- a/EagerLoadable.php +++ b/EagerLoadable.php @@ -311,4 +311,16 @@ public function asContainArray(): array ], ]; } + + /** + * Handles cloning eager loadables. + * + * @return void + */ + public function __clone() + { + foreach ($this->_associations as $i => $association) { + $this->_associations[$i] = clone $association; + } + } } diff --git a/EagerLoader.php b/EagerLoader.php index 25267312..a4694814 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -862,9 +862,7 @@ protected function _groupKeys(BufferedStatement $statement, array $collectKeys): } /** - * Clone hook implementation - * - * Clone the _matching eager loader as well. + * Handles cloning eager loaders and eager loadables. * * @return void */ diff --git a/Query.php b/Query.php index 29d044be..2c19d18f 100644 --- a/Query.php +++ b/Query.php @@ -835,7 +835,6 @@ public function applyOptions(array $options) public function cleanCopy() { $clone = clone $this; - $clone->setEagerLoader(clone $this->getEagerLoader()); $clone->triggerBeforeFind(); $clone->disableAutoFields(); $clone->limit(null); @@ -850,11 +849,9 @@ public function cleanCopy() } /** - * Object clone hook. - * - * Destroys the clones inner iterator and clones the value binder, and eagerloader instances. + * {@inheritDoc} * - * @return void + * Handles cloning eager loaders. */ public function __clone() { From 64a698c7c952c168c66761f0d934099260246b0d Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 11 May 2020 19:51:22 +0530 Subject: [PATCH 1533/2059] Upate psalm's annotations/config. --- Association/DependentDeleteHelper.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Association/DependentDeleteHelper.php b/Association/DependentDeleteHelper.php index 0b4bb18c..9b6c5161 100644 --- a/Association/DependentDeleteHelper.php +++ b/Association/DependentDeleteHelper.php @@ -42,6 +42,7 @@ public function cascadeDelete(Association $association, EntityInterface $entity, return true; } $table = $association->getTarget(); + /** @psalm-suppress InvalidArgument */ $foreignKey = array_map([$association, 'aliasField'], (array)$association->getForeignKey()); $bindingKey = (array)$association->getBindingKey(); $conditions = array_combine($foreignKey, $entity->extract($bindingKey)); From e9f67af857347b37140e9a4308698c24b7e5405b Mon Sep 17 00:00:00 2001 From: mscherer Date: Mon, 11 May 2020 17:09:13 +0200 Subject: [PATCH 1534/2059] Clean up tests using PHPStan level 2/3. --- Query.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Query.php b/Query.php index 2c19d18f..5c6c456c 100644 --- a/Query.php +++ b/Query.php @@ -40,7 +40,7 @@ * * @see \Cake\Collection\CollectionInterface For a full description of the collection methods supported by this class * @method \Cake\Collection\CollectionInterface each(callable $c) Passes each of the query results to the callable - * @method \Cake\Collection\CollectionInterface sortBy($callback, int $dir, int $type) Sorts the query with the callback + * @method \Cake\Collection\CollectionInterface sortBy($callback, int $dir) Sorts the query with the callback * @method \Cake\Collection\CollectionInterface filter(callable $c = null) Keeps the results using passing the callable test * @method \Cake\Collection\CollectionInterface reject(callable $c) Removes the results passing the callable test * @method bool every(callable $c) Returns true if all the results pass the callable test @@ -48,8 +48,8 @@ * @method \Cake\Collection\CollectionInterface map(callable $c) Modifies each of the results using the callable * @method mixed reduce(callable $c, $zero = null) Folds all the results into a single value using the callable. * @method \Cake\Collection\CollectionInterface extract($field) Extracts a single column from each row - * @method mixed max($field, int $type) Returns the maximum value for a single column in all the results. - * @method mixed min($field, int $type) Returns the minimum value for a single column in all the results. + * @method mixed max($field) Returns the maximum value for a single column in all the results. + * @method mixed min($field) Returns the minimum value for a single column in all the results. * @method \Cake\Collection\CollectionInterface groupBy(string|callable $field) In-memory group all results by the value of a column. * @method \Cake\Collection\CollectionInterface indexBy(string|callable $callback) Returns the results indexed by the value of a column. * @method \Cake\Collection\CollectionInterface countBy(string|callable $field) Returns the number of unique values for a column From 4b09ce733ab9ba8e3c8e6f9883388005be21ec88 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 17 May 2020 23:10:38 +0530 Subject: [PATCH 1535/2059] Allow passing "locale" as find option. This allows switching locale easily for a single find call. --- Behavior/Translate/EavStrategy.php | 3 ++- Behavior/Translate/ShadowTableStrategy.php | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index 5c33edfb..730a8737 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -26,6 +26,7 @@ use Cake\ORM\Locator\LocatorAwareTrait; use Cake\ORM\Query; use Cake\ORM\Table; +use Cake\Utility\Hash; /** * This class provides a way to translate dynamic data by keeping translations @@ -162,7 +163,7 @@ protected function setupAssociations() */ public function beforeFind(EventInterface $event, Query $query, ArrayObject $options) { - $locale = $this->getLocale(); + $locale = Hash::get($options, 'locale', $this->getLocale()); if ($locale === $this->getConfig('defaultLocale')) { return; diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 4365a7f7..88351ef3 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -26,6 +26,7 @@ use Cake\ORM\Marshaller; use Cake\ORM\Query; use Cake\ORM\Table; +use Cake\Utility\Hash; /** * This class provides a way to translate dynamic data by keeping translations @@ -119,7 +120,7 @@ protected function setupAssociations() */ public function beforeFind(EventInterface $event, Query $query, ArrayObject $options) { - $locale = $this->getLocale(); + $locale = Hash::get($options, 'locale', $this->getLocale()); if ($locale === $this->getConfig('defaultLocale')) { return; From 270d0395ce27cfbf3493dcf7418468ba762ca87f Mon Sep 17 00:00:00 2001 From: "Frank de Graaf (Phally)" Date: Sun, 17 May 2020 21:01:36 +0200 Subject: [PATCH 1536/2059] Adds Query::clearResult(). Clears the internal resultset and count value. Closes #14564 --- Query.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Query.php b/Query.php index 5c6c456c..9f9ef379 100644 --- a/Query.php +++ b/Query.php @@ -848,6 +848,19 @@ public function cleanCopy() return $clone; } + /** + * Clears the internal result cache and the internal count value from the current + * query object. + * + * @return $this + */ + public function clearResult() + { + $this->_dirty(); + + return $this; + } + /** * {@inheritDoc} * From bee517fe269b8806896b14366830b2bf652f9759 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 18 May 2020 12:52:54 +0530 Subject: [PATCH 1537/2059] Fix incorrect locale set for records. Use passed argument instead of getLocale() method since locale can be changed per find() call. --- Behavior/Translate/ShadowTableStrategy.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 88351ef3..6c993e49 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -439,7 +439,7 @@ protected function rowMapper($results, $locale) { $allowEmpty = $this->_config['allowEmptyTranslations']; - return $results->map(function ($row) use ($allowEmpty) { + return $results->map(function ($row) use ($allowEmpty, $locale) { /** @var \Cake\Datasource\EntityInterface|array|null $row */ if ($row === null) { return $row; @@ -448,7 +448,7 @@ protected function rowMapper($results, $locale) $hydrated = !is_array($row); if (empty($row['translation'])) { - $row['_locale'] = $this->getLocale(); + $row['_locale'] = $locale; unset($row['translation']); if ($hydrated) { From 3d052fb0db4d5917763e5984d22ebfb8225b3a1b Mon Sep 17 00:00:00 2001 From: mscherer Date: Tue, 19 May 2020 16:39:19 +0200 Subject: [PATCH 1538/2059] Refactor deprecations to clickable links in IDE. --- TableRegistry.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/TableRegistry.php b/TableRegistry.php index 800a9ae0..77900310 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -105,7 +105,7 @@ public static function setTableLocator(LocatorInterface $tableLocator): void * @param string $alias The alias name you want to get. * @param array $options The options you want to build the table with. * @return \Cake\ORM\Table - * @deprecated 3.6.0 Use \Cake\ORM\Locator\TableLocator::get() instead. Will be removed in 5.0 + * @deprecated 3.6.0 Use {@link \Cake\ORM\Locator\TableLocator::get()} instead. Will be removed in 5.0. */ public static function get(string $alias, array $options = []): Table { @@ -117,7 +117,7 @@ public static function get(string $alias, array $options = []): Table * * @param string $alias The alias to check for. * @return bool - * @deprecated 3.6.0 Use \Cake\ORM\Locator\TableLocator::exists() instead. Will be removed in 5.0 + * @deprecated 3.6.0 Use {@link \Cake\ORM\Locator\TableLocator::exists()} instead. Will be removed in 5.0 */ public static function exists(string $alias): bool { @@ -130,7 +130,7 @@ public static function exists(string $alias): bool * @param string $alias The alias to set. * @param \Cake\ORM\Table $object The table to set. * @return \Cake\ORM\Table - * @deprecated 3.6.0 Use \Cake\ORM\Locator\TableLocator::set() instead. Will be removed in 5.0 + * @deprecated 3.6.0 Use {@link \Cake\ORM\Locator\TableLocator::set()} instead. Will be removed in 5.0 */ public static function set(string $alias, Table $object): Table { @@ -142,7 +142,7 @@ public static function set(string $alias, Table $object): Table * * @param string $alias The alias to remove. * @return void - * @deprecated 3.6.0 Use \Cake\ORM\Locator\TableLocator::remove() instead. Will be removed in 5.0 + * @deprecated 3.6.0 Use {@link \Cake\ORM\Locator\TableLocator::remove()} instead. Will be removed in 5.0 */ public static function remove(string $alias): void { @@ -153,7 +153,7 @@ public static function remove(string $alias): void * Clears the registry of configuration and instances. * * @return void - * @deprecated 3.6.0 Use \Cake\ORM\Locator\TableLocator::clear() instead. Will be removed in 5.0 + * @deprecated 3.6.0 Use {@link \Cake\ORM\Locator\TableLocator::clear()} instead. Will be removed in 5.0 */ public static function clear(): void { From 2505a1b43bf77eec1fcb76bd3f9a48bf8abbe8cc Mon Sep 17 00:00:00 2001 From: mscherer Date: Tue, 19 May 2020 16:43:31 +0200 Subject: [PATCH 1539/2059] Refactor deprecations to clickable links in IDE. --- Locator/TableLocator.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 2f9c42fe..70b0b448 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -145,13 +145,13 @@ public function getConfig(?string $alias = null): array * `App\Model\Table\UsersTable` being used. If this class does not exist, * then the default `Cake\ORM\Table` class will be used. By setting the `className` * option you can define the specific class to use. The className option supports - * plugin short class references {@link Cake\Core\App::shortName()}. + * plugin short class references {@link \Cake\Core\App::shortName()}. * - `table` Define the table name to use. If undefined, this option will default to the underscored * version of the alias name. * - `connection` Inject the specific connection object to use. If this option and `connectionName` are undefined, * The table class' `defaultConnectionName()` method will be invoked to fetch the connection name. * - `connectionName` Define the connection name to use. The named connection will be fetched from - * Cake\Datasource\ConnectionManager. + * {@link \Cake\Datasource\ConnectionManager}. * * *Note* If your `$alias` uses plugin syntax only the name part will be used as * key in the registry. This means that if two plugins, or a plugin and app provide From a145c7c4a04c14ba995dccad054ab499d8679593 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Tue, 19 May 2020 20:09:22 -0500 Subject: [PATCH 1540/2059] Updated parameter names for inherited methods to match. --- Query.php | 4 ++-- Table.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Query.php b/Query.php index 29d044be..5b9c7618 100644 --- a/Query.php +++ b/Query.php @@ -1072,13 +1072,13 @@ public function triggerBeforeFind(): void /** * @inheritDoc */ - public function sql(?ValueBinder $binder = null): string + public function sql(?ValueBinder $generator = null): string { $this->triggerBeforeFind(); $this->_transformQuery(); - return parent::sql($binder); + return parent::sql($generator); } /** diff --git a/Table.php b/Table.php index 4e0f81cd..53e7d3cf 100644 --- a/Table.php +++ b/Table.php @@ -3012,9 +3012,9 @@ public function loadInto($entities, array $contain) /** * @inheritDoc */ - protected function validationMethodExists(string $method): bool + protected function validationMethodExists(string $name): bool { - return method_exists($this, $method) || $this->behaviors()->hasMethod($method); + return method_exists($this, $name) || $this->behaviors()->hasMethod($name); } /** From 24e5172027b0af37116f26d6fdae70ba41cc6fcb Mon Sep 17 00:00:00 2001 From: mscherer Date: Wed, 20 May 2020 16:35:50 +0200 Subject: [PATCH 1541/2059] Fix CS: Use single quotes where applicable. --- Table.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Table.php b/Table.php index 4e0f81cd..73740698 100644 --- a/Table.php +++ b/Table.php @@ -564,7 +564,7 @@ protected function checkAliasLengths(): void } $maxLength = null; - if (method_exists($this->getConnection()->getDriver(), "getMaxAliasLength")) { + if (method_exists($this->getConnection()->getDriver(), 'getMaxAliasLength')) { $maxLength = $this->getConnection()->getDriver()->getMaxAliasLength(); } if ($maxLength === null) { @@ -576,10 +576,10 @@ protected function checkAliasLengths(): void if (strlen($table . '__' . $name) > $maxLength) { $nameLength = $maxLength - 2; throw new RuntimeException( - "ORM queries generate field aliases using the table name/alias and column name. " . + 'ORM queries generate field aliases using the table name/alias and column name. ' . "The table alias `{$table}` and column `{$name}` create an alias longer than ({$nameLength}). " . - "You must change the table schema in the database and shorten either the table or column " . - "identifier so they fit within the database alias limits." + 'You must change the table schema in the database and shorten either the table or column ' . + 'identifier so they fit within the database alias limits.' ); } } From f4f6e1132fdb618e26c8ad9681fd363f95e02c2b Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 24 May 2020 22:50:08 +0530 Subject: [PATCH 1542/2059] Add Datasource\LocatorInface. ORM\LocatorInterface now extends the above interface. --- Locator/LocatorInterface.php | 49 ++---------------------------------- Locator/TableLocator.php | 2 +- 2 files changed, 3 insertions(+), 48 deletions(-) diff --git a/Locator/LocatorInterface.php b/Locator/LocatorInterface.php index 7559e87f..ce99506a 100644 --- a/Locator/LocatorInterface.php +++ b/Locator/LocatorInterface.php @@ -21,30 +21,8 @@ /** * Registries for Table objects should implement this interface. */ -interface LocatorInterface +interface LocatorInterface extends \Cake\Datasource\LocatorInterface { - /** - * Returns configuration for an alias or the full configuration array for - * all aliases. - * - * @param string|null $alias Alias to get config for, null for complete config. - * @return array The config data. - */ - public function getConfig(?string $alias = null): array; - - /** - * Stores a list of options to be used when instantiating an object - * with a matching alias. - * - * @param string|array $alias Name of the alias or array to completely - * overwrite current config. - * @param array|null $options list of options for the alias - * @return $this - * @throws \RuntimeException When you attempt to configure an existing - * table instance. - */ - public function setConfig($alias, $options = null); - /** * Get a table instance from the registry. * @@ -54,14 +32,6 @@ public function setConfig($alias, $options = null); */ public function get(string $alias, array $options = []): Table; - /** - * Check to see if an instance exists in the registry. - * - * @param string $alias The alias to check for. - * @return bool - */ - public function exists(string $alias): bool; - /** * Set an instance. * @@ -69,20 +39,5 @@ public function exists(string $alias): bool; * @param \Cake\ORM\Table $object The table to set. * @return \Cake\ORM\Table */ - public function set(string $alias, Table $object): Table; - - /** - * Clears the registry of configuration and instances. - * - * @return void - */ - public function clear(): void; - - /** - * Removes an instance from the registry. - * - * @param string $alias The alias to remove. - * @return void - */ - public function remove(string $alias): void; + public function set(string $alias, $object): Table; } diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 70b0b448..225fa254 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -274,7 +274,7 @@ public function exists(string $alias): bool /** * @inheritDoc */ - public function set(string $alias, Table $object): Table + public function set(string $alias, $object): Table { return $this->_instances[$alias] = $object; } From 50489a87d775ffaf6985ee0a2222285cee183333 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 24 May 2020 23:18:31 +0530 Subject: [PATCH 1543/2059] Use FactoryLocator as the single common source to get table locator. --- Locator/LocatorAwareTrait.php | 4 ++-- TableRegistry.php | 24 +++--------------------- 2 files changed, 5 insertions(+), 23 deletions(-) diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index 203d78b0..4c4e0444 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -16,7 +16,7 @@ */ namespace Cake\ORM\Locator; -use Cake\ORM\TableRegistry; +use Cake\Datasource\FactoryLocator; /** * Contains method for setting and accessing LocatorInterface instance @@ -51,7 +51,7 @@ public function setTableLocator(LocatorInterface $tableLocator) public function getTableLocator(): LocatorInterface { if ($this->_tableLocator === null) { - $this->_tableLocator = TableRegistry::getTableLocator(); + $this->_tableLocator = FactoryLocator::get('Table'); } return $this->_tableLocator; diff --git a/TableRegistry.php b/TableRegistry.php index 77900310..d763e465 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -16,6 +16,7 @@ */ namespace Cake\ORM; +use Cake\Datasource\FactoryLocator; use Cake\ORM\Locator\LocatorInterface; /** @@ -57,21 +58,6 @@ */ class TableRegistry { - /** - * LocatorInterface implementation instance. - * - * @var \Cake\ORM\Locator\LocatorInterface - */ - protected static $_locator; - - /** - * Default LocatorInterface implementation class. - * - * @var string - * @psalm-var class-string<\Cake\ORM\Locator\TableLocator> - */ - protected static $_defaultLocatorClass = Locator\TableLocator::class; - /** * Returns a singleton instance of LocatorInterface implementation. * @@ -79,11 +65,7 @@ class TableRegistry */ public static function getTableLocator(): LocatorInterface { - if (static::$_locator === null) { - static::$_locator = new static::$_defaultLocatorClass(); - } - - return static::$_locator; + return FactoryLocator::get('Table'); } /** @@ -94,7 +76,7 @@ public static function getTableLocator(): LocatorInterface */ public static function setTableLocator(LocatorInterface $tableLocator): void { - static::$_locator = $tableLocator; + FactoryLocator::add('Table', $tableLocator); } /** From a86da144765bbb2e0912c13457d29fcbf15ca5d4 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 26 May 2020 00:43:43 +0530 Subject: [PATCH 1544/2059] Fix typehint and update errors reported by psalm --- Locator/LocatorAwareTrait.php | 2 ++ Locator/LocatorInterface.php | 8 +++++--- Locator/TableLocator.php | 5 +++-- TableRegistry.php | 1 + 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index 4c4e0444..ae257fa7 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -51,9 +51,11 @@ public function setTableLocator(LocatorInterface $tableLocator) public function getTableLocator(): LocatorInterface { if ($this->_tableLocator === null) { + /** @var \Cake\ORM\Locator\LocatorInterface $this->_tableLocator */ $this->_tableLocator = FactoryLocator::get('Table'); } + /** @var \Cake\ORM\Locator\LocatorInterface */ return $this->_tableLocator; } } diff --git a/Locator/LocatorInterface.php b/Locator/LocatorInterface.php index ce99506a..3c6e103e 100644 --- a/Locator/LocatorInterface.php +++ b/Locator/LocatorInterface.php @@ -16,6 +16,7 @@ */ namespace Cake\ORM\Locator; +use Cake\Datasource\RepositoryInterface; use Cake\ORM\Table; /** @@ -33,11 +34,12 @@ interface LocatorInterface extends \Cake\Datasource\LocatorInterface public function get(string $alias, array $options = []): Table; /** - * Set an instance. + * Set a table instance. * * @param string $alias The alias to set. - * @param \Cake\ORM\Table $object The table to set. + * @param \Cake\ORM\Table $repository The table to set. * @return \Cake\ORM\Table + * @psalm-suppress MoreSpecificImplementedParamType */ - public function set(string $alias, $object): Table; + public function set(string $alias, RepositoryInterface $repository): Table; } diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 225fa254..a4471ba6 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -18,6 +18,7 @@ use Cake\Core\App; use Cake\Datasource\ConnectionManager; +use Cake\Datasource\RepositoryInterface; use Cake\ORM\AssociationCollection; use Cake\ORM\Table; use Cake\Utility\Inflector; @@ -274,9 +275,9 @@ public function exists(string $alias): bool /** * @inheritDoc */ - public function set(string $alias, $object): Table + public function set(string $alias, RepositoryInterface $repository): Table { - return $this->_instances[$alias] = $object; + return $this->_instances[$alias] = $repository; } /** diff --git a/TableRegistry.php b/TableRegistry.php index d763e465..52a0c42a 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -65,6 +65,7 @@ class TableRegistry */ public static function getTableLocator(): LocatorInterface { + /** @var \Cake\ORM\Locator\LocatorInterface */ return FactoryLocator::get('Table'); } From e3928572b0d15e55e0fc802e212b98c8ecc22c1b Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 26 May 2020 21:31:41 +0530 Subject: [PATCH 1545/2059] Move Datasource\LocatorInterface to Datasource\Loctor\LocatorInterface --- Locator/LocatorInterface.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Locator/LocatorInterface.php b/Locator/LocatorInterface.php index 3c6e103e..d49fdd3f 100644 --- a/Locator/LocatorInterface.php +++ b/Locator/LocatorInterface.php @@ -22,7 +22,7 @@ /** * Registries for Table objects should implement this interface. */ -interface LocatorInterface extends \Cake\Datasource\LocatorInterface +interface LocatorInterface extends \Cake\Datasource\Locator\LocatorInterface { /** * Get a table instance from the registry. From 35e162c33a5bd9e9385bb3a6f21bc7e2cd7c7e58 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 26 May 2020 21:56:00 +0530 Subject: [PATCH 1546/2059] Add Datasource\Locator\AbstractLocator Classes implementing Datasource\Locator\LocatorInterface can extend this class to avoid code duplication. --- Locator/TableLocator.php | 90 +++++++--------------------------------- 1 file changed, 14 insertions(+), 76 deletions(-) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index a4471ba6..e1690c39 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -18,6 +18,7 @@ use Cake\Core\App; use Cake\Datasource\ConnectionManager; +use Cake\Datasource\Locator\AbstractLocator; use Cake\Datasource\RepositoryInterface; use Cake\ORM\AssociationCollection; use Cake\ORM\Table; @@ -27,7 +28,7 @@ /** * Provides a default registry/factory for Table objects. */ -class TableLocator implements LocatorInterface +class TableLocator extends AbstractLocator implements LocatorInterface { /** * Contains a list of locations where table classes should be looked for. @@ -36,13 +37,6 @@ class TableLocator implements LocatorInterface */ protected $locations = []; - /** - * Configuration for aliases. - * - * @var array - */ - protected $_config = []; - /** * Instances that belong to the registry. * @@ -58,13 +52,6 @@ class TableLocator implements LocatorInterface */ protected $_fallbacked = []; - /** - * Contains a list of options that were passed to get() method. - * - * @var array - */ - protected $_options = []; - /** * Constructor. * @@ -84,50 +71,6 @@ public function __construct(?array $locations = null) } } - /** - * Stores a list of options to be used when instantiating an object - * with a matching alias. - * - * @param string|array $alias Name of the alias or array to completely overwrite current config. - * @param array|null $options list of options for the alias - * @return $this - * @throws \RuntimeException When you attempt to configure an existing table instance. - */ - public function setConfig($alias, $options = null) - { - if (!is_string($alias)) { - $this->_config = $alias; - - return $this; - } - - if (isset($this->_instances[$alias])) { - throw new RuntimeException(sprintf( - 'You cannot configure "%s", it has already been constructed.', - $alias - )); - } - - $this->_config[$alias] = $options; - - return $this; - } - - /** - * Returns configuration for an alias or the full configuration array for all aliases. - * - * @param string|null $alias Alias to get config for, null for complete config. - * @return array The config data. - */ - public function getConfig(?string $alias = null): array - { - if ($alias === null) { - return $this->_config; - } - - return $this->_config[$alias] ?? []; - } - /** * Get a table instance from the registry. * @@ -265,15 +208,13 @@ protected function _create(array $options): Table } /** - * @inheritDoc - */ - public function exists(string $alias): bool - { - return isset($this->_instances[$alias]); - } - - /** - * @inheritDoc + * Set a Table instance. + * + * @param string $alias The alias to set. + * @param \Cake\ORM\Table $repository The Table to set. + * @return \Cake\ORM\Table + * @psalm-suppress MoreSpecificImplementedParamType + * @psalm-suppress MoreSpecificReturnType */ public function set(string $alias, RepositoryInterface $repository): Table { @@ -285,10 +226,9 @@ public function set(string $alias, RepositoryInterface $repository): Table */ public function clear(): void { - $this->_instances = []; - $this->_config = []; + parent::clear(); + $this->_fallbacked = []; - $this->_options = []; } /** @@ -309,11 +249,9 @@ public function genericInstances(): array */ public function remove(string $alias): void { - unset( - $this->_instances[$alias], - $this->_config[$alias], - $this->_fallbacked[$alias] - ); + parent::remove($alias); + + unset($this->_fallbacked[$alias]); } /** From 38516073687e0f579695c2a3f87f0c92acaa9469 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 27 May 2020 12:59:50 +0530 Subject: [PATCH 1547/2059] Remove getConfig(), setConfig() for Datasource's LocatorInterface. Very few would need this feature. --- Locator/LocatorInterface.php | 22 +++++++++++ Locator/TableLocator.php | 73 +++++++++++++++++++++++++++--------- 2 files changed, 78 insertions(+), 17 deletions(-) diff --git a/Locator/LocatorInterface.php b/Locator/LocatorInterface.php index d49fdd3f..68db76d8 100644 --- a/Locator/LocatorInterface.php +++ b/Locator/LocatorInterface.php @@ -24,6 +24,28 @@ */ interface LocatorInterface extends \Cake\Datasource\Locator\LocatorInterface { + /** + * Returns configuration for an alias or the full configuration array for + * all aliases. + * + * @param string|null $alias Alias to get config for, null for complete config. + * @return array The config data. + */ + public function getConfig(?string $alias = null): array; + + /** + * Stores a list of options to be used when instantiating an object + * with a matching alias. + * + * @param string|array $alias Name of the alias or array to completely + * overwrite current config. + * @param array|null $options list of options for the alias + * @return $this + * @throws \RuntimeException When you attempt to configure an existing + * table instance. + */ + public function setConfig($alias, $options = null); + /** * Get a table instance from the registry. * diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index e1690c39..91be7e26 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -37,12 +37,19 @@ class TableLocator extends AbstractLocator implements LocatorInterface */ protected $locations = []; + /** + * Configuration for aliases. + * + * @var array + */ + protected $_config = []; + /** * Instances that belong to the registry. * * @var \Cake\ORM\Table[] */ - protected $_instances = []; + protected $instances = []; /** * Contains a list of Table objects that were created out of the @@ -71,6 +78,41 @@ public function __construct(?array $locations = null) } } + /** + * @inheritDoc + */ + public function setConfig($alias, $options = null) + { + if (!is_string($alias)) { + $this->_config = $alias; + + return $this; + } + + if (isset($this->instances[$alias])) { + throw new RuntimeException(sprintf( + 'You cannot configure "%s", it has already been constructed.', + $alias + )); + } + + $this->_config[$alias] = $options; + + return $this; + } + + /** + * @inheritDoc + */ + public function getConfig(?string $alias = null): array + { + if ($alias === null) { + return $this->_config; + } + + return $this->_config[$alias] ?? []; + } + /** * Get a table instance from the registry. * @@ -109,18 +151,15 @@ public function __construct(?array $locations = null) */ public function get(string $alias, array $options = []): Table { - if (isset($this->_instances[$alias])) { - if (!empty($options) && $this->_options[$alias] !== $options) { - throw new RuntimeException(sprintf( - 'You cannot configure "%s", it already exists in the registry.', - $alias - )); - } - - return $this->_instances[$alias]; - } + /** @var \Cake\ORM\Table */ + return parent::get($alias, $options); + } - $this->_options[$alias] = $options; + /** + * @inheritDoc + */ + protected function createInstance(string $alias, array $options) + { [, $classAlias] = pluginSplit($alias); $options = ['alias' => $classAlias] + $options; @@ -158,13 +197,13 @@ public function get(string $alias, array $options = []): Table } $options['registryAlias'] = $alias; - $this->_instances[$alias] = $this->_create($options); + $instance = $this->_create($options); if ($options['className'] === Table::class) { - $this->_fallbacked[$alias] = $this->_instances[$alias]; + $this->_fallbacked[$alias] = $instance; } - return $this->_instances[$alias]; + return $instance; } /** @@ -202,7 +241,6 @@ protected function _getClassName(string $alias, array $options = []): ?string */ protected function _create(array $options): Table { - // phpcs:ignore SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration.InvalidFormat /** @var \Cake\ORM\Table */ return new $options['className']($options); } @@ -218,7 +256,7 @@ protected function _create(array $options): Table */ public function set(string $alias, RepositoryInterface $repository): Table { - return $this->_instances[$alias] = $repository; + return $this->instances[$alias] = $repository; } /** @@ -229,6 +267,7 @@ public function clear(): void parent::clear(); $this->_fallbacked = []; + $this->_config = []; } /** From bf2e62797ffc69ba7e02989ed98f9d13bf59edd7 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 25 May 2020 01:15:14 +0530 Subject: [PATCH 1548/2059] Abort deletion of primary entity if deletion of dependent entity fails. Fixes #14434 --- Association/BelongsToMany.php | 5 +++- Association/DependentDeleteHelper.php | 5 +++- AssociationCollection.php | 33 ++++++++++++--------------- Table.php | 5 +++- 4 files changed, 26 insertions(+), 22 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index a1e07ba2..1a39101e 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -590,7 +590,10 @@ public function cascadeDelete(EntityInterface $entity, array $options = []): boo $hasMany = $this->getSource()->getAssociation($table->getAlias()); if ($this->_cascadeCallbacks) { foreach ($hasMany->find('all')->where($conditions)->all()->toList() as $related) { - $table->delete($related, $options); + $success = $table->delete($related, $options); + if (!$success) { + return false; + } } return true; diff --git a/Association/DependentDeleteHelper.php b/Association/DependentDeleteHelper.php index 9b6c5161..bbb6f0e6 100644 --- a/Association/DependentDeleteHelper.php +++ b/Association/DependentDeleteHelper.php @@ -49,7 +49,10 @@ public function cascadeDelete(Association $association, EntityInterface $entity, if ($association->getCascadeCallbacks()) { foreach ($association->find()->where($conditions)->all()->toList() as $related) { - $table->delete($related, $options); + $success = $table->delete($related, $options); + if (!$success) { + return false; + } } return true; diff --git a/AssociationCollection.php b/AssociationCollection.php index 9b91720d..979f96ef 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -323,24 +323,9 @@ protected function _save( * * @param \Cake\Datasource\EntityInterface $entity The entity to delete associations for. * @param array $options The options used in the delete operation. - * @return void - */ - public function cascadeDelete(EntityInterface $entity, array $options): void - { - $noCascade = $this->_getNoCascadeItems($entity, $options); - foreach ($noCascade as $assoc) { - $assoc->cascadeDelete($entity, $options); - } - } - - /** - * Returns items that have no cascade callback. - * - * @param \Cake\Datasource\EntityInterface $entity The entity to delete associations for. - * @param array $options The options used in the delete operation. - * @return \Cake\ORM\Association[] + * @return bool */ - protected function _getNoCascadeItems(EntityInterface $entity, array $options): array + public function cascadeDelete(EntityInterface $entity, array $options): bool { $noCascade = []; foreach ($this->_items as $assoc) { @@ -348,10 +333,20 @@ protected function _getNoCascadeItems(EntityInterface $entity, array $options): $noCascade[] = $assoc; continue; } - $assoc->cascadeDelete($entity, $options); + $success = $assoc->cascadeDelete($entity, $options); + if (!$success) { + return false; + } + } + + foreach ($noCascade as $assoc) { + $success = $assoc->cascadeDelete($entity, $options); + if (!$success) { + return false; + } } - return $noCascade; + return true; } /** diff --git a/Table.php b/Table.php index b20a0725..6545ee65 100644 --- a/Table.php +++ b/Table.php @@ -2413,10 +2413,13 @@ protected function _processDelete(EntityInterface $entity, ArrayObject $options) return (bool)$event->getResult(); } - $this->_associations->cascadeDelete( + $success = $this->_associations->cascadeDelete( $entity, ['_primary' => false] + $options->getArrayCopy() ); + if (!$success) { + return $success; + } $query = $this->query(); $conditions = (array)$entity->extract($primaryKey); From 1a1b87cf2dfcbed65001ec0a70fd625cf5d2178d Mon Sep 17 00:00:00 2001 From: Walther Lalk <83255+dakota@users.noreply.github.com> Date: Mon, 1 Jun 2020 12:07:40 +0200 Subject: [PATCH 1549/2059] SaveMany should call `afterSaveCommit` --- Table.php | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 6545ee65..788d88dd 100644 --- a/Table.php +++ b/Table.php @@ -2180,6 +2180,14 @@ public function saveManyOrFail(iterable $entities, $options = []): iterable */ protected function _saveMany(iterable $entities, $options = []): iterable { + $options = new ArrayObject( + (array)$options + [ + 'atomic' => true, + 'checkRules' => true, + '_primary' => true, + ] + ); + /** @var bool[] $isNew */ $isNew = []; $cleanup = function ($entities) use (&$isNew): void { @@ -2217,7 +2225,13 @@ protected function _saveMany(iterable $entities, $options = []): iterable throw new PersistenceFailedException($failed, ['saveMany']); } - + + if ($this->_transactionCommitted($options['atomic'], $options['_primary'])) { + foreach ($entities as $entity) { + $this->dispatchEvent('Model.afterSaveCommit', compact('entity', 'options')); + } + } + return $entities; } From 83a402c9496ddeb6c78b593d06d3a17f01184d79 Mon Sep 17 00:00:00 2001 From: Walther Lalk Date: Mon, 1 Jun 2020 18:29:45 +0200 Subject: [PATCH 1550/2059] Fix CS issue --- Table.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index 788d88dd..342cf67c 100644 --- a/Table.php +++ b/Table.php @@ -2225,13 +2225,13 @@ protected function _saveMany(iterable $entities, $options = []): iterable throw new PersistenceFailedException($failed, ['saveMany']); } - + if ($this->_transactionCommitted($options['atomic'], $options['_primary'])) { foreach ($entities as $entity) { $this->dispatchEvent('Model.afterSaveCommit', compact('entity', 'options')); } } - + return $entities; } From 98d97bee944367d2b7d99e97482bbcc94f73e10d Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 5 Jun 2020 22:14:10 +0530 Subject: [PATCH 1551/2059] Fix deleting record without any dependent associated records. Closes #14672. --- Association/DependentDeleteHelper.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Association/DependentDeleteHelper.php b/Association/DependentDeleteHelper.php index bbb6f0e6..1b7a7d80 100644 --- a/Association/DependentDeleteHelper.php +++ b/Association/DependentDeleteHelper.php @@ -58,6 +58,8 @@ public function cascadeDelete(Association $association, EntityInterface $entity, return true; } - return (bool)$association->deleteAll($conditions); + $association->deleteAll($conditions); + + return true; } } From 2beb985dfc0450b2db47643a33638723f8df54e0 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 6 Jun 2020 06:44:20 +0530 Subject: [PATCH 1552/2059] Deprecate TableRegistry. (#14666) * Deprecate TableRegistry. * Remove use of deprecated TableRegistry. --- TableRegistry.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/TableRegistry.php b/TableRegistry.php index 52a0c42a..d91531d4 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -55,6 +55,9 @@ * // Prior to 3.6.0 * $table = TableRegistry::get('Users', $config); * ``` + * + * @deprecated 4.1.0 Use {@see \Cake\ORM\Locator\LocatorAwareTrait::getTableLocator()} + * or \Cake\Datasource\FactoryLocator::get('Table') to get the table locator instance instead. */ class TableRegistry { From 46361fe32902bfe3d63e04c49628cf262aef8596 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 10 Jun 2020 13:22:17 +0530 Subject: [PATCH 1553/2059] Fix errors reported by phpstan and psalm. --- Query.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Query.php b/Query.php index 5b9c7618..de6ac103 100644 --- a/Query.php +++ b/Query.php @@ -39,6 +39,9 @@ * required. * * @see \Cake\Collection\CollectionInterface For a full description of the collection methods supported by this class + * @property \Cake\ORM\Table $_repository Instance of a table object this query is bound to. + * @method \Cake\ORM\Table getRepository() Returns the default table object that will be used by this query, + * that is, the table that will appear in the from clause. * @method \Cake\Collection\CollectionInterface each(callable $c) Passes each of the query results to the callable * @method \Cake\Collection\CollectionInterface sortBy($callback, int $dir, int $type) Sorts the query with the callback * @method \Cake\Collection\CollectionInterface filter(callable $c = null) Keeps the results using passing the callable test @@ -214,7 +217,6 @@ public function __construct(Connection $connection, Table $table) * to be added to the list. * @param bool $overwrite whether to reset fields with passed list or not * @return $this - * @psalm-suppress MoreSpecificImplementedParamType */ public function select($fields = [], bool $overwrite = false) { From 4529eb7c02472a1a981b0f8e5b3541eba6fc67a1 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 12 Jun 2020 22:24:58 -0400 Subject: [PATCH 1554/2059] Remove more whitelist usage Replace whitelist with more appropriate and descriptive wording. --- Marshaller.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index be71bb6c..b3fc7c96 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -143,7 +143,7 @@ protected function _buildPropertyMap(array $data, array $options): array * - validate: Set to false to disable validation. Can also be a string of the validator ruleset to be applied. * Defaults to true/default. * - associated: Associations listed here will be marshalled as well. Defaults to null. - * - fields: A whitelist of fields to be assigned to the entity. If not present, + * - fields: An allowed list of fields to be assigned to the entity. If not present, * the accessible fields list in the entity will be used. Defaults to null. * - accessibleFields: A list of fields to allow or deny in entity accessible fields. Defaults to null * - forceNew: When enabled, belongsToMany associations will have 'new' entities created @@ -341,7 +341,7 @@ protected function _marshalAssociation(Association $assoc, $value, array $option * - validate: Set to false to disable validation. Can also be a string of the validator ruleset to be applied. * Defaults to true/default. * - associated: Associations listed here will be marshalled as well. Defaults to null. - * - fields: A whitelist of fields to be assigned to the entity. If not present, + * - fields: An allowed list of fields to be assigned to the entity. If not present, * the accessible fields list in the entity will be used. Defaults to null. * - accessibleFields: A list of fields to allow or deny in entity accessible fields. Defaults to null * - forceNew: When enabled, belongsToMany associations will have 'new' entities created @@ -512,7 +512,7 @@ protected function _loadAssociatedByIds(Association $assoc, array $ids): array * - associated: Associations listed here will be marshalled as well. * - validate: Whether or not to validate data before hydrating the entities. Can * also be set to a string to use a specific validator. Defaults to true/default. - * - fields: A whitelist of fields to be assigned to the entity. If not present + * - fields: An allowed list of fields to be assigned to the entity. If not present * the accessible fields list in the entity will be used. * - accessibleFields: A list of fields to allow or deny in entity accessible fields. * @@ -639,7 +639,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) * - validate: Whether or not to validate data before hydrating the entities. Can * also be set to a string to use a specific validator. Defaults to true/default. * - associated: Associations listed here will be marshalled as well. - * - fields: A whitelist of fields to be assigned to the entity. If not present, + * - fields: An allowed list of fields to be assigned to the entity. If not present, * the accessible fields list in the entity will be used. * - accessibleFields: A list of fields to allow or deny in entity accessible fields. * From 9501d6fdc530deb04f79fa85348dca5e5db49ad7 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 15 Jun 2020 01:35:44 +0530 Subject: [PATCH 1555/2059] Remove unused psalm's error suppression annotations. --- Locator/TableLocator.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 91be7e26..88ef8fb1 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -252,7 +252,6 @@ protected function _create(array $options): Table * @param \Cake\ORM\Table $repository The Table to set. * @return \Cake\ORM\Table * @psalm-suppress MoreSpecificImplementedParamType - * @psalm-suppress MoreSpecificReturnType */ public function set(string $alias, RepositoryInterface $repository): Table { From b34cb1bcec8f72b55e3f8ee60067bc637abc5959 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 22 Jun 2020 14:03:27 +0530 Subject: [PATCH 1556/2059] Avoid use of call_user_func() and call_user_func_array(). These methods are not very efficient. See https://github.com/fab2s/call_user_func --- Behavior/TranslateBehavior.php | 2 +- BehaviorRegistry.php | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index d8c0d049..5daecbb6 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -335,7 +335,7 @@ public function findTranslations(Query $query, array $options): Query */ public function __call($method, $args) { - return call_user_func_array([$this->strategy, $method], $args); + return $this->strategy->{$method}(...$args); } /** diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 772145a0..7f7b2c2c 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -249,7 +249,7 @@ public function call(string $method, array $args = []) if ($this->hasMethod($method) && $this->has($this->_methodMap[$method][0])) { [$behavior, $callMethod] = $this->_methodMap[$method]; - return call_user_func_array([$this->_loaded[$behavior], $callMethod], $args); + return $this->_loaded[$behavior]->{$callMethod}(...$args); } throw new BadMethodCallException( @@ -271,8 +271,9 @@ public function callFinder(string $type, array $args = []): Query if ($this->hasFinder($type) && $this->has($this->_finderMap[$type][0])) { [$behavior, $callMethod] = $this->_finderMap[$type]; + $callable = [$this->_loaded[$behavior], $callMethod]; - return call_user_func_array([$this->_loaded[$behavior], $callMethod], $args); + return $callable(...$args); } throw new BadMethodCallException( From f87217ff1806862f61c4af5cfd72ea70b05a6f4c Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 23 Jun 2020 23:57:20 +0530 Subject: [PATCH 1557/2059] Abort atomic save if deletion of junction record fails. --- Association/BelongsToMany.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 1a39101e..2e77b83b 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1195,6 +1195,10 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { $jointEntities = $this->_collectJointEntities($sourceEntity, $targetEntities); $inserts = $this->_diffLinks($existing, $jointEntities, $targetEntities, $options); + if ($inserts === false) { + return false; + } + if ($inserts && !$this->_saveTarget($sourceEntity, $inserts, $options)) { return false; } @@ -1228,14 +1232,14 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { * @param array $targetEntities entities in target table that are related to * the `$jointEntities` * @param array $options list of options accepted by `Table::delete()` - * @return array + * @return array|false Array of entities not deleted or false in case of deletion failure for atomic saves. */ protected function _diffLinks( Query $existing, array $jointEntities, array $targetEntities, array $options = [] - ): array { + ) { $junction = $this->junction(); $target = $this->getTarget(); $belongsTo = $junction->getAssociation($target->getAlias()); @@ -1281,9 +1285,9 @@ protected function _diffLinks( } } - if ($deletes) { - foreach ($deletes as $entity) { - $junction->delete($entity, $options); + foreach ($deletes as $entity) { + if (!$junction->delete($entity, $options) && !empty($options['atomic'])) { + return false; } } From 4d3da49c38469b4f682156a0e63d0ed37ccc14fa Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Mon, 29 Jun 2020 01:23:44 -0500 Subject: [PATCH 1558/2059] Added exception when BelongsToMany has same source and target table. --- Association/BelongsToMany.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 2e77b83b..37056fae 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -283,8 +283,16 @@ public function junction($table = null): Table if (is_string($table)) { $table = $tableLocator->get($table); } + $source = $this->getSource(); $target = $this->getTarget(); + if ($source->getAlias() === $target->getAlias()) { + throw new InvalidArgumentException(sprintf( + 'The `%s` association on `%s` cannot target the same table.', + $this->getName(), + $source->getAlias() + )); + } $this->_generateSourceAssociations($table, $source); $this->_generateTargetAssociations($table, $source, $target); @@ -409,7 +417,7 @@ protected function _generateJunctionAssociations(Table $junction, Table $source, $target !== $belongsTo->getTarget() ) { throw new InvalidArgumentException( - "The existing `{$tAlias}` association on `{$junction->getAlias()} " . + "The existing `{$tAlias}` association on `{$junction->getAlias()}` " . "is incompatible with the `{$this->getName()}` association on `{$source->getAlias()}`" ); } From c59bf795a68ae9e33c736ae2ad72ebfd18c49f10 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Fri, 3 Jul 2020 01:21:36 -0500 Subject: [PATCH 1559/2059] Added Query::subquery() which disables field aliasing --- Query.php | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/Query.php b/Query.php index 978611b7..605c968b 100644 --- a/Query.php +++ b/Query.php @@ -130,6 +130,13 @@ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface */ protected $_hydrate = true; + /** + * Whether aliases are generated for fields. + * + * @var bool + */ + protected $aliasingEnabled = true; + /** * A callable function that can be used to calculate the total amount of * records this query will match when not using `limit` @@ -225,7 +232,11 @@ public function select($fields = [], bool $overwrite = false) } if ($fields instanceof Table) { - $fields = $this->aliasFields($fields->getSchema()->columns(), $fields->getAlias()); + if ($this->aliasingEnabled) { + $fields = $this->aliasFields($fields->getSchema()->columns(), $fields->getAlias()); + } else { + $fields = $fields->getSchema()->columns(); + } } return parent::select($fields, $overwrite); @@ -255,9 +266,11 @@ public function selectAllExcept($table, array $excludedFields, bool $overwrite = } $fields = array_diff($table->getSchema()->columns(), $excludedFields); - $aliasedFields = $this->aliasFields($fields); + if ($this->aliasingEnabled) { + $fields = $this->aliasFields($fields); + } - return $this->select($aliasedFields, $overwrite); + return $this->select($fields, $overwrite); } /** @@ -1163,8 +1176,10 @@ protected function _addDefaultFields(): void $select = $this->clause('select'); } - $aliased = $this->aliasFields($select, $repository->getAlias()); - $this->select($aliased, true); + if ($this->aliasingEnabled) { + $select = $this->aliasFields($select, $repository->getAlias()); + } + $this->select($select, true); } /** @@ -1285,6 +1300,20 @@ public function insert(array $columns, array $types = []) return parent::insert($columns, $types); } + /** + * Returns a new Query that has automatic field aliasing disabled. + * + * @param \Cake\ORM\Table $table The table this query is starting on + * @return self + */ + public function subquery(Table $table): self + { + $query = new self($this->getConnection(), $table); + $query->aliasingEnabled = false; + + return $query; + } + /** * {@inheritDoc} * From 4860937a82905f732101a613457ef512f0f66765 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Fri, 3 Jul 2020 07:49:19 -0500 Subject: [PATCH 1560/2059] Changed subquery() to return static --- Query.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Query.php b/Query.php index 605c968b..e3b339ab 100644 --- a/Query.php +++ b/Query.php @@ -1304,11 +1304,11 @@ public function insert(array $columns, array $types = []) * Returns a new Query that has automatic field aliasing disabled. * * @param \Cake\ORM\Table $table The table this query is starting on - * @return self + * @return static */ - public function subquery(Table $table): self + public function subquery(Table $table) { - $query = new self($this->getConnection(), $table); + $query = new static($this->getConnection(), $table); $query->aliasingEnabled = false; return $query; From 437576b7d38a4d9a4238b600b37e809c24a741c4 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Thu, 16 Jul 2020 14:40:36 -0500 Subject: [PATCH 1561/2059] Updated psalm to 3.12.3 --- Behavior/Translate/EavStrategy.php | 1 + Behavior/Translate/ShadowTableStrategy.php | 1 + 2 files changed, 2 insertions(+) diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index 730a8737..e36bc689 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -380,6 +380,7 @@ protected function rowMapper($results, $locale) $row['_locale'] = $locale; if ($hydrated) { + /** @psalm-suppress PossiblyInvalidMethodCall */ $row->clean(); } diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 6c993e49..1e60a49b 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -483,6 +483,7 @@ protected function rowMapper($results, $locale) unset($row['translation']); if ($hydrated) { + /** @psalm-suppress PossiblyInvalidMethodCall */ $row->clean(); } From cdf07df78b24b6927c1cbc0d4052886353e624f4 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Fri, 17 Jul 2020 00:16:37 -0500 Subject: [PATCH 1562/2059] Changed subquery() to static function and use connection from Table --- Query.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Query.php b/Query.php index e3b339ab..21415d19 100644 --- a/Query.php +++ b/Query.php @@ -1306,9 +1306,9 @@ public function insert(array $columns, array $types = []) * @param \Cake\ORM\Table $table The table this query is starting on * @return static */ - public function subquery(Table $table) + public static function subquery(Table $table) { - $query = new static($this->getConnection(), $table); + $query = new static($table->getConnection(), $table); $query->aliasingEnabled = false; return $query; From 8a899ed722369353395cbda8e1106656309e635e Mon Sep 17 00:00:00 2001 From: mcsknp Date: Tue, 21 Jul 2020 14:36:45 +0900 Subject: [PATCH 1563/2059] cast array key to string cakephp#14810 --- Marshaller.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Marshaller.php b/Marshaller.php index b3fc7c96..932e13df 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -72,6 +72,7 @@ protected function _buildPropertyMap(array $data, array $options): array // Is a concrete column? foreach (array_keys($data) as $prop) { + $prop = (string) $prop; $columnType = $schema->getColumnType($prop); if ($columnType) { $map[$prop] = function ($value, $entity) use ($columnType) { From 0f294568b37ab8cfa1ee064a05140db9d08c3201 Mon Sep 17 00:00:00 2001 From: mcsknp Date: Tue, 21 Jul 2020 14:44:43 +0900 Subject: [PATCH 1564/2059] fix cs error #14810 --- Marshaller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index 932e13df..45c3363d 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -72,7 +72,7 @@ protected function _buildPropertyMap(array $data, array $options): array // Is a concrete column? foreach (array_keys($data) as $prop) { - $prop = (string) $prop; + $prop = (string)$prop; $columnType = $schema->getColumnType($prop); if ($columnType) { $map[$prop] = function ($value, $entity) use ($columnType) { From b7e437f6da48bf51df5099b2fc5dc314a1809d15 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 22 Jul 2020 13:35:42 +0530 Subject: [PATCH 1565/2059] Remove unused variables. Found using "psalm --alter --issues=UnusedVariable" --- Table.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Table.php b/Table.php index 342cf67c..1fee74ae 100644 --- a/Table.php +++ b/Table.php @@ -2543,7 +2543,6 @@ protected function _dynamicFinder(string $method, array $args) ); } - $conditions = []; if ($hasOr === false && $hasAnd === false) { $conditions = $makeConditions([$fields], $args); } elseif ($hasOr !== false) { From f613ee587669ba32538d7c7f2152ef53fb5b9c54 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 22 Jul 2020 13:47:25 +0530 Subject: [PATCH 1566/2059] Remove unused property. --- Rule/IsUnique.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index 76ea4a01..d36de6bd 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -30,13 +30,6 @@ class IsUnique */ protected $_fields; - /** - * The options to use. - * - * @var array - */ - protected $_options; - /** * Constructor. * From bdf715015340da4034e05702b6ab3829cd396bde Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Fri, 24 Jul 2020 00:00:12 -0500 Subject: [PATCH 1567/2059] Add Table::subquery() wrapper --- Table.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Table.php b/Table.php index 342cf67c..5b3b4ed3 100644 --- a/Table.php +++ b/Table.php @@ -1679,6 +1679,17 @@ public function query(): Query return new Query($this->getConnection(), $this); } + /** + * Creates a new Query::subquery() instance for a table. + * + * @return \Cake\ORM\Query + * @see \Cake\ORM\Query::subquery() + */ + public function subquery(): Query + { + return Query::subquery($this); + } + /** * @inheritDoc */ From 19593cb5c12a2725fabf4466c5d6267180a2069e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20W=C3=BCrth?= Date: Thu, 30 Jul 2020 13:58:57 +0200 Subject: [PATCH 1568/2059] Name dynamic finder prefix correctly https://book.cakephp.org/4/en/orm/retrieving-data-and-resultsets.html#dynamic-finders --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 1fee74ae..2285a0b5 100644 --- a/Table.php +++ b/Table.php @@ -2498,7 +2498,7 @@ public function callFinder(string $type, Query $query, array $options = []): Que } /** - * Provides the dynamic findBy and findByAll methods. + * Provides the dynamic findBy and findAllBy methods. * * @param string $method The method name that was fired. * @param array $args List of arguments passed to the function. From 2b06daa084247aaf9f6658444da4bb3230cfe381 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 31 Jul 2020 01:56:06 +0530 Subject: [PATCH 1569/2059] Supress errors reported by psalm. --- Behavior/Translate/ShadowTableStrategy.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 1e60a49b..93859115 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -452,6 +452,7 @@ protected function rowMapper($results, $locale) unset($row['translation']); if ($hydrated) { + /** @psalm-suppress PossiblyInvalidMethodCall */ $row->clean(); } From cdc32ba74351ec31f3b6a375adf23c5857973137 Mon Sep 17 00:00:00 2001 From: ndm2 Date: Sat, 1 Aug 2020 15:46:55 +0200 Subject: [PATCH 1570/2059] Add support for receiving the query object in result formatter callables --- Association.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association.php b/Association.php index 41bcf486..0516c9fa 100644 --- a/Association.php +++ b/Association.php @@ -1014,7 +1014,7 @@ protected function _formatAssociationResults(Query $query, Query $surrogate, arr } $extracted = new Collection($extracted); foreach ($formatters as $callable) { - $extracted = new ResultSetDecorator($callable($extracted)); + $extracted = new ResultSetDecorator($callable($extracted, $query)); } /** @var \Cake\Collection\CollectionInterface $results */ From d86bb0716e4ed222749567d7a3cba3283f37d0c5 Mon Sep 17 00:00:00 2001 From: ndm2 Date: Sat, 1 Aug 2020 22:07:28 +0200 Subject: [PATCH 1571/2059] Use the new query argument in the internally used result formatters. --- Association.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association.php b/Association.php index 0516c9fa..3aeee015 100644 --- a/Association.php +++ b/Association.php @@ -1000,7 +1000,7 @@ protected function _formatAssociationResults(Query $query, Query $surrogate, arr $property = $options['propertyPath']; $propertyPath = explode('.', $property); - $query->formatResults(function ($results) use ($formatters, $property, $propertyPath, $query) { + $query->formatResults(function ($results, $query) use ($formatters, $property, $propertyPath) { $extracted = []; foreach ($results as $result) { foreach ($propertyPath as $propertyPathItem) { From e974cddd9331f995dde79b9e23755128d11401ba Mon Sep 17 00:00:00 2001 From: Mark Sch Date: Sat, 8 Aug 2020 12:20:29 +0200 Subject: [PATCH 1572/2059] Update Table.php --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 2285a0b5..3a46d63c 100644 --- a/Table.php +++ b/Table.php @@ -416,7 +416,7 @@ public function getAlias(): string if ($this->_alias === null) { $alias = namespaceSplit(static::class); $alias = substr(end($alias), 0, -5) ?: $this->_table; - $this->_alias = $alias; + $this->_alias = (string)$alias; } return $this->_alias; From c6f6bb3b58a8ee0c38f4dd0c9d958f29820bf199 Mon Sep 17 00:00:00 2001 From: mscherer Date: Mon, 17 Aug 2020 15:45:25 +0200 Subject: [PATCH 1573/2059] Rename getTable() to table() to remove naming collision. --- Behavior.php | 11 +++++++++++ Behavior/TimestampBehavior.php | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Behavior.php b/Behavior.php index fcd69e6f..0d1f5abd 100644 --- a/Behavior.php +++ b/Behavior.php @@ -184,8 +184,19 @@ public function initialize(array $config): void * Get the table instance this behavior is bound to. * * @return \Cake\ORM\Table The bound table instance. + * @deprecated 4.2.0 Use table() instead. */ public function getTable(): Table + { + return $this->table(); + } + + /** + * Get the table instance this behavior is bound to. + * + * @return \Cake\ORM\Table The bound table instance. + */ + public function table(): Table { return $this->_table; } diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index dfeb2913..e053cea7 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -211,7 +211,7 @@ protected function _updateField(EntityInterface $entity, string $field, bool $re $ts = $this->timestamp(null, $refreshTimestamp); - $columnType = $this->getTable()->getSchema()->getColumnType($field); + $columnType = $this->table()->getSchema()->getColumnType($field); if (!$columnType) { return; } From 682285fedb8da7b219f17f44df3512d1e4f8e2eb Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 18 Aug 2020 20:59:55 -0400 Subject: [PATCH 1574/2059] Formalize current deprecations Add formal warnings for deprecations that have been added so far in 4.x. I'll follow this up with a change to rector as well. --- Behavior.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Behavior.php b/Behavior.php index 0d1f5abd..422d4622 100644 --- a/Behavior.php +++ b/Behavior.php @@ -188,6 +188,8 @@ public function initialize(array $config): void */ public function getTable(): Table { + deprecationWarning('Behavior::getTable() is deprecated. Use table() instead.'); + return $this->table(); } From 36a241b2b8d8a3c84de96a928ff57f06d90f1a26 Mon Sep 17 00:00:00 2001 From: Mark Sch Date: Mon, 24 Aug 2020 16:43:16 +0200 Subject: [PATCH 1575/2059] Undeprecate class --- TableRegistry.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/TableRegistry.php b/TableRegistry.php index d91531d4..52a0c42a 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -55,9 +55,6 @@ * // Prior to 3.6.0 * $table = TableRegistry::get('Users', $config); * ``` - * - * @deprecated 4.1.0 Use {@see \Cake\ORM\Locator\LocatorAwareTrait::getTableLocator()} - * or \Cake\Datasource\FactoryLocator::get('Table') to get the table locator instance instead. */ class TableRegistry { From 8f7ed8a321c70e4c7650571718c3bac6c815bb38 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 1 Sep 2020 17:35:02 +0530 Subject: [PATCH 1576/2059] Fix generation of alias when using FQCN with TableLocator::get(). Closes #14948 --- Behavior/Translate/ShadowTableStrategy.php | 3 ++- Locator/TableLocator.php | 10 ++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 93859115..21a570f0 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -99,7 +99,8 @@ protected function setupAssociations() { $config = $this->getConfig(); - $this->table->hasMany($config['translationTable'], [ + $targetAlias = $this->translationTable->getAlias(); + $this->table->hasMany($targetAlias, [ 'className' => $config['translationTable'], 'foreignKey' => 'id', 'strategy' => $config['strategy'], diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 88ef8fb1..a69e7b37 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -160,8 +160,14 @@ public function get(string $alias, array $options = []): Table */ protected function createInstance(string $alias, array $options) { - [, $classAlias] = pluginSplit($alias); - $options = ['alias' => $classAlias] + $options; + if (strpos($alias, '\\') === false) { + [, $classAlias] = pluginSplit($alias); + $options = ['alias' => $classAlias] + $options; + } elseif (!isset($options['alias'])) { + $options['className'] = $alias; + /** @psalm-suppress PossiblyFalseOperand */ + $alias = substr($alias, strrpos($alias, '\\') + 1, -5); + } if (isset($this->_config[$alias])) { $options += $this->_config[$alias]; From dc421f12c3d359f7c827e073187e0e260a3adf60 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 6 Sep 2020 13:42:14 +0530 Subject: [PATCH 1577/2059] Update psalm's baseline file. --- Behavior/Translate/EavStrategy.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index e36bc689..6184c956 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -282,7 +282,6 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array $preexistent = []; if ($key) { - /** @psalm-suppress UndefinedClass */ $preexistent = $this->translationTable->find() ->select(['id', 'field']) ->where([ From 84957346dbf9f581f78a64c239fcd1558497885e Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 14 Oct 2020 18:17:05 +0530 Subject: [PATCH 1578/2059] Remove uneeded psalm annotation --- Query.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Query.php b/Query.php index 21415d19..4f2e2340 100644 --- a/Query.php +++ b/Query.php @@ -1084,7 +1084,6 @@ public function triggerBeforeFind(): void if (!$this->_beforeFindFired && $this->_type === 'select') { $this->_beforeFindFired = true; - /** @var \Cake\Event\EventDispatcherInterface $repository */ $repository = $this->getRepository(); $repository->dispatchEvent('Model.beforeFind', [ $this, From 53995a00a69336952d1ebf1ce9487d13194556cc Mon Sep 17 00:00:00 2001 From: ravage84 Date: Mon, 19 Oct 2020 18:20:21 +0200 Subject: [PATCH 1579/2059] Improve wording, grammar, punctuation etc. --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 077b5fd5..5af443ac 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ use Cake\ORM\Locator\LocatorAwareTrait; $articles = $this->getTableLocator()->get('Articles'); ``` -By default classes using `LocatorAwareTrait` will share a global locator instance. +By default, classes using `LocatorAwareTrait` will share a global locator instance. You can inject your own locator instance into the object: ```php @@ -78,8 +78,8 @@ In your table classes you can define the relations between your tables. CakePHP' supports 4 association types out of the box: * belongsTo - E.g. Many articles belong to a user. -* hasOne - E.g. A user has one profile -* hasMany - E.g. A user has many articles +* hasOne - E.g. A user has one profile. +* hasMany - E.g. A user has many articles. * belongsToMany - E.g. An article belongsToMany tags. You define associations in your table's `initialize()` method. See the @@ -149,7 +149,7 @@ $articles->delete($article); ## Meta Data Cache -It is recommended to enable meta data cache for production systems to avoid performance issues. +It is recommended to enable metadata cache for production systems to avoid performance issues. For e.g. file system strategy your bootstrap file could look like this: ```php @@ -164,7 +164,7 @@ $cacheConfig = [ Cache::setConfig('_cake_model_', $cacheConfig); ``` -Cache configs are optional so you must require ``cachephp/cache`` to add one. +Cache configs are optional, so you must require ``cachephp/cache`` to add one. ## Creating Custom Table and Entity Classes From 52411a5a0ae1b2863bac6a49e86fe24c30aa9c33 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Mon, 19 Oct 2020 19:13:55 -0500 Subject: [PATCH 1580/2059] Update and parameter names --- Query.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Query.php b/Query.php index 4f2e2340..9937f3f9 100644 --- a/Query.php +++ b/Query.php @@ -1096,13 +1096,13 @@ public function triggerBeforeFind(): void /** * @inheritDoc */ - public function sql(?ValueBinder $generator = null): string + public function sql(?ValueBinder $binder = null): string { $this->triggerBeforeFind(); $this->_transformQuery(); - return parent::sql($generator); + return parent::sql($binder); } /** From 3e04b70ce5ecf25911df2c8b035cc58f83efde0c Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 29 Oct 2020 21:51:14 +0530 Subject: [PATCH 1581/2059] Fix regression in object value comparison when marshalling entity. Closes #15099 --- Marshaller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index 45c3363d..5f0e0cfb 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -583,7 +583,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) || ( is_object($value) && !($value instanceof EntityInterface) - && $original === $value + && $original == $value ) ) { continue; From 9f32e675979666d0628f8ba68051230235347fa8 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Sun, 1 Nov 2020 09:48:55 -0600 Subject: [PATCH 1582/2059] Replace all uses of Core\Exception\Exception with Core\Exception\CakeException --- Behavior.php | 6 +++--- Exception/MissingBehaviorException.php | 4 ++-- Exception/MissingEntityException.php | 4 ++-- Exception/MissingTableClassException.php | 4 ++-- Exception/PersistenceFailedException.php | 4 ++-- Exception/RolledbackTransactionException.php | 4 ++-- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Behavior.php b/Behavior.php index 422d4622..1f7939ab 100644 --- a/Behavior.php +++ b/Behavior.php @@ -16,7 +16,7 @@ */ namespace Cake\ORM; -use Cake\Core\Exception\Exception; +use Cake\Core\Exception\CakeException; use Cake\Core\InstanceConfigTrait; use Cake\Event\EventListenerInterface; use ReflectionClass; @@ -242,7 +242,7 @@ protected function _resolveMethodAliases(string $key, array $defaults, array $co * Checks that implemented keys contain values pointing at callable. * * @return void - * @throws \Cake\Core\Exception\Exception if config are invalid + * @throws \Cake\Core\Exception\CakeException if config are invalid */ public function verifyConfig(): void { @@ -254,7 +254,7 @@ public function verifyConfig(): void foreach ($this->_config[$key] as $method) { if (!is_callable([$this, $method])) { - throw new Exception(sprintf( + throw new CakeException(sprintf( 'The method %s is not callable on class %s', $method, static::class diff --git a/Exception/MissingBehaviorException.php b/Exception/MissingBehaviorException.php index eaaf511a..fe6411eb 100644 --- a/Exception/MissingBehaviorException.php +++ b/Exception/MissingBehaviorException.php @@ -14,12 +14,12 @@ */ namespace Cake\ORM\Exception; -use Cake\Core\Exception\Exception; +use Cake\Core\Exception\CakeException; /** * Used when a behavior cannot be found. */ -class MissingBehaviorException extends Exception +class MissingBehaviorException extends CakeException { /** * @var string diff --git a/Exception/MissingEntityException.php b/Exception/MissingEntityException.php index 50001358..c363c123 100644 --- a/Exception/MissingEntityException.php +++ b/Exception/MissingEntityException.php @@ -18,12 +18,12 @@ */ namespace Cake\ORM\Exception; -use Cake\Core\Exception\Exception; +use Cake\Core\Exception\CakeException; /** * Exception raised when an Entity could not be found. */ -class MissingEntityException extends Exception +class MissingEntityException extends CakeException { /** * @var string diff --git a/Exception/MissingTableClassException.php b/Exception/MissingTableClassException.php index 699a0f29..accf334b 100644 --- a/Exception/MissingTableClassException.php +++ b/Exception/MissingTableClassException.php @@ -16,12 +16,12 @@ */ namespace Cake\ORM\Exception; -use Cake\Core\Exception\Exception; +use Cake\Core\Exception\CakeException; /** * Exception raised when a Table could not be found. */ -class MissingTableClassException extends Exception +class MissingTableClassException extends CakeException { /** * @var string diff --git a/Exception/PersistenceFailedException.php b/Exception/PersistenceFailedException.php index edf14231..5d9d0302 100644 --- a/Exception/PersistenceFailedException.php +++ b/Exception/PersistenceFailedException.php @@ -14,7 +14,7 @@ */ namespace Cake\ORM\Exception; -use Cake\Core\Exception\Exception; +use Cake\Core\Exception\CakeException; use Cake\Datasource\EntityInterface; use Cake\Utility\Hash; use Throwable; @@ -22,7 +22,7 @@ /** * Used when a strict save or delete fails */ -class PersistenceFailedException extends Exception +class PersistenceFailedException extends CakeException { /** * The entity on which the persistence operation failed diff --git a/Exception/RolledbackTransactionException.php b/Exception/RolledbackTransactionException.php index ebabcd79..b1f107bd 100644 --- a/Exception/RolledbackTransactionException.php +++ b/Exception/RolledbackTransactionException.php @@ -14,12 +14,12 @@ */ namespace Cake\ORM\Exception; -use Cake\Core\Exception\Exception; +use Cake\Core\Exception\CakeException; /** * Used when a transaction was rolled back from a callback event. */ -class RolledbackTransactionException extends Exception +class RolledbackTransactionException extends CakeException { /** * @var string From 181565dc42065adc8638b8be56aac5aa840c1284 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Sun, 1 Nov 2020 10:54:19 -0600 Subject: [PATCH 1583/2059] Replace all uses of Database\Exception with Database\Exception\DatabaseException --- ResultSet.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ResultSet.php b/ResultSet.php index b5b0333b..1ef11de6 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -18,7 +18,7 @@ use Cake\Collection\Collection; use Cake\Collection\CollectionTrait; -use Cake\Database\Exception; +use Cake\Database\Exception\DatabaseException; use Cake\Database\StatementInterface; use Cake\Datasource\EntityInterface; use Cake\Datasource\ResultSetInterface; @@ -219,7 +219,7 @@ public function next(): void * * Part of Iterator interface. * - * @throws \Cake\Database\Exception + * @throws \Cake\Database\Exception\DatabaseException * @return void */ public function rewind(): void @@ -231,7 +231,7 @@ public function rewind(): void if (!$this->_useBuffering) { $msg = 'You cannot rewind an un-buffered ResultSet. ' . 'Use Query::bufferResults() to get a buffered ResultSet.'; - throw new Exception($msg); + throw new DatabaseException($msg); } $this->_index = 0; @@ -303,7 +303,7 @@ public function serialize(): string if (!$this->_useBuffering) { $msg = 'You cannot serialize an un-buffered ResultSet. ' . 'Use Query::bufferResults() to get a buffered ResultSet.'; - throw new Exception($msg); + throw new DatabaseException($msg); } while ($this->valid()) { From 4057151d0e437a9c3eee72020ee10b50c3c3403b Mon Sep 17 00:00:00 2001 From: mscherer Date: Tue, 10 Nov 2020 16:17:52 +0100 Subject: [PATCH 1584/2059] Fix up event docs. --- Table.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Table.php b/Table.php index 3a46d63c..c4e3c885 100644 --- a/Table.php +++ b/Table.php @@ -125,6 +125,8 @@ * lifecycle methods below: * * - `beforeFind(EventInterface $event, Query $query, ArrayObject $options, boolean $primary)` + * - `beforeMarshal(EventInterface $event, ArrayObject $data, ArrayObject $options)` + * - `afterMarshal(EventInterface $event, EntityInterface $entity, ArrayObject $options)` * - `buildValidator(EventInterface $event, Validator $validator, string $name)` * - `buildRules(RulesChecker $rules)` * - `beforeRules(EventInterface $event, EntityInterface $entity, ArrayObject $options, string $operation)` @@ -134,8 +136,10 @@ * - `afterSaveCommit(EventInterface $event, EntityInterface $entity, ArrayObject $options)` * - `beforeDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options)` * - `afterDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options)` + * - `afterDeleteCommit(EventInterface $event, EntityInterface $entity, ArrayObject $options)` * * @see \Cake\Event\EventManager for reference on the events system. + * @link https://book.cakephp.org/4/en/orm/table-objects.html#event-list */ class Table implements RepositoryInterface, EventListenerInterface, EventDispatcherInterface, ValidatorAwareInterface { From 188b6e24d936f004a703e7ad2576058228ef4044 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 11 Nov 2020 20:01:22 +0530 Subject: [PATCH 1585/2059] Add variable for fallback table class name. --- Locator/TableLocator.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index a69e7b37..295f44c0 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -59,6 +59,13 @@ class TableLocator extends AbstractLocator implements LocatorInterface */ protected $_fallbacked = []; + /** + * Fallback class to use + * + * @var \Cake\ORM\Table; + */ + protected $fallbackClassName = Table::class; + /** * Constructor. * @@ -184,7 +191,7 @@ protected function createInstance(string $alias, array $options) [, $table] = pluginSplit($options['className']); $options['table'] = Inflector::underscore($table); } - $options['className'] = Table::class; + $options['className'] = $this->fallbackClassName; } if (empty($options['connection'])) { @@ -205,7 +212,7 @@ protected function createInstance(string $alias, array $options) $options['registryAlias'] = $alias; $instance = $this->_create($options); - if ($options['className'] === Table::class) { + if ($options['className'] === $this->fallbackClassName) { $this->_fallbacked[$alias] = $instance; } From 7788b781d35f51d6af356cd9a041f8df0e12f151 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 12 Nov 2020 01:28:20 +0530 Subject: [PATCH 1586/2059] Allow turning off fallback class usage for TableLocator. --- Locator/TableLocator.php | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 295f44c0..d9993ffd 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -21,6 +21,7 @@ use Cake\Datasource\Locator\AbstractLocator; use Cake\Datasource\RepositoryInterface; use Cake\ORM\AssociationCollection; +use Cake\ORM\Exception\MissingTableClassException; use Cake\ORM\Table; use Cake\Utility\Inflector; use RuntimeException; @@ -66,6 +67,13 @@ class TableLocator extends AbstractLocator implements LocatorInterface */ protected $fallbackClassName = Table::class; + /** + * Whether fallback class should be used if a table class could not be found. + * + * @var bool + */ + protected $useFallbackClass = true; + /** * Constructor. * @@ -180,10 +188,11 @@ protected function createInstance(string $alias, array $options) $options += $this->_config[$alias]; } + $useFallbackClass = $options['useFallbackClass'] ?? $this->useFallbackClass; $className = $this->_getClassName($alias, $options); if ($className) { $options['className'] = $className; - } else { + } elseif ($useFallbackClass) { if (empty($options['className'])) { $options['className'] = $alias; } @@ -192,6 +201,13 @@ protected function createInstance(string $alias, array $options) $options['table'] = Inflector::underscore($table); } $options['className'] = $this->fallbackClassName; + } else { + $message = $options['className'] ?? $alias; + $message = '`' . $message . '`'; + if (strpos($message, '\\') === false) { + $message = 'for alias ' . $message; + } + throw new MissingTableClassException([$message]); } if (empty($options['connection'])) { From 63def610e6885232a4b370206d9f37833ecba0b8 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 12 Nov 2020 01:56:43 +0530 Subject: [PATCH 1587/2059] Ensure fallback class is used for junction table. Fallback class usage is explicity turned on for junction table of belongsToMany assocation to override locator's default, unless "through" is specified. --- Association/BelongsToMany.php | 2 +- Locator/TableLocator.php | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 37056fae..1c1d81f5 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -270,7 +270,7 @@ public function junction($table = null): Table $config = []; if (!$tableLocator->exists($tableAlias)) { - $config = ['table' => $tableName]; + $config = ['table' => $tableName, 'useFallbackClass' => true]; // Propagate the connection if we'll get an auto-model if (!App::className($tableAlias, 'Model/Table', 'Table')) { diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index d9993ffd..9a9be94b 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -93,6 +93,17 @@ public function __construct(?array $locations = null) } } + /** + * Set if fallback class should be used. + * + * @param bool $enable Flag to enable or disable fallback + * @return void + */ + public function useFallbackClass(bool $enable): void + { + $this->useFallbackClass = $enable; + } + /** * @inheritDoc */ From ea213f4eb5e6016165b59978bb7911dcb4a059c5 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 12 Nov 2020 02:24:30 +0530 Subject: [PATCH 1588/2059] Always use fallback class for I18n tables. --- Behavior/Translate/EavStrategy.php | 7 +++++-- Behavior/Translate/ShadowTableStrategy.php | 5 ++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index 6184c956..41e4854d 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -80,7 +80,10 @@ public function __construct(Table $table, array $config = []) $this->setConfig($config); $this->table = $table; - $this->translationTable = $this->getTableLocator()->get($this->_config['translationTable']); + $this->translationTable = $this->getTableLocator()->get( + $this->_config['translationTable'], + ['useFallbackClass' => true] + ); $this->setupAssociations(); } @@ -116,7 +119,7 @@ protected function setupAssociations() 'table' => $this->translationTable->getTable(), ]); } else { - $fieldTable = $tableLocator->get($name); + $fieldTable = $tableLocator->get($name, ['useFallbackClass' => true]); } $conditions = [ diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 21a570f0..8c54cfcb 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -82,7 +82,10 @@ public function __construct(Table $table, array $config = []) $this->setConfig($config); $this->table = $table; - $this->translationTable = $this->getTableLocator()->get($this->_config['translationTable']); + $this->translationTable = $this->getTableLocator()->get( + $this->_config['translationTable'], + ['useFallbackClass' => true] + ); $this->setupAssociations(); } From 558f3ef2acb0afc8ac2f3e611f8b7a35c477658e Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 12 Nov 2020 03:02:03 +0530 Subject: [PATCH 1589/2059] Fix docblock --- Locator/TableLocator.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 9a9be94b..dde2718f 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -63,7 +63,8 @@ class TableLocator extends AbstractLocator implements LocatorInterface /** * Fallback class to use * - * @var \Cake\ORM\Table; + * @var string + * @psalm-var class-string<\Cake\ORM\Table> */ protected $fallbackClassName = Table::class; From 46c5d5dd81b8d599ec194b5da934d012d310502d Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 12 Nov 2020 18:24:02 +0530 Subject: [PATCH 1590/2059] Return $this instead of void --- Locator/TableLocator.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index dde2718f..9f551959 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -98,11 +98,13 @@ public function __construct(?array $locations = null) * Set if fallback class should be used. * * @param bool $enable Flag to enable or disable fallback - * @return void + * @return $this */ - public function useFallbackClass(bool $enable): void + public function useFallbackClass(bool $enable) { $this->useFallbackClass = $enable; + + return $this; } /** From 0ca089e3cb78a2b92767411b35228bdf690805b5 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 12 Nov 2020 18:32:56 +0530 Subject: [PATCH 1591/2059] Allow changing fallback class name for TableLocator. --- Locator/TableLocator.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 9f551959..b34e7571 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -107,6 +107,20 @@ public function useFallbackClass(bool $enable) return $this; } + /** + * Set fallback class name. + * + * @param string $className Fallback class name + * @return $this + * @psalm-param class-string<\Cake\ORM\Table> $className + */ + public function setFallbackClassName($className) + { + $this->fallbackClassName = $className; + + return $this; + } + /** * @inheritDoc */ From eb989d797e7ffebe2b18d3883433096e2b2986e3 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 14 Nov 2020 23:36:12 +0530 Subject: [PATCH 1592/2059] Update docblocks --- Locator/TableLocator.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index b34e7571..580acdca 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -97,6 +97,9 @@ public function __construct(?array $locations = null) /** * Set if fallback class should be used. * + * Controls whether a fallback class should be used to create a table + * instance if a concrete class for alias used in `get()` could not be found. + * * @param bool $enable Flag to enable or disable fallback * @return $this */ @@ -110,6 +113,10 @@ public function useFallbackClass(bool $enable) /** * Set fallback class name. * + * The class that should be used to create a table instance if a concrete + * class for alias used in `get()` could not be found. Defaults to + * `Cake\ORM\Table`. + * * @param string $className Fallback class name * @return $this * @psalm-param class-string<\Cake\ORM\Table> $className From 205762a86feea993f29c7cddaf6a925dece9f7ef Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 17 Nov 2020 00:08:26 +0530 Subject: [PATCH 1593/2059] Rename option "useFallbackClass" to "allowFallbackClass". --- Association/BelongsToMany.php | 2 +- Behavior/Translate/EavStrategy.php | 4 ++-- Behavior/Translate/ShadowTableStrategy.php | 2 +- Locator/TableLocator.php | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 1c1d81f5..cfe7c263 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -270,7 +270,7 @@ public function junction($table = null): Table $config = []; if (!$tableLocator->exists($tableAlias)) { - $config = ['table' => $tableName, 'useFallbackClass' => true]; + $config = ['table' => $tableName, 'allowFallbackClass' => true]; // Propagate the connection if we'll get an auto-model if (!App::className($tableAlias, 'Model/Table', 'Table')) { diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index 41e4854d..f147d467 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -82,7 +82,7 @@ public function __construct(Table $table, array $config = []) $this->table = $table; $this->translationTable = $this->getTableLocator()->get( $this->_config['translationTable'], - ['useFallbackClass' => true] + ['allowFallbackClass' => true] ); $this->setupAssociations(); @@ -119,7 +119,7 @@ protected function setupAssociations() 'table' => $this->translationTable->getTable(), ]); } else { - $fieldTable = $tableLocator->get($name, ['useFallbackClass' => true]); + $fieldTable = $tableLocator->get($name, ['allowFallbackClass' => true]); } $conditions = [ diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 8c54cfcb..22e79be0 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -84,7 +84,7 @@ public function __construct(Table $table, array $config = []) $this->table = $table; $this->translationTable = $this->getTableLocator()->get( $this->_config['translationTable'], - ['useFallbackClass' => true] + ['allowFallbackClass' => true] ); $this->setupAssociations(); diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 580acdca..d57b6cd7 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -223,7 +223,7 @@ protected function createInstance(string $alias, array $options) $options += $this->_config[$alias]; } - $useFallbackClass = $options['useFallbackClass'] ?? $this->useFallbackClass; + $useFallbackClass = $options['allowFallbackClass'] ?? $this->useFallbackClass; $className = $this->_getClassName($alias, $options); if ($className) { $options['className'] = $className; From 3e248bb7a4a09ba718fd66a867de84b87557ecf9 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Mon, 16 Nov 2020 16:12:55 -0600 Subject: [PATCH 1594/2059] Rename useFallbackClass property and method to allowFallbackClass --- Locator/TableLocator.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index d57b6cd7..d469950a 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -73,7 +73,7 @@ class TableLocator extends AbstractLocator implements LocatorInterface * * @var bool */ - protected $useFallbackClass = true; + protected $allowFallbackClass = true; /** * Constructor. @@ -100,12 +100,12 @@ public function __construct(?array $locations = null) * Controls whether a fallback class should be used to create a table * instance if a concrete class for alias used in `get()` could not be found. * - * @param bool $enable Flag to enable or disable fallback + * @param bool $allow Flag to enable or disable fallback * @return $this */ - public function useFallbackClass(bool $enable) + public function allowFallbackClass(bool $allow) { - $this->useFallbackClass = $enable; + $this->allowFallbackClass = $allow; return $this; } @@ -223,11 +223,11 @@ protected function createInstance(string $alias, array $options) $options += $this->_config[$alias]; } - $useFallbackClass = $options['allowFallbackClass'] ?? $this->useFallbackClass; + $allowFallbackClass = $options['allowFallbackClass'] ?? $this->allowFallbackClass; $className = $this->_getClassName($alias, $options); if ($className) { $options['className'] = $className; - } elseif ($useFallbackClass) { + } elseif ($allowFallbackClass) { if (empty($options['className'])) { $options['className'] = $alias; } From 3a5cc69c211ca4ff75578f02bff1028ca85a55e3 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 17 Nov 2020 23:59:08 +0530 Subject: [PATCH 1595/2059] Allow fallback table class for translation table aliases. Earlier use of fallback class wasn't properly enabled for table alias used for hasOne association with translation table. --- Behavior/Translate/EavStrategy.php | 3 +- Behavior/Translate/ShadowTableStrategy.php | 56 ++++++++++++++++------ 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index f147d467..2a9f0e46 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -117,9 +117,10 @@ protected function setupAssociations() 'className' => $table, 'alias' => $name, 'table' => $this->translationTable->getTable(), + 'allowFallbackClass' => true, ]); } else { - $fieldTable = $tableLocator->get($name, ['allowFallbackClass' => true]); + $fieldTable = $tableLocator->get($name); } $conditions = [ diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 22e79be0..efcfecee 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -125,13 +125,53 @@ protected function setupAssociations() public function beforeFind(EventInterface $event, Query $query, ArrayObject $options) { $locale = Hash::get($options, 'locale', $this->getLocale()); + $config = $this->getConfig(); - if ($locale === $this->getConfig('defaultLocale')) { + if ($locale === $config['defaultLocale']) { return; } + $this->setupHasOneAssociation($locale, $options); + + $fieldsAdded = $this->addFieldsToQuery($query, $config); + $orderByTranslatedField = $this->iterateClause($query, 'order', $config); + $filteredByTranslatedField = $this->traverseClause($query, 'where', $config); + + if (!$fieldsAdded && !$orderByTranslatedField && !$filteredByTranslatedField) { + return; + } + + $query->contain([$config['hasOneAlias']]); + + $query->formatResults(function ($results) use ($locale) { + return $this->rowMapper($results, $locale); + }, $query::PREPEND); + } + + /** + * Create a hasOne association for record with required locale. + * + * @param string $locale Locale + * @param \ArrayObject $options Find options + * @return void + */ + protected function setupHasOneAssociation(string $locale, ArrayObject $options): void + { $config = $this->getConfig(); + [$plugin] = pluginSplit($config['translationTable']); + $hasOneTargetAlias = $plugin ? ($plugin . '.' . $config['hasOneAlias']) : $config['hasOneAlias']; + if (!$this->getTableLocator()->exists($hasOneTargetAlias)) { + // Load table before hand with fallback class usage enabled + $this->getTableLocator()->get( + $hasOneTargetAlias, + [ + 'className' => $config['translationTable'], + 'allowFallbackClass' => true, + ] + ); + } + if (isset($options['filterByCurrentLocale'])) { $joinType = $options['filterByCurrentLocale'] ? 'INNER' : 'LEFT'; } else { @@ -147,20 +187,6 @@ public function beforeFind(EventInterface $event, Query $query, ArrayObject $opt $config['hasOneAlias'] . '.locale' => $locale, ], ]); - - $fieldsAdded = $this->addFieldsToQuery($query, $config); - $orderByTranslatedField = $this->iterateClause($query, 'order', $config); - $filteredByTranslatedField = $this->traverseClause($query, 'where', $config); - - if (!$fieldsAdded && !$orderByTranslatedField && !$filteredByTranslatedField) { - return; - } - - $query->contain([$config['hasOneAlias']]); - - $query->formatResults(function ($results) use ($locale) { - return $this->rowMapper($results, $locale); - }, $query::PREPEND); } /** From 2f9701c331ec1419ff2b3a30c05305a293ed5dfb Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 20 Nov 2020 18:15:02 +0530 Subject: [PATCH 1596/2059] Bump up pslam to 4.2. --- Behavior/TreeBehavior.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 66971c4a..af4f3282 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -135,7 +135,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity) return; } - if (!$isNew && $dirty && $parent) { + if ($dirty && $parent) { $this->_setParent($entity, $parent); if ($level) { @@ -146,7 +146,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity) return; } - if (!$isNew && $dirty && !$parent) { + if ($dirty && !$parent) { $this->_setAsRoot($entity); if ($level) { From a635ce644c87521f60cb3152cee40ed937e11149 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 3 Dec 2020 18:47:55 +0530 Subject: [PATCH 1597/2059] Fix errors reported by psalm. --- Association/Loader/SelectLoader.php | 2 +- AssociationCollection.php | 2 +- AssociationsNormalizerTrait.php | 1 + EagerLoadable.php | 2 +- EagerLoader.php | 5 +++-- Marshaller.php | 12 ++++++------ SaveOptionsBuilder.php | 8 ++++---- Table.php | 8 ++++---- 8 files changed, 21 insertions(+), 19 deletions(-) diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 421baa08..f3be34f5 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -267,7 +267,7 @@ protected function _assertFieldsPresent(Query $fetchQuery, array $key): void throw new InvalidArgumentException( sprintf( 'You are required to select the "%s" field(s)', - implode(', ', (array)$key) + implode(', ', $key) ) ); } diff --git a/AssociationCollection.php b/AssociationCollection.php index 979f96ef..6a025dd3 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -311,7 +311,7 @@ protected function _save( return true; } if (!empty($nested)) { - $options = (array)$nested + $options; + $options = $nested + $options; } return (bool)$association->saveAssociated($entity, $options); diff --git a/AssociationsNormalizerTrait.php b/AssociationsNormalizerTrait.php index 6d6ba450..5b27a7db 100644 --- a/AssociationsNormalizerTrait.php +++ b/AssociationsNormalizerTrait.php @@ -47,6 +47,7 @@ protected function _normalizeAssociations($associations): array $path = explode('.', $table); $table = array_pop($path); + /** @var string $first */ $first = array_shift($path); $pointer += [$first => []]; $pointer = &$pointer[$first]; diff --git a/EagerLoadable.php b/EagerLoadable.php index b782571f..c6e98c88 100644 --- a/EagerLoadable.php +++ b/EagerLoadable.php @@ -217,7 +217,7 @@ public function propertyPath(): ?string */ public function setCanBeJoined(bool $possible) { - $this->_canBeJoined = (bool)$possible; + $this->_canBeJoined = $possible; return $this; } diff --git a/EagerLoader.php b/EagerLoader.php index a4694814..9c4ac531 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -308,7 +308,7 @@ public function normalized(Table $repository): array $contain = []; foreach ($this->_containments as $alias => $options) { if (!empty($options['instance'])) { - $contain = (array)$this->_containments; + $contain = $this->_containments; break; } $contain[$alias] = $this->_normalizeContain( @@ -336,7 +336,7 @@ protected function _reformatContain(array $associations, array $original): array { $result = $original; - foreach ((array)$associations as $table => $options) { + foreach ($associations as $table => $options) { $pointer = &$result; if (is_int($table)) { $table = $options; @@ -388,6 +388,7 @@ protected function _reformatContain(array $associations, array $original): array } if (!is_array($options)) { + /** @psalm-suppress InvalidArrayOffset */ $options = [$options => []]; } diff --git a/Marshaller.php b/Marshaller.php index 5f0e0cfb..8afbe1d4 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -314,7 +314,7 @@ protected function _marshalAssociation(Association $assoc, $value, array $option $types = [Association::ONE_TO_ONE, Association::MANY_TO_ONE]; $type = $assoc->type(); if (in_array($type, $types, true)) { - return $marshaller->one($value, (array)$options); + return $marshaller->one($value, $options); } if ($type === Association::ONE_TO_MANY || $type === Association::MANY_TO_MANY) { $hasIds = array_key_exists('_ids', $value); @@ -328,10 +328,10 @@ protected function _marshalAssociation(Association $assoc, $value, array $option } } if ($type === Association::MANY_TO_MANY) { - return $marshaller->_belongsToMany($assoc, $value, (array)$options); + return $marshaller->_belongsToMany($assoc, $value, $options); } - return $marshaller->many($value, (array)$options); + return $marshaller->many($value, $options); } /** @@ -748,11 +748,11 @@ protected function _mergeAssociation($original, Association $assoc, $value, arra $type = $assoc->type(); if (in_array($type, $types, true)) { /** @psalm-suppress PossiblyInvalidArgument */ - return $marshaller->merge($original, $value, (array)$options); + return $marshaller->merge($original, $value, $options); } if ($type === Association::MANY_TO_MANY) { /** @psalm-suppress PossiblyInvalidArgument */ - return $marshaller->_mergeBelongsToMany($original, $assoc, $value, (array)$options); + return $marshaller->_mergeBelongsToMany($original, $assoc, $value, $options); } if ($type === Association::ONE_TO_MANY) { @@ -767,7 +767,7 @@ protected function _mergeAssociation($original, Association $assoc, $value, arra } /** @psalm-suppress PossiblyInvalidArgument */ - return $marshaller->mergeMany($original, $value, (array)$options); + return $marshaller->mergeMany($original, $value, $options); } /** diff --git a/SaveOptionsBuilder.php b/SaveOptionsBuilder.php index 5553e4e4..12270d58 100644 --- a/SaveOptionsBuilder.php +++ b/SaveOptionsBuilder.php @@ -141,7 +141,7 @@ protected function _checkAssociation(Table $table, string $association): void */ public function guard(bool $guard) { - $this->_options['guard'] = (bool)$guard; + $this->_options['guard'] = $guard; return $this; } @@ -168,7 +168,7 @@ public function validate(string $validate) */ public function checkExisting(bool $checkExisting) { - $this->_options['checkExisting'] = (bool)$checkExisting; + $this->_options['checkExisting'] = $checkExisting; return $this; } @@ -181,7 +181,7 @@ public function checkExisting(bool $checkExisting) */ public function checkRules(bool $checkRules) { - $this->_options['checkRules'] = (bool)$checkRules; + $this->_options['checkRules'] = $checkRules; return $this; } @@ -194,7 +194,7 @@ public function checkRules(bool $checkRules) */ public function atomic(bool $atomic) { - $this->_options['atomic'] = (bool)$atomic; + $this->_options['atomic'] = $atomic; return $this; } diff --git a/Table.php b/Table.php index 80ec2033..28d0042a 100644 --- a/Table.php +++ b/Table.php @@ -420,7 +420,7 @@ public function getAlias(): string if ($this->_alias === null) { $alias = namespaceSplit(static::class); $alias = substr(end($alias), 0, -5) ?: $this->_table; - $this->_alias = (string)$alias; + $this->_alias = $alias; } return $this->_alias; @@ -650,7 +650,7 @@ public function setPrimaryKey($key) public function getPrimaryKey() { if ($this->_primaryKey === null) { - $key = (array)$this->getSchema()->getPrimaryKey(); + $key = $this->getSchema()->getPrimaryKey(); if (count($key) === 1) { $key = $key[0]; } @@ -2091,7 +2091,7 @@ protected function _insert(EntityInterface $entity, array $data) */ protected function _newId(array $primary) { - if (!$primary || count((array)$primary) > 1) { + if (!$primary || count($primary) > 1) { return null; } /** @var string $typeName */ @@ -2451,7 +2451,7 @@ protected function _processDelete(EntityInterface $entity, ArrayObject $options) } $query = $this->query(); - $conditions = (array)$entity->extract($primaryKey); + $conditions = $entity->extract($primaryKey); $statement = $query->delete() ->where($conditions) ->execute(); From 78fa4c0d38befa0cf24cd681e21bbbea072aed2b Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 4 Dec 2020 18:26:12 +0530 Subject: [PATCH 1598/2059] Remove unneeded inline @var annoations. --- EagerLoader.php | 1 - Marshaller.php | 1 - Query.php | 7 ------- ResultSet.php | 1 - Table.php | 1 - 5 files changed, 11 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index 9c4ac531..671acfa3 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -628,7 +628,6 @@ protected function _resolveJoins(array $associations, array $matching = []): arr */ public function loadExternal(Query $query, StatementInterface $statement): StatementInterface { - /** @var \Cake\ORM\Table $table */ $table = $query->getRepository(); $external = $this->externalAssociations($table); if (empty($external)) { diff --git a/Marshaller.php b/Marshaller.php index 8afbe1d4..465f823b 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -181,7 +181,6 @@ public function one(array $data, array $options = []): EntityInterface $primaryKey = (array)$this->_table->getPrimaryKey(); $entityClass = $this->_table->getEntityClass(); - /** @var \Cake\Datasource\EntityInterface $entity */ $entity = new $entityClass(); $entity->setSource($this->_table->getRegistryAlias()); diff --git a/Query.php b/Query.php index 9937f3f9..840ee107 100644 --- a/Query.php +++ b/Query.php @@ -1144,7 +1144,6 @@ protected function _transformQuery(): void return; } - /** @var \Cake\ORM\Table $repository */ $repository = $this->getRepository(); if (empty($this->_parts['from'])) { @@ -1166,7 +1165,6 @@ protected function _addDefaultFields(): void $select = $this->clause('select'); $this->_hasFields = true; - /** @var \Cake\ORM\Table $repository */ $repository = $this->getRepository(); if (!count($select) || $this->_autoFields === true) { @@ -1217,7 +1215,6 @@ protected function _addDefaultSelectTypes(): void */ public function find(string $finder, array $options = []) { - /** @var \Cake\ORM\Table $table */ $table = $this->getRepository(); /** @psalm-suppress LessSpecificReturnStatement */ @@ -1249,7 +1246,6 @@ protected function _dirty(): void public function update($table = null) { if (!$table) { - /** @var \Cake\ORM\Table $repository */ $repository = $this->getRepository(); $table = $repository->getTable(); } @@ -1268,7 +1264,6 @@ public function update($table = null) */ public function delete(?string $table = null) { - /** @var \Cake\ORM\Table $repository */ $repository = $this->getRepository(); $this->from([$repository->getAlias() => $repository->getTable()]); @@ -1291,7 +1286,6 @@ public function delete(?string $table = null) */ public function insert(array $columns, array $types = []) { - /** @var \Cake\ORM\Table $repository */ $repository = $this->getRepository(); $table = $repository->getTable(); $this->into($table); @@ -1416,7 +1410,6 @@ protected function _decorateResults(Traversable $result): ResultSetInterface if (!($result instanceof ResultSet) && $this->isBufferedResultsEnabled()) { $class = $this->_decoratorClass(); - /** @var \Cake\Datasource\ResultSetInterface $result */ $result = new $class($result->buffered()); } diff --git a/ResultSet.php b/ResultSet.php index 1ef11de6..147322b1 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -159,7 +159,6 @@ class ResultSet implements ResultSetInterface */ public function __construct(Query $query, StatementInterface $statement) { - /** @var \Cake\ORM\Table $repository */ $repository = $query->getRepository(); $this->_statement = $statement; $this->_driver = $query->getConnection()->getDriver(); diff --git a/Table.php b/Table.php index 28d0042a..88db55f6 100644 --- a/Table.php +++ b/Table.php @@ -860,7 +860,6 @@ public function getBehavior(string $name): Behavior )); } - /** @var \Cake\ORM\Behavior $behavior */ $behavior = $this->_behaviors->get($name); return $behavior; From 61c350d9048aa869d83559cda7b62f55ed50fd85 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 4 Dec 2020 19:26:49 +0530 Subject: [PATCH 1599/2059] Update docblocks and checks for nullable properties. --- Association.php | 2 +- Association/BelongsToMany.php | 2 +- Behavior/TimestampBehavior.php | 2 +- Table.php | 10 +++++----- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Association.php b/Association.php index 41bcf486..baa366dc 100644 --- a/Association.php +++ b/Association.php @@ -106,7 +106,7 @@ abstract class Association /** * The field name in the owning side table that is used to match with the foreignKey * - * @var string|string[] + * @var string|string[]|null */ protected $_bindingKey; diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index cfe7c263..b17be3b1 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -106,7 +106,7 @@ class BelongsToMany extends Association /** * The name of the field representing the foreign key to the target table * - * @var string|string[] + * @var string|string[]|null */ protected $_targetForeignKey; diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index e053cea7..d2b6f7f8 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -64,7 +64,7 @@ class TimestampBehavior extends Behavior /** * Current timestamp * - * @var \Cake\I18n\Time + * @var \Cake\I18n\Time|null */ protected $_ts; diff --git a/Table.php b/Table.php index 88db55f6..3f3e93cc 100644 --- a/Table.php +++ b/Table.php @@ -185,7 +185,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc /** * Name of the table as it can be found in the database * - * @var string + * @var string|null */ protected $_table; @@ -193,7 +193,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc * Human name giving to this particular instance. Multiple objects representing * the same database table can exist by using different aliases. * - * @var string + * @var string|null */ protected $_alias; @@ -214,14 +214,14 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc /** * The name of the field that represents the primary key in the table * - * @var string|string[] + * @var string|string[]|null */ protected $_primaryKey; /** * The name of the field that represents a human readable representation of a row * - * @var string|string[] + * @var string|string[]|null */ protected $_displayField; @@ -419,7 +419,7 @@ public function getAlias(): string { if ($this->_alias === null) { $alias = namespaceSplit(static::class); - $alias = substr(end($alias), 0, -5) ?: $this->_table; + $alias = substr(end($alias), 0, -5) ?: $this->getTable(); $this->_alias = $alias; } From a1fd59c8ac1bb85afc5bfea0055d3f7827a1d7e2 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 12 Dec 2020 19:15:35 +0530 Subject: [PATCH 1600/2059] Add 'valueSeparator' option for find('list'). --- Table.php | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index 3f3e93cc..3e4474e2 100644 --- a/Table.php +++ b/Table.php @@ -1312,6 +1312,25 @@ public function findAll(Query $query, array $options): Query * ]); * ``` * + * The `valueField` can also be an array, in which case you can also specify + * the `valueSeparator` option to control how the values will be concatinated: + * + * ``` + * $table->find('list', [ + * 'valueField' => ['first_name', 'last_name'], + * 'valueSeparator' => ' | ', + * ]); + * + * + * The results of this finder will be in the following form: + * + * ``` + * [ + * 1 => 'John | Doe', + * 2 => 'Steve | Smith' + * ] + * ``` + * * Results can be put together in bigger groups when they share a property, you * can customize the property to use for grouping by setting `groupField`: * @@ -1345,6 +1364,7 @@ public function findList(Query $query, array $options): Query 'keyField' => $this->getPrimaryKey(), 'valueField' => $this->getDisplayField(), 'groupField' => null, + 'valueSeparator' => ';', ]; if ( @@ -1445,13 +1465,14 @@ protected function _setFieldMatchers(array $options, array $keys): array } $fields = $options[$field]; - $options[$field] = function ($row) use ($fields) { + $glue = in_array($field, ['keyField', 'parentField'], true) ? ';' : $options['valueSeparator']; + $options[$field] = function ($row) use ($fields, $glue) { $matches = []; foreach ($fields as $field) { $matches[] = $row[$field]; } - return implode(';', $matches); + return implode($glue, $matches); }; } From 125475b2a2b189a8400cb86a02160b8617c23685 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 17 Dec 2020 22:56:33 -0500 Subject: [PATCH 1601/2059] Fix replacing belongstomany junction records with complex types When the ORM was attempting to clear the junction records for a BelongsToMany junction table there were a few related problems causing it to fail: 1. The query used to locate the junction records did not have the correct types set for where conditions. 2. If the junction table has a surrogate primary key that has a different datatype than the association target table, the ORM would use the target table type. In the committed fixtures this means that the `id` column on the junction table would be typed as `binaryuuid` instead of `integer`. These factors combined together would result in either junction rows not being deleted. Thankfully the query to find junction records would always fail preventing more serious data corruption issues. These problems can be solved by applying conditions *after* adding the junction join information and type mappings, and also setting junction table types for our aliased entity query. I optimized object traversal inside the `_appendJunctionJoin` method as there were some redundant calls being made. Fixes #15212 --- Association/BelongsToMany.php | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 37056fae..7f2394b1 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1091,8 +1091,9 @@ public function find($type = null, array $options = []): Query */ protected function _appendJunctionJoin(Query $query, ?array $conditions = null): Query { + $junctionTable = $this->junction(); if ($conditions === null) { - $belongsTo = $this->junction()->getAssociation($this->getTarget()->getAlias()); + $belongsTo = $junctionTable->getAssociation($this->getTarget()->getAlias()); $conditions = $belongsTo->_joinCondition([ 'foreignKey' => $this->getTargetForeignKey(), ]); @@ -1104,15 +1105,14 @@ protected function _appendJunctionJoin(Query $query, ?array $conditions = null): $joins = $query->clause('join'); $matching = [ $name => [ - 'table' => $this->junction()->getTable(), + 'table' => $junctionTable->getTable(), 'conditions' => $conditions, 'type' => Query::JOIN_TYPE_INNER, ], ]; - $assoc = $this->getTarget()->getAssociation($name); $query - ->addDefaultTypes($assoc->getTarget()) + ->addDefaultTypes($junctionTable) ->join($matching + $joins, [], true); return $query; @@ -1195,14 +1195,21 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { // Find existing rows so that we can diff with new entities. // Only hydrate primary/foreign key columns to save time. - $existing = $this->find() + // Attach joins first to ensure where conditions have correct + // column types set. + $existing = $this->_appendJunctionJoin($this->find()) ->select($keys) ->where(array_combine($prefixedForeignKey, $primaryValue)); - $existing = $this->_appendJunctionJoin($existing); + + // Because we're aliasing key fields to look like they are not + // from joined table we need to overwrite the type map as the junction + // table can have a surrogate primary key that doesn't share a type + // with the target table. + $junctionTypes = array_intersect_key($junction->getSchema()->typeMap(), $keys); + $existing->getSelectTypeMap()->setTypes($junctionTypes); $jointEntities = $this->_collectJointEntities($sourceEntity, $targetEntities); $inserts = $this->_diffLinks($existing, $jointEntities, $targetEntities, $options); - if ($inserts === false) { return false; } From 60092059cae1632aa9b4c05e95c9023f07569be4 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Thu, 17 Dec 2020 18:49:46 -0600 Subject: [PATCH 1602/2059] Add allowUniqueNulls to IsUnique rule check --- Rule/IsUnique.php | 24 ++++++++++++++++++++++-- RulesChecker.php | 12 ++++++++++-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index d36de6bd..24cfeae0 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -30,14 +30,29 @@ class IsUnique */ protected $_fields; + /** + * The unique check options + * + * @var array + */ + protected $_options = [ + 'allowMultipleNulls' => false, + ]; + /** * Constructor. * + * ### Options + * + * - `allowMultipleNulls` Allows any field to have multiple null values. Defaults to false. + * * @param string[] $fields The list of fields to check uniqueness for + * @param array $options The options for unique checks. */ - public function __construct(array $fields) + public function __construct(array $fields, array $options = []) { $this->_fields = $fields; + $this->_options = $options + $this->_options; } /** @@ -54,8 +69,13 @@ public function __invoke(EntityInterface $entity, array $options): bool return true; } + $fields = $entity->extract($this->_fields); + if ($this->_options['allowMultipleNulls'] && array_filter($fields, 'is_null')) { + return true; + } + $alias = $options['repository']->getAlias(); - $conditions = $this->_alias($alias, $entity->extract($this->_fields)); + $conditions = $this->_alias($alias, $fields); if ($entity->isNew() === false) { $keys = (array)$options['repository']->getPrimaryKey(); $keys = $this->_alias($alias, $entity->extract($keys)); diff --git a/RulesChecker.php b/RulesChecker.php index bb12b95d..56184322 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -37,12 +37,16 @@ class RulesChecker extends BaseRulesChecker * Returns a callable that can be used as a rule for checking the uniqueness of a value * in the table. * - * ### Example: + * ### Example * * ``` * $rules->add($rules->isUnique(['email'], 'The email should be unique')); * ``` * + * ### Options + * + * - `allowMultipleNulls` Allows any field to have multiple null values. Defaults to false. + * * @param string[] $fields The list of fields to check for uniqueness. * @param string|array|null $message The error message to show in case the rule does not pass. Can * also be an array of options. When an array, the 'message' key can be used to provide a message. @@ -50,6 +54,10 @@ class RulesChecker extends BaseRulesChecker */ public function isUnique(array $fields, $message = null): RuleInvoker { + $options = is_array($message) ? $message : ['message' => $message]; + $message = $options['message'] ?? null; + unset($options['message']); + if (!$message) { if ($this->_useI18n) { $message = __d('cake', 'This value is already in use'); @@ -60,7 +68,7 @@ public function isUnique(array $fields, $message = null): RuleInvoker $errorField = current($fields); - return $this->_addError(new IsUnique($fields), '_isUnique', compact('errorField', 'message')); + return $this->_addError(new IsUnique($fields, $options), '_isUnique', compact('errorField', 'message')); } /** From f460ed5956b2df40520d770213ad22f3edf77c13 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 28 Dec 2020 21:05:46 -0500 Subject: [PATCH 1603/2059] Add missing LICENSE file to console package Also update the date in each sub-split. Fixes #15252 --- LICENSE.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.txt b/LICENSE.txt index 0c4b7932..b938c9e8 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,7 +1,7 @@ The MIT License (MIT) CakePHP(tm) : The Rapid Development PHP Framework (https://cakephp.org) -Copyright (c) 2005-2016, Cake Software Foundation, Inc. (https://cakefoundation.org) +Copyright (c) 2005-2020, Cake Software Foundation, Inc. (https://cakefoundation.org) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 3891088a52954724b7c1eeab90b1f7ad24a6520e Mon Sep 17 00:00:00 2001 From: ndm2 Date: Sat, 2 Jan 2021 20:10:54 +0100 Subject: [PATCH 1604/2059] Make association queries inherit the results casting mode. This brings the behavior in line with hydration, which is already being inherited accordingly. --- Association/Loader/SelectLoader.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index f3be34f5..f16856c2 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -178,6 +178,11 @@ protected function _buildQuery(array $options): Query ->where($options['conditions']) ->eagerLoaded(true) ->enableHydration($options['query']->isHydrationEnabled()); + if ($options['query']->isResultsCastingEnabled()) { + $fetchQuery->enableResultsCasting(); + } else { + $fetchQuery->disableResultsCasting(); + } if ($useSubquery) { $filter = $this->_buildSubquery($options['query']); From 2ea90007628af75b303a873753a017a2781daafa Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 3 Jan 2021 22:24:36 +0530 Subject: [PATCH 1605/2059] Allow auto selection of multi column display field. --- Table.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Table.php b/Table.php index 3e4474e2..42a890ab 100644 --- a/Table.php +++ b/Table.php @@ -682,8 +682,7 @@ public function getDisplayField() { if ($this->_displayField === null) { $schema = $this->getSchema(); - $primary = (array)$this->getPrimaryKey(); - $this->_displayField = array_shift($primary); + $this->_displayField = $this->getPrimaryKey(); if ($schema->getColumn('title')) { $this->_displayField = 'title'; } From e3919c3cf20553c5264e66e1f3576bb64453e3fb Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 3 Jan 2021 22:28:43 +0530 Subject: [PATCH 1606/2059] Allow auto selection of "label" column as display field. --- Table.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Table.php b/Table.php index 42a890ab..87ded74c 100644 --- a/Table.php +++ b/Table.php @@ -683,11 +683,11 @@ public function getDisplayField() if ($this->_displayField === null) { $schema = $this->getSchema(); $this->_displayField = $this->getPrimaryKey(); - if ($schema->getColumn('title')) { - $this->_displayField = 'title'; - } - if ($schema->getColumn('name')) { - $this->_displayField = 'name'; + foreach (['title', 'name', 'label'] as $field) { + if ($schema->hasColumn($field)) { + $this->_displayField = $field; + break; + } } } From ad03ad10f651aa4370f76b864dcbf569ec2ec14a Mon Sep 17 00:00:00 2001 From: Joseph Shanak Date: Fri, 8 Jan 2021 11:38:14 -0600 Subject: [PATCH 1607/2059] Handle missing translations when saving. Fixes #15272 --- Behavior/Translate/TranslateStrategyTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/Translate/TranslateStrategyTrait.php b/Behavior/Translate/TranslateStrategyTrait.php index 42ce87a4..0ddb2e38 100644 --- a/Behavior/Translate/TranslateStrategyTrait.php +++ b/Behavior/Translate/TranslateStrategyTrait.php @@ -111,7 +111,7 @@ protected function unsetEmptyFields($entity) foreach ($translations as $locale => $translation) { $fields = $translation->extract($this->_config['fields'], false); foreach ($fields as $field => $value) { - if (strlen($value) === 0) { + if ($value === null || strlen($value) === 0) { $translation->unset($field); } } From 97ce2afdba3825e8af4d9010af083c2bf2e03399 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 9 Jan 2021 12:00:50 +0530 Subject: [PATCH 1608/2059] Micro optimization. --- Behavior/Translate/TranslateStrategyTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/Translate/TranslateStrategyTrait.php b/Behavior/Translate/TranslateStrategyTrait.php index 0ddb2e38..fceda8fb 100644 --- a/Behavior/Translate/TranslateStrategyTrait.php +++ b/Behavior/Translate/TranslateStrategyTrait.php @@ -111,7 +111,7 @@ protected function unsetEmptyFields($entity) foreach ($translations as $locale => $translation) { $fields = $translation->extract($this->_config['fields'], false); foreach ($fields as $field => $value) { - if ($value === null || strlen($value) === 0) { + if ($value === null || $value === '') { $translation->unset($field); } } From 422079e65334bddfe9197a0b6afb1f74c2206e6c Mon Sep 17 00:00:00 2001 From: ndm2 Date: Mon, 18 Jan 2021 00:14:39 +0100 Subject: [PATCH 1609/2059] Add joins when `onlyTranslated` or `filterByCurrentLocale` is enabled. These options indicate the possible intent of applying filtering solely based on translations existing, hence the join should always apply when they are enabled. --- Behavior/Translate/ShadowTableStrategy.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index efcfecee..9e1cad7b 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -135,7 +135,10 @@ public function beforeFind(EventInterface $event, Query $query, ArrayObject $opt $fieldsAdded = $this->addFieldsToQuery($query, $config); $orderByTranslatedField = $this->iterateClause($query, 'order', $config); - $filteredByTranslatedField = $this->traverseClause($query, 'where', $config); + $filteredByTranslatedField = + $this->traverseClause($query, 'where', $config) || + $config['onlyTranslated'] || + ($options['filterByCurrentLocale'] ?? null); if (!$fieldsAdded && !$orderByTranslatedField && !$filteredByTranslatedField) { return; From 7e03abdd8b9a6b296568ee51b35ffba15aa77934 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 4 Feb 2021 16:56:34 +0530 Subject: [PATCH 1610/2059] Add `@see` tags to allow easy navigation to related marshaller methods. --- Table.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Table.php b/Table.php index 3e4474e2..25a85d2b 100644 --- a/Table.php +++ b/Table.php @@ -2739,6 +2739,7 @@ public function newEmptyEntity(): EntityInterface * @param array $data The data to build an entity with. * @param array $options A list of options for the object hydration. * @return \Cake\Datasource\EntityInterface + * @see \Cake\ORM\Marshaller::one() */ public function newEntity(array $data, array $options = []): EntityInterface { @@ -2841,6 +2842,7 @@ public function newEntities(array $data, array $options = []): array * @param array $data key value list of fields to be merged into the entity * @param array $options A list of options for the object hydration. * @return \Cake\Datasource\EntityInterface + * @see \Cake\ORM\Marshaller::merge() */ public function patchEntity(EntityInterface $entity, array $data, array $options = []): EntityInterface { From 9534dba8ba7aba385d0044d7a274055b6f1405e5 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Thu, 25 Feb 2021 09:56:42 -0600 Subject: [PATCH 1611/2059] Remove invalid options from attachTo --- Association.php | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/Association.php b/Association.php index 218cade8..fd93b412 100644 --- a/Association.php +++ b/Association.php @@ -696,8 +696,6 @@ protected function _options(array $options): void * - conditions: array with a list of conditions to filter the join with, this * will be merged with any conditions originally configured for this association * - fields: a list of fields in the target table to include in the result - * - type: The type of join to be used (e.g. INNER) - * the records found on this association * - aliasPath: A dot separated string representing the path of association names * followed from the passed query main table to this association. * - propertyPath: A dot separated string representing the path of association @@ -710,21 +708,19 @@ protected function _options(array $options): void * @param \Cake\ORM\Query $query the query to be altered to include the target table data * @param array $options Any extra options or overrides to be taken in account * @return void - * @throws \RuntimeException if the query builder passed does not return a query - * object + * @throws \RuntimeException Unable to build the query or associations. */ public function attachTo(Query $query, array $options = []): void { $target = $this->getTarget(); - $joinType = empty($options['joinType']) ? $this->getJoinType() : $options['joinType']; $table = $target->getTable(); $options += [ 'includeFields' => true, 'foreignKey' => $this->getForeignKey(), 'conditions' => [], + 'joinType' => $this->getJoinType(), 'fields' => [], - 'type' => $joinType, 'table' => $table, 'finder' => $this->getFinder(), ]; @@ -764,9 +760,11 @@ public function attachTo(Query $query, array $options = []): void $dummy->where($options['conditions']); $this->_dispatchBeforeFind($dummy); - $joinOptions = ['table' => 1, 'conditions' => 1, 'type' => 1]; - $options['conditions'] = $dummy->clause('where'); - $query->join([$this->_name => array_intersect_key($options, $joinOptions)]); + $query->join([$this->_name => [ + 'table' => $options['table'], + 'conditions' => $dummy->clause('where'), + 'type' => $options['joinType'], + ]]); $this->_appendFields($query, $dummy, $options); $this->_formatAssociationResults($query, $dummy, $options); From 9515304ffa4304b6388c720b02045c6d5bfad3b7 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 27 Feb 2021 14:48:51 +0530 Subject: [PATCH 1612/2059] Update psalm and phpstan's version. --- Locator/LocatorAwareTrait.php | 2 +- Locator/TableLocator.php | 2 +- Marshaller.php | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index ae257fa7..3c17d437 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -51,7 +51,7 @@ public function setTableLocator(LocatorInterface $tableLocator) public function getTableLocator(): LocatorInterface { if ($this->_tableLocator === null) { - /** @var \Cake\ORM\Locator\LocatorInterface $this->_tableLocator */ + /** @psalm-suppress InvalidPropertyAssignmentValue */ $this->_tableLocator = FactoryLocator::get('Table'); } diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index d469950a..a89d1401 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -48,7 +48,7 @@ class TableLocator extends AbstractLocator implements LocatorInterface /** * Instances that belong to the registry. * - * @var \Cake\ORM\Table[] + * @var array */ protected $instances = []; diff --git a/Marshaller.php b/Marshaller.php index 465f823b..1a07a2fd 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -669,8 +669,9 @@ public function mergeMany(iterable $entities, array $data, array $options = []): }) ->toArray(); + /** @psalm-suppress InvalidArrayOffset */ $new = $indexed[null] ?? []; - /** @psalm-suppress PossiblyNullArrayOffset */ + /** @psalm-suppress InvalidArrayOffset */ unset($indexed[null]); $output = []; From 22c66fe70ace0e11f8b9a8fa4c205e891e800aa8 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Sun, 28 Feb 2021 07:25:53 -0600 Subject: [PATCH 1613/2059] Simplfy disabling of target fields using fields => false --- Association.php | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/Association.php b/Association.php index fd93b412..6d760037 100644 --- a/Association.php +++ b/Association.php @@ -725,6 +725,12 @@ public function attachTo(Query $query, array $options = []): void 'finder' => $this->getFinder(), ]; + // This is set by joinWith to disable matching results + if ($options['fields'] === false) { + $options['fields'] = []; + $options['includeFields'] = false; + } + if (!empty($options['foreignKey'])) { $joinCondition = $this->_joinCondition($options); if ($joinCondition) { @@ -954,25 +960,17 @@ protected function _appendFields(Query $query, Query $surrogate, array $options) return; } - $fields = $surrogate->clause('select') ?: $options['fields']; - $target = $this->_targetTable; - $autoFields = $surrogate->isAutoFieldsEnabled(); + $fields = array_merge($surrogate->clause('select'), $options['fields']); - if (empty($fields) && !$autoFields) { - if ($options['includeFields'] && ($fields === null || $fields !== false)) { - $fields = $target->getSchema()->columns(); - } - } - - if ($autoFields === true) { - $fields = array_filter((array)$fields); - $fields = array_merge($fields, $target->getSchema()->columns()); + if ( + (empty($fields) && $options['includeFields']) || + $surrogate->isAutoFieldsEnabled() + ) { + $fields = array_merge($fields, $this->_targetTable->getSchema()->columns()); } - if ($fields) { - $query->select($query->aliasFields($fields, $this->_name)); - } - $query->addDefaultTypes($target); + $query->select($query->aliasFields($fields, $this->_name)); + $query->addDefaultTypes($this->_targetTable); } /** From bdab8fd0d437bdb965de2e82eafec211ed6a186a Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 28 Feb 2021 20:26:50 +0530 Subject: [PATCH 1614/2059] Use null coalescing operator where possible. --- Association/BelongsToMany.php | 9 ++------- Association/Loader/SelectLoader.php | 5 +---- AssociationCollection.php | 6 +----- Behavior/CounterCacheBehavior.php | 5 +---- Marshaller.php | 4 +--- ResultSet.php | 4 +--- Table.php | 16 ++++------------ 7 files changed, 11 insertions(+), 38 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 82196dbc..c9890d73 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -461,10 +461,7 @@ public function attachTo(Query $query, array $options = []): void $cond = $belongsTo->_joinCondition(['foreignKey' => $belongsTo->getForeignKey()]); $cond += $this->junctionConditions(); - $includeFields = null; - if (isset($options['includeFields'])) { - $includeFields = $options['includeFields']; - } + $includeFields = $options['includeFields'] ?? null; // Attach the junction table as well we need it to populate _joinData. $assoc = $this->_targetTable->getAssociation($junction->getAlias()); @@ -492,9 +489,7 @@ protected function _appendNotMatching(Query $query, array $options): void if (empty($options['negateMatch'])) { return; } - if (!isset($options['conditions'])) { - $options['conditions'] = []; - } + $options['conditions'] = $options['conditions'] ?? []; $junction = $this->junction(); $belongsTo = $junction->getAssociation($this->getSource()->getAlias()); $conds = $belongsTo->_joinCondition(['foreignKey' => $belongsTo->getForeignKey()]); diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index f16856c2..015a82c7 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -161,10 +161,7 @@ protected function _buildQuery(array $options): Query $filter = $options['keys']; $useSubquery = $options['strategy'] === Association::STRATEGY_SUBQUERY; $finder = $this->finder; - - if (!isset($options['fields'])) { - $options['fields'] = []; - } + $options['fields'] = $options['fields'] ?? []; /** @var \Cake\ORM\Query $query */ $query = $finder(); diff --git a/AssociationCollection.php b/AssociationCollection.php index 6a025dd3..5d3a5ca2 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -110,11 +110,7 @@ public function load(string $className, string $associated, array $options = []) */ public function get(string $alias): ?Association { - if (isset($this->_items[$alias])) { - return $this->_items[$alias]; - } - - return null; + return $this->_items[$alias] ?? null; } /** diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 68746fb0..22a8562f 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -301,10 +301,7 @@ protected function _getCount(array $config, array $conditions): int unset($config['finder']); } - if (!isset($config['conditions'])) { - $config['conditions'] = []; - } - $config['conditions'] = array_merge($conditions, $config['conditions']); + $config['conditions'] = array_merge($conditions, $config['conditions'] ?? []); $query = $this->_table->find($finder, $config); return $query->count(); diff --git a/Marshaller.php b/Marshaller.php index 465f823b..7a57614b 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -82,9 +82,7 @@ protected function _buildPropertyMap(array $data, array $options): array } // Map associations - if (!isset($options['associated'])) { - $options['associated'] = []; - } + $options['associated'] = $options['associated'] ?? []; $include = $this->_normalizeAssociations($options['associated']); foreach ($include as $key => $nested) { if (is_int($key) && is_scalar($nested)) { diff --git a/ResultSet.php b/ResultSet.php index 147322b1..ef1b755a 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -472,9 +472,7 @@ protected function _groupResult(array $row) // If the default table is not in the results, set // it to an empty array so that any contained // associations hydrate correctly. - if (!isset($results[$defaultAlias])) { - $results[$defaultAlias] = []; - } + $results[$defaultAlias] = $results[$defaultAlias] ?? []; unset($presentAliases[$defaultAlias]); diff --git a/Table.php b/Table.php index 79bd5280..1947317f 100644 --- a/Table.php +++ b/Table.php @@ -2742,9 +2742,7 @@ public function newEmptyEntity(): EntityInterface */ public function newEntity(array $data, array $options = []): EntityInterface { - if (!isset($options['associated'])) { - $options['associated'] = $this->_associations->keys(); - } + $options['associated'] = $options['associated'] ?? $this->_associations->keys(); $marshaller = $this->marshaller(); return $marshaller->one($data, $options); @@ -2784,9 +2782,7 @@ public function newEntity(array $data, array $options = []): EntityInterface */ public function newEntities(array $data, array $options = []): array { - if (!isset($options['associated'])) { - $options['associated'] = $this->_associations->keys(); - } + $options['associated'] = $options['associated'] ?? $this->_associations->keys(); $marshaller = $this->marshaller(); return $marshaller->many($data, $options); @@ -2845,9 +2841,7 @@ public function newEntities(array $data, array $options = []): array */ public function patchEntity(EntityInterface $entity, array $data, array $options = []): EntityInterface { - if (!isset($options['associated'])) { - $options['associated'] = $this->_associations->keys(); - } + $options['associated'] = $options['associated'] ?? $this->_associations->keys(); $marshaller = $this->marshaller(); return $marshaller->merge($entity, $data, $options); @@ -2886,9 +2880,7 @@ public function patchEntity(EntityInterface $entity, array $data, array $options */ public function patchEntities(iterable $entities, array $data, array $options = []): array { - if (!isset($options['associated'])) { - $options['associated'] = $this->_associations->keys(); - } + $options['associated'] = $options['associated'] ?? $this->_associations->keys(); $marshaller = $this->marshaller(); return $marshaller->mergeMany($entities, $data, $options); From 91bf63b76275c98ea84220a47e1591754ad8dcbb Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Tue, 2 Mar 2021 00:07:32 -0600 Subject: [PATCH 1615/2059] Simplify generating nested contains in setMatching() --- EagerLoader.php | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index 671acfa3..771fa396 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -230,41 +230,37 @@ public function isAutoFieldsEnabled(): bool * * ### Options * - * - 'joinType': INNER, OUTER, ... - * - 'fields': Fields to contain + * - `joinType`: INNER, OUTER, ... + * - `fields`: Fields to contain + * - `negateMatch`: Whether to add conditions negate match on target association * - * @param string $assoc A single association or a dot separated path of associations. + * @param string $path Dot separated association path * @param callable|null $builder the callback function to be used for setting extra * options to the filtering query * @param array $options Extra options for the association matching. * @return $this */ - public function setMatching(string $assoc, ?callable $builder = null, array $options = []) + public function setMatching(string $path, ?callable $builder = null, array $options = []) { if ($this->_matching === null) { $this->_matching = new static(); } - if (!isset($options['joinType'])) { - $options['joinType'] = Query::JOIN_TYPE_INNER; - } + $options += ['joinType' => Query::JOIN_TYPE_INNER]; + $sharedOptions = ['negateMatch' => false, 'matching' => true] + $options; - $assocs = explode('.', $assoc); - $last = array_pop($assocs); - $containments = []; - $pointer = &$containments; - $opts = ['matching' => true] + $options; - /** @psalm-suppress InvalidArrayOffset */ - unset($opts['negateMatch']); - - foreach ($assocs as $name) { - $pointer[$name] = $opts; - $pointer = &$pointer[$name]; + $contains = []; + $nested = &$contains; + foreach (explode('.', $path) as $association) { + // Add contain to parent contain using association name as key + $nested[$association] = $sharedOptions; + // Set to next nested level + $nested = &$nested[$association]; } - $pointer[$last] = ['queryBuilder' => $builder, 'matching' => true] + $options; - - $this->_matching->contain($containments); + // Add all options to target association contain which is the last in nested chain + $nested = ['matching' => true, 'queryBuilder' => $builder] + $options; + $this->_matching->contain($contains); return $this; } From 9aef2e51c7aaac02602c708e94a0418773344711 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Tue, 2 Mar 2021 00:53:03 -0600 Subject: [PATCH 1616/2059] Change to --- EagerLoader.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index 771fa396..159301bb 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -234,13 +234,13 @@ public function isAutoFieldsEnabled(): bool * - `fields`: Fields to contain * - `negateMatch`: Whether to add conditions negate match on target association * - * @param string $path Dot separated association path + * @param string $associationPath Dot separated association path, 'Name1.Name2.Name3' * @param callable|null $builder the callback function to be used for setting extra * options to the filtering query * @param array $options Extra options for the association matching. * @return $this */ - public function setMatching(string $path, ?callable $builder = null, array $options = []) + public function setMatching(string $associationPath, ?callable $builder = null, array $options = []) { if ($this->_matching === null) { $this->_matching = new static(); @@ -251,7 +251,7 @@ public function setMatching(string $path, ?callable $builder = null, array $opti $contains = []; $nested = &$contains; - foreach (explode('.', $path) as $association) { + foreach (explode('.', $associationPath) as $association) { // Add contain to parent contain using association name as key $nested[$association] = $sharedOptions; // Set to next nested level From 781b2b801e53896f11e0e43698f8c42f91f4c3f6 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Thu, 4 Mar 2021 14:48:15 -0600 Subject: [PATCH 1617/2059] Detect infinite recursion when initializing Table --- Locator/TableLocator.php | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index a89d1401..bc8a347e 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -52,6 +52,13 @@ class TableLocator extends AbstractLocator implements LocatorInterface */ protected $instances = []; + /** + * List of Table classes being initialized. + * + * @var array + */ + protected $initializing = []; + /** * Contains a list of Table objects that were created out of the * built-in Table class. The list is indexed by table alias @@ -201,8 +208,13 @@ public function getConfig(?string $alias = null): array */ public function get(string $alias, array $options = []): Table { - /** @var \Cake\ORM\Table */ - return parent::get($alias, $options); + /** @var \Cake\ORM\Table $table */ + $table = parent::get($alias, $options); + + $className = get_class($table); + unset($this->initializing[$className]); + + return $table; } /** @@ -226,6 +238,10 @@ protected function createInstance(string $alias, array $options) $allowFallbackClass = $options['allowFallbackClass'] ?? $this->allowFallbackClass; $className = $this->_getClassName($alias, $options); if ($className) { + if (isset($this->initializing[$className])) { + throw new RuntimeException('Infinite recursion. Cannot call get() on Table that is being constructed.'); + } + $this->initializing[$className] = true; $options['className'] = $className; } elseif ($allowFallbackClass) { if (empty($options['className'])) { @@ -329,6 +345,7 @@ public function clear(): void { parent::clear(); + $this->initializing = []; $this->_fallbacked = []; $this->_config = []; } From 4a9a7d72ad6a626cdd74f203ba8be6a535e27c97 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Fri, 5 Mar 2021 20:31:29 -0600 Subject: [PATCH 1618/2059] Use passed-in class name when clearing initializing list --- Locator/TableLocator.php | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index bc8a347e..4d4977b8 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -208,13 +208,8 @@ public function getConfig(?string $alias = null): array */ public function get(string $alias, array $options = []): Table { - /** @var \Cake\ORM\Table $table */ - $table = parent::get($alias, $options); - - $className = get_class($table); - unset($this->initializing[$className]); - - return $table; + /** @var \Cake\ORM\Table */ + return parent::get($alias, $options); } /** @@ -283,6 +278,9 @@ protected function createInstance(string $alias, array $options) $this->_fallbacked[$alias] = $instance; } + // clear initializing flag after construction complete + unset($this->initializing[$options['className']]); + return $instance; } From 47988f3cfe3c9d5ab5a2c5188db55e67b29546cc Mon Sep 17 00:00:00 2001 From: othercorey Date: Tue, 9 Mar 2021 02:01:01 -0600 Subject: [PATCH 1619/2059] Revert "Use passed-in class name when clearing initializing list" --- Locator/TableLocator.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 4d4977b8..bc8a347e 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -208,8 +208,13 @@ public function getConfig(?string $alias = null): array */ public function get(string $alias, array $options = []): Table { - /** @var \Cake\ORM\Table */ - return parent::get($alias, $options); + /** @var \Cake\ORM\Table $table */ + $table = parent::get($alias, $options); + + $className = get_class($table); + unset($this->initializing[$className]); + + return $table; } /** @@ -278,9 +283,6 @@ protected function createInstance(string $alias, array $options) $this->_fallbacked[$alias] = $instance; } - // clear initializing flag after construction complete - unset($this->initializing[$options['className']]); - return $instance; } From 0b525366411e47f32b797dcdc064f57b9f4407ac Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Tue, 9 Mar 2021 02:08:14 -0600 Subject: [PATCH 1620/2059] Revert "Detect infinite recursion when initializing Table" This reverts commit 4068cb88d82c182a81556fbc40d2a2e7c51d7e28. --- Locator/TableLocator.php | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index bc8a347e..a89d1401 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -52,13 +52,6 @@ class TableLocator extends AbstractLocator implements LocatorInterface */ protected $instances = []; - /** - * List of Table classes being initialized. - * - * @var array - */ - protected $initializing = []; - /** * Contains a list of Table objects that were created out of the * built-in Table class. The list is indexed by table alias @@ -208,13 +201,8 @@ public function getConfig(?string $alias = null): array */ public function get(string $alias, array $options = []): Table { - /** @var \Cake\ORM\Table $table */ - $table = parent::get($alias, $options); - - $className = get_class($table); - unset($this->initializing[$className]); - - return $table; + /** @var \Cake\ORM\Table */ + return parent::get($alias, $options); } /** @@ -238,10 +226,6 @@ protected function createInstance(string $alias, array $options) $allowFallbackClass = $options['allowFallbackClass'] ?? $this->allowFallbackClass; $className = $this->_getClassName($alias, $options); if ($className) { - if (isset($this->initializing[$className])) { - throw new RuntimeException('Infinite recursion. Cannot call get() on Table that is being constructed.'); - } - $this->initializing[$className] = true; $options['className'] = $className; } elseif ($allowFallbackClass) { if (empty($options['className'])) { @@ -345,7 +329,6 @@ public function clear(): void { parent::clear(); - $this->initializing = []; $this->_fallbacked = []; $this->_config = []; } From e76611996a8ba85102b24e3712e6a1a70554ee3e Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 29 Mar 2021 22:21:56 +0530 Subject: [PATCH 1621/2059] Bump up psalm to 4.7. --- Association/BelongsToMany.php | 1 + Marshaller.php | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 82196dbc..ee745eda 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -937,6 +937,7 @@ function () use ($sourceEntity, $targetEntities, $options): void { return true; } + /** @var \SplObjectStorage<\Cake\Datasource\EntityInterface, null> $storage*/ $storage = new SplObjectStorage(); foreach ($targetEntities as $e) { $storage->attach($e); diff --git a/Marshaller.php b/Marshaller.php index 1a07a2fd..d27d2728 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -327,6 +327,7 @@ protected function _marshalAssociation(Association $assoc, $value, array $option } } if ($type === Association::MANY_TO_MANY) { + /** @psalm-suppress ArgumentTypeCoercion */ return $marshaller->_belongsToMany($assoc, $value, $options); } @@ -747,11 +748,11 @@ protected function _mergeAssociation($original, Association $assoc, $value, arra $types = [Association::ONE_TO_ONE, Association::MANY_TO_ONE]; $type = $assoc->type(); if (in_array($type, $types, true)) { - /** @psalm-suppress PossiblyInvalidArgument */ + /** @psalm-suppress PossiblyInvalidArgument, ArgumentTypeCoercion */ return $marshaller->merge($original, $value, $options); } if ($type === Association::MANY_TO_MANY) { - /** @psalm-suppress PossiblyInvalidArgument */ + /** @psalm-suppress PossiblyInvalidArgument, ArgumentTypeCoercion */ return $marshaller->_mergeBelongsToMany($original, $assoc, $value, $options); } From dbed2bdf1b8da09c4ad83574c624219c40e3ce42 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Sat, 10 Apr 2021 16:44:26 -0500 Subject: [PATCH 1622/2059] Simplify TreeBehavior::recover() --- Behavior/TreeBehavior.php | 48 +++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index af4f3282..9be7f99b 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -828,47 +828,41 @@ public function recover(): void /** * Recursive method used to recover a single level of the tree * - * @param int $counter The Last left column value that was assigned + * @param int $lftRght The starting lft/rght value * @param mixed $parentId the parent id of the level to be recovered * @param int $level Node level - * @return int The next value to use for the left column + * @return int The next lftRght value */ - protected function _recoverTree(int $counter = 0, $parentId = null, $level = -1): int + protected function _recoverTree(int $lftRght = 1, $parentId = null, $level = 0): int { $config = $this->getConfig(); [$parent, $left, $right] = [$config['parent'], $config['left'], $config['right']]; $primaryKey = $this->_getPrimaryKey(); - $aliasedPrimaryKey = $this->_table->aliasField($primaryKey); - $order = $config['recoverOrder'] ?: $aliasedPrimaryKey; + $order = $config['recoverOrder'] ?: $primaryKey; - $query = $this->_scope($this->_table->query()) - ->select([$aliasedPrimaryKey]) - ->where([$this->_table->aliasField($parent) . ' IS' => $parentId]) + $nodes = $this->_scope($this->_table->query()) + ->select($primaryKey) + ->where([$parent . ' IS' => $parentId]) ->order($order) - ->disableHydration(); + ->disableHydration() + ->all(); - $leftCounter = $counter; - $nextLevel = $level + 1; - foreach ($query as $row) { - $counter++; - $counter = $this->_recoverTree($counter, $row[$primaryKey], $nextLevel); - } + foreach ($nodes as $node) { + $nodeLft = $lftRght++; + $lftRght = $this->_recoverTree($lftRght, $node[$primaryKey], $level + 1); - if ($parentId === null) { - return $counter; - } + $fields = [$left => $nodeLft, $right => $lftRght++]; + if ($config['level']) { + $fields[$config['level']] = $level; + } - $fields = [$left => $leftCounter, $right => $counter + 1]; - if ($config['level']) { - $fields[$config['level']] = $level; + $this->_table->updateAll( + $fields, + [$primaryKey => $node[$primaryKey]] + ); } - $this->_table->updateAll( - $fields, - [$primaryKey => $parentId] - ); - - return $counter + 1; + return $lftRght; } /** From d099204d153fd55478c19e98888d75e6e3f7cfcd Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Sun, 11 Apr 2021 10:43:22 -0500 Subject: [PATCH 1623/2059] Revert "Simplify TreeBehavior::recover()" This reverts commit 916432d621019529a7d1ecb763042595752c61f0. --- Behavior/TreeBehavior.php | 48 ++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 9be7f99b..af4f3282 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -828,41 +828,47 @@ public function recover(): void /** * Recursive method used to recover a single level of the tree * - * @param int $lftRght The starting lft/rght value + * @param int $counter The Last left column value that was assigned * @param mixed $parentId the parent id of the level to be recovered * @param int $level Node level - * @return int The next lftRght value + * @return int The next value to use for the left column */ - protected function _recoverTree(int $lftRght = 1, $parentId = null, $level = 0): int + protected function _recoverTree(int $counter = 0, $parentId = null, $level = -1): int { $config = $this->getConfig(); [$parent, $left, $right] = [$config['parent'], $config['left'], $config['right']]; $primaryKey = $this->_getPrimaryKey(); - $order = $config['recoverOrder'] ?: $primaryKey; + $aliasedPrimaryKey = $this->_table->aliasField($primaryKey); + $order = $config['recoverOrder'] ?: $aliasedPrimaryKey; - $nodes = $this->_scope($this->_table->query()) - ->select($primaryKey) - ->where([$parent . ' IS' => $parentId]) + $query = $this->_scope($this->_table->query()) + ->select([$aliasedPrimaryKey]) + ->where([$this->_table->aliasField($parent) . ' IS' => $parentId]) ->order($order) - ->disableHydration() - ->all(); + ->disableHydration(); - foreach ($nodes as $node) { - $nodeLft = $lftRght++; - $lftRght = $this->_recoverTree($lftRght, $node[$primaryKey], $level + 1); + $leftCounter = $counter; + $nextLevel = $level + 1; + foreach ($query as $row) { + $counter++; + $counter = $this->_recoverTree($counter, $row[$primaryKey], $nextLevel); + } - $fields = [$left => $nodeLft, $right => $lftRght++]; - if ($config['level']) { - $fields[$config['level']] = $level; - } + if ($parentId === null) { + return $counter; + } - $this->_table->updateAll( - $fields, - [$primaryKey => $node[$primaryKey]] - ); + $fields = [$left => $leftCounter, $right => $counter + 1]; + if ($config['level']) { + $fields[$config['level']] = $level; } - return $lftRght; + $this->_table->updateAll( + $fields, + [$primaryKey => $parentId] + ); + + return $counter + 1; } /** From 14453adeeef919ff213cbae1b31bb5decb3fbfea Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Sun, 11 Apr 2021 10:43:59 -0500 Subject: [PATCH 1624/2059] Simplify TreeBehavior::recover() Moved to 4.3. --- Behavior/TreeBehavior.php | 48 +++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index af4f3282..9be7f99b 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -828,47 +828,41 @@ public function recover(): void /** * Recursive method used to recover a single level of the tree * - * @param int $counter The Last left column value that was assigned + * @param int $lftRght The starting lft/rght value * @param mixed $parentId the parent id of the level to be recovered * @param int $level Node level - * @return int The next value to use for the left column + * @return int The next lftRght value */ - protected function _recoverTree(int $counter = 0, $parentId = null, $level = -1): int + protected function _recoverTree(int $lftRght = 1, $parentId = null, $level = 0): int { $config = $this->getConfig(); [$parent, $left, $right] = [$config['parent'], $config['left'], $config['right']]; $primaryKey = $this->_getPrimaryKey(); - $aliasedPrimaryKey = $this->_table->aliasField($primaryKey); - $order = $config['recoverOrder'] ?: $aliasedPrimaryKey; + $order = $config['recoverOrder'] ?: $primaryKey; - $query = $this->_scope($this->_table->query()) - ->select([$aliasedPrimaryKey]) - ->where([$this->_table->aliasField($parent) . ' IS' => $parentId]) + $nodes = $this->_scope($this->_table->query()) + ->select($primaryKey) + ->where([$parent . ' IS' => $parentId]) ->order($order) - ->disableHydration(); + ->disableHydration() + ->all(); - $leftCounter = $counter; - $nextLevel = $level + 1; - foreach ($query as $row) { - $counter++; - $counter = $this->_recoverTree($counter, $row[$primaryKey], $nextLevel); - } + foreach ($nodes as $node) { + $nodeLft = $lftRght++; + $lftRght = $this->_recoverTree($lftRght, $node[$primaryKey], $level + 1); - if ($parentId === null) { - return $counter; - } + $fields = [$left => $nodeLft, $right => $lftRght++]; + if ($config['level']) { + $fields[$config['level']] = $level; + } - $fields = [$left => $leftCounter, $right => $counter + 1]; - if ($config['level']) { - $fields[$config['level']] = $level; + $this->_table->updateAll( + $fields, + [$primaryKey => $node[$primaryKey]] + ); } - $this->_table->updateAll( - $fields, - [$primaryKey => $parentId] - ); - - return $counter + 1; + return $lftRght; } /** From 4f5fb22dde4cf1411d3a417abf5951b3452d99d3 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 12 Apr 2021 21:01:32 +0530 Subject: [PATCH 1625/2059] Cleanup callable for marshalling '_translations' property. Removed unnecessary loop. --- Behavior/Translate/TranslateStrategyTrait.php | 41 +++++++++++-------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/Behavior/Translate/TranslateStrategyTrait.php b/Behavior/Translate/TranslateStrategyTrait.php index fceda8fb..ea1fc290 100644 --- a/Behavior/Translate/TranslateStrategyTrait.php +++ b/Behavior/Translate/TranslateStrategyTrait.php @@ -152,27 +152,32 @@ public function buildMarshalMap(Marshaller $marshaller, array $map, array $optio return [ '_translations' => function ($value, $entity) use ($marshaller, $options) { - /** @var \Cake\Datasource\EntityInterface $entity */ + if (!is_array($value)) { + return null; + } + + /** @var array|null $translations */ $translations = $entity->get('_translations'); - foreach ($this->_config['fields'] as $field) { - $options['validate'] = $this->_config['validator']; - $errors = []; - if (!is_array($value)) { - return null; + if ($translations === null) { + $translations = []; + } + + $options['validate'] = $this->_config['validator']; + $errors = []; + foreach ($value as $language => $fields) { + if (!isset($translations[$language])) { + $translations[$language] = $this->table->newEmptyEntity(); } - foreach ($value as $language => $fields) { - if (!isset($translations[$language])) { - $translations[$language] = $this->table->newEmptyEntity(); - } - $marshaller->merge($translations[$language], $fields, $options); - /** @var \Cake\Datasource\EntityInterface $translation */ - $translation = $translations[$language]; - if ((bool)$translation->getErrors()) { - $errors[$language] = $translation->getErrors(); - } + $marshaller->merge($translations[$language], $fields, $options); + + $translationErrors = $translations[$language]->getErrors(); + if ($translationErrors) { + $errors[$language] = $translationErrors; } - // Set errors into the root entity, so validation errors - // match the original form data position. + } + + // Set errors into the root entity, so validation errors match the original form data position. + if ($errors) { $entity->setErrors($errors); } From c4f7097172a4dd575fe93d8db459161b491e360a Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 16 Apr 2021 12:15:18 +0530 Subject: [PATCH 1626/2059] Fixed setting of errors for translated records in the primary entity. For .e.g. the error should be set for key '_translations.{lang}.{field}' not '{lang}.{field}', for the error to be properly displayed in the form. --- Behavior/Translate/TranslateStrategyTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/Translate/TranslateStrategyTrait.php b/Behavior/Translate/TranslateStrategyTrait.php index ea1fc290..ba702c3f 100644 --- a/Behavior/Translate/TranslateStrategyTrait.php +++ b/Behavior/Translate/TranslateStrategyTrait.php @@ -178,7 +178,7 @@ public function buildMarshalMap(Marshaller $marshaller, array $map, array $optio // Set errors into the root entity, so validation errors match the original form data position. if ($errors) { - $entity->setErrors($errors); + $entity->setErrors(['_translations' => $errors]); } return $translations; From 76174817a1b84e35eec20d6c58f550065801db66 Mon Sep 17 00:00:00 2001 From: ndm2 Date: Tue, 11 May 2021 14:07:32 +0200 Subject: [PATCH 1627/2059] Fix types not being used when marshalling composite keys via `_ids`. Unlike for single keys that are being directly passed to the query as conditions, composite keys are wrapped in a tuple comparison expression, which requires the types to be explicitly defined, otherwise the values are being bound with a type of `null`. --- Marshaller.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index d27d2728..f79d5569 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -489,7 +489,12 @@ protected function _loadAssociatedByIds(Association $assoc, array $ids): array if (!is_array($first) || count($first) !== count($primaryKey)) { return []; } - $filter = new TupleComparison($primaryKey, $ids, [], 'IN'); + $type = []; + $schema = $target->getSchema(); + foreach ((array)$target->getPrimaryKey() as $column) { + $type[] = $schema->getColumnType($column); + } + $filter = new TupleComparison($primaryKey, $ids, $type, 'IN'); } else { $filter = [$primaryKey[0] . ' IN' => $ids]; } From 81581ffeabd385303b41b881e2bc0e1487d2e391 Mon Sep 17 00:00:00 2001 From: Hache_raw Date: Sat, 15 May 2021 21:25:13 +0100 Subject: [PATCH 1628/2059] Missing DocBlock clossing braces I have tried to respect the spacing by matching it to the rest of the examples within each DocBlock, but there are style differences between one block and another. --- Query.php | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Query.php b/Query.php index 840ee107..101723b8 100644 --- a/Query.php +++ b/Query.php @@ -526,7 +526,7 @@ protected function _addAssociationsToTypeMap(Table $table, TypeMap $typeMap, arr * // Bring only articles that were tagged with 'cake' * $query->matching('Tags', function ($q) { * return $q->where(['name' => 'cake']); - * ); + * }); * ``` * * It is possible to filter by deep associations by using dot notation: @@ -537,7 +537,7 @@ protected function _addAssociationsToTypeMap(Table $table, TypeMap $typeMap, arr * // Bring only articles that were commented by 'markstory' * $query->matching('Comments.Users', function ($q) { * return $q->where(['username' => 'markstory']); - * ); + * }); * ``` * * As this function will create `INNER JOIN`, you might want to consider @@ -550,9 +550,9 @@ protected function _addAssociationsToTypeMap(Table $table, TypeMap $typeMap, arr * ``` * // Bring unique articles that were commented by 'markstory' * $query->distinct(['Articles.id']) - * ->matching('Comments.Users', function ($q) { - * return $q->where(['username' => 'markstory']); - * ); + * ->matching('Comments.Users', function ($q) { + * return $q->where(['username' => 'markstory']); + * }); * ``` * * Please note that the query passed to the closure will only accept calling @@ -620,11 +620,11 @@ public function matching(string $assoc, ?callable $builder = null) * ``` * // Total comments in articles by 'markstory' * $query - * ->select(['total_comments' => $query->func()->count('Comments.id')]) - * ->leftJoinWith('Comments.Users', function ($q) { - * return $q->where(['username' => 'markstory']); - * ) - * ->group(['Users.id']); + * ->select(['total_comments' => $query->func()->count('Comments.id')]) + * ->leftJoinWith('Comments.Users', function ($q) { + * return $q->where(['username' => 'markstory']); + * }) + * ->group(['Users.id']); * ``` * * Please note that the query passed to the closure will only accept calling @@ -663,7 +663,7 @@ public function leftJoinWith(string $assoc, ?callable $builder = null) * // Bring only articles that were tagged with 'cake' * $query->innerJoinWith('Tags', function ($q) { * return $q->where(['name' => 'cake']); - * ); + * }); * ``` * * This will create the following SQL: @@ -711,7 +711,7 @@ public function innerJoinWith(string $assoc, ?callable $builder = null) * // Bring only articles that were not tagged with 'cake' * $query->notMatching('Tags', function ($q) { * return $q->where(['name' => 'cake']); - * ); + * }); * ``` * * It is possible to filter by deep associations by using dot notation: @@ -722,7 +722,7 @@ public function innerJoinWith(string $assoc, ?callable $builder = null) * // Bring only articles that weren't commented by 'markstory' * $query->notMatching('Comments.Users', function ($q) { * return $q->where(['username' => 'markstory']); - * ); + * }); * ``` * * As this function will create a `LEFT JOIN`, you might want to consider @@ -735,9 +735,9 @@ public function innerJoinWith(string $assoc, ?callable $builder = null) * ``` * // Bring unique articles that were commented by 'markstory' * $query->distinct(['Articles.id']) - * ->notMatching('Comments.Users', function ($q) { - * return $q->where(['username' => 'markstory']); - * ); + * ->notMatching('Comments.Users', function ($q) { + * return $q->where(['username' => 'markstory']); + * }); * ``` * * Please note that the query passed to the closure will only accept calling From c9beada2f19b0ba8bd866087c11be1fa19ba2af1 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 20 May 2021 21:54:50 +0530 Subject: [PATCH 1629/2059] Update types in docblocks. Refs #15524, #15525. --- Table.php | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Table.php b/Table.php index 25a85d2b..2cb3e958 100644 --- a/Table.php +++ b/Table.php @@ -2174,9 +2174,9 @@ protected function _update(EntityInterface $entity, array $data) * any one of the records fails to save due to failed validation or database * error. * - * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to save. + * @param array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface $entities Entities to save. * @param array|\ArrayAccess|\Cake\ORM\SaveOptionsBuilder $options Options used when calling Table::save() for each entity. - * @return \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface|false False on failure, entities list on success. + * @return array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface|false False on failure, entities list on success. * @throws \Exception */ public function saveMany(iterable $entities, $options = []) @@ -2195,9 +2195,9 @@ public function saveMany(iterable $entities, $options = []) * any one of the records fails to save due to failed validation or database * error. * - * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to save. + * @param array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface $entities Entities to save. * @param array|\ArrayAccess $options Options used when calling Table::save() for each entity. - * @return \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface Entities list. + * @return array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface Entities list. * @throws \Exception * @throws \Cake\ORM\Exception\PersistenceFailedException If an entity couldn't be saved. */ @@ -2207,11 +2207,11 @@ public function saveManyOrFail(iterable $entities, $options = []): iterable } /** - * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to save. + * @param array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface $entities Entities to save. * @param array|\ArrayAccess|\Cake\ORM\SaveOptionsBuilder $options Options used when calling Table::save() for each entity. * @throws \Cake\ORM\Exception\PersistenceFailedException If an entity couldn't be saved. * @throws \Exception If an entity couldn't be saved. - * @return \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface Entities list. + * @return array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface Entities list. */ protected function _saveMany(iterable $entities, $options = []): iterable { @@ -2226,7 +2226,7 @@ protected function _saveMany(iterable $entities, $options = []): iterable /** @var bool[] $isNew */ $isNew = []; $cleanup = function ($entities) use (&$isNew): void { - /** @var \Cake\Datasource\EntityInterface[] $entities */ + /** @var array<\Cake\Datasource\EntityInterface> $entities */ foreach ($entities as $key => $entity) { if (isset($isNew[$key]) && $isNew[$key]) { $entity->unset($this->getPrimaryKey()); @@ -2329,9 +2329,9 @@ public function delete(EntityInterface $entity, $options = []): bool * any one of the records fails to delete due to failed validation or database * error. * - * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to delete. + * @param array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface $entities Entities to delete. * @param array|\ArrayAccess $options Options used when calling Table::save() for each entity. - * @return \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface|false Entities list + * @return array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface|false Entities list * on success, false on failure. * @see \Cake\ORM\Table::delete() for options and events related to this method. */ @@ -2353,9 +2353,9 @@ public function deleteMany(iterable $entities, $options = []) * any one of the records fails to delete due to failed validation or database * error. * - * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to delete. + * @param array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface $entities Entities to delete. * @param array|\ArrayAccess $options Options used when calling Table::save() for each entity. - * @return \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface Entities list. + * @return array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface Entities list. * @throws \Cake\ORM\Exception\PersistenceFailedException * @see \Cake\ORM\Table::delete() for options and events related to this method. */ @@ -2371,7 +2371,7 @@ public function deleteManyOrFail(iterable $entities, $options = []): iterable } /** - * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to delete. + * @param array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface $entities Entities to delete. * @param array|\ArrayAccess $options Options used. * @return \Cake\Datasource\EntityInterface|null */ @@ -2781,7 +2781,7 @@ public function newEntity(array $data, array $options = []): EntityInterface * * @param array $data The data to build an entity with. * @param array $options A list of options for the objects hydration. - * @return \Cake\Datasource\EntityInterface[] An array of hydrated records. + * @return array<\Cake\Datasource\EntityInterface> An array of hydrated records. */ public function newEntities(array $data, array $options = []): array { @@ -2879,11 +2879,11 @@ public function patchEntity(EntityInterface $entity, array $data, array $options * You can use the `Model.beforeMarshal` event to modify request data * before it is converted into entities. * - * @param \Cake\Datasource\EntityInterface[]|\Traversable $entities the entities that will get the + * @param array<\Cake\Datasource\EntityInterface>|\Traversable $entities the entities that will get the * data merged in * @param array $data list of arrays to be merged into the entities * @param array $options A list of options for the objects hydration. - * @return \Cake\Datasource\EntityInterface[] + * @return array<\Cake\Datasource\EntityInterface> */ public function patchEntities(iterable $entities, array $data, array $options = []): array { @@ -3060,10 +3060,10 @@ public function getSaveOptionsBuilder(array $options = []): SaveOptionsBuilder * * The properties for the associations to be loaded will be overwritten on each entity. * - * @param \Cake\Datasource\EntityInterface|\Cake\Datasource\EntityInterface[] $entities a single entity or list of entities + * @param \Cake\Datasource\EntityInterface|array<\Cake\Datasource\EntityInterface> $entities a single entity or list of entities * @param array $contain A `contain()` compatible array. * @see \Cake\ORM\Query::contain() - * @return \Cake\Datasource\EntityInterface|\Cake\Datasource\EntityInterface[] + * @return \Cake\Datasource\EntityInterface|array<\Cake\Datasource\EntityInterface> */ public function loadInto($entities, array $contain) { From 177f94f81cacfb1a14a61c6cfacdf1a1bb16ea41 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 22 May 2021 02:05:29 +0530 Subject: [PATCH 1630/2059] Fix TypeError on PHP 8.1 when strlen() is called with null. Fixes #15539. --- Association/BelongsToMany.php | 3 ++- Marshaller.php | 3 ++- Rule/IsUnique.php | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 26db5bba..de966b52 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -25,6 +25,7 @@ use Cake\ORM\Association\Loader\SelectWithPivotLoader; use Cake\ORM\Query; use Cake\ORM\Table; +use Cake\Utility\Hash; use Cake\Utility\Inflector; use Closure; use InvalidArgumentException; @@ -1168,7 +1169,7 @@ public function replaceLinks(EntityInterface $sourceEntity, array $targetEntitie $bindingKey = (array)$this->getBindingKey(); $primaryValue = $sourceEntity->extract($bindingKey); - if (count(array_filter($primaryValue, 'strlen')) !== count($bindingKey)) { + if (count(Hash::filter($primaryValue)) !== count($bindingKey)) { $message = 'Could not find primary key value for source entity'; throw new InvalidArgumentException($message); } diff --git a/Marshaller.php b/Marshaller.php index 17b98fa4..6d73e0c6 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -23,6 +23,7 @@ use Cake\Datasource\EntityInterface; use Cake\Datasource\InvalidPropertyInterface; use Cake\ORM\Association\BelongsToMany; +use Cake\Utility\Hash; use InvalidArgumentException; use RuntimeException; @@ -698,7 +699,7 @@ public function mergeMany(iterable $entities, array $data, array $options = []): return explode(';', (string)$key); }) ->filter(function ($keys) use ($primary) { - return count(array_filter($keys, 'strlen')) === count($primary); + return count(Hash::filter($keys)) === count($primary); }) ->reduce(function ($conditions, $keys) use ($primary) { $fields = array_map([$this->_table, 'aliasField'], $primary); diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index 24cfeae0..5382252a 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -17,6 +17,7 @@ namespace Cake\ORM\Rule; use Cake\Datasource\EntityInterface; +use Cake\Utility\Hash; /** * Checks that a list of fields from an entity are unique in the table @@ -79,7 +80,7 @@ public function __invoke(EntityInterface $entity, array $options): bool if ($entity->isNew() === false) { $keys = (array)$options['repository']->getPrimaryKey(); $keys = $this->_alias($alias, $entity->extract($keys)); - if (array_filter($keys, 'strlen')) { + if (Hash::filter($keys)) { $conditions['NOT'] = $keys; } } From 97dc02da60d2cd9b90664aef2dddd98cd2504869 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 22 May 2021 01:24:42 +0530 Subject: [PATCH 1631/2059] Add __serialize()/__unserialize() magic methods. The Serializable interface is deprecated in PHP 8.1 and adding these methods avoids deprecation errors. --- ResultSet.php | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/ResultSet.php b/ResultSet.php index ef1b755a..7f189eaa 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -298,6 +298,16 @@ public function first() * @return string Serialized object */ public function serialize(): string + { + return serialize($this->__serialize()); + } + + /** + * Serializes a resultset. + * + * @return array + */ + public function __serialize(): array { if (!$this->_useBuffering) { $msg = 'You cannot serialize an un-buffered ResultSet. ' @@ -310,10 +320,10 @@ public function serialize(): string } if ($this->_results instanceof SplFixedArray) { - return serialize($this->_results->toArray()); + return $this->_results->toArray(); } - return serialize($this->_results); + return $this->_results; } /** @@ -326,8 +336,18 @@ public function serialize(): string */ public function unserialize($serialized) { - $results = (array)(unserialize($serialized) ?: []); - $this->_results = SplFixedArray::fromArray($results); + $this->__unserialize((array)(unserialize($serialized) ?: [])); + } + + /** + * Unserializes a resultset. + * + * @param array $data Data array. + * @return void + */ + public function __unserialize(array $data): void + { + $this->_results = SplFixedArray::fromArray($data); $this->_useBuffering = true; $this->_count = $this->_results->count(); } From 6cf393c63839c2e23d4f6a0e7ee07bdfedf32a91 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 24 May 2021 22:45:55 +0530 Subject: [PATCH 1632/2059] Fix errors reported by psalm. --- Association/BelongsToMany.php | 24 ++++++++++++------------ Association/HasMany.php | 12 ++++++------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index ee745eda..1ba41d9e 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1452,25 +1452,25 @@ protected function _junctionTableName(?string $name = null): string /** * Parse extra options passed in the constructor. * - * @param array $opts original list of options passed in constructor + * @param array $options original list of options passed in constructor * @return void */ - protected function _options(array $opts): void + protected function _options(array $options): void { - if (!empty($opts['targetForeignKey'])) { - $this->setTargetForeignKey($opts['targetForeignKey']); + if (!empty($options['targetForeignKey'])) { + $this->setTargetForeignKey($options['targetForeignKey']); } - if (!empty($opts['joinTable'])) { - $this->_junctionTableName($opts['joinTable']); + if (!empty($options['joinTable'])) { + $this->_junctionTableName($options['joinTable']); } - if (!empty($opts['through'])) { - $this->setThrough($opts['through']); + if (!empty($options['through'])) { + $this->setThrough($options['through']); } - if (!empty($opts['saveStrategy'])) { - $this->setSaveStrategy($opts['saveStrategy']); + if (!empty($options['saveStrategy'])) { + $this->setSaveStrategy($options['saveStrategy']); } - if (isset($opts['sort'])) { - $this->setSort($opts['sort']); + if (isset($options['sort'])) { + $this->setSort($options['sort']); } } } diff --git a/Association/HasMany.php b/Association/HasMany.php index 235017b7..fa59e21f 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -637,16 +637,16 @@ public function defaultRowValue(array $row, bool $joined): array /** * Parse extra options passed in the constructor. * - * @param array $opts original list of options passed in constructor + * @param array $options original list of options passed in constructor * @return void */ - protected function _options(array $opts): void + protected function _options(array $options): void { - if (!empty($opts['saveStrategy'])) { - $this->setSaveStrategy($opts['saveStrategy']); + if (!empty($options['saveStrategy'])) { + $this->setSaveStrategy($options['saveStrategy']); } - if (isset($opts['sort'])) { - $this->setSort($opts['sort']); + if (isset($options['sort'])) { + $this->setSort($options['sort']); } } From 007f65a2cbefae9fba74908f836f7424baec4e24 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Wed, 16 Jun 2021 13:17:49 -0500 Subject: [PATCH 1633/2059] Require php 8.0 and bump package dependency versions to 5.0 --- composer.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index c0f2675d..1a8e0a12 100644 --- a/composer.json +++ b/composer.json @@ -23,14 +23,14 @@ "source": "https://github.com/cakephp/orm" }, "require": { - "php": ">=7.2.0", - "cakephp/collection": "^4.0", - "cakephp/core": "^4.0", - "cakephp/datasource": "^4.0", - "cakephp/database": "^4.0", - "cakephp/event": "^4.0", - "cakephp/utility": "^4.0", - "cakephp/validation": "^4.0" + "php": ">=8.0", + "cakephp/collection": "^5.0", + "cakephp/core": "^5.0", + "cakephp/datasource": "^5.0", + "cakephp/database": "^5.0", + "cakephp/event": "^5.0", + "cakephp/utility": "^5.0", + "cakephp/validation": "^5.0" }, "suggest": { "cakephp/cache": "If you decide to use Query caching.", From b44376477b67a8ebd9811b3c7ed6126d8bdb2b7a Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Thu, 17 Jun 2021 20:39:43 -0500 Subject: [PATCH 1634/2059] Fix order of ArrayAccess|array param hints --- Table.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Table.php b/Table.php index c1c13729..7eff60c0 100644 --- a/Table.php +++ b/Table.php @@ -1839,7 +1839,7 @@ public function exists($conditions): bool * ``` * * @param \Cake\Datasource\EntityInterface $entity the entity to be saved - * @param array|\ArrayAccess|\Cake\ORM\SaveOptionsBuilder $options The options to use when saving. + * @param \ArrayAccess|array|\Cake\ORM\SaveOptionsBuilder $options The options to use when saving. * @return \Cake\Datasource\EntityInterface|false * @throws \Cake\ORM\Exception\RolledbackTransactionException If the transaction is aborted in the afterSave event. */ @@ -1888,7 +1888,7 @@ public function save(EntityInterface $entity, $options = []) * the entity contains errors or the save was aborted by a callback. * * @param \Cake\Datasource\EntityInterface $entity the entity to be saved - * @param array|\ArrayAccess $options The options to use when saving. + * @param \ArrayAccess|array $options The options to use when saving. * @return \Cake\Datasource\EntityInterface * @throws \Cake\ORM\Exception\PersistenceFailedException When the entity couldn't be saved * @see \Cake\ORM\Table::save() @@ -2174,7 +2174,7 @@ protected function _update(EntityInterface $entity, array $data) * error. * * @param array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface $entities Entities to save. - * @param array|\ArrayAccess|\Cake\ORM\SaveOptionsBuilder $options Options used when calling Table::save() for each entity. + * @param \ArrayAccess|array|\Cake\ORM\SaveOptionsBuilder $options Options used when calling Table::save() for each entity. * @return array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface|false False on failure, entities list on success. * @throws \Exception */ @@ -2195,7 +2195,7 @@ public function saveMany(iterable $entities, $options = []) * error. * * @param array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface $entities Entities to save. - * @param array|\ArrayAccess $options Options used when calling Table::save() for each entity. + * @param \ArrayAccess|array $options Options used when calling Table::save() for each entity. * @return array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface Entities list. * @throws \Exception * @throws \Cake\ORM\Exception\PersistenceFailedException If an entity couldn't be saved. @@ -2207,7 +2207,7 @@ public function saveManyOrFail(iterable $entities, $options = []): iterable /** * @param array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface $entities Entities to save. - * @param array|\ArrayAccess|\Cake\ORM\SaveOptionsBuilder $options Options used when calling Table::save() for each entity. + * @param \ArrayAccess|array|\Cake\ORM\SaveOptionsBuilder $options Options used when calling Table::save() for each entity. * @throws \Cake\ORM\Exception\PersistenceFailedException If an entity couldn't be saved. * @throws \Exception If an entity couldn't be saved. * @return array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface Entities list. @@ -2296,7 +2296,7 @@ protected function _saveMany(iterable $entities, $options = []): iterable * the options used in the delete operation. * * @param \Cake\Datasource\EntityInterface $entity The entity to remove. - * @param array|\ArrayAccess $options The options for the delete. + * @param \ArrayAccess|array $options The options for the delete. * @return bool success */ public function delete(EntityInterface $entity, $options = []): bool @@ -2329,7 +2329,7 @@ public function delete(EntityInterface $entity, $options = []): bool * error. * * @param array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface $entities Entities to delete. - * @param array|\ArrayAccess $options Options used when calling Table::save() for each entity. + * @param \ArrayAccess|array $options Options used when calling Table::save() for each entity. * @return array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface|false Entities list * on success, false on failure. * @see \Cake\ORM\Table::delete() for options and events related to this method. @@ -2353,7 +2353,7 @@ public function deleteMany(iterable $entities, $options = []) * error. * * @param array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface $entities Entities to delete. - * @param array|\ArrayAccess $options Options used when calling Table::save() for each entity. + * @param \ArrayAccess|array $options Options used when calling Table::save() for each entity. * @return array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface Entities list. * @throws \Cake\ORM\Exception\PersistenceFailedException * @see \Cake\ORM\Table::delete() for options and events related to this method. @@ -2371,7 +2371,7 @@ public function deleteManyOrFail(iterable $entities, $options = []): iterable /** * @param array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface $entities Entities to delete. - * @param array|\ArrayAccess $options Options used. + * @param \ArrayAccess|array $options Options used. * @return \Cake\Datasource\EntityInterface|null */ protected function _deleteMany(iterable $entities, $options = []): ?EntityInterface @@ -2409,7 +2409,7 @@ protected function _deleteMany(iterable $entities, $options = []): ?EntityInterf * has no primary key value, application rules checks failed or the delete was aborted by a callback. * * @param \Cake\Datasource\EntityInterface $entity The entity to remove. - * @param array|\ArrayAccess $options The options for the delete. + * @param \ArrayAccess|array $options The options for the delete. * @return true * @throws \Cake\ORM\Exception\PersistenceFailedException * @see \Cake\ORM\Table::delete() From 6ed0e3b3ab89f5f0cc76ce949ed3b4740f438453 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Thu, 17 Jun 2021 22:44:36 -0500 Subject: [PATCH 1635/2059] Normalize order of union types in docblocks --- Association.php | 14 +++++++------- Association/BelongsToMany.php | 6 +++--- Association/Loader/SelectLoader.php | 6 +++--- Association/Loader/SelectWithPivotLoader.php | 2 +- AssociationCollection.php | 4 ++-- Behavior/TreeBehavior.php | 2 +- Exception/PersistenceFailedException.php | 2 +- Locator/LocatorInterface.php | 2 +- Query.php | 14 +++++++------- Rule/ExistsIn.php | 2 +- RulesChecker.php | 4 ++-- SaveOptionsBuilder.php | 2 +- Table.php | 8 ++++---- 13 files changed, 34 insertions(+), 34 deletions(-) diff --git a/Association.php b/Association.php index 6d760037..b1425f71 100644 --- a/Association.php +++ b/Association.php @@ -182,7 +182,7 @@ abstract class Association * The default finder name to use for fetching rows from the target table * With array value, finder name and default options are allowed. * - * @var string|array + * @var array|string */ protected $_finder = 'all'; @@ -422,7 +422,7 @@ public function getTarget(): Table * Sets a list of conditions to be always included when fetching records from * the target association. * - * @param array|\Closure $conditions list of conditions to be used + * @param \Closure|array $conditions list of conditions to be used * @see \Cake\Database\Query::where() for examples on the format of the array * @return \Cake\ORM\Association */ @@ -653,7 +653,7 @@ public function getStrategy(): string /** * Gets the default finder to use for fetching rows from the target table. * - * @return string|array + * @return array|string */ public function getFinder() { @@ -663,7 +663,7 @@ public function getFinder() /** * Sets the default finder to use for fetching rows from the target table. * - * @param string|array $finder the finder name to use or array of finder name and option. + * @param array|string $finder the finder name to use or array of finder name and option. * @return $this */ public function setFinder($finder) @@ -850,7 +850,7 @@ public function defaultRowValue(array $row, bool $joined): array * and modifies the query accordingly based of this association * configuration * - * @param string|array|null $type the type of query to perform, if an array is passed, + * @param array|string|null $type the type of query to perform, if an array is passed, * it will be interpreted as the `$options` parameter * @param array $options The options to for the find * @see \Cake\ORM\Table::find() @@ -870,7 +870,7 @@ public function find($type = null, array $options = []): Query * Proxies the operation to the target table's exists method after * appending the default conditions for this association * - * @param array|\Closure|\Cake\Database\ExpressionInterface $conditions The conditions to use + * @param \Cake\Database\ExpressionInterface|\Closure|array $conditions The conditions to use * for checking if any record matches. * @see \Cake\ORM\Table::exists() * @return bool @@ -1126,7 +1126,7 @@ protected function _joinCondition(array $options): array * $query->contain(['Comments' => ['finder' => ['translations' => []]]]); * $query->contain(['Comments' => ['finder' => ['translations' => ['locales' => ['en_US']]]]]); * - * @param string|array $finderData The finder name or an array having the name as key + * @param array|string $finderData The finder name or an array having the name as key * and options as value. * @return array */ diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index d721497b..d4606495 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -252,7 +252,7 @@ public function defaultRowValue(array $row, bool $joined): array * Sets the table instance for the junction relation. If no arguments * are passed, the current configured table instance is returned * - * @param string|\Cake\ORM\Table|null $table Name or instance for the join table + * @param \Cake\ORM\Table|string|null $table Name or instance for the join table * @return \Cake\ORM\Table * @throws \InvalidArgumentException If the expected associations are incompatible with existing associations. */ @@ -965,7 +965,7 @@ public function setConditions($conditions) /** * Sets the current join table, either the name of the Table instance or the instance itself. * - * @param string|\Cake\ORM\Table $through Name of the Table instance or the instance itself + * @param \Cake\ORM\Table|string $through Name of the Table instance or the instance itself * @return $this */ public function setThrough($through) @@ -1057,7 +1057,7 @@ protected function junctionConditions(): array * If your association includes conditions or a finder, the junction table will be * included in the query's contained associations. * - * @param string|array|null $type the type of query to perform, if an array is passed, + * @param array|string|null $type the type of query to perform, if an array is passed, * it will be interpreted as the `$options` parameter * @param array $options The options to for the find * @see \Cake\ORM\Table::find() diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 015a82c7..3700187a 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -56,7 +56,7 @@ class SelectLoader /** * The foreignKey to the target association * - * @var string|array + * @var array|string */ protected $foreignKey; @@ -217,7 +217,7 @@ protected function _buildQuery(array $options): Query * $query->contain(['Comments' => ['finder' => ['translations' => []]]]); * $query->contain(['Comments' => ['finder' => ['translations' => ['locales' => ['en_US']]]]]); * - * @param string|array $finderData The finder name or an array having the name as key + * @param array|string $finderData The finder name or an array having the name as key * and options as value. * @return array */ @@ -317,7 +317,7 @@ protected function _addFilteringJoin(Query $query, $key, $subquery): Query * target table query given a filter key and some filtering values. * * @param \Cake\ORM\Query $query Target table's query - * @param string|array $key The fields that should be used for filtering + * @param array|string $key The fields that should be used for filtering * @param mixed $filter The value that should be used to match for $key * @return \Cake\ORM\Query */ diff --git a/Association/Loader/SelectWithPivotLoader.php b/Association/Loader/SelectWithPivotLoader.php index 128f52f5..586fe06b 100644 --- a/Association/Loader/SelectWithPivotLoader.php +++ b/Association/Loader/SelectWithPivotLoader.php @@ -50,7 +50,7 @@ class SelectWithPivotLoader extends SelectLoader /** * Custom conditions for the junction association * - * @var string|array|\Cake\Database\ExpressionInterface|\Closure|null + * @var array|string|\Cake\Database\ExpressionInterface|\Closure|null */ protected $junctionConditions; diff --git a/AssociationCollection.php b/AssociationCollection.php index 5d3a5ca2..0c08cddc 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -154,7 +154,7 @@ public function keys(): array /** * Get an array of associations matching a specific type. * - * @param string|array $class The type of associations you want. + * @param array|string $class The type of associations you want. * For example 'BelongsTo' or array like ['BelongsTo', 'HasOne'] * @return \Cake\ORM\Association[] An array of Association objects. * @since 3.5.3 @@ -350,7 +350,7 @@ public function cascadeDelete(EntityInterface $entity, array $options): bool * array. If true is passed, then it returns all association names * in this collection. * - * @param bool|array $keys the list of association names to normalize + * @param array|bool $keys the list of association names to normalize * @return array */ public function normalizeKeys($keys): array diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 9be7f99b..4f68f7fd 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -988,7 +988,7 @@ protected function _getPrimaryKey(): string /** * Returns the depth level of a node in the tree. * - * @param int|string|\Cake\Datasource\EntityInterface $entity The entity or primary key get the level of. + * @param \Cake\Datasource\EntityInterface|string|int $entity The entity or primary key get the level of. * @return int|false Integer of the level or false if the node does not exist. */ public function getLevel($entity) diff --git a/Exception/PersistenceFailedException.php b/Exception/PersistenceFailedException.php index 5d9d0302..ef238694 100644 --- a/Exception/PersistenceFailedException.php +++ b/Exception/PersistenceFailedException.php @@ -40,7 +40,7 @@ class PersistenceFailedException extends CakeException * Constructor. * * @param \Cake\Datasource\EntityInterface $entity The entity on which the persistence operation failed - * @param string|array $message Either the string of the error message, or an array of attributes + * @param array|string $message Either the string of the error message, or an array of attributes * that are made available in the view, and sprintf()'d into Exception::$_messageTemplate * @param int $code The code of the error, is also the HTTP status code for the error. * @param \Throwable|null $previous the previous exception. diff --git a/Locator/LocatorInterface.php b/Locator/LocatorInterface.php index 68db76d8..2ffb7b6a 100644 --- a/Locator/LocatorInterface.php +++ b/Locator/LocatorInterface.php @@ -37,7 +37,7 @@ public function getConfig(?string $alias = null): array; * Stores a list of options to be used when instantiating an object * with a matching alias. * - * @param string|array $alias Name of the alias or array to completely + * @param array|string $alias Name of the alias or array to completely * overwrite current config. * @param array|null $options list of options for the alias * @return $this diff --git a/Query.php b/Query.php index 101723b8..a53bd286 100644 --- a/Query.php +++ b/Query.php @@ -53,10 +53,10 @@ * @method \Cake\Collection\CollectionInterface extract($field) Extracts a single column from each row * @method mixed max($field) Returns the maximum value for a single column in all the results. * @method mixed min($field) Returns the minimum value for a single column in all the results. - * @method \Cake\Collection\CollectionInterface groupBy(string|callable $field) In-memory group all results by the value of a column. - * @method \Cake\Collection\CollectionInterface indexBy(string|callable $callback) Returns the results indexed by the value of a column. - * @method \Cake\Collection\CollectionInterface countBy(string|callable $field) Returns the number of unique values for a column - * @method float sumOf(string|callable $field) Returns the sum of all values for a single column + * @method \Cake\Collection\CollectionInterface groupBy(callable|string $field) In-memory group all results by the value of a column. + * @method \Cake\Collection\CollectionInterface indexBy(callable|string $callback) Returns the results indexed by the value of a column. + * @method \Cake\Collection\CollectionInterface countBy(callable|string $field) Returns the number of unique values for a column + * @method float sumOf(callable|string $field) Returns the sum of all values for a single column * @method \Cake\Collection\CollectionInterface shuffle() In-memory randomize the order the results are returned * @method \Cake\Collection\CollectionInterface sample(int $size = 10) In-memory shuffle the results and return a subset of them. * @method \Cake\Collection\CollectionInterface take(int $size = 1, int $from = 0) In-memory limit and offset for the query results. @@ -220,7 +220,7 @@ public function __construct(Connection $connection, Table $table) * all the fields in the schema of the table or the association will be added to * the select clause. * - * @param array|\Cake\Database\ExpressionInterface|callable|string|\Cake\ORM\Table|\Cake\ORM\Association $fields Fields + * @param \Cake\Database\ExpressionInterface|array|callable|string|\Cake\ORM\Table|\Cake\ORM\Association $fields Fields * to be added to the list. * @param bool $overwrite whether to reset fields with passed list or not * @return $this @@ -1041,7 +1041,7 @@ public function isHydrationEnabled(): bool * * @param \Closure|string|false $key Either the cache key or a function to generate the cache key. * When using a function, this query instance will be supplied as an argument. - * @param string|\Cake\Cache\CacheEngine $config Either the name of the cache config to use, or + * @param \Cake\Cache\CacheEngine|string $config Either the name of the cache config to use, or * a cache config instance. * @return $this * @throws \RuntimeException When you attempt to cache a non-select query. @@ -1240,7 +1240,7 @@ protected function _dirty(): void * This changes the query type to be 'update'. * Can be combined with set() and where() methods to create update queries. * - * @param string|\Cake\Database\ExpressionInterface|null $table Unused parameter. + * @param \Cake\Database\ExpressionInterface|string|null $table Unused parameter. * @return $this */ public function update($table = null) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 6494b2a5..20dc6312 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -54,7 +54,7 @@ class ExistsIn * Available option for $options is 'allowNullableNulls' flag. * Set to true to accept composite foreign keys where one or more nullable columns are null. * - * @param string|array $fields The field or fields to check existence as primary key. + * @param array|string $fields The field or fields to check existence as primary key. * @param \Cake\ORM\Table|\Cake\ORM\Association|string $repository The repository where the * field will be looked for, or the association name for the repository. * @param array $options The options that modify the rules behavior. diff --git a/RulesChecker.php b/RulesChecker.php index 56184322..e8fc5c91 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -48,7 +48,7 @@ class RulesChecker extends BaseRulesChecker * - `allowMultipleNulls` Allows any field to have multiple null values. Defaults to false. * * @param string[] $fields The list of fields to check for uniqueness. - * @param string|array|null $message The error message to show in case the rule does not pass. Can + * @param array|string|null $message The error message to show in case the rule does not pass. Can * also be an array of options. When an array, the 'message' key can be used to provide a message. * @return \Cake\Datasource\RuleInvoker */ @@ -92,7 +92,7 @@ public function isUnique(array $fields, $message = null): RuleInvoker * @param string|string[] $field The field or list of fields to check for existence by * primary key lookup in the other table. * @param \Cake\ORM\Table|\Cake\ORM\Association|string $table The table name where the fields existence will be checked. - * @param string|array|null $message The error message to show in case the rule does not pass. Can + * @param array|string|null $message The error message to show in case the rule does not pass. Can * also be an array of options. When an array, the 'message' key can be used to provide a message. * @return \Cake\Datasource\RuleInvoker */ diff --git a/SaveOptionsBuilder.php b/SaveOptionsBuilder.php index 12270d58..8da1e144 100644 --- a/SaveOptionsBuilder.php +++ b/SaveOptionsBuilder.php @@ -80,7 +80,7 @@ public function parseArrayOptions(array $array) /** * Set associated options. * - * @param string|array $associated String or array of associations. + * @param array|string $associated String or array of associations. * @return $this */ public function associated($associated) diff --git a/Table.php b/Table.php index 7eff60c0..9af9b8b1 100644 --- a/Table.php +++ b/Table.php @@ -526,7 +526,7 @@ public function getSchema(): TableSchemaInterface * If an array is passed, a new TableSchemaInterface will be constructed * out of it and used as the schema for this table. * - * @param array|\Cake\Database\Schema\TableSchemaInterface $schema Schema to be used for this table + * @param \Cake\Database\Schema\TableSchemaInterface|array $schema Schema to be used for this table * @return $this */ public function setSchema($schema) @@ -1598,7 +1598,7 @@ protected function _transactionCommitted(bool $atomic, bool $primary): bool * transaction (default: true) * - defaults: Whether to use the search criteria as default values for the new entity (default: true) * - * @param array|callable|\Cake\ORM\Query $search The criteria to find existing + * @param \Cake\ORM\Query|callable|array $search The criteria to find existing * records by. Note that when you pass a query object you'll have to use * the 2nd arg of the method to modify the entity data before saving. * @param callable|null $callback A callback that will be invoked for newly @@ -1629,7 +1629,7 @@ public function findOrCreate($search, ?callable $callback = null, $options = []) /** * Performs the actual find and/or create of an entity based on the passed options. * - * @param array|callable|\Cake\ORM\Query $search The criteria to find an existing record by, or a callable tha will + * @param \Cake\ORM\Query|callable|array $search The criteria to find an existing record by, or a callable tha will * customize the find query. * @param callable|null $callback A callback that will be invoked for newly * created entities. This callback will be called *before* the entity @@ -1670,7 +1670,7 @@ protected function _processFindOrCreate($search, ?callable $callback = null, $op /** * Gets the query object for findOrCreate(). * - * @param array|callable|\Cake\ORM\Query $search The criteria to find existing records by. + * @param \Cake\ORM\Query|callable|array $search The criteria to find existing records by. * @return \Cake\ORM\Query */ protected function _getFindOrCreateQuery($search): Query From 888e6e9e9e9b14ae22b842520b961fb326bf86c8 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Sat, 19 Jun 2021 14:10:51 -0500 Subject: [PATCH 1636/2059] Normalize order of types in return tags --- Association.php | 2 +- Association/BelongsToMany.php | 2 +- Behavior/Translate/TranslateTrait.php | 2 +- ResultSet.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Association.php b/Association.php index b1425f71..51b568b7 100644 --- a/Association.php +++ b/Association.php @@ -438,7 +438,7 @@ public function setConditions($conditions) * the target association. * * @see \Cake\Database\Query::where() for examples on the format of the array - * @return array|\Closure + * @return \Closure|array */ public function getConditions() { diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index d4606495..9c987e1c 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -978,7 +978,7 @@ public function setThrough($through) /** * Gets the current join table, either the name of the Table instance or the instance itself. * - * @return string|\Cake\ORM\Table + * @return \Cake\ORM\Table|string */ public function getThrough() { diff --git a/Behavior/Translate/TranslateTrait.php b/Behavior/Translate/TranslateTrait.php index 23670a31..95741243 100644 --- a/Behavior/Translate/TranslateTrait.php +++ b/Behavior/Translate/TranslateTrait.php @@ -31,7 +31,7 @@ trait TranslateTrait * it. * * @param string $language Language to return entity for. - * @return $this|\Cake\Datasource\EntityInterface + * @return \Cake\Datasource\EntityInterface|$this */ public function translation(string $language) { diff --git a/ResultSet.php b/ResultSet.php index 7f189eaa..42a05b34 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -455,7 +455,7 @@ protected function _fetchResult() * Correctly nests results keys including those coming from associations * * @param array $row Array containing columns and values or false if there is no results - * @return array|\Cake\Datasource\EntityInterface Results + * @return \Cake\Datasource\EntityInterface|array Results */ protected function _groupResult(array $row) { From dc79ab8884cd2d890402bac437b6b454727fa76a Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Sat, 19 Jun 2021 14:21:03 -0500 Subject: [PATCH 1637/2059] Remove database-related deprecated code --- Behavior.php | 13 ---------- TableRegistry.php | 63 ----------------------------------------------- 2 files changed, 76 deletions(-) diff --git a/Behavior.php b/Behavior.php index 1f7939ab..b6dcf6db 100644 --- a/Behavior.php +++ b/Behavior.php @@ -180,19 +180,6 @@ public function initialize(array $config): void { } - /** - * Get the table instance this behavior is bound to. - * - * @return \Cake\ORM\Table The bound table instance. - * @deprecated 4.2.0 Use table() instead. - */ - public function getTable(): Table - { - deprecationWarning('Behavior::getTable() is deprecated. Use table() instead.'); - - return $this->table(); - } - /** * Get the table instance this behavior is bound to. * diff --git a/TableRegistry.php b/TableRegistry.php index 52a0c42a..0445606f 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -79,67 +79,4 @@ public static function setTableLocator(LocatorInterface $tableLocator): void { FactoryLocator::add('Table', $tableLocator); } - - /** - * Get a table instance from the registry. - * - * See options specification in {@link TableLocator::get()}. - * - * @param string $alias The alias name you want to get. - * @param array $options The options you want to build the table with. - * @return \Cake\ORM\Table - * @deprecated 3.6.0 Use {@link \Cake\ORM\Locator\TableLocator::get()} instead. Will be removed in 5.0. - */ - public static function get(string $alias, array $options = []): Table - { - return static::getTableLocator()->get($alias, $options); - } - - /** - * Check to see if an instance exists in the registry. - * - * @param string $alias The alias to check for. - * @return bool - * @deprecated 3.6.0 Use {@link \Cake\ORM\Locator\TableLocator::exists()} instead. Will be removed in 5.0 - */ - public static function exists(string $alias): bool - { - return static::getTableLocator()->exists($alias); - } - - /** - * Set an instance. - * - * @param string $alias The alias to set. - * @param \Cake\ORM\Table $object The table to set. - * @return \Cake\ORM\Table - * @deprecated 3.6.0 Use {@link \Cake\ORM\Locator\TableLocator::set()} instead. Will be removed in 5.0 - */ - public static function set(string $alias, Table $object): Table - { - return static::getTableLocator()->set($alias, $object); - } - - /** - * Removes an instance from the registry. - * - * @param string $alias The alias to remove. - * @return void - * @deprecated 3.6.0 Use {@link \Cake\ORM\Locator\TableLocator::remove()} instead. Will be removed in 5.0 - */ - public static function remove(string $alias): void - { - static::getTableLocator()->remove($alias); - } - - /** - * Clears the registry of configuration and instances. - * - * @return void - * @deprecated 3.6.0 Use {@link \Cake\ORM\Locator\TableLocator::clear()} instead. Will be removed in 5.0 - */ - public static function clear(): void - { - static::getTableLocator()->clear(); - } } From c2b289debdeca7dbea6183251ed632ca94e576e0 Mon Sep 17 00:00:00 2001 From: ndm2 Date: Sat, 3 Jul 2021 12:28:16 +0200 Subject: [PATCH 1638/2059] Clarify `Query::applyOptions()` behavior. --- Query.php | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/Query.php b/Query.php index 101723b8..d7dba8ec 100644 --- a/Query.php +++ b/Query.php @@ -766,7 +766,9 @@ public function notMatching(string $assoc, ?callable $builder = null) /** * Populates or adds parts to current query clauses using an array. - * This is handy for passing all query clauses at once. The option array accepts: + * This is handy for passing all query clauses at once. + * + * The method accepts the following query clause related options: * * - fields: Maps to the select method * - conditions: Maps to the where method @@ -779,6 +781,10 @@ public function notMatching(string $assoc, ?callable $builder = null) * - join: Maps to the join method * - page: Maps to the page method * + * All other options will not affect the query, but will be stored + * as custom options that can be read via `getOptions()`. Furthermore + * they are automatically passed to `Model.beforeFind`. + * * ### Example: * * ``` @@ -787,7 +793,7 @@ public function notMatching(string $assoc, ?callable $builder = null) * 'conditions' => [ * 'created >=' => '2013-01-01' * ], - * 'limit' => 10 + * 'limit' => 10, * ]); * ``` * @@ -800,8 +806,26 @@ public function notMatching(string $assoc, ?callable $builder = null) * ->limit(10) * ``` * - * @param array $options the options to be applied + * Custom options can be read via `getOptions()`: + * + * ``` + * $query->applyOptions([ + * 'fields' => ['id', 'name'], + * 'custom' => 'value', + * ]); + * ``` + * + * Here `$options` will hold `['custom' => 'value']` (the `fields` + * option will be applied to the query instead of being stored, as + * it's a query clause related option): + * + * ``` + * $options = $query->getOptions(); + * ``` + * + * @param array $options The options to be applied * @return $this + * @see getOptions() */ public function applyOptions(array $options) { From 8a9a506dad9286176c4be754f5acded8cb93e7e1 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Sat, 26 Jun 2021 03:16:24 -0500 Subject: [PATCH 1639/2059] Deprecate calling ResultSetInterface methods on query instances directly --- LazyEagerLoader.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index cf2ddb55..5fd31050 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -136,7 +136,7 @@ protected function _getPropertyMap(Table $source, array $associations): array * entities. * * @param \Cake\Datasource\EntityInterface[]|\Traversable $objects The original list of entities - * @param \Cake\Collection\CollectionInterface|\Cake\ORM\Query $results The loaded results + * @param \Cake\ORM\Query $results The loaded results * @param string[] $associations The top level associations that were loaded * @param \Cake\ORM\Table $source The table where the entities came from * @return array @@ -147,6 +147,7 @@ protected function _injectResults(iterable $objects, $results, array $associatio $properties = $this->_getPropertyMap($source, $associations); $primaryKey = (array)$source->getPrimaryKey(); $results = $results + ->all() ->indexBy(function ($e) use ($primaryKey) { /** @var \Cake\Datasource\EntityInterface $e */ return implode(';', $e->extract($primaryKey)); From 08efbc12ad9b6c8a29da0b7eccb17cc4ff8b6cdc Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Thu, 8 Jul 2021 20:16:50 -0500 Subject: [PATCH 1640/2059] Remove deprecated ResultSet method proxying --- Query.php | 56 ------------------------------------------------------- 1 file changed, 56 deletions(-) diff --git a/Query.php b/Query.php index a53bd286..31004307 100644 --- a/Query.php +++ b/Query.php @@ -17,7 +17,6 @@ namespace Cake\ORM; use ArrayObject; -use BadMethodCallException; use Cake\Database\Connection; use Cake\Database\ExpressionInterface; use Cake\Database\Query as DatabaseQuery; @@ -38,44 +37,9 @@ * into a specific iterator that will be responsible for hydrating results if * required. * - * @see \Cake\Collection\CollectionInterface For a full description of the collection methods supported by this class * @property \Cake\ORM\Table $_repository Instance of a table object this query is bound to. * @method \Cake\ORM\Table getRepository() Returns the default table object that will be used by this query, * that is, the table that will appear in the from clause. - * @method \Cake\Collection\CollectionInterface each(callable $c) Passes each of the query results to the callable - * @method \Cake\Collection\CollectionInterface sortBy($callback, int $dir) Sorts the query with the callback - * @method \Cake\Collection\CollectionInterface filter(callable $c = null) Keeps the results using passing the callable test - * @method \Cake\Collection\CollectionInterface reject(callable $c) Removes the results passing the callable test - * @method bool every(callable $c) Returns true if all the results pass the callable test - * @method bool some(callable $c) Returns true if at least one of the results pass the callable test - * @method \Cake\Collection\CollectionInterface map(callable $c) Modifies each of the results using the callable - * @method mixed reduce(callable $c, $zero = null) Folds all the results into a single value using the callable. - * @method \Cake\Collection\CollectionInterface extract($field) Extracts a single column from each row - * @method mixed max($field) Returns the maximum value for a single column in all the results. - * @method mixed min($field) Returns the minimum value for a single column in all the results. - * @method \Cake\Collection\CollectionInterface groupBy(callable|string $field) In-memory group all results by the value of a column. - * @method \Cake\Collection\CollectionInterface indexBy(callable|string $callback) Returns the results indexed by the value of a column. - * @method \Cake\Collection\CollectionInterface countBy(callable|string $field) Returns the number of unique values for a column - * @method float sumOf(callable|string $field) Returns the sum of all values for a single column - * @method \Cake\Collection\CollectionInterface shuffle() In-memory randomize the order the results are returned - * @method \Cake\Collection\CollectionInterface sample(int $size = 10) In-memory shuffle the results and return a subset of them. - * @method \Cake\Collection\CollectionInterface take(int $size = 1, int $from = 0) In-memory limit and offset for the query results. - * @method \Cake\Collection\CollectionInterface skip(int $howMany) Skips some rows from the start of the query result. - * @method mixed last() Return the last row of the query result - * @method \Cake\Collection\CollectionInterface append(array|\Traversable $items) Appends more rows to the result of the query. - * @method \Cake\Collection\CollectionInterface combine($k, $v, $g = null) Returns the values of the column $v index by column $k, - * and grouped by $g. - * @method \Cake\Collection\CollectionInterface nest($k, $p, $n = 'children') Creates a tree structure by nesting the values of column $p into that - * with the same value for $k using $n as the nesting key. - * @method array toArray() Returns a key-value array with the results of this query. - * @method array toList() Returns a numerically indexed array with the results of this query. - * @method \Cake\Collection\CollectionInterface stopWhen(callable $c) Returns each row until the callable returns true. - * @method \Cake\Collection\CollectionInterface zip(array|\Traversable $c) Returns the first result of both the query and $c in an array, - * then the second results and so on. - * @method \Cake\Collection\CollectionInterface zipWith($collections, callable $callable) Returns each of the results out of calling $c - * with the first rows of the query and each of the items, then the second rows and so on. - * @method \Cake\Collection\CollectionInterface chunk(int $size) Groups the results in arrays of $size rows each. - * @method bool isEmpty() Returns true if this query found no results. */ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface { @@ -83,7 +47,6 @@ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface cache as private _cache; all as private _all; _decorateResults as private _applyDecorators; - __call as private _call; } /** @@ -1307,25 +1270,6 @@ public static function subquery(Table $table) return $query; } - /** - * {@inheritDoc} - * - * @param string $method the method to call - * @param array $arguments list of arguments for the method to call - * @return mixed - * @throws \BadMethodCallException if the method is called for a non-select query - */ - public function __call(string $method, array $arguments) - { - if ($this->type() === 'select') { - return $this->_call($method, $arguments); - } - - throw new BadMethodCallException( - sprintf('Cannot call method "%s" on a "%s" query', $method, $this->type()) - ); - } - /** * @inheritDoc */ From a847c27b658a3b9e98a8a27797855e21c16efc99 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Sat, 10 Jul 2021 18:53:56 -0500 Subject: [PATCH 1641/2059] Update order of var, param and return tag type hints. Convert all type[] hints to array. --- Association.php | 14 +++---- Association/BelongsTo.php | 2 +- Association/BelongsToMany.php | 10 ++--- Association/HasMany.php | 2 +- Association/HasOne.php | 2 +- Association/Loader/SelectLoader.php | 4 +- Association/Loader/SelectWithPivotLoader.php | 4 +- EagerLoader.php | 2 +- LazyEagerLoader.php | 6 +-- Marshaller.php | 6 +-- Query.php | 2 +- ResultSet.php | 8 ++-- RulesChecker.php | 2 +- Table.php | 42 ++++++++++---------- 14 files changed, 53 insertions(+), 53 deletions(-) diff --git a/Association.php b/Association.php index 51b568b7..729394be 100644 --- a/Association.php +++ b/Association.php @@ -106,14 +106,14 @@ abstract class Association /** * The field name in the owning side table that is used to match with the foreignKey * - * @var string|string[]|null + * @var array|string|null */ protected $_bindingKey; /** * The name of the field representing the foreign key to the table to load * - * @var string|string[] + * @var array|string */ protected $_foreignKey; @@ -121,7 +121,7 @@ abstract class Association * A list of conditions to be always included when fetching records from * the target association * - * @var array|\Closure + * @var \Closure|array */ protected $_conditions = []; @@ -449,7 +449,7 @@ public function getConditions() * Sets the name of the field representing the binding field with the target table. * When not manually specified the primary key of the owning side table is used. * - * @param string|string[] $key the table field or fields to be used to link both tables together + * @param array|string $key the table field or fields to be used to link both tables together * @return $this */ public function setBindingKey($key) @@ -463,7 +463,7 @@ public function setBindingKey($key) * Gets the name of the field representing the binding field with the target table. * When not manually specified the primary key of the owning side table is used. * - * @return string|string[] + * @return array|string */ public function getBindingKey() { @@ -479,7 +479,7 @@ public function getBindingKey() /** * Gets the name of the field representing the foreign key to the target table. * - * @return string|string[] + * @return array|string */ public function getForeignKey() { @@ -489,7 +489,7 @@ public function getForeignKey() /** * Sets the name of the field representing the foreign key to the target table. * - * @param string|string[] $key the key or keys to be used to link both tables together + * @param array|string $key the key or keys to be used to link both tables together * @return $this */ public function setForeignKey($key) diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index fb354148..9cbe46e9 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -46,7 +46,7 @@ class BelongsTo extends Association /** * Gets the name of the field representing the foreign key to the target table. * - * @return string|string[] + * @return array|string */ public function getForeignKey() { diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 9c987e1c..c5628647 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -107,14 +107,14 @@ class BelongsToMany extends Association /** * The name of the field representing the foreign key to the target table * - * @var string|string[]|null + * @var array|string|null */ protected $_targetForeignKey; /** * The table instance for the junction relation. * - * @var string|\Cake\ORM\Table + * @var \Cake\ORM\Table|string */ protected $_through; @@ -162,7 +162,7 @@ class BelongsToMany extends Association /** * Sets the name of the field representing the foreign key to the target table. * - * @param string|string[] $key the key to be used to link both tables together + * @param array|string $key the key to be used to link both tables together * @return $this */ public function setTargetForeignKey($key) @@ -175,7 +175,7 @@ public function setTargetForeignKey($key) /** * Gets the name of the field representing the foreign key to the target table. * - * @return string|string[] + * @return array|string */ public function getTargetForeignKey() { @@ -201,7 +201,7 @@ public function canBeJoined(array $options = []): bool /** * Gets the name of the field representing the foreign key to the source table. * - * @return string|string[] + * @return array|string */ public function getForeignKey() { diff --git a/Association/HasMany.php b/Association/HasMany.php index fa59e21f..60067316 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -587,7 +587,7 @@ public function canBeJoined(array $options = []): bool /** * Gets the name of the field representing the foreign key to the source table. * - * @return string|string[] + * @return array|string */ public function getForeignKey() { diff --git a/Association/HasOne.php b/Association/HasOne.php index bd10694b..765a8012 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -44,7 +44,7 @@ class HasOne extends Association /** * Gets the name of the field representing the foreign key to the target table. * - * @return string|string[] + * @return array|string */ public function getForeignKey() { diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 3700187a..9953b26d 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -281,7 +281,7 @@ protected function _assertFieldsPresent(Query $fetchQuery, array $key): void * filtering needs to be done using a subquery. * * @param \Cake\ORM\Query $query Target table's query - * @param string|string[] $key the fields that should be used for filtering + * @param array|string $key the fields that should be used for filtering * @param \Cake\ORM\Query $subquery The Subquery to use for filtering * @return \Cake\ORM\Query */ @@ -360,7 +360,7 @@ protected function _createTupleCondition(Query $query, array $keys, $filter, $op * which the filter should be applied * * @param array $options The options for getting the link field. - * @return string|string[] + * @return array|string * @throws \RuntimeException */ protected function _linkField(array $options) diff --git a/Association/Loader/SelectWithPivotLoader.php b/Association/Loader/SelectWithPivotLoader.php index 586fe06b..abe6adde 100644 --- a/Association/Loader/SelectWithPivotLoader.php +++ b/Association/Loader/SelectWithPivotLoader.php @@ -50,7 +50,7 @@ class SelectWithPivotLoader extends SelectLoader /** * Custom conditions for the junction association * - * @var array|string|\Cake\Database\ExpressionInterface|\Closure|null + * @var \Cake\Database\ExpressionInterface|\Closure|array|string|null */ protected $junctionConditions; @@ -142,7 +142,7 @@ protected function _assertFieldsPresent(Query $fetchQuery, array $key): void * which the filter should be applied * * @param array $options the options to use for getting the link field. - * @return string|string[] + * @return array|string */ protected function _linkField(array $options) { diff --git a/EagerLoader.php b/EagerLoader.php index 159301bb..bf648b12 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -42,7 +42,7 @@ class EagerLoader * Contains a nested array with the compiled containments tree * This is a normalized version of the user provided containments array. * - * @var \Cake\ORM\EagerLoadable[]|\Cake\ORM\EagerLoadable|null + * @var \Cake\ORM\EagerLoadable|array<\Cake\ORM\EagerLoadable>|null */ protected $_normalized; diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 5fd31050..b9495e01 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -36,11 +36,11 @@ class LazyEagerLoader * * The properties for the associations to be loaded will be overwritten on each entity. * - * @param \Cake\Datasource\EntityInterface|\Cake\Datasource\EntityInterface[] $entities a single entity or list of entities + * @param \Cake\Datasource\EntityInterface|array<\Cake\Datasource\EntityInterface> $entities a single entity or list of entities * @param array $contain A `contain()` compatible array. * @see \Cake\ORM\Query::contain() * @param \Cake\ORM\Table $source The table to use for fetching the top level entities - * @return \Cake\Datasource\EntityInterface|\Cake\Datasource\EntityInterface[] + * @return \Cake\Datasource\EntityInterface|array<\Cake\Datasource\EntityInterface> */ public function loadInto($entities, array $contain, Table $source) { @@ -135,7 +135,7 @@ protected function _getPropertyMap(Table $source, array $associations): array * Injects the results of the eager loader query into the original list of * entities. * - * @param \Cake\Datasource\EntityInterface[]|\Traversable $objects The original list of entities + * @param \Traversable|array<\Cake\Datasource\EntityInterface> $objects The original list of entities * @param \Cake\ORM\Query $results The loaded results * @param string[] $associations The top level associations that were loaded * @param \Cake\ORM\Table $source The table where the entities came from diff --git a/Marshaller.php b/Marshaller.php index 6d73e0c6..9076a6ab 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -300,7 +300,7 @@ protected function _prepareDataAndOptions(array $data, array $options): array * @param \Cake\ORM\Association $assoc The association to marshall * @param mixed $value The data to hydrate. If not an array, this method will return null. * @param array $options List of options. - * @return \Cake\Datasource\EntityInterface|\Cake\Datasource\EntityInterface[]|null + * @return \Cake\Datasource\EntityInterface|array<\Cake\Datasource\EntityInterface>|null */ protected function _marshalAssociation(Association $assoc, $value, array $options) { @@ -732,11 +732,11 @@ public function mergeMany(iterable $entities, array $data, array $options = []): /** * Creates a new sub-marshaller and merges the associated data. * - * @param \Cake\Datasource\EntityInterface|\Cake\Datasource\EntityInterface[] $original The original entity + * @param \Cake\Datasource\EntityInterface|array<\Cake\Datasource\EntityInterface> $original The original entity * @param \Cake\ORM\Association $assoc The association to merge * @param mixed $value The array of data to hydrate. If not an array, this method will return null. * @param array $options List of options. - * @return \Cake\Datasource\EntityInterface|\Cake\Datasource\EntityInterface[]|null + * @return \Cake\Datasource\EntityInterface|array<\Cake\Datasource\EntityInterface>|null */ protected function _mergeAssociation($original, Association $assoc, $value, array $options) { diff --git a/Query.php b/Query.php index a53bd286..adfa37d4 100644 --- a/Query.php +++ b/Query.php @@ -220,7 +220,7 @@ public function __construct(Connection $connection, Table $table) * all the fields in the schema of the table or the association will be added to * the select clause. * - * @param \Cake\Database\ExpressionInterface|array|callable|string|\Cake\ORM\Table|\Cake\ORM\Association $fields Fields + * @param \Cake\Database\ExpressionInterface|\Cake\ORM\Table|\Cake\ORM\Association|callable|array|string $fields Fields * to be added to the list. * @param bool $overwrite whether to reset fields with passed list or not * @return $this diff --git a/ResultSet.php b/ResultSet.php index 42a05b34..f718a5fd 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -51,7 +51,7 @@ class ResultSet implements ResultSetInterface /** * Last record fetched from the statement * - * @var array|object + * @var object|array */ protected $_current; @@ -103,7 +103,7 @@ class ResultSet implements ResultSetInterface /** * Results that have been fetched or hydrated into the results. * - * @var array|\SplFixedArray + * @var \SplFixedArray|array */ protected $_results = []; @@ -182,7 +182,7 @@ public function __construct(Query $query, StatementInterface $statement) * * Part of Iterator interface. * - * @return array|object + * @return object|array */ public function current() { @@ -275,7 +275,7 @@ public function valid(): bool * * This method will also close the underlying statement cursor. * - * @return array|object|null + * @return object|array|null */ public function first() { diff --git a/RulesChecker.php b/RulesChecker.php index e8fc5c91..0986c512 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -89,7 +89,7 @@ public function isUnique(array $fields, $message = null): RuleInvoker * 'message' sets a custom error message. * Set 'allowNullableNulls' to true to accept composite foreign keys where one or more nullable columns are null. * - * @param string|string[] $field The field or list of fields to check for existence by + * @param array|string $field The field or list of fields to check for existence by * primary key lookup in the other table. * @param \Cake\ORM\Table|\Cake\ORM\Association|string $table The table name where the fields existence will be checked. * @param array|string|null $message The error message to show in case the rule does not pass. Can diff --git a/Table.php b/Table.php index 9af9b8b1..b3355178 100644 --- a/Table.php +++ b/Table.php @@ -214,14 +214,14 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc /** * The name of the field that represents the primary key in the table * - * @var string|string[]|null + * @var array|string|null */ protected $_primaryKey; /** * The name of the field that represents a human readable representation of a row * - * @var string|string[]|null + * @var array|string|null */ protected $_displayField; @@ -632,7 +632,7 @@ public function hasField(string $field): bool /** * Sets the primary key field name. * - * @param string|string[] $key Sets a new name to be used as primary key + * @param array|string $key Sets a new name to be used as primary key * @return $this */ public function setPrimaryKey($key) @@ -645,7 +645,7 @@ public function setPrimaryKey($key) /** * Returns the primary key field name. * - * @return string|string[] + * @return array|string */ public function getPrimaryKey() { @@ -663,7 +663,7 @@ public function getPrimaryKey() /** * Sets the display field. * - * @param string|string[] $field Name to be used as display field. + * @param array|string $field Name to be used as display field. * @return $this */ public function setDisplayField($field) @@ -676,7 +676,7 @@ public function setDisplayField($field) /** * Returns the display field. * - * @return string|string[]|null + * @return array|string|null */ public function getDisplayField() { @@ -1839,7 +1839,7 @@ public function exists($conditions): bool * ``` * * @param \Cake\Datasource\EntityInterface $entity the entity to be saved - * @param \ArrayAccess|array|\Cake\ORM\SaveOptionsBuilder $options The options to use when saving. + * @param \Cake\ORM\SaveOptionsBuilder|\ArrayAccess|array $options The options to use when saving. * @return \Cake\Datasource\EntityInterface|false * @throws \Cake\ORM\Exception\RolledbackTransactionException If the transaction is aborted in the afterSave event. */ @@ -2173,9 +2173,9 @@ protected function _update(EntityInterface $entity, array $data) * any one of the records fails to save due to failed validation or database * error. * - * @param array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface $entities Entities to save. - * @param \ArrayAccess|array|\Cake\ORM\SaveOptionsBuilder $options Options used when calling Table::save() for each entity. - * @return array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface|false False on failure, entities list on success. + * @param \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface> $entities Entities to save. + * @param \Cake\ORM\SaveOptionsBuilder|\ArrayAccess|array $options Options used when calling Table::save() for each entity. + * @return \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface>|false False on failure, entities list on success. * @throws \Exception */ public function saveMany(iterable $entities, $options = []) @@ -2194,9 +2194,9 @@ public function saveMany(iterable $entities, $options = []) * any one of the records fails to save due to failed validation or database * error. * - * @param array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface $entities Entities to save. + * @param \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface> $entities Entities to save. * @param \ArrayAccess|array $options Options used when calling Table::save() for each entity. - * @return array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface Entities list. + * @return \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface> Entities list. * @throws \Exception * @throws \Cake\ORM\Exception\PersistenceFailedException If an entity couldn't be saved. */ @@ -2206,11 +2206,11 @@ public function saveManyOrFail(iterable $entities, $options = []): iterable } /** - * @param array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface $entities Entities to save. - * @param \ArrayAccess|array|\Cake\ORM\SaveOptionsBuilder $options Options used when calling Table::save() for each entity. + * @param \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface> $entities Entities to save. + * @param \Cake\ORM\SaveOptionsBuilder|\ArrayAccess|array $options Options used when calling Table::save() for each entity. * @throws \Cake\ORM\Exception\PersistenceFailedException If an entity couldn't be saved. * @throws \Exception If an entity couldn't be saved. - * @return array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface Entities list. + * @return \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface> Entities list. */ protected function _saveMany(iterable $entities, $options = []): iterable { @@ -2328,9 +2328,9 @@ public function delete(EntityInterface $entity, $options = []): bool * any one of the records fails to delete due to failed validation or database * error. * - * @param array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface $entities Entities to delete. + * @param \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface> $entities Entities to delete. * @param \ArrayAccess|array $options Options used when calling Table::save() for each entity. - * @return array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface|false Entities list + * @return \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface>|false Entities list * on success, false on failure. * @see \Cake\ORM\Table::delete() for options and events related to this method. */ @@ -2352,9 +2352,9 @@ public function deleteMany(iterable $entities, $options = []) * any one of the records fails to delete due to failed validation or database * error. * - * @param array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface $entities Entities to delete. + * @param \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface> $entities Entities to delete. * @param \ArrayAccess|array $options Options used when calling Table::save() for each entity. - * @return array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface Entities list. + * @return \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface> Entities list. * @throws \Cake\ORM\Exception\PersistenceFailedException * @see \Cake\ORM\Table::delete() for options and events related to this method. */ @@ -2370,7 +2370,7 @@ public function deleteManyOrFail(iterable $entities, $options = []): iterable } /** - * @param array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface $entities Entities to delete. + * @param \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface> $entities Entities to delete. * @param \ArrayAccess|array $options Options used. * @return \Cake\Datasource\EntityInterface|null */ @@ -2872,7 +2872,7 @@ public function patchEntity(EntityInterface $entity, array $data, array $options * You can use the `Model.beforeMarshal` event to modify request data * before it is converted into entities. * - * @param array<\Cake\Datasource\EntityInterface>|\Traversable $entities the entities that will get the + * @param \Traversable|array<\Cake\Datasource\EntityInterface> $entities the entities that will get the * data merged in * @param array $data list of arrays to be merged into the entities * @param array $options A list of options for the objects hydration. From 4882ba51df7235b37698c64a93b1582b74f4902f Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Wed, 14 Jul 2021 02:22:04 -0500 Subject: [PATCH 1642/2059] Convert single type[] array hints to array hints --- Association.php | 2 +- Association/BelongsTo.php | 4 ++-- Association/BelongsToMany.php | 14 +++++++------- Association/HasMany.php | 2 +- Association/HasOne.php | 2 +- Association/Loader/SelectLoader.php | 4 ++-- AssociationCollection.php | 8 ++++---- Behavior/Translate/TranslateStrategyTrait.php | 2 +- EagerLoadable.php | 4 ++-- EagerLoader.php | 16 ++++++++-------- LazyEagerLoader.php | 6 +++--- Locator/TableLocator.php | 4 ++-- Marshaller.php | 16 ++++++++-------- Query.php | 2 +- Rule/IsUnique.php | 4 ++-- RulesChecker.php | 2 +- Table.php | 6 +++--- 17 files changed, 49 insertions(+), 49 deletions(-) diff --git a/Association.php b/Association.php index 729394be..1c3960c4 100644 --- a/Association.php +++ b/Association.php @@ -189,7 +189,7 @@ abstract class Association /** * Valid strategies for this association. Subclasses can narrow this down. * - * @var string[] + * @var array */ protected $_validStrategies = [ self::STRATEGY_JOIN, diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 9cbe46e9..672ad07e 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -36,7 +36,7 @@ class BelongsTo extends Association /** * Valid strategies for this type of association * - * @var string[] + * @var array */ protected $_validStrategies = [ self::STRATEGY_JOIN, @@ -145,7 +145,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) * clause for getting the results on the target table. * * @param array $options list of options passed to attachTo method - * @return \Cake\Database\Expression\IdentifierExpression[] + * @return array<\Cake\Database\Expression\IdentifierExpression> * @throws \RuntimeException if the number of columns in the foreignKey do not * match the number of columns in the target table primaryKey */ diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index c5628647..fcb4d3d0 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -121,7 +121,7 @@ class BelongsToMany extends Association /** * Valid strategies for this type of association * - * @var string[] + * @var array */ protected $_validStrategies = [ self::STRATEGY_SELECT, @@ -774,7 +774,7 @@ protected function _saveTarget(EntityInterface $parentEntity, array $entities, $ * * @param \Cake\Datasource\EntityInterface $sourceEntity the entity from source table in this * association - * @param \Cake\Datasource\EntityInterface[] $targetEntities list of entities to link to link to the source entity using the + * @param array<\Cake\Datasource\EntityInterface> $targetEntities list of entities to link to link to the source entity using the * junction table * @param array $options list of options accepted by `Table::save()` * @return bool success @@ -848,7 +848,7 @@ protected function _saveLinks(EntityInterface $sourceEntity, array $targetEntiti * * @param \Cake\Datasource\EntityInterface $sourceEntity the row belonging to the `source` side * of this association - * @param \Cake\Datasource\EntityInterface[] $targetEntities list of entities belonging to the `target` side + * @param array<\Cake\Datasource\EntityInterface> $targetEntities list of entities belonging to the `target` side * of this association * @param array $options list of options to be passed to the internal `save` call * @throws \InvalidArgumentException when any of the values in $targetEntities is @@ -898,7 +898,7 @@ function () use ($sourceEntity, $targetEntities, $options) { * * @param \Cake\Datasource\EntityInterface $sourceEntity An entity persisted in the source table for * this association. - * @param \Cake\Datasource\EntityInterface[] $targetEntities List of entities persisted in the target table for + * @param array<\Cake\Datasource\EntityInterface> $targetEntities List of entities persisted in the target table for * this association. * @param array|bool $options List of options to be passed to the internal `delete` call, * or a `boolean` as `cleanProperty` key shortcut. @@ -1240,7 +1240,7 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { * `$targetEntities` that were not deleted from calculating the difference. * * @param \Cake\ORM\Query $existing a query for getting existing links - * @param \Cake\Datasource\EntityInterface[] $jointEntities link entities that should be persisted + * @param array<\Cake\Datasource\EntityInterface> $jointEntities link entities that should be persisted * @param array $targetEntities entities in target table that are related to * the `$jointEntities` * @param array $options list of options accepted by `Table::delete()` @@ -1311,7 +1311,7 @@ protected function _diffLinks( * * @param \Cake\Datasource\EntityInterface $sourceEntity the row belonging to the `source` side * of this association - * @param \Cake\Datasource\EntityInterface[] $targetEntities list of entities belonging to the `target` side + * @param array<\Cake\Datasource\EntityInterface> $targetEntities list of entities belonging to the `target` side * of this association * @return bool * @throws \InvalidArgumentException @@ -1343,7 +1343,7 @@ protected function _checkPersistenceStatus(EntityInterface $sourceEntity, array * association. * @throws \InvalidArgumentException if any of the entities is lacking a primary * key value - * @return \Cake\Datasource\EntityInterface[] + * @return array<\Cake\Datasource\EntityInterface> */ protected function _collectJointEntities(EntityInterface $sourceEntity, array $targetEntities): array { diff --git a/Association/HasMany.php b/Association/HasMany.php index 60067316..7e91afaa 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -60,7 +60,7 @@ class HasMany extends Association /** * Valid strategies for this type of association * - * @var string[] + * @var array */ protected $_validStrategies = [ self::STRATEGY_SELECT, diff --git a/Association/HasOne.php b/Association/HasOne.php index 765a8012..77e1e25d 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -34,7 +34,7 @@ class HasOne extends Association /** * Valid strategies for this type of association * - * @var string[] + * @var array */ protected $_validStrategies = [ self::STRATEGY_JOIN, diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 9953b26d..46678001 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -337,7 +337,7 @@ protected function _addFilteringCondition(Query $query, $key, $filter): Query * from $keys with the tuple values in $filter using the provided operator. * * @param \Cake\ORM\Query $query Target table's query - * @param string[] $keys the fields that should be used for filtering + * @param array $keys the fields that should be used for filtering * @param mixed $filter the value that should be used to match for $key * @param string $operator The operator for comparing the tuples * @return \Cake\Database\Expression\TupleComparison @@ -528,7 +528,7 @@ protected function _resultInjector(Query $fetchQuery, array $resultMap, array $o * be done with multiple foreign keys * * @param array $resultMap A keyed arrays containing the target table - * @param string[] $sourceKeys An array with aliased keys to match + * @param array $sourceKeys An array with aliased keys to match * @param string $nestKey The key under which results should be nested * @return \Closure */ diff --git a/AssociationCollection.php b/AssociationCollection.php index 0c08cddc..416221ea 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -38,7 +38,7 @@ class AssociationCollection implements IteratorAggregate /** * Stored associations * - * @var \Cake\ORM\Association[] + * @var array<\Cake\ORM\Association> */ protected $_items = []; @@ -144,7 +144,7 @@ public function has(string $alias): bool /** * Get the names of all the associations in the collection. * - * @return string[] + * @return array */ public function keys(): array { @@ -156,7 +156,7 @@ public function keys(): array * * @param array|string $class The type of associations you want. * For example 'BelongsTo' or array like ['BelongsTo', 'HasOne'] - * @return \Cake\ORM\Association[] An array of Association objects. + * @return array<\Cake\ORM\Association> An array of Association objects. * @since 3.5.3 */ public function getByType($class): array @@ -369,7 +369,7 @@ public function normalizeKeys($keys): array /** * Allow looping through the associations * - * @return \Cake\ORM\Association[] + * @return array<\Cake\ORM\Association> * @psalm-return \Traversable */ public function getIterator(): Traversable diff --git a/Behavior/Translate/TranslateStrategyTrait.php b/Behavior/Translate/TranslateStrategyTrait.php index ba702c3f..56e05bec 100644 --- a/Behavior/Translate/TranslateStrategyTrait.php +++ b/Behavior/Translate/TranslateStrategyTrait.php @@ -106,7 +106,7 @@ public function getLocale(): string */ protected function unsetEmptyFields($entity) { - /** @var \Cake\ORM\Entity[] $translations */ + /** @var array<\Cake\ORM\Entity> $translations */ $translations = (array)$entity->get('_translations'); foreach ($translations as $locale => $translation) { $fields = $translation->extract($this->_config['fields'], false); diff --git a/EagerLoadable.php b/EagerLoadable.php index c6e98c88..98aba9c0 100644 --- a/EagerLoadable.php +++ b/EagerLoadable.php @@ -36,7 +36,7 @@ class EagerLoadable /** * A list of other associations to load from this level. * - * @var \Cake\ORM\EagerLoadable[] + * @var array<\Cake\ORM\EagerLoadable> */ protected $_associations = []; @@ -157,7 +157,7 @@ public function addAssociation(string $name, EagerLoadable $association): void /** * Returns the Association class instance to use for loading the records. * - * @return \Cake\ORM\EagerLoadable[] + * @return array<\Cake\ORM\EagerLoadable> */ public function associations(): array { diff --git a/EagerLoader.php b/EagerLoader.php index bf648b12..90084867 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -69,7 +69,7 @@ class EagerLoader /** * A list of associations that should be loaded with a separate query * - * @var \Cake\ORM\EagerLoadable[] + * @var array<\Cake\ORM\EagerLoadable> */ protected $_loadExternal = []; @@ -438,7 +438,7 @@ public function attachAssociations(Query $query, Table $repository, bool $includ * * @param \Cake\ORM\Table $repository The table containing the associations to be * attached - * @return \Cake\ORM\EagerLoadable[] + * @return array<\Cake\ORM\EagerLoadable> */ public function attachableAssociations(Table $repository): array { @@ -456,7 +456,7 @@ public function attachableAssociations(Table $repository): array * * @param \Cake\ORM\Table $repository The table containing the associations * to be loaded - * @return \Cake\ORM\EagerLoadable[] + * @return array<\Cake\ORM\EagerLoadable> */ public function externalAssociations(Table $repository): array { @@ -582,9 +582,9 @@ protected function _correctStrategy(EagerLoadable $loadable): void * Helper function used to compile a list of all associations that can be * joined in the query. * - * @param \Cake\ORM\EagerLoadable[] $associations list of associations from which to obtain joins. - * @param \Cake\ORM\EagerLoadable[] $matching list of associations that should be forcibly joined. - * @return \Cake\ORM\EagerLoadable[] + * @param array<\Cake\ORM\EagerLoadable> $associations list of associations from which to obtain joins. + * @param array<\Cake\ORM\EagerLoadable> $matching list of associations that should be forcibly joined. + * @return array<\Cake\ORM\EagerLoadable> */ protected function _resolveJoins(array $associations, array $matching = []): array { @@ -716,7 +716,7 @@ public function associationsMap(Table $table): array * associationsMap() method. * * @param array $map An initial array for the map. - * @param \Cake\ORM\EagerLoadable[] $level An array of EagerLoadable instances. + * @param array<\Cake\ORM\EagerLoadable> $level An array of EagerLoadable instances. * @param bool $matching Whether or not it is an association loaded through `matching()`. * @return array */ @@ -777,7 +777,7 @@ public function addToJoinsMap( * Helper function used to return the keys from the query records that will be used * to eagerly load associations. * - * @param \Cake\ORM\EagerLoadable[] $external the list of external associations to be loaded + * @param array<\Cake\ORM\EagerLoadable> $external the list of external associations to be loaded * @param \Cake\ORM\Query $query The query from which the results where generated * @param \Cake\Database\StatementInterface $statement The statement to work on * @return array diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index b9495e01..2fd6cb9e 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -116,8 +116,8 @@ protected function _getQuery(CollectionInterface $objects, array $contain, Table * in the top level entities. * * @param \Cake\ORM\Table $source The table having the top level associations - * @param string[] $associations The name of the top level associations - * @return string[] + * @param array $associations The name of the top level associations + * @return array */ protected function _getPropertyMap(Table $source, array $associations): array { @@ -137,7 +137,7 @@ protected function _getPropertyMap(Table $source, array $associations): array * * @param \Traversable|array<\Cake\Datasource\EntityInterface> $objects The original list of entities * @param \Cake\ORM\Query $results The loaded results - * @param string[] $associations The top level associations that were loaded + * @param array $associations The top level associations that were loaded * @param \Cake\ORM\Table $source The table where the entities came from * @return array */ diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index a89d1401..eac0b02f 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -56,7 +56,7 @@ class TableLocator extends AbstractLocator implements LocatorInterface * Contains a list of Table objects that were created out of the * built-in Table class. The list is indexed by table alias * - * @var \Cake\ORM\Table[] + * @var array<\Cake\ORM\Table> */ protected $_fallbacked = []; @@ -339,7 +339,7 @@ public function clear(): void * debugging common mistakes when setting up associations or created new table * classes. * - * @return \Cake\ORM\Table[] + * @return array<\Cake\ORM\Table> */ public function genericInstances(): array { diff --git a/Marshaller.php b/Marshaller.php index 9076a6ab..880c8a24 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -350,7 +350,7 @@ protected function _marshalAssociation(Association $assoc, $value, array $option * * @param array $data The data to hydrate. * @param array $options List of options - * @return \Cake\Datasource\EntityInterface[] An array of hydrated records. + * @return array<\Cake\Datasource\EntityInterface> An array of hydrated records. * @see \Cake\ORM\Table::newEntities() * @see \Cake\ORM\Entity::$_accessible */ @@ -376,7 +376,7 @@ public function many(array $data, array $options = []): array * @param \Cake\ORM\Association\BelongsToMany $assoc The association to marshal. * @param array $data The data to convert into entities. * @param array $options List of options. - * @return \Cake\Datasource\EntityInterface[] An array of built entities. + * @return array<\Cake\Datasource\EntityInterface> An array of built entities. * @throws \BadMethodCallException * @throws \InvalidArgumentException * @throws \RuntimeException @@ -470,7 +470,7 @@ protected function _belongsToMany(BelongsToMany $assoc, array $data, array $opti * * @param \Cake\ORM\Association $assoc The association class for the belongsToMany association. * @param array $ids The list of ids to load. - * @return \Cake\Datasource\EntityInterface[] An array of entities. + * @return array<\Cake\Datasource\EntityInterface> An array of entities. */ protected function _loadAssociatedByIds(Association $assoc, array $ids): array { @@ -652,7 +652,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) * data merged in * @param array $data list of arrays to be merged into the entities * @param array $options List of options. - * @return \Cake\Datasource\EntityInterface[] + * @return array<\Cake\Datasource\EntityInterface> * @see \Cake\ORM\Entity::$_accessible * @psalm-suppress NullArrayOffset */ @@ -779,11 +779,11 @@ protected function _mergeAssociation($original, Association $assoc, $value, arra * Creates a new sub-marshaller and merges the associated data for a BelongstoMany * association. * - * @param \Cake\Datasource\EntityInterface[] $original The original entities list. + * @param array<\Cake\Datasource\EntityInterface> $original The original entities list. * @param \Cake\ORM\Association\BelongsToMany $assoc The association to marshall * @param array $value The data to hydrate * @param array $options List of options. - * @return \Cake\Datasource\EntityInterface[] + * @return array<\Cake\Datasource\EntityInterface> */ protected function _mergeBelongsToMany(array $original, BelongsToMany $assoc, array $value, array $options): array { @@ -809,11 +809,11 @@ protected function _mergeBelongsToMany(array $original, BelongsToMany $assoc, ar /** * Merge the special _joinData property into the entity set. * - * @param \Cake\Datasource\EntityInterface[] $original The original entities list. + * @param array<\Cake\Datasource\EntityInterface> $original The original entities list. * @param \Cake\ORM\Association\BelongsToMany $assoc The association to marshall * @param array $value The data to hydrate * @param array $options List of options. - * @return \Cake\Datasource\EntityInterface[] An array of entities + * @return array<\Cake\Datasource\EntityInterface> An array of entities */ protected function _mergeJoinData(array $original, BelongsToMany $assoc, array $value, array $options): array { diff --git a/Query.php b/Query.php index adfa37d4..e7d74ac6 100644 --- a/Query.php +++ b/Query.php @@ -250,7 +250,7 @@ public function select($fields = [], bool $overwrite = false) * pass overwrite boolean true which will reset the select clause removing all previous additions. * * @param \Cake\ORM\Table|\Cake\ORM\Association $table The table to use to get an array of columns - * @param string[] $excludedFields The un-aliased column names you do not want selected from $table + * @param array $excludedFields The un-aliased column names you do not want selected from $table * @param bool $overwrite Whether to reset/remove previous selected fields * @return $this * @throws \InvalidArgumentException If Association|Table is not passed in first argument diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index 5382252a..b0c80139 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -27,7 +27,7 @@ class IsUnique /** * The list of fields to check * - * @var string[] + * @var array */ protected $_fields; @@ -47,7 +47,7 @@ class IsUnique * * - `allowMultipleNulls` Allows any field to have multiple null values. Defaults to false. * - * @param string[] $fields The list of fields to check uniqueness for + * @param array $fields The list of fields to check uniqueness for * @param array $options The options for unique checks. */ public function __construct(array $fields, array $options = []) diff --git a/RulesChecker.php b/RulesChecker.php index 0986c512..2784dcc6 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -47,7 +47,7 @@ class RulesChecker extends BaseRulesChecker * * - `allowMultipleNulls` Allows any field to have multiple null values. Defaults to false. * - * @param string[] $fields The list of fields to check for uniqueness. + * @param array $fields The list of fields to check for uniqueness. * @param array|string|null $message The error message to show in case the rule does not pass. Can * also be an array of options. When an array, the 'message' key can be used to provide a message. * @return \Cake\Datasource\RuleInvoker diff --git a/Table.php b/Table.php index b3355178..ada3c3f1 100644 --- a/Table.php +++ b/Table.php @@ -1447,7 +1447,7 @@ public function findThreaded(Query $query, array $options): Query * composite keys when comparing values. * * @param array $options the original options passed to a finder - * @param string[] $keys the keys to check in $options to build matchers from + * @param array $keys the keys to check in $options to build matchers from * the associated value * @return array */ @@ -2105,7 +2105,7 @@ protected function _insert(EntityInterface $entity, array $data) * Note: The ORM will not generate primary key values for composite primary keys. * You can overwrite _newId() in your table class. * - * @param string[] $primary The primary key columns to get a new ID for. + * @param array $primary The primary key columns to get a new ID for. * @return string|null Either null or the primary key value or a list of primary key values. */ protected function _newId(array $primary) @@ -2222,7 +2222,7 @@ protected function _saveMany(iterable $entities, $options = []): iterable ] ); - /** @var bool[] $isNew */ + /** @var array $isNew */ $isNew = []; $cleanup = function ($entities) use (&$isNew): void { /** @var array<\Cake\Datasource\EntityInterface> $entities */ From 1cbad6ec65bc0387c08a26e965d6b04ccd8c0767 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Sun, 11 Jul 2021 23:38:50 -0500 Subject: [PATCH 1643/2059] Add native type hints for Core --- BehaviorRegistry.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 7f7b2c2c..c817c562 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -133,14 +133,18 @@ protected function _throwMissingClassError(string $class, ?string $plugin): void * Part of the template method for Cake\Core\ObjectRegistry::load() * Enabled behaviors will be registered with the event manager. * - * @param string $class The classname that is missing. + * @param \Cake\ORM\Behavior|string $class The classname that is missing. * @param string $alias The alias of the object. * @param array $config An array of config to use for the behavior. * @return \Cake\ORM\Behavior The constructed behavior class. * @psalm-suppress MoreSpecificImplementedParamType */ - protected function _create($class, string $alias, array $config): Behavior + protected function _create(object|string $class, string $alias, array $config): Behavior { + if (is_object($class)) { + return $class; + } + /** @var \Cake\ORM\Behavior $instance */ $instance = new $class($this->_table, $config); $enable = $config['enabled'] ?? true; From 2c843441264fc964eb5bc250b092f794889e6624 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Thu, 15 Jul 2021 22:06:24 -0500 Subject: [PATCH 1644/2059] Remove use of deprecated Serializable --- ResultSet.php | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/ResultSet.php b/ResultSet.php index f718a5fd..1eba38dc 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -290,18 +290,6 @@ public function first() return null; } - /** - * Serializes a resultset. - * - * Part of Serializable interface. - * - * @return string Serialized object - */ - public function serialize(): string - { - return serialize($this->__serialize()); - } - /** * Serializes a resultset. * @@ -326,19 +314,6 @@ public function __serialize(): array return $this->_results; } - /** - * Unserializes a resultset. - * - * Part of Serializable interface. - * - * @param string $serialized Serialized object - * @return void - */ - public function unserialize($serialized) - { - $this->__unserialize((array)(unserialize($serialized) ?: [])); - } - /** * Unserializes a resultset. * From 8860d3fdf65c80b6ab948d29950158f6072ea85a Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 16 Jul 2021 15:29:34 +0530 Subject: [PATCH 1645/2059] Replace getTypeName() with PHP's get_debug_type(). --- Marshaller.php | 2 +- Rule/LinkConstraint.php | 2 +- RulesChecker.php | 2 +- Table.php | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 880c8a24..a3d2ab22 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -263,7 +263,7 @@ protected function _validate(array $data, array $options, bool $isNew): array if ($validator === null) { throw new RuntimeException( - sprintf('validate must be a boolean, a string or an object. Got %s.', getTypeName($options['validate'])) + sprintf('validate must be a boolean, a string or an object. Got %s.', get_debug_type($options['validate'])) ); } diff --git a/Rule/LinkConstraint.php b/Rule/LinkConstraint.php index 6a25ba23..eca0e92c 100644 --- a/Rule/LinkConstraint.php +++ b/Rule/LinkConstraint.php @@ -68,7 +68,7 @@ public function __construct($association, string $requiredLinkStatus) ) { throw new \InvalidArgumentException(sprintf( 'Argument 1 is expected to be of type `\Cake\ORM\Association|string`, `%s` given.', - getTypeName($association) + get_debug_type($association) )); } diff --git a/RulesChecker.php b/RulesChecker.php index 2784dcc6..1f2c5be6 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -223,7 +223,7 @@ protected function _addLinkConstraintRule( } else { throw new \InvalidArgumentException(sprintf( 'Argument 1 is expected to be of type `\Cake\ORM\Association|string`, `%s` given.', - getTypeName($association) + get_debug_type($association) )); } diff --git a/Table.php b/Table.php index ada3c3f1..2503aef1 100644 --- a/Table.php +++ b/Table.php @@ -1685,7 +1685,7 @@ protected function _getFindOrCreateQuery($search): Query } else { throw new InvalidArgumentException(sprintf( 'Search criteria must be an array, callable or Query. Got "%s"', - getTypeName($search) + get_debug_type($search) )); } @@ -1943,7 +1943,7 @@ protected function _processSave(EntityInterface $entity, ArrayObject $options) if ($result !== false && !($result instanceof EntityInterface)) { throw new RuntimeException(sprintf( 'The beforeSave callback must return `false` or `EntityInterface` instance. Got `%s` instead.', - getTypeName($result) + get_debug_type($result) )); } From 642a7544905865fb8e671dd4369f1cfc04090258 Mon Sep 17 00:00:00 2001 From: othercorey Date: Fri, 16 Jul 2021 23:15:42 -0500 Subject: [PATCH 1646/2059] Fix line length --- Marshaller.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index a3d2ab22..8e2e62a6 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -262,9 +262,10 @@ protected function _validate(array $data, array $options, bool $isNew): array } if ($validator === null) { - throw new RuntimeException( - sprintf('validate must be a boolean, a string or an object. Got %s.', get_debug_type($options['validate'])) - ); + throw new RuntimeException(sprintf( + 'validate must be a boolean, a string or an object. Got %s.', + get_debug_type($options['validate']) + )); } return $validator->validate($data, $isNew); From ea10b641ccd50517e5c2250be5e0451442af3640 Mon Sep 17 00:00:00 2001 From: othercorey Date: Fri, 16 Jul 2021 23:22:16 -0500 Subject: [PATCH 1647/2059] Fix trailing whitespace --- Marshaller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index 8e2e62a6..ab51f05a 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -263,7 +263,7 @@ protected function _validate(array $data, array $options, bool $isNew): array if ($validator === null) { throw new RuntimeException(sprintf( - 'validate must be a boolean, a string or an object. Got %s.', + 'validate must be a boolean, a string or an object. Got %s.', get_debug_type($options['validate']) )); } From a795e4736b4734c16e611a3df2eea59fe9798f74 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Sat, 24 Jul 2021 02:03:11 -0500 Subject: [PATCH 1648/2059] Add ReturnTypeWillChange attributes to iterators --- ResultSet.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ResultSet.php b/ResultSet.php index f718a5fd..1637ae17 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -22,6 +22,7 @@ use Cake\Database\StatementInterface; use Cake\Datasource\EntityInterface; use Cake\Datasource\ResultSetInterface; +use ReturnTypeWillChange; use SplFixedArray; /** @@ -184,6 +185,7 @@ public function __construct(Query $query, StatementInterface $statement) * * @return object|array */ + #[ReturnTypeWillChange] public function current() { return $this->_current; From 7af35a2b008870be60267b1593af0ec272478173 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Sun, 25 Jul 2021 04:18:10 -0500 Subject: [PATCH 1649/2059] Fix php 8.1 warnings --- Association/BelongsToMany.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index fcb4d3d0..5ee87101 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -721,10 +721,12 @@ public function saveAssociated(EntityInterface $entity, array $options = []) protected function _saveTarget(EntityInterface $parentEntity, array $entities, $options) { $joinAssociations = false; - if (!empty($options['associated'][$this->_junctionProperty]['associated'])) { - $joinAssociations = $options['associated'][$this->_junctionProperty]['associated']; + if (isset($options['associated']) && is_array($options['associated'])) { + if (!empty($options['associated'][$this->_junctionProperty]['associated'])) { + $joinAssociations = $options['associated'][$this->_junctionProperty]['associated']; + } + unset($options['associated'][$this->_junctionProperty]); } - unset($options['associated'][$this->_junctionProperty]); $table = $this->getTarget(); $original = $entities; From b09a3e7d4d3af01f2af697efd664f93877e61e05 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Mon, 26 Jul 2021 00:39:20 -0500 Subject: [PATCH 1650/2059] Add native type hints for Core properties --- Exception/MissingBehaviorException.php | 2 +- Exception/MissingEntityException.php | 2 +- Exception/MissingTableClassException.php | 2 +- Exception/PersistenceFailedException.php | 2 +- Exception/RolledbackTransactionException.php | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Exception/MissingBehaviorException.php b/Exception/MissingBehaviorException.php index fe6411eb..2a524300 100644 --- a/Exception/MissingBehaviorException.php +++ b/Exception/MissingBehaviorException.php @@ -24,5 +24,5 @@ class MissingBehaviorException extends CakeException /** * @var string */ - protected $_messageTemplate = 'Behavior class %s could not be found.'; + protected string $_messageTemplate = 'Behavior class %s could not be found.'; } diff --git a/Exception/MissingEntityException.php b/Exception/MissingEntityException.php index c363c123..2f0c57f3 100644 --- a/Exception/MissingEntityException.php +++ b/Exception/MissingEntityException.php @@ -28,5 +28,5 @@ class MissingEntityException extends CakeException /** * @var string */ - protected $_messageTemplate = 'Entity class %s could not be found.'; + protected string $_messageTemplate = 'Entity class %s could not be found.'; } diff --git a/Exception/MissingTableClassException.php b/Exception/MissingTableClassException.php index accf334b..5bea8ef3 100644 --- a/Exception/MissingTableClassException.php +++ b/Exception/MissingTableClassException.php @@ -26,5 +26,5 @@ class MissingTableClassException extends CakeException /** * @var string */ - protected $_messageTemplate = 'Table class %s could not be found.'; + protected string $_messageTemplate = 'Table class %s could not be found.'; } diff --git a/Exception/PersistenceFailedException.php b/Exception/PersistenceFailedException.php index ef238694..dabd95e8 100644 --- a/Exception/PersistenceFailedException.php +++ b/Exception/PersistenceFailedException.php @@ -34,7 +34,7 @@ class PersistenceFailedException extends CakeException /** * @inheritDoc */ - protected $_messageTemplate = 'Entity %s failure.'; + protected string $_messageTemplate = 'Entity %s failure.'; /** * Constructor. diff --git a/Exception/RolledbackTransactionException.php b/Exception/RolledbackTransactionException.php index b1f107bd..b577c327 100644 --- a/Exception/RolledbackTransactionException.php +++ b/Exception/RolledbackTransactionException.php @@ -24,6 +24,6 @@ class RolledbackTransactionException extends CakeException /** * @var string */ - protected $_messageTemplate = 'The afterSave event in "%s" is aborting the transaction' + protected string $_messageTemplate = 'The afterSave event in "%s" is aborting the transaction' . ' before the save process is done.'; } From 59506de1ddb51371f7f79fee7255c515a1065a6d Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Fri, 30 Jul 2021 18:28:25 -0500 Subject: [PATCH 1651/2059] Add native type hints for Collection --- ResultSet.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ResultSet.php b/ResultSet.php index 25f428dc..bf37a462 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -279,7 +279,7 @@ public function valid(): bool * * @return object|array|null */ - public function first() + public function first(): object|array|null { foreach ($this as $result) { if ($this->_statement !== null && !$this->_useBuffering) { From 02519a16130c7aed61f52f5046f7895c21c7e1c7 Mon Sep 17 00:00:00 2001 From: Lars Willighagen Date: Mon, 9 Aug 2021 03:09:12 +0200 Subject: [PATCH 1652/2059] Fix broken markdown in Table::findList() docs Add a missing markdown fence after a code block for the valueField feature option in the documentation of \Cake\ORM\Table::findList(). --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 2cb3e958..bc837d5b 100644 --- a/Table.php +++ b/Table.php @@ -1320,7 +1320,7 @@ public function findAll(Query $query, array $options): Query * 'valueField' => ['first_name', 'last_name'], * 'valueSeparator' => ' | ', * ]); - * + * ``` * * The results of this finder will be in the following form: * From 7b570a954ed3e438891d0591aa42e756ebae9510 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Tue, 3 Aug 2021 20:32:58 -0500 Subject: [PATCH 1653/2059] Add native type hints for Datasource --- Association/BelongsToMany.php | 5 ++--- Behavior/TreeBehavior.php | 4 ++++ Locator/TableLocator.php | 2 +- Marshaller.php | 1 + Query.php | 10 ++++++---- Table.php | 19 +++++++++++++------ 6 files changed, 27 insertions(+), 14 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 5ee87101..bcbd8c8a 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -753,9 +753,8 @@ protected function _saveTarget(EntityInterface $parentEntity, array $entities, $ if (!empty($options['atomic'])) { $original[$k]->setErrors($entity->getErrors()); } - if ($saved === false) { - return false; - } + + return false; } $options['associated'] = $joinAssociations; diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 4f68f7fd..647d514f 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -680,7 +680,9 @@ protected function _moveUp(EntityInterface $node, $number): EntityInterface $this->_sync($shift, '+', "BETWEEN {$leftBoundary} AND {$rightBoundary}"); $this->_sync($nodeToHole, '-', "> {$edge}"); + /** @var string $left */ $node->set($left, $targetLeft); + /** @var string $right */ $node->set($right, $targetLeft + $nodeRight - $nodeLeft); $node->setDirty($left, false); @@ -772,7 +774,9 @@ protected function _moveDown(EntityInterface $node, $number): EntityInterface $this->_sync($shift, '-', "BETWEEN {$leftBoundary} AND {$rightBoundary}"); $this->_sync($nodeToHole, '-', "> {$edge}"); + /** @var string $left */ $node->set($left, $targetRight - ($nodeRight - $nodeLeft)); + /** @var string $right */ $node->set($right, $targetRight); $node->setDirty($left, false); diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index eac0b02f..065d8582 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -208,7 +208,7 @@ public function get(string $alias, array $options = []): Table /** * @inheritDoc */ - protected function createInstance(string $alias, array $options) + protected function createInstance(string $alias, array $options): Table { if (strpos($alias, '\\') === false) { [, $classAlias] = pluginSplit($alias); diff --git a/Marshaller.php b/Marshaller.php index ab51f05a..dda177cb 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -612,6 +612,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) } foreach ((array)$options['fields'] as $field) { + /** @var string $field */ if (!array_key_exists($field, $properties)) { continue; } diff --git a/Query.php b/Query.php index 79de048f..e4545b7f 100644 --- a/Query.php +++ b/Query.php @@ -183,13 +183,15 @@ public function __construct(Connection $connection, Table $table) * all the fields in the schema of the table or the association will be added to * the select clause. * - * @param \Cake\Database\ExpressionInterface|\Cake\ORM\Table|\Cake\ORM\Association|callable|array|string $fields Fields + * @param \Cake\Database\ExpressionInterface|\Cake\ORM\Table|\Cake\ORM\Association|callable|array|string|float|int $fields Fields * to be added to the list. * @param bool $overwrite whether to reset fields with passed list or not * @return $this */ - public function select($fields = [], bool $overwrite = false) - { + public function select( + ExpressionInterface|Table|Association|callable|array|string|float|int $fields = [], + bool $overwrite = false + ) { if ($fields instanceof Association) { $fields = $fields->getTarget(); } @@ -1200,7 +1202,7 @@ protected function _addDefaultSelectTypes(): void * @return static Returns a modified query. * @psalm-suppress MoreSpecificReturnType */ - public function find(string $finder, array $options = []) + public function find(string $finder, array $options = []): static { $table = $this->getRepository(); diff --git a/Table.php b/Table.php index 2503aef1..5ecbd83a 100644 --- a/Table.php +++ b/Table.php @@ -16,11 +16,13 @@ */ namespace Cake\ORM; +use ArrayAccess; use ArrayObject; use BadMethodCallException; use Cake\Core\App; use Cake\Core\Configure; use Cake\Database\Connection; +use Cake\Database\Expression\QueryExpression; use Cake\Database\Schema\TableSchemaInterface; use Cake\Database\TypeFactory; use Cake\Datasource\ConnectionManager; @@ -43,6 +45,7 @@ use Cake\Utility\Inflector; use Cake\Validation\ValidatorAwareInterface; use Cake\Validation\ValidatorAwareTrait; +use Closure; use Exception; use InvalidArgumentException; use RuntimeException; @@ -1716,8 +1719,10 @@ public function subquery(): Query /** * @inheritDoc */ - public function updateAll($fields, $conditions): int - { + public function updateAll( + QueryExpression|Closure|array|string $fields, + QueryExpression|Closure|array|string|null $conditions + ): int { $query = $this->query(); $query->update() ->set($fields) @@ -1731,7 +1736,7 @@ public function updateAll($fields, $conditions): int /** * @inheritDoc */ - public function deleteAll($conditions): int + public function deleteAll(QueryExpression|Closure|array|string|null $conditions): int { $query = $this->query() ->delete() @@ -1745,7 +1750,7 @@ public function deleteAll($conditions): int /** * @inheritDoc */ - public function exists($conditions): bool + public function exists(QueryExpression|Closure|array|string|null $conditions): bool { return (bool)count( $this->find('all') @@ -1843,8 +1848,10 @@ public function exists($conditions): bool * @return \Cake\Datasource\EntityInterface|false * @throws \Cake\ORM\Exception\RolledbackTransactionException If the transaction is aborted in the afterSave event. */ - public function save(EntityInterface $entity, $options = []) - { + public function save( + EntityInterface $entity, + SaveOptionsBuilder|ArrayAccess|array $options = [] + ): EntityInterface|false { if ($options instanceof SaveOptionsBuilder) { $options = $options->toArray(); } From 3a1acd5b8dffb57b5f9cb97f69a635cd7e89f6af Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 9 Aug 2021 01:46:11 +0530 Subject: [PATCH 1654/2059] Fix type specified for FunctionExpression not being respected. Refs #13049 --- Query.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Query.php b/Query.php index ee967f21..9ef04ca8 100644 --- a/Query.php +++ b/Query.php @@ -1215,6 +1215,10 @@ protected function _addDefaultSelectTypes(): void $types = []; foreach ($select as $alias => $value) { + if ($value instanceof TypedResultInterface) { + $types[$alias] = $value->getReturnType(); + continue; + } if (isset($typeMap[$alias])) { $types[$alias] = $typeMap[$alias]; continue; @@ -1222,9 +1226,6 @@ protected function _addDefaultSelectTypes(): void if (is_string($value) && isset($typeMap[$value])) { $types[$alias] = $typeMap[$value]; } - if ($value instanceof TypedResultInterface) { - $types[$alias] = $value->getReturnType(); - } } $this->getSelectTypeMap()->addDefaults($types); } From 0d038e83fd1abf5924fdf06721788ba198b50b26 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Thu, 12 Aug 2021 00:22:25 -0500 Subject: [PATCH 1655/2059] Add native type hints for Database --- Behavior/TreeBehavior.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 647d514f..16fccb31 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -906,6 +906,7 @@ protected function _sync(int $shift, string $dir, string $conditions, bool $mark { $config = $this->_config; + /** @var \Cake\Database\Expression\IdentifierExpression $field */ foreach ([$config['leftField'], $config['rightField']] as $field) { $query = $this->_scope($this->_table->query()); $exp = $query->newExpr(); From c4edf2a235aecf5bb85ef18e79889aceed5780e1 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 14 Aug 2021 18:22:28 +0530 Subject: [PATCH 1656/2059] Deprecate the mutable Time and Date classes. The immutable alternatives should be used instead. --- Behavior/TimestampBehavior.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index d2b6f7f8..6622b11b 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -20,7 +20,7 @@ use Cake\Database\TypeFactory; use Cake\Datasource\EntityInterface; use Cake\Event\EventInterface; -use Cake\I18n\Time; +use Cake\I18n\FrozenTime; use Cake\ORM\Behavior; use DateTimeInterface; use RuntimeException; @@ -64,7 +64,7 @@ class TimestampBehavior extends Behavior /** * Current timestamp * - * @var \Cake\I18n\Time|null + * @var \Cake\I18n\FrozenTime|null */ protected $_ts; @@ -147,7 +147,7 @@ public function implementedEvents(): array * * @param \DateTimeInterface|null $ts Timestamp * @param bool $refreshTimestamp If true timestamp is refreshed. - * @return \Cake\I18n\Time + * @return \Cake\I18n\FrozenTime */ public function timestamp(?DateTimeInterface $ts = null, bool $refreshTimestamp = false): DateTimeInterface { @@ -155,9 +155,9 @@ public function timestamp(?DateTimeInterface $ts = null, bool $refreshTimestamp if ($this->_config['refreshTimestamp']) { $this->_config['refreshTimestamp'] = false; } - $this->_ts = new Time($ts); + $this->_ts = new FrozenTime($ts); } elseif ($this->_ts === null || $refreshTimestamp) { - $this->_ts = new Time(); + $this->_ts = new FrozenTime(); } return $this->_ts; From d2f8d6e4102464da3d6b54137c1a47575c9139cf Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 18 Aug 2021 12:11:07 +0530 Subject: [PATCH 1657/2059] Fix issues reported by static analyzers. --- Behavior/Translate/ShadowTableStrategy.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index efcfecee..f1391aa5 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -263,6 +263,7 @@ function ($c, &$field) use ($fields, $alias, $mainTableAlias, $mainTableFields, return $c; } + /** @psalm-suppress ParadoxicalCondition */ if (in_array($field, $fields, true)) { $joinRequired = true; $field = "$alias.$field"; @@ -319,6 +320,7 @@ function ($expression) use ($fields, $alias, $mainTableAlias, $mainTableFields, return; } + /** @psalm-suppress ParadoxicalCondition */ if (in_array($field, $mainTableFields, true)) { $expression->setField("$mainTableAlias.$field"); } From 863287be3cd3004bec545995318116e9838f0263 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 13 Aug 2021 22:28:32 +0530 Subject: [PATCH 1658/2059] Remove mutable versions of datetime classes. Maintaining both mutable and immutable versions is unnecessary. --- Behavior/TimestampBehavior.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 6622b11b..d2b6f7f8 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -20,7 +20,7 @@ use Cake\Database\TypeFactory; use Cake\Datasource\EntityInterface; use Cake\Event\EventInterface; -use Cake\I18n\FrozenTime; +use Cake\I18n\Time; use Cake\ORM\Behavior; use DateTimeInterface; use RuntimeException; @@ -64,7 +64,7 @@ class TimestampBehavior extends Behavior /** * Current timestamp * - * @var \Cake\I18n\FrozenTime|null + * @var \Cake\I18n\Time|null */ protected $_ts; @@ -147,7 +147,7 @@ public function implementedEvents(): array * * @param \DateTimeInterface|null $ts Timestamp * @param bool $refreshTimestamp If true timestamp is refreshed. - * @return \Cake\I18n\FrozenTime + * @return \Cake\I18n\Time */ public function timestamp(?DateTimeInterface $ts = null, bool $refreshTimestamp = false): DateTimeInterface { @@ -155,9 +155,9 @@ public function timestamp(?DateTimeInterface $ts = null, bool $refreshTimestamp if ($this->_config['refreshTimestamp']) { $this->_config['refreshTimestamp'] = false; } - $this->_ts = new FrozenTime($ts); + $this->_ts = new Time($ts); } elseif ($this->_ts === null || $refreshTimestamp) { - $this->_ts = new FrozenTime(); + $this->_ts = new Time(); } return $this->_ts; From aa9a56230608cdd43e4735158bdbe91abdefcc0b Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Mon, 16 Aug 2021 22:22:51 -0500 Subject: [PATCH 1659/2059] Add native type hints for ORM --- Association.php | 61 +++++----- Association/BelongsTo.php | 9 +- Association/BelongsToMany.php | 61 +++++----- Association/HasMany.php | 16 +-- Association/HasOne.php | 9 +- Association/Loader/SelectLoader.php | 16 ++- Association/Loader/SelectWithPivotLoader.php | 2 +- AssociationCollection.php | 4 +- AssociationsNormalizerTrait.php | 2 +- Behavior/CounterCacheBehavior.php | 8 +- Behavior/Translate/EavStrategy.php | 18 +-- Behavior/Translate/ShadowTableStrategy.php | 28 ++--- .../Translate/TranslateStrategyInterface.php | 9 +- Behavior/Translate/TranslateStrategyTrait.php | 4 +- Behavior/TranslateBehavior.php | 6 +- Behavior/TreeBehavior.php | 26 ++--- BehaviorRegistry.php | 2 +- EagerLoadable.php | 4 +- EagerLoader.php | 5 +- Exception/PersistenceFailedException.php | 8 +- LazyEagerLoader.php | 4 +- Locator/LocatorInterface.php | 2 +- Locator/TableLocator.php | 2 +- Marshaller.php | 12 +- Query.php | 16 +-- ResultSet.php | 14 +-- Rule/ExistsIn.php | 2 +- Rule/LinkConstraint.php | 22 ++-- RulesChecker.php | 32 +++--- SaveOptionsBuilder.php | 4 +- Table.php | 105 +++++++++++------- 31 files changed, 278 insertions(+), 235 deletions(-) diff --git a/Association.php b/Association.php index 1c3960c4..a7bcd34e 100644 --- a/Association.php +++ b/Association.php @@ -20,6 +20,8 @@ use Cake\Core\App; use Cake\Core\ConventionsTrait; use Cake\Database\Expression\IdentifierExpression; +use Cake\Database\Expression\QueryExpression; +use Cake\Database\ExpressionInterface; use Cake\Datasource\EntityInterface; use Cake\Datasource\ResultSetDecorator; use Cake\ORM\Locator\LocatorAwareTrait; @@ -113,7 +115,7 @@ abstract class Association /** * The name of the field representing the foreign key to the table to load * - * @var array|string + * @var array|string|false */ protected $_foreignKey; @@ -424,9 +426,9 @@ public function getTarget(): Table * * @param \Closure|array $conditions list of conditions to be used * @see \Cake\Database\Query::where() for examples on the format of the array - * @return \Cake\ORM\Association + * @return $this */ - public function setConditions($conditions) + public function setConditions(Closure|array $conditions) { $this->_conditions = $conditions; @@ -440,7 +442,7 @@ public function setConditions($conditions) * @see \Cake\Database\Query::where() for examples on the format of the array * @return \Closure|array */ - public function getConditions() + public function getConditions(): Closure|array { return $this->_conditions; } @@ -452,7 +454,7 @@ public function getConditions() * @param array|string $key the table field or fields to be used to link both tables together * @return $this */ - public function setBindingKey($key) + public function setBindingKey(array|string $key) { $this->_bindingKey = $key; @@ -465,7 +467,7 @@ public function setBindingKey($key) * * @return array|string */ - public function getBindingKey() + public function getBindingKey(): array|string { if ($this->_bindingKey === null) { $this->_bindingKey = $this->isOwningSide($this->getSource()) ? @@ -479,9 +481,9 @@ public function getBindingKey() /** * Gets the name of the field representing the foreign key to the target table. * - * @return array|string + * @return array|string|false */ - public function getForeignKey() + public function getForeignKey(): array|string|false { return $this->_foreignKey; } @@ -492,7 +494,7 @@ public function getForeignKey() * @param array|string $key the key or keys to be used to link both tables together * @return $this */ - public function setForeignKey($key) + public function setForeignKey(array|string $key) { $this->_foreignKey = $key; @@ -655,7 +657,7 @@ public function getStrategy(): string * * @return array|string */ - public function getFinder() + public function getFinder(): array|string { return $this->_finder; } @@ -666,7 +668,7 @@ public function getFinder() * @param array|string $finder the finder name to use or array of finder name and option. * @return $this */ - public function setFinder($finder) + public function setFinder(array|string $finder) { $this->_finder = $finder; @@ -856,7 +858,7 @@ public function defaultRowValue(array $row, bool $joined): array * @see \Cake\ORM\Table::find() * @return \Cake\ORM\Query */ - public function find($type = null, array $options = []): Query + public function find(array|string|null $type = null, array $options = []): Query { $type = $type ?: $this->getFinder(); [$type, $opts] = $this->_extractFinder($type); @@ -875,7 +877,7 @@ public function find($type = null, array $options = []): Query * @see \Cake\ORM\Table::exists() * @return bool */ - public function exists($conditions): bool + public function exists(ExpressionInterface|Closure|array $conditions): bool { $conditions = $this->find() ->where($conditions) @@ -885,16 +887,17 @@ public function exists($conditions): bool } /** - * Proxies the update operation to the target table's updateAll method + * Proxies the update operation to the target `Table::updateAll()` method * - * @param array $fields A hash of field => new value. - * @param mixed $conditions Conditions to be used, accepts anything Query::where() - * can take. - * @see \Cake\ORM\Table::updateAll() + * @param \Cake\Database\Expression\QueryExpression|\Closure|array|string $fields A hash of field => new value. + * @param \Cake\Database\Expression\QueryExpression|\Closure|array|string|null $conditions Conditions to be used, accepts anything Query::where() * @return int Count Returns the affected rows. + * @see \Cake\ORM\Table::updateAll() */ - public function updateAll(array $fields, $conditions): int - { + public function updateAll( + QueryExpression|Closure|array|string $fields, + QueryExpression|Closure|array|string|null $conditions + ): int { $expression = $this->find() ->where($conditions) ->clause('where'); @@ -903,14 +906,14 @@ public function updateAll(array $fields, $conditions): int } /** - * Proxies the delete operation to the target table's deleteAll method + * Proxies the delete operation to the target `Table::deleteAll()` method * - * @param mixed $conditions Conditions to be used, accepts anything Query::where() + * @param \Cake\Database\Expression\QueryExpression|\Closure|array|string|null $conditions Conditions to be used, accepts anything Query::where() * can take. * @return int Returns the number of affected rows. * @see \Cake\ORM\Table::deleteAll() */ - public function deleteAll($conditions): int + public function deleteAll(QueryExpression|Closure|array|string|null $conditions): int { $expression = $this->find() ->where($conditions) @@ -1130,7 +1133,7 @@ protected function _joinCondition(array $options): array * and options as value. * @return array */ - protected function _extractFinder($finderData): array + protected function _extractFinder(array|string $finderData): array { $finderData = (array)$finderData; @@ -1146,10 +1149,10 @@ protected function _extractFinder($finderData): array * association's associations * * @param string $property the property name - * @return \Cake\ORM\Association + * @return self * @throws \RuntimeException if no association with such name exists */ - public function __get($property) + public function __get(string $property): Association { return $this->getTarget()->{$property}; } @@ -1161,7 +1164,7 @@ public function __get($property) * @param string $property the property name * @return bool true if the property exists */ - public function __isset($property) + public function __isset(string $property): bool { return isset($this->getTarget()->{$property}); } @@ -1174,7 +1177,7 @@ public function __isset($property) * @return mixed * @throws \BadMethodCallException */ - public function __call($method, $argument) + public function __call(string $method, array $argument): mixed { return $this->getTarget()->$method(...$argument); } @@ -1250,5 +1253,5 @@ abstract public function isOwningSide(Table $side): bool; * the saved entity * @see \Cake\ORM\Table::save() */ - abstract public function saveAssociated(EntityInterface $entity, array $options = []); + abstract public function saveAssociated(EntityInterface $entity, array $options = []): EntityInterface|false; } diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 672ad07e..ba31e8e2 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -44,11 +44,9 @@ class BelongsTo extends Association ]; /** - * Gets the name of the field representing the foreign key to the target table. - * - * @return array|string + * @inheritDoc */ - public function getForeignKey() + public function getForeignKey(): array|string|false { if ($this->_foreignKey === null) { $this->_foreignKey = $this->_modelKey($this->getTarget()->getAlias()); @@ -118,7 +116,7 @@ public function type(): string * the saved entity * @see \Cake\ORM\Table::save() */ - public function saveAssociated(EntityInterface $entity, array $options = []) + public function saveAssociated(EntityInterface $entity, array $options = []): EntityInterface|false { $targetEntity = $entity->get($this->getProperty()); if (empty($targetEntity) || !($targetEntity instanceof EntityInterface)) { @@ -131,6 +129,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) return false; } + /** @psalm-suppress InvalidScalarArgument getForeign() returns false */ $properties = array_combine( (array)$this->getForeignKey(), $targetEntity->extract((array)$this->getBindingKey()) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index bcbd8c8a..aacf054c 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -165,7 +165,7 @@ class BelongsToMany extends Association * @param array|string $key the key to be used to link both tables together * @return $this */ - public function setTargetForeignKey($key) + public function setTargetForeignKey(array|string $key) { $this->_targetForeignKey = $key; @@ -177,7 +177,7 @@ public function setTargetForeignKey($key) * * @return array|string */ - public function getTargetForeignKey() + public function getTargetForeignKey(): array|string { if ($this->_targetForeignKey === null) { $this->_targetForeignKey = $this->_modelKey($this->getTarget()->getAlias()); @@ -199,11 +199,9 @@ public function canBeJoined(array $options = []): bool } /** - * Gets the name of the field representing the foreign key to the source table. - * - * @return array|string + * @inheritDoc */ - public function getForeignKey() + public function getForeignKey(): array|string|false { if ($this->_foreignKey === null) { $this->_foreignKey = $this->_modelKey($this->getSource()->getTable()); @@ -215,10 +213,10 @@ public function getForeignKey() /** * Sets the sort order in which target records should be returned. * - * @param mixed $sort A find() compatible order clause + * @param \Cake\Database\ExpressionInterface|\Closure|array|string $sort A find() compatible order clause * @return $this */ - public function setSort($sort) + public function setSort(ExpressionInterface|Closure|array|string $sort) { $this->_sort = $sort; @@ -228,9 +226,9 @@ public function setSort($sort) /** * Gets the sort order in which target records should be returned. * - * @return mixed + * @return \Cake\Database\ExpressionInterface|\Closure|array|string|null */ - public function getSort() + public function getSort(): ExpressionInterface|Closure|array|string|null { return $this->_sort; } @@ -256,7 +254,7 @@ public function defaultRowValue(array $row, bool $joined): array * @return \Cake\ORM\Table * @throws \InvalidArgumentException If the expected associations are incompatible with existing associations. */ - public function junction($table = null): Table + public function junction(Table|string|null $table = null): Table { if ($table === null && $this->_junctionTable !== null) { return $this->_junctionTable; @@ -587,6 +585,7 @@ public function cascadeDelete(EntityInterface $entity, array $options = []): boo $conditions = []; if (!empty($bindingKey)) { + /** @psalm-suppress InvalidScalarArgument getForeignKey() returns false */ $conditions = array_combine($foreignKey, $entity->extract($bindingKey)); } @@ -680,7 +679,7 @@ public function getSaveStrategy(): string * @see \Cake\ORM\Table::save() * @see \Cake\ORM\Association\BelongsToMany::replaceLinks() */ - public function saveAssociated(EntityInterface $entity, array $options = []) + public function saveAssociated(EntityInterface $entity, array $options = []): EntityInterface|false { $targetEntity = $entity->get($this->getProperty()); $strategy = $this->getSaveStrategy(); @@ -718,8 +717,11 @@ public function saveAssociated(EntityInterface $entity, array $options = []) * @return \Cake\Datasource\EntityInterface|false The parent entity after all links have been * created if no errors happened, false otherwise */ - protected function _saveTarget(EntityInterface $parentEntity, array $entities, $options) - { + protected function _saveTarget( + EntityInterface $parentEntity, + array $entities, + array $options + ): EntityInterface|false { $joinAssociations = false; if (isset($options['associated']) && is_array($options['associated'])) { if (!empty($options['associated'][$this->_junctionProperty]['associated'])) { @@ -798,13 +800,15 @@ protected function _saveLinks(EntityInterface $sourceEntity, array $targetEntiti if (!$joint || !($joint instanceof EntityInterface)) { $joint = new $entityClass([], ['markNew' => true, 'source' => $junctionRegistryAlias]); } + /** @psalm-suppress InvalidScalarArgument getForeignKey() returns false */ $sourceKeys = array_combine($foreignKey, $sourceEntity->extract($bindingKey)); + /** @psalm-suppress InvalidScalarArgument getForeignKey() returns false */ $targetKeys = array_combine($assocForeignKey, $e->extract($targetBindingKey)); - $changedKeys = ( - $sourceKeys !== $joint->extract($foreignKey) || - $targetKeys !== $joint->extract($assocForeignKey) - ); + /** @psalm-suppress InvalidScalarArgument getForeignKey() returns false */ + $changedKeys = $sourceKeys !== $joint->extract($foreignKey) || + $targetKeys !== $joint->extract($assocForeignKey); + // Keys were changed, the junction table record _could_ be // new. By clearing the primary key values, and marking the entity // as new, we let save() sort out whether or not we have a new link @@ -907,7 +911,7 @@ function () use ($sourceEntity, $targetEntities, $options) { * any of them is lacking a primary key value. * @return bool Success */ - public function unlink(EntityInterface $sourceEntity, array $targetEntities, $options = []): bool + public function unlink(EntityInterface $sourceEntity, array $targetEntities, array|bool $options = []): bool { if (is_bool($options)) { $options = [ @@ -955,7 +959,7 @@ function () use ($sourceEntity, $targetEntities, $options): void { /** * @inheritDoc */ - public function setConditions($conditions) + public function setConditions(Closure|array $conditions) { parent::setConditions($conditions); $this->_targetConditions = $this->_junctionConditions = null; @@ -969,7 +973,7 @@ public function setConditions($conditions) * @param \Cake\ORM\Table|string $through Name of the Table instance or the instance itself * @return $this */ - public function setThrough($through) + public function setThrough(Table|string $through) { $this->_through = $through; @@ -981,7 +985,7 @@ public function setThrough($through) * * @return \Cake\ORM\Table|string */ - public function getThrough() + public function getThrough(): Table|string { return $this->_through; } @@ -996,7 +1000,7 @@ public function getThrough() * are not an array, the association conditions will be * returned unmodified. */ - protected function targetConditions() + protected function targetConditions(): mixed { if ($this->_targetConditions !== null) { return $this->_targetConditions; @@ -1064,7 +1068,7 @@ protected function junctionConditions(): array * @see \Cake\ORM\Table::find() * @return \Cake\ORM\Query */ - public function find($type = null, array $options = []): Query + public function find(array|string|null $type = null, array $options = []): Query { $type = $type ?: $this->getFinder(); [$type, $opts] = $this->_extractFinder($type); @@ -1181,13 +1185,16 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { $target = $this->getTarget(); $foreignKey = (array)$this->getForeignKey(); + /** @psalm-suppress PossiblyFalseArgument getForeignKey() returns false */ $prefixedForeignKey = array_map([$junction, 'aliasField'], $foreignKey); $junctionPrimaryKey = (array)$junction->getPrimaryKey(); $assocForeignKey = (array)$junction->getAssociation($target->getAlias())->getForeignKey(); + /** @psalm-suppress InvalidScalarArgument getForeignKey() returns false */ $keys = array_combine($foreignKey, $prefixedForeignKey); foreach (array_merge($assocForeignKey, $junctionPrimaryKey) as $key) { + /** @psalm-suppress PossiblyFalseArgument getForeignKey() returns false */ $keys[$key] = $junction->aliasField($key); } @@ -1252,7 +1259,7 @@ protected function _diffLinks( array $jointEntities, array $targetEntities, array $options = [] - ) { + ): array|false { $junction = $this->junction(); $target = $this->getTarget(); $belongsTo = $junction->getAssociation($target->getAlias()); @@ -1263,7 +1270,9 @@ protected function _diffLinks( $deletes = $indexed = $present = []; foreach ($jointEntities as $i => $entity) { + /** @psalm-suppress InvalidScalarArgument getForeignKey() returns false */ $indexed[$i] = $entity->extract($keys); + /** @psalm-suppress InvalidScalarArgument getForeignKey() returns false */ $present[$i] = array_values($entity->extract($assocForeignKey)); } @@ -1379,10 +1388,12 @@ protected function _collectJointEntities(EntityInterface $sourceEntity, array $t $hasMany = $source->getAssociation($junction->getAlias()); $foreignKey = (array)$this->getForeignKey(); $foreignKey = array_map(function ($key) { + /** @psalm-suppress PossiblyFalseOperand getForeignKey() returns false */ return $key . ' IS'; }, $foreignKey); $assocForeignKey = (array)$belongsTo->getForeignKey(); $assocForeignKey = array_map(function ($key) { + /** @psalm-suppress PossiblyFalseOperand getForeignKey() returns false */ return $key . ' IS'; }, $assocForeignKey); $sourceKey = $sourceEntity->extract((array)$source->getPrimaryKey()); diff --git a/Association/HasMany.php b/Association/HasMany.php index 7e91afaa..92af15ad 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -143,7 +143,7 @@ public function getSaveStrategy(): string * @see \Cake\ORM\Table::save() * @throws \InvalidArgumentException when the association data cannot be traversed. */ - public function saveAssociated(EntityInterface $entity, array $options = []) + public function saveAssociated(EntityInterface $entity, array $options = []): EntityInterface|false { $targetEntities = $entity->get($this->getProperty()); @@ -165,6 +165,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) throw new InvalidArgumentException($message); } + /** @psalm-suppress InvalidScalarArgument getForeignKey() returns false */ $foreignKeyReference = array_combine( (array)$this->getForeignKey(), $entity->extract((array)$this->getBindingKey()) @@ -341,7 +342,7 @@ public function link(EntityInterface $sourceEntity, array $targetEntities, array * any of them is lacking a primary key value * @return void */ - public function unlink(EntityInterface $sourceEntity, array $targetEntities, $options = []): void + public function unlink(EntityInterface $sourceEntity, array $targetEntities, array|bool $options = []): void { if (is_bool($options)) { $options = [ @@ -363,6 +364,7 @@ public function unlink(EntityInterface $sourceEntity, array $targetEntities, $op 'OR' => (new Collection($targetEntities)) ->map(function ($entity) use ($targetPrimaryKey) { /** @var \Cake\Datasource\EntityInterface $entity */ + /** @psalm-suppress InvalidScalarArgument getForeignKey() returns false */ return $entity->extract($targetPrimaryKey); }) ->toList(), @@ -585,11 +587,9 @@ public function canBeJoined(array $options = []): bool } /** - * Gets the name of the field representing the foreign key to the source table. - * - * @return array|string + * @inheritDoc */ - public function getForeignKey() + public function getForeignKey(): array|string|false { if ($this->_foreignKey === null) { $this->_foreignKey = $this->_modelKey($this->getSource()->getTable()); @@ -604,7 +604,7 @@ public function getForeignKey() * @param mixed $sort A find() compatible order clause * @return $this */ - public function setSort($sort) + public function setSort(mixed $sort) { $this->_sort = $sort; @@ -616,7 +616,7 @@ public function setSort($sort) * * @return mixed */ - public function getSort() + public function getSort(): mixed { return $this->_sort; } diff --git a/Association/HasOne.php b/Association/HasOne.php index 77e1e25d..1ee562f3 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -42,11 +42,9 @@ class HasOne extends Association ]; /** - * Gets the name of the field representing the foreign key to the target table. - * - * @return array|string + * @inheritDoc */ - public function getForeignKey() + public function getForeignKey(): array|string|false { if ($this->_foreignKey === null) { $this->_foreignKey = $this->_modelKey($this->getSource()->getAlias()); @@ -102,13 +100,14 @@ public function type(): string * the saved entity * @see \Cake\ORM\Table::save() */ - public function saveAssociated(EntityInterface $entity, array $options = []) + public function saveAssociated(EntityInterface $entity, array $options = []): EntityInterface|false { $targetEntity = $entity->get($this->getProperty()); if (empty($targetEntity) || !($targetEntity instanceof EntityInterface)) { return $entity; } + /** @psalm-suppress InvalidScalarArgument getForeignKey() returns false */ $properties = array_combine( (array)$this->getForeignKey(), $entity->extract((array)$this->getBindingKey()) diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 46678001..fd51a583 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -221,7 +221,7 @@ protected function _buildQuery(array $options): Query * and options as value. * @return array */ - protected function _extractFinder($finderData): array + protected function _extractFinder(array|string $finderData): array { $finderData = (array)$finderData; @@ -285,7 +285,7 @@ protected function _assertFieldsPresent(Query $fetchQuery, array $key): void * @param \Cake\ORM\Query $subquery The Subquery to use for filtering * @return \Cake\ORM\Query */ - protected function _addFilteringJoin(Query $query, $key, $subquery): Query + protected function _addFilteringJoin(Query $query, array|string $key, Query $subquery): Query { $filter = []; $aliasedTable = $this->sourceAlias; @@ -321,7 +321,7 @@ protected function _addFilteringJoin(Query $query, $key, $subquery): Query * @param mixed $filter The value that should be used to match for $key * @return \Cake\ORM\Query */ - protected function _addFilteringCondition(Query $query, $key, $filter): Query + protected function _addFilteringCondition(Query $query, array|string $key, mixed $filter): Query { if (is_array($key)) { $conditions = $this->_createTupleCondition($query, $key, $filter, 'IN'); @@ -342,8 +342,12 @@ protected function _addFilteringCondition(Query $query, $key, $filter): Query * @param string $operator The operator for comparing the tuples * @return \Cake\Database\Expression\TupleComparison */ - protected function _createTupleCondition(Query $query, array $keys, $filter, $operator): TupleComparison - { + protected function _createTupleCondition( + Query $query, + array $keys, + mixed $filter, + string $operator + ): TupleComparison { $types = []; $defaults = $query->getDefaultTypes(); foreach ($keys as $k) { @@ -363,7 +367,7 @@ protected function _createTupleCondition(Query $query, array $keys, $filter, $op * @return array|string * @throws \RuntimeException */ - protected function _linkField(array $options) + protected function _linkField(array $options): array|string { $links = []; $name = $this->alias; diff --git a/Association/Loader/SelectWithPivotLoader.php b/Association/Loader/SelectWithPivotLoader.php index abe6adde..30b3a4c9 100644 --- a/Association/Loader/SelectWithPivotLoader.php +++ b/Association/Loader/SelectWithPivotLoader.php @@ -144,7 +144,7 @@ protected function _assertFieldsPresent(Query $fetchQuery, array $key): void * @param array $options the options to use for getting the link field. * @return array|string */ - protected function _linkField(array $options) + protected function _linkField(array $options): array|string { $links = []; $name = $this->junctionAssociationName; diff --git a/AssociationCollection.php b/AssociationCollection.php index 416221ea..af3f4b58 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -159,7 +159,7 @@ public function keys(): array * @return array<\Cake\ORM\Association> An array of Association objects. * @since 3.5.3 */ - public function getByType($class): array + public function getByType(array|string $class): array { $class = array_map('strtolower', (array)$class); @@ -353,7 +353,7 @@ public function cascadeDelete(EntityInterface $entity, array $options): bool * @param array|bool $keys the list of association names to normalize * @return array */ - public function normalizeKeys($keys): array + public function normalizeKeys(array|bool $keys): array { if ($keys === true) { $keys = $this->keys(); diff --git a/AssociationsNormalizerTrait.php b/AssociationsNormalizerTrait.php index 5b27a7db..45a4226e 100644 --- a/AssociationsNormalizerTrait.php +++ b/AssociationsNormalizerTrait.php @@ -29,7 +29,7 @@ trait AssociationsNormalizerTrait * @param array|string $associations The array of included associations. * @return array An array having dot notation transformed into nested arrays */ - protected function _normalizeAssociations($associations): array + protected function _normalizeAssociations(array|string $associations): array { $result = []; foreach ((array)$associations as $table => $options) { diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 22a8562f..d4490367 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -120,7 +120,7 @@ class CounterCacheBehavior extends Behavior * @param \ArrayObject $options The options for the query * @return void */ - public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options) + public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void { if (isset($options['ignoreCounterCache']) && $options['ignoreCounterCache'] === true) { return; @@ -178,7 +178,7 @@ public function afterSave(EventInterface $event, EntityInterface $entity, ArrayO * @param \ArrayObject $options The options for the query * @return void */ - public function afterDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options) + public function afterDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options): void { if (isset($options['ignoreCounterCache']) && $options['ignoreCounterCache'] === true) { return; @@ -219,6 +219,7 @@ protected function _processAssociation( array $settings ): void { $foreignKeys = (array)$assoc->getForeignKey(); + /** @psalm-suppress InvalidScalarArgument getForeignKey() returns false */ $countConditions = $entity->extract($foreignKeys); foreach ($countConditions as $field => $value) { @@ -231,6 +232,7 @@ protected function _processAssociation( $primaryKeys = (array)$assoc->getBindingKey(); $updateConditions = array_combine($primaryKeys, $countConditions); + /** @psalm-suppress InvalidScalarArgument getForeignKey() returns false */ $countOriginalConditions = $entity->extractOriginalChanged($foreignKeys); if ($countOriginalConditions !== []) { $updateOriginalConditions = array_combine($primaryKeys, $countOriginalConditions); @@ -279,7 +281,7 @@ protected function _processAssociation( * @param array $conditions Conditions to update count. * @return bool True if the count update should happen, false otherwise. */ - protected function _shouldUpdateCount(array $conditions) + protected function _shouldUpdateCount(array $conditions): bool { return !empty(array_filter($conditions, function ($value) { return $value !== null; diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index 2a9f0e46..af0d88cb 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -97,7 +97,7 @@ public function __construct(Table $table, array $config = []) * * @return void */ - protected function setupAssociations() + protected function setupAssociations(): void { $fields = $this->_config['fields']; $table = $this->_config['translationTable']; @@ -165,7 +165,7 @@ protected function setupAssociations() * @param \ArrayObject $options The options for the query * @return void */ - public function beforeFind(EventInterface $event, Query $query, ArrayObject $options) + public function beforeFind(EventInterface $event, Query $query, ArrayObject $options): void { $locale = Hash::get($options, 'locale', $this->getLocale()); @@ -230,7 +230,7 @@ public function beforeFind(EventInterface $event, Query $query, ArrayObject $opt * @param \ArrayObject $options the options passed to the save method * @return void */ - public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options) + public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void { $locale = $entity->get('_locale') ?: $this->getLocale(); $newOptions = [$this->translationTable->getAlias() => ['validate' => false]]; @@ -351,11 +351,11 @@ public function translationField(string $field): string * Modifies the results from a table find in order to merge the translated fields * into each entity for a given locale. * - * @param \Cake\Datasource\ResultSetInterface $results Results to map. + * @param \Cake\Collection\CollectionInterface $results Results to map. * @param string $locale Locale string * @return \Cake\Collection\CollectionInterface */ - protected function rowMapper($results, $locale) + protected function rowMapper(CollectionInterface $results, string $locale): CollectionInterface { return $results->map(function ($row) use ($locale) { /** @var \Cake\Datasource\EntityInterface|array|null $row */ @@ -395,10 +395,10 @@ protected function rowMapper($results, $locale) * Modifies the results from a table find in order to merge full translation * records into each entity under the `_translations` key. * - * @param \Cake\Datasource\ResultSetInterface $results Results to modify. + * @param \Cake\Collection\CollectionInterface $results Results to modify. * @return \Cake\Collection\CollectionInterface */ - public function groupTranslations($results): CollectionInterface + public function groupTranslations(CollectionInterface $results): CollectionInterface { return $results->map(function ($row) { if (!$row instanceof EntityInterface) { @@ -438,7 +438,7 @@ public function groupTranslations($results): CollectionInterface * @param \Cake\Datasource\EntityInterface $entity Entity * @return void */ - protected function bundleTranslatedFields($entity) + protected function bundleTranslatedFields(EntityInterface $entity): void { $translations = (array)$entity->get('_translations'); @@ -492,7 +492,7 @@ protected function bundleTranslatedFields($entity) * @param array $ruleSet An array of array of conditions to be used for finding each * @return array */ - protected function findExistingTranslations($ruleSet) + protected function findExistingTranslations(array $ruleSet): array { $association = $this->table->getAssociation($this->translationTable->getAlias()); diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 9e1cad7b..72d24db9 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -98,7 +98,7 @@ public function __construct(Table $table, array $config = []) * * @return void */ - protected function setupAssociations() + protected function setupAssociations(): void { $config = $this->getConfig(); @@ -122,7 +122,7 @@ protected function setupAssociations() * @param \ArrayObject $options The options for the query. * @return void */ - public function beforeFind(EventInterface $event, Query $query, ArrayObject $options) + public function beforeFind(EventInterface $event, Query $query, ArrayObject $options): void { $locale = Hash::get($options, 'locale', $this->getLocale()); $config = $this->getConfig(); @@ -205,7 +205,7 @@ protected function setupHasOneAssociation(string $locale, ArrayObject $options): * @param array $config The config to use for adding fields. * @return bool Whether a join to the translation table is required. */ - protected function addFieldsToQuery($query, array $config) + protected function addFieldsToQuery(Query $query, array $config): bool { if ($query->isAutoFieldsEnabled()) { return true; @@ -247,7 +247,7 @@ protected function addFieldsToQuery($query, array $config) * @param array $config The config to use for adding fields. * @return bool Whether a join to the translation table is required. */ - protected function iterateClause($query, $name = '', $config = []): bool + protected function iterateClause(Query $query, string $name = '', array $config = []): bool { $clause = $query->clause($name); if (!$clause || !$clause->count()) { @@ -292,7 +292,7 @@ function ($c, &$field) use ($fields, $alias, $mainTableAlias, $mainTableFields, * @param array $config The config to use for adding fields. * @return bool Whether a join to the translation table is required. */ - protected function traverseClause($query, $name = '', $config = []): bool + protected function traverseClause(Query $query, string $name = '', array $config = []): bool { $clause = $query->clause($name); if (!$clause || !$clause->count()) { @@ -306,7 +306,7 @@ protected function traverseClause($query, $name = '', $config = []): bool $joinRequired = false; $clause->traverse( - function ($expression) use ($fields, $alias, $mainTableAlias, $mainTableFields, &$joinRequired) { + function ($expression) use ($fields, $alias, $mainTableAlias, $mainTableFields, &$joinRequired): void { if (!($expression instanceof FieldInterface)) { return; } @@ -340,7 +340,7 @@ function ($expression) use ($fields, $alias, $mainTableAlias, $mainTableFields, * @param \ArrayObject $options the options passed to the save method. * @return void */ - public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options) + public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void { $locale = $entity->get('_locale') ?: $this->getLocale(); $newOptions = [$this->translationTable->getAlias() => ['validate' => false]]; @@ -464,11 +464,11 @@ public function translationField(string $field): string * Modifies the results from a table find in order to merge the translated * fields into each entity for a given locale. * - * @param \Cake\Datasource\ResultSetInterface $results Results to map. + * @param \Cake\Collection\CollectionInterface $results Results to map. * @param string $locale Locale string * @return \Cake\Collection\CollectionInterface */ - protected function rowMapper($results, $locale) + protected function rowMapper(CollectionInterface $results, string $locale): CollectionInterface { $allowEmpty = $this->_config['allowEmptyTranslations']; @@ -529,10 +529,10 @@ protected function rowMapper($results, $locale) * Modifies the results from a table find in order to merge full translation * records into each entity under the `_translations` key. * - * @param \Cake\Datasource\ResultSetInterface $results Results to modify. + * @param \Cake\Collection\CollectionInterface $results Results to modify. * @return \Cake\Collection\CollectionInterface */ - public function groupTranslations($results): CollectionInterface + public function groupTranslations(CollectionInterface $results): CollectionInterface { return $results->map(function ($row) { $translations = (array)$row['_i18n']; @@ -564,7 +564,7 @@ public function groupTranslations($results): CollectionInterface * @param \Cake\Datasource\EntityInterface $entity Entity. * @return void */ - protected function bundleTranslatedFields($entity) + protected function bundleTranslatedFields(EntityInterface $entity): void { $translations = (array)$entity->get('_translations'); @@ -593,7 +593,7 @@ protected function bundleTranslatedFields($entity) * * @return array */ - protected function mainFields() + protected function mainFields(): array { $fields = $this->getConfig('mainTableFields'); @@ -613,7 +613,7 @@ protected function mainFields() * * @return array */ - protected function translatedFields() + protected function translatedFields(): array { $fields = $this->getConfig('fields'); diff --git a/Behavior/Translate/TranslateStrategyInterface.php b/Behavior/Translate/TranslateStrategyInterface.php index 9f8d9a20..d5ce109c 100644 --- a/Behavior/Translate/TranslateStrategyInterface.php +++ b/Behavior/Translate/TranslateStrategyInterface.php @@ -19,6 +19,7 @@ use ArrayObject; use Cake\Collection\CollectionInterface; use Cake\Datasource\EntityInterface; +use Cake\Datasource\ResultSetInterface; use Cake\Event\EventInterface; use Cake\ORM\PropertyMarshalInterface; use Cake\ORM\Query; @@ -82,7 +83,7 @@ public function translationField(string $field): string; * @param \Cake\Datasource\ResultSetInterface $results Results to modify. * @return \Cake\Collection\CollectionInterface */ - public function groupTranslations($results): CollectionInterface; + public function groupTranslations(ResultSetInterface $results): CollectionInterface; /** * Callback method that listens to the `beforeFind` event in the bound @@ -94,7 +95,7 @@ public function groupTranslations($results): CollectionInterface; * @param \ArrayObject $options The options for the query * @return void */ - public function beforeFind(EventInterface $event, Query $query, ArrayObject $options); + public function beforeFind(EventInterface $event, Query $query, ArrayObject $options): void; /** * Modifies the entity before it is saved so that translated fields are persisted @@ -105,7 +106,7 @@ public function beforeFind(EventInterface $event, Query $query, ArrayObject $opt * @param \ArrayObject $options the options passed to the save method * @return void */ - public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options); + public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void; /** * Unsets the temporary `_i18n` property after the entity has been saved @@ -114,5 +115,5 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved * @return void */ - public function afterSave(EventInterface $event, EntityInterface $entity); + public function afterSave(EventInterface $event, EntityInterface $entity): void; } diff --git a/Behavior/Translate/TranslateStrategyTrait.php b/Behavior/Translate/TranslateStrategyTrait.php index 56e05bec..d0ee5c56 100644 --- a/Behavior/Translate/TranslateStrategyTrait.php +++ b/Behavior/Translate/TranslateStrategyTrait.php @@ -104,7 +104,7 @@ public function getLocale(): string * @param \Cake\Datasource\EntityInterface $entity The entity to check for empty translations fields inside. * @return void */ - protected function unsetEmptyFields($entity) + protected function unsetEmptyFields(EntityInterface $entity): void { /** @var array<\Cake\ORM\Entity> $translations */ $translations = (array)$entity->get('_translations'); @@ -193,7 +193,7 @@ public function buildMarshalMap(Marshaller $marshaller, array $map, array $optio * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved * @return void */ - public function afterSave(EventInterface $event, EntityInterface $entity) + public function afterSave(EventInterface $event, EntityInterface $entity): void { $entity->unset('_i18n'); } diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 5daecbb6..3890d3f5 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -135,7 +135,7 @@ public function initialize(array $config): void * @since 4.0.0 * @psalm-param class-string<\Cake\ORM\Behavior\Translate\TranslateStrategyInterface> $class */ - public static function setDefaultStrategyClass(string $class) + public static function setDefaultStrategyClass(string $class): void { static::$defaultStrategyClass = $class; } @@ -173,7 +173,7 @@ public function getStrategy(): TranslateStrategyInterface * @return \Cake\ORM\Behavior\Translate\TranslateStrategyInterface * @since 4.0.0 */ - protected function createStrategy() + protected function createStrategy(): TranslateStrategyInterface { $config = array_diff_key( $this->_config, @@ -333,7 +333,7 @@ public function findTranslations(Query $query, array $options): Query * @param array $args Method arguments. * @return mixed */ - public function __call($method, $args) + public function __call(string $method, array $args): mixed { return $this->strategy->{$method}(...$args); } diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 16fccb31..f213d5b5 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -96,7 +96,7 @@ public function initialize(array $config): void * @return void * @throws \RuntimeException if the parent to set for the node is invalid */ - public function beforeSave(EventInterface $event, EntityInterface $entity) + public function beforeSave(EventInterface $event, EntityInterface $entity): void { $isNew = $entity->isNew(); $config = $this->getConfig(); @@ -164,7 +164,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity) * @param \Cake\Datasource\EntityInterface $entity the entity that is going to be saved * @return void */ - public function afterSave(EventInterface $event, EntityInterface $entity) + public function afterSave(EventInterface $event, EntityInterface $entity): void { if (!$this->_config['level'] || $entity->isNew()) { return; @@ -217,7 +217,7 @@ protected function _setChildrenLevel(EntityInterface $entity): void * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved * @return void */ - public function beforeDelete(EventInterface $event, EntityInterface $entity) + public function beforeDelete(EventInterface $event, EntityInterface $entity): void { $config = $this->getConfig(); $this->_ensureFields($entity); @@ -251,7 +251,7 @@ public function beforeDelete(EventInterface $event, EntityInterface $entity) * @return void * @throws \RuntimeException if the parent to set to the entity is not valid */ - protected function _setParent(EntityInterface $entity, $parent): void + protected function _setParent(EntityInterface $entity, mixed $parent): void { $config = $this->getConfig(); $parentNode = $this->_getNode($parent); @@ -547,7 +547,7 @@ public function formatTreeList(Query $query, array $options = []): Query * @return \Cake\Datasource\EntityInterface|false the node after being removed from the tree or * false on error */ - public function removeFromTree(EntityInterface $node) + public function removeFromTree(EntityInterface $node): EntityInterface|false { return $this->_table->getConnection()->transactional(function () use ($node) { $this->_ensureFields($node); @@ -563,7 +563,7 @@ public function removeFromTree(EntityInterface $node) * @return \Cake\Datasource\EntityInterface|false the node after being removed from the tree or * false on error */ - protected function _removeFromTree(EntityInterface $node) + protected function _removeFromTree(EntityInterface $node): EntityInterface|false { $config = $this->getConfig(); $left = $node->get($config['left']); @@ -608,7 +608,7 @@ protected function _removeFromTree(EntityInterface $node) * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found * @return \Cake\Datasource\EntityInterface|false $node The node after being moved or false on failure */ - public function moveUp(EntityInterface $node, $number = 1) + public function moveUp(EntityInterface $node, int|bool $number = 1): EntityInterface|false { if ($number < 1) { return false; @@ -629,7 +629,7 @@ public function moveUp(EntityInterface $node, $number = 1) * @return \Cake\Datasource\EntityInterface $node The node after being moved or false on failure * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found */ - protected function _moveUp(EntityInterface $node, $number): EntityInterface + protected function _moveUp(EntityInterface $node, int|bool $number): EntityInterface { $config = $this->getConfig(); [$parent, $left, $right] = [$config['parent'], $config['left'], $config['right']]; @@ -702,7 +702,7 @@ protected function _moveUp(EntityInterface $node, $number): EntityInterface * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found * @return \Cake\Datasource\EntityInterface|false the entity after being moved or false on failure */ - public function moveDown(EntityInterface $node, $number = 1) + public function moveDown(EntityInterface $node, int|bool $number = 1): EntityInterface|false { if ($number < 1) { return false; @@ -723,7 +723,7 @@ public function moveDown(EntityInterface $node, $number = 1) * @return \Cake\Datasource\EntityInterface $node The node after being moved or false on failure * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found */ - protected function _moveDown(EntityInterface $node, $number): EntityInterface + protected function _moveDown(EntityInterface $node, int|bool $number): EntityInterface { $config = $this->getConfig(); [$parent, $left, $right] = [$config['parent'], $config['left'], $config['right']]; @@ -793,7 +793,7 @@ protected function _moveDown(EntityInterface $node, $number): EntityInterface * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found * @psalm-suppress InvalidReturnType */ - protected function _getNode($id): EntityInterface + protected function _getNode(mixed $id): EntityInterface { $config = $this->getConfig(); [$parent, $left, $right] = [$config['parent'], $config['left'], $config['right']]; @@ -837,7 +837,7 @@ public function recover(): void * @param int $level Node level * @return int The next lftRght value */ - protected function _recoverTree(int $lftRght = 1, $parentId = null, $level = 0): int + protected function _recoverTree(int $lftRght = 1, mixed $parentId = null, int $level = 0): int { $config = $this->getConfig(); [$parent, $left, $right] = [$config['parent'], $config['left'], $config['right']]; @@ -996,7 +996,7 @@ protected function _getPrimaryKey(): string * @param \Cake\Datasource\EntityInterface|string|int $entity The entity or primary key get the level of. * @return int|false Integer of the level or false if the node does not exist. */ - public function getLevel($entity) + public function getLevel(EntityInterface|string|int $entity): int|false { $primaryKey = $this->_getPrimaryKey(); $id = $entity; diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index c817c562..5384a70c 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -247,7 +247,7 @@ public function hasFinder(string $method): bool * @return mixed The return value depends on the underlying behavior method. * @throws \BadMethodCallException When the method is unknown. */ - public function call(string $method, array $args = []) + public function call(string $method, array $args = []): mixed { $method = strtolower($method); if ($this->hasMethod($method) && $this->has($this->_methodMap[$method][0])) { diff --git a/EagerLoadable.php b/EagerLoadable.php index 98aba9c0..352d7ea3 100644 --- a/EagerLoadable.php +++ b/EagerLoadable.php @@ -16,6 +16,8 @@ */ namespace Cake\ORM; +use RuntimeException; + /** * Represents a single level in the associations tree to be eagerly loaded * for a specific query. This contains all the information required to @@ -173,7 +175,7 @@ public function associations(): array public function instance(): Association { if ($this->_instance === null) { - throw new \RuntimeException('No instance set.'); + throw new RuntimeException('No instance set.'); } return $this->_instance; diff --git a/EagerLoader.php b/EagerLoader.php index 90084867..c33766cd 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -132,7 +132,7 @@ class EagerLoader * @return array Containments. * @throws \InvalidArgumentException When using $queryBuilder with an array of $associations */ - public function contain($associations, ?callable $queryBuilder = null): array + public function contain(array|string $associations, ?callable $queryBuilder = null): array { if ($queryBuilder) { if (!is_string($associations)) { @@ -782,7 +782,7 @@ public function addToJoinsMap( * @param \Cake\Database\StatementInterface $statement The statement to work on * @return array */ - protected function _collectKeys(array $external, Query $query, $statement): array + protected function _collectKeys(array $external, Query $query, StatementInterface $statement): array { $collectKeys = []; foreach ($external as $meta) { @@ -799,6 +799,7 @@ protected function _collectKeys(array $external, Query $query, $statement): arra $alias = $source->getAlias(); $pkFields = []; foreach ($keys as $key) { + /** @psalm-suppress PossiblyFalseArgument getForeignKey() returns false */ $pkFields[] = key($query->aliasField($key, $alias)); } $collectKeys[$meta->aliasPath()] = [$alias, $pkFields, count($pkFields) === 1]; diff --git a/Exception/PersistenceFailedException.php b/Exception/PersistenceFailedException.php index dabd95e8..870c42e8 100644 --- a/Exception/PersistenceFailedException.php +++ b/Exception/PersistenceFailedException.php @@ -45,8 +45,12 @@ class PersistenceFailedException extends CakeException * @param int $code The code of the error, is also the HTTP status code for the error. * @param \Throwable|null $previous the previous exception. */ - public function __construct(EntityInterface $entity, $message, ?int $code = null, ?Throwable $previous = null) - { + public function __construct( + EntityInterface $entity, + array|string $message, + ?int $code = null, + ?Throwable $previous = null + ) { $this->_entity = $entity; if (is_array($message)) { $errors = []; diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 2fd6cb9e..0452692c 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -42,7 +42,7 @@ class LazyEagerLoader * @param \Cake\ORM\Table $source The table to use for fetching the top level entities * @return \Cake\Datasource\EntityInterface|array<\Cake\Datasource\EntityInterface> */ - public function loadInto($entities, array $contain, Table $source) + public function loadInto(EntityInterface|array $entities, array $contain, Table $source): EntityInterface|array { $returnSingle = false; @@ -141,7 +141,7 @@ protected function _getPropertyMap(Table $source, array $associations): array * @param \Cake\ORM\Table $source The table where the entities came from * @return array */ - protected function _injectResults(iterable $objects, $results, array $associations, Table $source): array + protected function _injectResults(iterable $objects, Query $results, array $associations, Table $source): array { $injected = []; $properties = $this->_getPropertyMap($source, $associations); diff --git a/Locator/LocatorInterface.php b/Locator/LocatorInterface.php index 2ffb7b6a..7ec73c3a 100644 --- a/Locator/LocatorInterface.php +++ b/Locator/LocatorInterface.php @@ -44,7 +44,7 @@ public function getConfig(?string $alias = null): array; * @throws \RuntimeException When you attempt to configure an existing * table instance. */ - public function setConfig($alias, $options = null); + public function setConfig(array|string $alias, ?array $options = null); /** * Get a table instance from the registry. diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 065d8582..a7a43337 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -121,7 +121,7 @@ public function allowFallbackClass(bool $allow) * @return $this * @psalm-param class-string<\Cake\ORM\Table> $className */ - public function setFallbackClassName($className) + public function setFallbackClassName(string $className) { $this->fallbackClassName = $className; diff --git a/Marshaller.php b/Marshaller.php index dda177cb..096007a7 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -303,7 +303,7 @@ protected function _prepareDataAndOptions(array $data, array $options): array * @param array $options List of options. * @return \Cake\Datasource\EntityInterface|array<\Cake\Datasource\EntityInterface>|null */ - protected function _marshalAssociation(Association $assoc, $value, array $options) + protected function _marshalAssociation(Association $assoc, mixed $value, array $options): EntityInterface|array|null { if (!is_array($value)) { return null; @@ -734,14 +734,18 @@ public function mergeMany(iterable $entities, array $data, array $options = []): /** * Creates a new sub-marshaller and merges the associated data. * - * @param \Cake\Datasource\EntityInterface|array<\Cake\Datasource\EntityInterface> $original The original entity + * @param \Cake\Datasource\EntityInterface|array<\Cake\Datasource\EntityInterface>|null $original The original entity * @param \Cake\ORM\Association $assoc The association to merge * @param mixed $value The array of data to hydrate. If not an array, this method will return null. * @param array $options List of options. * @return \Cake\Datasource\EntityInterface|array<\Cake\Datasource\EntityInterface>|null */ - protected function _mergeAssociation($original, Association $assoc, $value, array $options) - { + protected function _mergeAssociation( + EntityInterface|array|null $original, + Association $assoc, + mixed $value, + array $options + ): EntityInterface|array|null { if (!$original) { return $this->_marshalAssociation($assoc, $value, $options); } diff --git a/Query.php b/Query.php index e4545b7f..95f5b393 100644 --- a/Query.php +++ b/Query.php @@ -26,7 +26,6 @@ use Cake\Datasource\QueryInterface; use Cake\Datasource\QueryTrait; use Cake\Datasource\ResultSetInterface; -use InvalidArgumentException; use JsonSerializable; use RuntimeException; use Traversable; @@ -218,18 +217,13 @@ public function select( * @param array $excludedFields The un-aliased column names you do not want selected from $table * @param bool $overwrite Whether to reset/remove previous selected fields * @return $this - * @throws \InvalidArgumentException If Association|Table is not passed in first argument */ - public function selectAllExcept($table, array $excludedFields, bool $overwrite = false) + public function selectAllExcept(Table|Association $table, array $excludedFields, bool $overwrite = false) { if ($table instanceof Association) { $table = $table->getTarget(); } - if (!($table instanceof Table)) { - throw new InvalidArgumentException('You must provide either an Association or a Table object'); - } - $fields = array_diff($table->getSchema()->columns(), $excludedFields); if ($this->aliasingEnabled) { $fields = $this->aliasFields($fields); @@ -406,7 +400,7 @@ public function getEagerLoader(): EagerLoader * defaults to merging previous list with the new one. * @return $this */ - public function contain($associations, $override = false) + public function contain(array|string $associations, callable|bool $override = false) { $loader = $this->getEagerLoader(); if ($override === true) { @@ -836,7 +830,7 @@ public function applyOptions(array $options) * * @return static */ - public function cleanCopy() + public function cleanCopy(): static { $clone = clone $this; $clone->triggerBeforeFind(); @@ -1232,7 +1226,7 @@ protected function _dirty(): void * @param \Cake\Database\ExpressionInterface|string|null $table Unused parameter. * @return $this */ - public function update($table = null) + public function update(ExpressionInterface|string|null $table = null) { if (!$table) { $repository = $this->getRepository(); @@ -1288,7 +1282,7 @@ public function insert(array $columns, array $types = []) * @param \Cake\ORM\Table $table The table this query is starting on * @return static */ - public static function subquery(Table $table) + public static function subquery(Table $table): static { $query = new static($table->getConnection(), $table); $query->aliasingEnabled = false; diff --git a/ResultSet.php b/ResultSet.php index bf37a462..7dd34685 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -22,7 +22,6 @@ use Cake\Database\StatementInterface; use Cake\Datasource\EntityInterface; use Cake\Datasource\ResultSetInterface; -use ReturnTypeWillChange; use SplFixedArray; /** @@ -52,7 +51,7 @@ class ResultSet implements ResultSetInterface /** * Last record fetched from the statement * - * @var object|array + * @var \Cake\Datasource\EntityInterface|array */ protected $_current; @@ -183,10 +182,9 @@ public function __construct(Query $query, StatementInterface $statement) * * Part of Iterator interface. * - * @return object|array + * @return \Cake\Datasource\EntityInterface|array */ - #[ReturnTypeWillChange] - public function current() + public function current(): EntityInterface|array { return $this->_current; } @@ -414,7 +412,7 @@ protected function _calculateColumnMap(Query $query): void * * @return mixed */ - protected function _fetchResult() + protected function _fetchResult(): mixed { if ($this->_statement === null) { return false; @@ -434,7 +432,7 @@ protected function _fetchResult() * @param array $row Array containing columns and values or false if there is no results * @return \Cake\Datasource\EntityInterface|array Results */ - protected function _groupResult(array $row) + protected function _groupResult(array $row): EntityInterface|array { $defaultAlias = $this->_defaultAlias; $results = $presentAliases = []; @@ -546,7 +544,7 @@ protected function _groupResult(array $row) * * @return array */ - public function __debugInfo() + public function __debugInfo(): array { return [ 'items' => $this->toArray(), diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 20dc6312..66dcd867 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -61,7 +61,7 @@ class ExistsIn * Options 'allowNullableNulls' will make the rule pass if given foreign keys are set to `null`. * Notice: allowNullableNulls cannot pass by database columns set to `NOT NULL`. */ - public function __construct($fields, $repository, array $options = []) + public function __construct(array|string $fields, Table|Association|string $repository, array $options = []) { $options += ['allowNullableNulls' => false]; $this->_options = $options; diff --git a/Rule/LinkConstraint.php b/Rule/LinkConstraint.php index eca0e92c..4cbe4a8c 100644 --- a/Rule/LinkConstraint.php +++ b/Rule/LinkConstraint.php @@ -19,6 +19,8 @@ use Cake\Datasource\EntityInterface; use Cake\ORM\Association; use Cake\ORM\Table; +use InvalidArgumentException; +use RuntimeException; /** * Checks whether links to a given association exist / do not exist. @@ -60,20 +62,10 @@ class LinkConstraint * @param string $requiredLinkStatus The link status that is required to be present in order for the check to * succeed. */ - public function __construct($association, string $requiredLinkStatus) + public function __construct(Association|string $association, string $requiredLinkStatus) { - if ( - !is_string($association) && - !($association instanceof Association) - ) { - throw new \InvalidArgumentException(sprintf( - 'Argument 1 is expected to be of type `\Cake\ORM\Association|string`, `%s` given.', - get_debug_type($association) - )); - } - if (!in_array($requiredLinkStatus, [static::STATUS_LINKED, static::STATUS_NOT_LINKED], true)) { - throw new \InvalidArgumentException( + throw new InvalidArgumentException( 'Argument 2 is expected to match one of the `\Cake\ORM\Rule\LinkConstraint::STATUS_*` constants.' ); } @@ -95,7 +87,7 @@ public function __invoke(EntityInterface $entity, array $options): bool { $table = $options['repository'] ?? null; if (!($table instanceof Table)) { - throw new \InvalidArgumentException( + throw new InvalidArgumentException( 'Argument 2 is expected to have a `repository` key that holds an instance of `\Cake\ORM\Table`.' ); } @@ -149,7 +141,7 @@ protected function _aliasFields(array $fields, Table $source): array protected function _buildConditions(array $fields, array $values): array { if (count($fields) !== count($values)) { - throw new \InvalidArgumentException(sprintf( + throw new InvalidArgumentException(sprintf( 'The number of fields is expected to match the number of values, got %d field(s) and %d value(s).', count($fields), count($values) @@ -172,7 +164,7 @@ protected function _countLinks(Association $association, EntityInterface $entity $primaryKey = (array)$source->getPrimaryKey(); if (!$entity->has($primaryKey)) { - throw new \RuntimeException(sprintf( + throw new RuntimeException(sprintf( 'LinkConstraint rule on `%s` requires all primary key values for building the counting ' . 'conditions, expected values for `(%s)`, got `(%s)`.', $source->getAlias(), diff --git a/RulesChecker.php b/RulesChecker.php index 1f2c5be6..261e69c1 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -52,7 +52,7 @@ class RulesChecker extends BaseRulesChecker * also be an array of options. When an array, the 'message' key can be used to provide a message. * @return \Cake\Datasource\RuleInvoker */ - public function isUnique(array $fields, $message = null): RuleInvoker + public function isUnique(array $fields, array|string|null $message = null): RuleInvoker { $options = is_array($message) ? $message : ['message' => $message]; $message = $options['message'] ?? null; @@ -96,8 +96,11 @@ public function isUnique(array $fields, $message = null): RuleInvoker * also be an array of options. When an array, the 'message' key can be used to provide a message. * @return \Cake\Datasource\RuleInvoker */ - public function existsIn($field, $table, $message = null): RuleInvoker - { + public function existsIn( + array|string $field, + Table|Association|string $table, + array|string|null $message = null + ): RuleInvoker { $options = []; if (is_array($message)) { $options = $message + ['message' => null]; @@ -137,8 +140,11 @@ public function existsIn($field, $table, $message = null): RuleInvoker * @return \Cake\Datasource\RuleInvoker * @since 4.0.0 */ - public function isLinkedTo($association, ?string $field = null, ?string $message = null): RuleInvoker - { + public function isLinkedTo( + Association|string $association, + ?string $field = null, + ?string $message = null + ): RuleInvoker { return $this->_addLinkConstraintRule( $association, $field, @@ -167,8 +173,11 @@ public function isLinkedTo($association, ?string $field = null, ?string $message * @return \Cake\Datasource\RuleInvoker * @since 4.0.0 */ - public function isNotLinkedTo($association, ?string $field = null, ?string $message = null): RuleInvoker - { + public function isNotLinkedTo( + Association|string $association, + ?string $field = null, + ?string $message = null + ): RuleInvoker { return $this->_addLinkConstraintRule( $association, $field, @@ -196,7 +205,7 @@ public function isNotLinkedTo($association, ?string $field = null, ?string $mess * @see \Cake\ORM\Rule\LinkConstraint::STATUS_NOT_LINKED */ protected function _addLinkConstraintRule( - $association, + Association|string $association, ?string $errorField, ?string $message, string $linkStatus, @@ -208,7 +217,7 @@ protected function _addLinkConstraintRule( if ($errorField === null) { $errorField = $association->getProperty(); } - } elseif (is_string($association)) { + } else { $associationAlias = $association; if ($errorField === null) { @@ -220,11 +229,6 @@ protected function _addLinkConstraintRule( $errorField = Inflector::underscore($association); } } - } else { - throw new \InvalidArgumentException(sprintf( - 'Argument 1 is expected to be of type `\Cake\ORM\Association|string`, `%s` given.', - get_debug_type($association) - )); } if (!$message) { diff --git a/SaveOptionsBuilder.php b/SaveOptionsBuilder.php index 8da1e144..52ae2637 100644 --- a/SaveOptionsBuilder.php +++ b/SaveOptionsBuilder.php @@ -83,7 +83,7 @@ public function parseArrayOptions(array $array) * @param array|string $associated String or array of associations. * @return $this */ - public function associated($associated) + public function associated(array|string $associated) { $associated = $this->_normalizeAssociations($associated); $this->_associated($this->_table, $associated); @@ -214,7 +214,7 @@ public function toArray(): array * @param mixed $value Option value. * @return $this */ - public function set(string $option, $value) + public function set(string $option, mixed $value) { if (method_exists($this, $option)) { return $this->{$option}($value); diff --git a/Table.php b/Table.php index 5ecbd83a..cf969b8d 100644 --- a/Table.php +++ b/Table.php @@ -29,6 +29,7 @@ use Cake\Datasource\EntityInterface; use Cake\Datasource\Exception\InvalidPrimaryKeyException; use Cake\Datasource\RepositoryInterface; +use Cake\Datasource\ResultSetInterface; use Cake\Datasource\RulesAwareTrait; use Cake\Event\EventDispatcherInterface; use Cake\Event\EventDispatcherTrait; @@ -532,7 +533,7 @@ public function getSchema(): TableSchemaInterface * @param \Cake\Database\Schema\TableSchemaInterface|array $schema Schema to be used for this table * @return $this */ - public function setSchema($schema) + public function setSchema(TableSchemaInterface|array $schema) { if (is_array($schema)) { $constraints = []; @@ -638,7 +639,7 @@ public function hasField(string $field): bool * @param array|string $key Sets a new name to be used as primary key * @return $this */ - public function setPrimaryKey($key) + public function setPrimaryKey(array|string $key) { $this->_primaryKey = $key; @@ -650,7 +651,7 @@ public function setPrimaryKey($key) * * @return array|string */ - public function getPrimaryKey() + public function getPrimaryKey(): array|string { if ($this->_primaryKey === null) { $key = $this->getSchema()->getPrimaryKey(); @@ -669,7 +670,7 @@ public function getPrimaryKey() * @param array|string $field Name to be used as display field. * @return $this */ - public function setDisplayField($field) + public function setDisplayField(array|string $field) { $this->_displayField = $field; @@ -681,7 +682,7 @@ public function setDisplayField($field) * * @return array|string|null */ - public function getDisplayField() + public function getDisplayField(): array|string|null { if ($this->_displayField === null) { $schema = $this->getSchema(); @@ -1554,7 +1555,7 @@ public function get($primaryKey, array $options = []): EntityInterface * @param bool $atomic Whether to execute the worker inside a database transaction. * @return mixed */ - protected function _executeTransaction(callable $worker, bool $atomic = true) + protected function _executeTransaction(callable $worker, bool $atomic = true): mixed { if ($atomic) { return $this->getConnection()->transactional(function () use ($worker) { @@ -1611,8 +1612,11 @@ protected function _transactionCommitted(bool $atomic, bool $primary): bool * @return \Cake\Datasource\EntityInterface An entity. * @throws \Cake\ORM\Exception\PersistenceFailedException When the entity couldn't be saved */ - public function findOrCreate($search, ?callable $callback = null, $options = []): EntityInterface - { + public function findOrCreate( + Query|callable|array $search, + ?callable $callback = null, + array $options = [] + ): EntityInterface { $options = new ArrayObject($options + [ 'atomic' => true, 'defaults' => true, @@ -1642,8 +1646,11 @@ public function findOrCreate($search, ?callable $callback = null, $options = []) * @throws \Cake\ORM\Exception\PersistenceFailedException When the entity couldn't be saved * @throws \InvalidArgumentException */ - protected function _processFindOrCreate($search, ?callable $callback = null, $options = []) - { + protected function _processFindOrCreate( + Query|callable|array $search, + ?callable $callback = null, + array $options = [] + ): EntityInterface|array { $query = $this->_getFindOrCreateQuery($search); $row = $query->first(); @@ -1676,20 +1683,15 @@ protected function _processFindOrCreate($search, ?callable $callback = null, $op * @param \Cake\ORM\Query|callable|array $search The criteria to find existing records by. * @return \Cake\ORM\Query */ - protected function _getFindOrCreateQuery($search): Query + protected function _getFindOrCreateQuery(Query|callable|array $search): Query { if (is_callable($search)) { $query = $this->find(); $search($query); } elseif (is_array($search)) { $query = $this->find()->where($search); - } elseif ($search instanceof Query) { - $query = $search; } else { - throw new InvalidArgumentException(sprintf( - 'Search criteria must be an array, callable or Query. Got "%s"', - get_debug_type($search) - )); + $query = $search; } return $query; @@ -1717,7 +1719,15 @@ public function subquery(): Query } /** - * @inheritDoc + * Update all matching records. + * + * Sets the $fields to the provided values based on $conditions. + * This method will *not* trigger beforeSave/afterSave events. If you need those + * first load a collection of records and update them. + * + * @param \Cake\Database\Expression\QueryExpression|\Closure|array|string $fields A hash of field => new value. + * @param \Cake\Database\Expression\QueryExpression|\Closure|array|string|null $conditions Conditions to be used, accepts anything Query::where() + * @return int Count Returns the affected rows. */ public function updateAll( QueryExpression|Closure|array|string $fields, @@ -1734,7 +1744,18 @@ public function updateAll( } /** - * @inheritDoc + * Deletes all records matching the provided conditions. + * + * This method will *not* trigger beforeDelete/afterDelete events. If you + * need those first load a collection of records and delete them. + * + * This method will *not* execute on associations' `cascade` attribute. You should + * use database foreign keys + ON CASCADE rules if you need cascading deletes combined + * with this method. + * + * @param \Cake\Database\Expression\QueryExpression|\Closure|array|string|null $conditions Conditions to be used, accepts anything Query::where() + * can take. + * @return int Returns the number of affected rows. */ public function deleteAll(QueryExpression|Closure|array|string|null $conditions): int { @@ -1900,7 +1921,7 @@ public function save( * @throws \Cake\ORM\Exception\PersistenceFailedException When the entity couldn't be saved * @see \Cake\ORM\Table::save() */ - public function saveOrFail(EntityInterface $entity, $options = []): EntityInterface + public function saveOrFail(EntityInterface $entity, ArrayAccess|array $options = []): EntityInterface { $saved = $this->save($entity, $options); if ($saved === false) { @@ -1920,7 +1941,7 @@ public function saveOrFail(EntityInterface $entity, $options = []): EntityInterf * @throws \Cake\ORM\Exception\RolledbackTransactionException If the transaction * is aborted in the afterSave event. */ - protected function _processSave(EntityInterface $entity, ArrayObject $options) + protected function _processSave(EntityInterface $entity, ArrayObject $options): EntityInterface|false { $primaryColumns = (array)$this->getPrimaryKey(); @@ -2036,7 +2057,7 @@ protected function _onSaveSuccess(EntityInterface $entity, ArrayObject $options) * @throws \RuntimeException if not all the primary keys where supplied or could * be generated when the table has composite primary keys. Or when the table has no primary key. */ - protected function _insert(EntityInterface $entity, array $data) + protected function _insert(EntityInterface $entity, array $data): EntityInterface|false { $primary = (array)$this->getPrimaryKey(); if (empty($primary)) { @@ -2115,7 +2136,7 @@ protected function _insert(EntityInterface $entity, array $data) * @param array $primary The primary key columns to get a new ID for. * @return string|null Either null or the primary key value or a list of primary key values. */ - protected function _newId(array $primary) + protected function _newId(array $primary): ?string { if (!$primary || count($primary) > 1) { return null; @@ -2135,7 +2156,7 @@ protected function _newId(array $primary) * @return \Cake\Datasource\EntityInterface|false * @throws \InvalidArgumentException When primary key data is missing. */ - protected function _update(EntityInterface $entity, array $data) + protected function _update(EntityInterface $entity, array $data): EntityInterface|false { $primaryColumns = (array)$this->getPrimaryKey(); $primaryKey = $entity->extract($primaryColumns); @@ -2185,8 +2206,10 @@ protected function _update(EntityInterface $entity, array $data) * @return \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface>|false False on failure, entities list on success. * @throws \Exception */ - public function saveMany(iterable $entities, $options = []) - { + public function saveMany( + ResultSetInterface|array $entities, + SaveOptionsBuilder|ArrayAccess|array $options = [] + ): ResultSetInterface|array|false { try { return $this->_saveMany($entities, $options); } catch (PersistenceFailedException $exception) { @@ -2207,7 +2230,7 @@ public function saveMany(iterable $entities, $options = []) * @throws \Exception * @throws \Cake\ORM\Exception\PersistenceFailedException If an entity couldn't be saved. */ - public function saveManyOrFail(iterable $entities, $options = []): iterable + public function saveManyOrFail(iterable $entities, ArrayAccess|array $options = []): ResultSetInterface|array { return $this->_saveMany($entities, $options); } @@ -2219,8 +2242,10 @@ public function saveManyOrFail(iterable $entities, $options = []): iterable * @throws \Exception If an entity couldn't be saved. * @return \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface> Entities list. */ - protected function _saveMany(iterable $entities, $options = []): iterable - { + protected function _saveMany( + ResultSetInterface|array $entities, + SaveOptionsBuilder|ArrayAccess|array $options = [] + ): ResultSetInterface|array { $options = new ArrayObject( (array)$options + [ 'atomic' => true, @@ -2341,7 +2366,7 @@ public function delete(EntityInterface $entity, $options = []): bool * on success, false on failure. * @see \Cake\ORM\Table::delete() for options and events related to this method. */ - public function deleteMany(iterable $entities, $options = []) + public function deleteMany(iterable $entities, ArrayAccess|array $options = []): ResultSetInterface|array|false { $failed = $this->_deleteMany($entities, $options); @@ -2365,7 +2390,7 @@ public function deleteMany(iterable $entities, $options = []) * @throws \Cake\ORM\Exception\PersistenceFailedException * @see \Cake\ORM\Table::delete() for options and events related to this method. */ - public function deleteManyOrFail(iterable $entities, $options = []): iterable + public function deleteManyOrFail(iterable $entities, ArrayAccess|array $options = []): iterable { $failed = $this->_deleteMany($entities, $options); @@ -2381,7 +2406,7 @@ public function deleteManyOrFail(iterable $entities, $options = []): iterable * @param \ArrayAccess|array $options Options used. * @return \Cake\Datasource\EntityInterface|null */ - protected function _deleteMany(iterable $entities, $options = []): ?EntityInterface + protected function _deleteMany(iterable $entities, ArrayAccess|array $options = []): ?EntityInterface { $options = new ArrayObject((array)$options + [ 'atomic' => true, @@ -2421,7 +2446,7 @@ protected function _deleteMany(iterable $entities, $options = []): ?EntityInterf * @throws \Cake\ORM\Exception\PersistenceFailedException * @see \Cake\ORM\Table::delete() */ - public function deleteOrFail(EntityInterface $entity, $options = []): bool + public function deleteOrFail(EntityInterface $entity, ArrayAccess|array $options = []): bool { $deleted = $this->delete($entity, $options); if ($deleted === false) { @@ -2547,7 +2572,7 @@ public function callFinder(string $type, Query $query, array $options = []): Que * @throws \BadMethodCallException when there are missing arguments, or when * and & or are combined. */ - protected function _dynamicFinder(string $method, array $args) + protected function _dynamicFinder(string $method, array $args): Query { $method = Inflector::underscore($method); preg_match('/^find_([\w]+)_by_/', $method, $matches); @@ -2612,7 +2637,7 @@ protected function _dynamicFinder(string $method, array $args) * @return mixed * @throws \BadMethodCallException */ - public function __call($method, $args) + public function __call(string $method, array $args): mixed { if ($this->_behaviors->hasMethod($method)) { return $this->_behaviors->call($method, $args); @@ -2634,7 +2659,7 @@ public function __call($method, $args) * @return \Cake\ORM\Association * @throws \RuntimeException if no association with such name exists */ - public function __get($property) + public function __get(string $property): Association { $association = $this->_associations->get($property); if (!$association) { @@ -2657,7 +2682,7 @@ public function __get($property) * @param string $property the association name * @return bool */ - public function __isset($property) + public function __isset(string $property): bool { return $this->_associations->has($property); } @@ -2927,7 +2952,7 @@ public function patchEntities(iterable $entities, array $data, array $options = * @param array|null $context Either the validation context or null. * @return bool True if the value is unique, or false if a non-scalar, non-unique value was given. */ - public function validateUnique($value, array $options, ?array $context = null): bool + public function validateUnique(mixed $value, array $options, ?array $context = null): bool { if ($context === null) { $context = $options; @@ -3063,7 +3088,7 @@ public function getSaveOptionsBuilder(array $options = []): SaveOptionsBuilder * @see \Cake\ORM\Query::contain() * @return \Cake\Datasource\EntityInterface|array<\Cake\Datasource\EntityInterface> */ - public function loadInto($entities, array $contain) + public function loadInto(EntityInterface|array $entities, array $contain): EntityInterface|array { return (new LazyEagerLoader())->loadInto($entities, $contain, $this); } @@ -3082,7 +3107,7 @@ protected function validationMethodExists(string $name): bool * * @return array */ - public function __debugInfo() + public function __debugInfo(): array { $conn = $this->getConnection(); From 365a8a16a65d34f8d8f53ad67cd627fbd0e404cb Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 24 Aug 2021 19:30:37 +0530 Subject: [PATCH 1660/2059] Rename Cake\I18n\Time to Cake\I18n\DateTime. --- Behavior/TimestampBehavior.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index d2b6f7f8..cb5a7c7d 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -20,7 +20,7 @@ use Cake\Database\TypeFactory; use Cake\Datasource\EntityInterface; use Cake\Event\EventInterface; -use Cake\I18n\Time; +use Cake\I18n\DateTime; use Cake\ORM\Behavior; use DateTimeInterface; use RuntimeException; @@ -64,7 +64,7 @@ class TimestampBehavior extends Behavior /** * Current timestamp * - * @var \Cake\I18n\Time|null + * @var \Cake\I18n\DateTime|null */ protected $_ts; @@ -147,7 +147,7 @@ public function implementedEvents(): array * * @param \DateTimeInterface|null $ts Timestamp * @param bool $refreshTimestamp If true timestamp is refreshed. - * @return \Cake\I18n\Time + * @return \Cake\I18n\DateTime */ public function timestamp(?DateTimeInterface $ts = null, bool $refreshTimestamp = false): DateTimeInterface { @@ -155,9 +155,9 @@ public function timestamp(?DateTimeInterface $ts = null, bool $refreshTimestamp if ($this->_config['refreshTimestamp']) { $this->_config['refreshTimestamp'] = false; } - $this->_ts = new Time($ts); + $this->_ts = new DateTime($ts); } elseif ($this->_ts === null || $refreshTimestamp) { - $this->_ts = new Time(); + $this->_ts = new DateTime(); } return $this->_ts; From 0ee2094841016a9b6e6bc1ab207e7fc4c000eafb Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Sat, 28 Aug 2021 14:53:35 -0500 Subject: [PATCH 1661/2059] Add use statements for all fully qualified classes --- Locator/LocatorInterface.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Locator/LocatorInterface.php b/Locator/LocatorInterface.php index 7ec73c3a..6e1b630b 100644 --- a/Locator/LocatorInterface.php +++ b/Locator/LocatorInterface.php @@ -16,13 +16,14 @@ */ namespace Cake\ORM\Locator; +use Cake\Datasource\Locator\LocatorInterface as BaseLocatorInterface; use Cake\Datasource\RepositoryInterface; use Cake\ORM\Table; /** * Registries for Table objects should implement this interface. */ -interface LocatorInterface extends \Cake\Datasource\Locator\LocatorInterface +interface LocatorInterface extends BaseLocatorInterface { /** * Returns configuration for an alias or the full configuration array for From 25a922e6e0a7c972085dc9ef8774443c691c63a1 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Sat, 28 Aug 2021 14:53:35 -0500 Subject: [PATCH 1662/2059] Add use statements for all fully qualified classes --- Locator/LocatorInterface.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Locator/LocatorInterface.php b/Locator/LocatorInterface.php index 2ffb7b6a..df93bdcc 100644 --- a/Locator/LocatorInterface.php +++ b/Locator/LocatorInterface.php @@ -16,13 +16,14 @@ */ namespace Cake\ORM\Locator; +use Cake\Datasource\Locator\LocatorInterface as BaseLocatorInterface; use Cake\Datasource\RepositoryInterface; use Cake\ORM\Table; /** * Registries for Table objects should implement this interface. */ -interface LocatorInterface extends \Cake\Datasource\Locator\LocatorInterface +interface LocatorInterface extends BaseLocatorInterface { /** * Returns configuration for an alias or the full configuration array for From 450fb1c2ea341b544b05b567b905c3a87d52eb7d Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Sat, 28 Aug 2021 16:41:11 -0500 Subject: [PATCH 1663/2059] Add native property type hints for Datasource --- Locator/TableLocator.php | 2 +- Query.php | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index a7a43337..cf65ba33 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -50,7 +50,7 @@ class TableLocator extends AbstractLocator implements LocatorInterface * * @var array */ - protected $instances = []; + protected array $instances = []; /** * Contains a list of Table objects that were created out of the diff --git a/Query.php b/Query.php index 9e54035f..6306ae80 100644 --- a/Query.php +++ b/Query.php @@ -141,10 +141,7 @@ public function __construct(Connection $connection, Table $table) { parent::__construct($connection); $this->repository($table); - - if ($this->_repository !== null) { - $this->addDefaultTypes($this->_repository); - } + $this->addDefaultTypes($table); } /** From a967822cc39ae24d4dcd96418dbc56617d43fa95 Mon Sep 17 00:00:00 2001 From: mscherer Date: Tue, 31 Aug 2021 22:41:11 +0200 Subject: [PATCH 1664/2059] Start documenting the assoc arrays as per their string key. --- Association.php | 28 ++++++------- Association/BelongsTo.php | 6 +-- Association/BelongsToMany.php | 24 +++++------ Association/DependentDeleteHelper.php | 2 +- Association/HasMany.php | 16 ++++---- Association/HasOne.php | 2 +- Association/Loader/SelectLoader.php | 12 +++--- Association/Loader/SelectWithPivotLoader.php | 6 +-- AssociationCollection.php | 12 +++--- Behavior/Translate/TranslateStrategyTrait.php | 2 +- Behavior/TranslateBehavior.php | 4 +- Behavior/TreeBehavior.php | 10 ++--- EagerLoader.php | 4 +- Entity.php | 2 +- Locator/LocatorInterface.php | 2 +- Locator/TableLocator.php | 6 +-- Marshaller.php | 26 ++++++------ PropertyMarshalInterface.php | 2 +- Query.php | 4 +- Rule/ExistsIn.php | 4 +- Rule/IsUnique.php | 4 +- Rule/LinkConstraint.php | 2 +- Rule/ValidCount.php | 2 +- SaveOptionsBuilder.php | 2 +- Table.php | 40 +++++++++---------- TableRegistry.php | 2 +- 26 files changed, 113 insertions(+), 113 deletions(-) diff --git a/Association.php b/Association.php index 1c3960c4..e127e84a 100644 --- a/Association.php +++ b/Association.php @@ -202,7 +202,7 @@ abstract class Association * list of passed options if expecting any other special key * * @param string $alias The name given to the association - * @param array $options A list of properties to be set on this object + * @param array $options A list of properties to be set on this object */ public function __construct(string $alias, array $options = []) { @@ -533,7 +533,7 @@ public function getDependent(): bool /** * Whether this association can be expressed directly in a query join * - * @param array $options custom options key that could alter the return value + * @param array $options custom options key that could alter the return value * @return bool */ public function canBeJoined(array $options = []): bool @@ -677,7 +677,7 @@ public function setFinder($finder) * Override this function to initialize any concrete association class, it will * get passed the original list of options used in the constructor * - * @param array $options List of options used for initialization + * @param array $options List of options used for initialization * @return void */ protected function _options(array $options): void @@ -706,7 +706,7 @@ protected function _options(array $options): void * with this association. * * @param \Cake\ORM\Query $query the query to be altered to include the target table data - * @param array $options Any extra options or overrides to be taken in account + * @param array $options Any extra options or overrides to be taken in account * @return void * @throws \RuntimeException Unable to build the query or associations. */ @@ -783,7 +783,7 @@ public function attachTo(Query $query, array $options = []): void * records where there is no match with this association. * * @param \Cake\ORM\Query $query The query to modify - * @param array $options Options array containing the `negateMatch` key. + * @param array $options Options array containing the `negateMatch` key. * @return void */ protected function _appendNotMatching(Query $query, array $options): void @@ -852,7 +852,7 @@ public function defaultRowValue(array $row, bool $joined): array * * @param array|string|null $type the type of query to perform, if an array is passed, * it will be interpreted as the `$options` parameter - * @param array $options The options to for the find + * @param array $options The options to for the find * @see \Cake\ORM\Table::find() * @return \Cake\ORM\Query */ @@ -923,7 +923,7 @@ public function deleteAll($conditions): int * Returns true if the eager loading process will require a set of the owning table's * binding keys in order to use them as a filter in the finder query. * - * @param array $options The options containing the strategy to be used. + * @param array $options The options containing the strategy to be used. * @return bool true if a list of keys will be required */ public function requiresKeys(array $options = []): bool @@ -951,7 +951,7 @@ protected function _dispatchBeforeFind(Query $query): void * * @param \Cake\ORM\Query $query the query that will get the fields appended to * @param \Cake\ORM\Query $surrogate the query having the fields to be copied from - * @param array $options options passed to the method `attachTo` + * @param array $options options passed to the method `attachTo` * @return void */ protected function _appendFields(Query $query, Query $surrogate, array $options): void @@ -983,7 +983,7 @@ protected function _appendFields(Query $query, Query $surrogate, array $options) * @param \Cake\ORM\Query $query the query that will get the formatter applied to * @param \Cake\ORM\Query $surrogate the query having formatters for the associated * target table. - * @param array $options options passed to the method `attachTo` + * @param array $options options passed to the method `attachTo` * @return void */ protected function _formatAssociationResults(Query $query, Query $surrogate, array $options): void @@ -1037,7 +1037,7 @@ protected function _formatAssociationResults(Query $query, Query $surrogate, arr * * @param \Cake\ORM\Query $query the query that will get the associations attached to * @param \Cake\ORM\Query $surrogate the query having the containments to be attached - * @param array $options options passed to the method `attachTo` + * @param array $options options passed to the method `attachTo` * @return void */ protected function _bindNewAssociations(Query $query, Query $surrogate, array $options): void @@ -1073,7 +1073,7 @@ protected function _bindNewAssociations(Query $query, Query $surrogate, array $o * Returns a single or multiple conditions to be appended to the generated join * clause for getting the results on the target table. * - * @param array $options list of options passed to attachTo method + * @param array $options list of options passed to attachTo method * @return array * @throws \RuntimeException if the number of columns in the foreignKey do not * match the number of columns in the source table primaryKey @@ -1213,7 +1213,7 @@ abstract public function type(): string; * - strategy: The name of strategy to use for finding target table records * - nestKey: The array key under which results will be found when transforming the row * - * @param array $options The options for eager loading. + * @param array $options The options for eager loading. * @return \Closure */ abstract public function eagerLoader(array $options): Closure; @@ -1225,7 +1225,7 @@ abstract public function eagerLoader(array $options): Closure; * required. * * @param \Cake\Datasource\EntityInterface $entity The entity that started the cascaded delete. - * @param array $options The options for the original delete. + * @param array $options The options for the original delete. * @return bool Success */ abstract public function cascadeDelete(EntityInterface $entity, array $options = []): bool; @@ -1245,7 +1245,7 @@ abstract public function isOwningSide(Table $side): bool; * the saving operation to the target table. * * @param \Cake\Datasource\EntityInterface $entity the data to be saved - * @param array $options The options for saving associated data. + * @param array $options The options for saving associated data. * @return \Cake\Datasource\EntityInterface|false false if $entity could not be saved, otherwise it returns * the saved entity * @see \Cake\ORM\Table::save() diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 672ad07e..ea8f34fb 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -63,7 +63,7 @@ public function getForeignKey() * BelongsTo associations are never cleared in a cascading delete scenario. * * @param \Cake\Datasource\EntityInterface $entity The entity that started the cascaded delete. - * @param array $options The options for the original delete. + * @param array $options The options for the original delete. * @return bool Success. */ public function cascadeDelete(EntityInterface $entity, array $options = []): bool @@ -113,7 +113,7 @@ public function type(): string * `$options` * * @param \Cake\Datasource\EntityInterface $entity an entity from the source table - * @param array $options options to be passed to the save method in the target table + * @param array $options options to be passed to the save method in the target table * @return \Cake\Datasource\EntityInterface|false false if $entity could not be saved, otherwise it returns * the saved entity * @see \Cake\ORM\Table::save() @@ -144,7 +144,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) * Returns a single or multiple conditions to be appended to the generated join * clause for getting the results on the target table. * - * @param array $options list of options passed to attachTo method + * @param array $options list of options passed to attachTo method * @return array<\Cake\Database\Expression\IdentifierExpression> * @throws \RuntimeException if the number of columns in the foreignKey do not * match the number of columns in the target table primaryKey diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 5ee87101..29d2cff8 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -189,7 +189,7 @@ public function getTargetForeignKey() /** * Whether this association can be expressed directly in a query join * - * @param array $options custom options key that could alter the return value + * @param array $options custom options key that could alter the return value * @return bool if the 'matching' key in $option is true then this function * will return true, false otherwise */ @@ -446,7 +446,7 @@ protected function _generateJunctionAssociations(Table $junction, Table $source, * - type: The type of join to be used (e.g. INNER) * * @param \Cake\ORM\Query $query the query to be altered to include the target table data - * @param array $options Any extra options or overrides to be taken in account + * @param array $options Any extra options or overrides to be taken in account * @return void */ public function attachTo(Query $query, array $options = []): void @@ -535,7 +535,7 @@ public function type(): string /** * Return false as join conditions are defined in the junction table * - * @param array $options list of options passed to attachTo method + * @param array $options list of options passed to attachTo method * @return array */ protected function _joinCondition(array $options): array @@ -574,7 +574,7 @@ public function eagerLoader(array $options): Closure * Clear out the data in the junction table for a given entity. * * @param \Cake\Datasource\EntityInterface $entity The entity that started the cascading delete. - * @param array $options The options for the original delete. + * @param array $options The options for the original delete. * @return bool Success. */ public function cascadeDelete(EntityInterface $entity, array $options = []): bool @@ -672,7 +672,7 @@ public function getSaveStrategy(): string * not deleted. * * @param \Cake\Datasource\EntityInterface $entity an entity from the source table - * @param array $options options to be passed to the save method in the target table + * @param array $options options to be passed to the save method in the target table * @throws \InvalidArgumentException if the property representing the association * in the parent entity cannot be traversed * @return \Cake\Datasource\EntityInterface|false false if $entity could not be saved, otherwise it returns @@ -712,7 +712,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) * entities to be saved. * @param array $entities list of entities to persist in target table and to * link to the parent entity - * @param array $options list of options accepted by `Table::save()` + * @param array $options list of options accepted by `Table::save()` * @throws \InvalidArgumentException if the property representing the association * in the parent entity cannot be traversed * @return \Cake\Datasource\EntityInterface|false The parent entity after all links have been @@ -778,7 +778,7 @@ protected function _saveTarget(EntityInterface $parentEntity, array $entities, $ * association * @param array<\Cake\Datasource\EntityInterface> $targetEntities list of entities to link to link to the source entity using the * junction table - * @param array $options list of options accepted by `Table::save()` + * @param array $options list of options accepted by `Table::save()` * @return bool success */ protected function _saveLinks(EntityInterface $sourceEntity, array $targetEntities, array $options): bool @@ -852,7 +852,7 @@ protected function _saveLinks(EntityInterface $sourceEntity, array $targetEntiti * of this association * @param array<\Cake\Datasource\EntityInterface> $targetEntities list of entities belonging to the `target` side * of this association - * @param array $options list of options to be passed to the internal `save` call + * @param array $options list of options to be passed to the internal `save` call * @throws \InvalidArgumentException when any of the values in $targetEntities is * detected to not be already persisted * @return bool true on success, false otherwise @@ -1061,7 +1061,7 @@ protected function junctionConditions(): array * * @param array|string|null $type the type of query to perform, if an array is passed, * it will be interpreted as the `$options` parameter - * @param array $options The options to for the find + * @param array $options The options to for the find * @see \Cake\ORM\Table::find() * @return \Cake\ORM\Query */ @@ -1160,7 +1160,7 @@ protected function _appendJunctionJoin(Query $query, ?array $conditions = null): * @param \Cake\Datasource\EntityInterface $sourceEntity an entity persisted in the source table for * this association * @param array $targetEntities list of entities from the target table to be linked - * @param array $options list of options to be passed to the internal `save`/`delete` calls + * @param array $options list of options to be passed to the internal `save`/`delete` calls * when persisting/updating new links, or deleting existing ones * @throws \InvalidArgumentException if non persisted entities are passed or if * any of them is lacking a primary key value @@ -1245,7 +1245,7 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { * @param array<\Cake\Datasource\EntityInterface> $jointEntities link entities that should be persisted * @param array $targetEntities entities in target table that are related to * the `$jointEntities` - * @param array $options list of options accepted by `Table::delete()` + * @param array $options list of options accepted by `Table::delete()` * @return array|false Array of entities not deleted or false in case of deletion failure for atomic saves. */ protected function _diffLinks( @@ -1450,7 +1450,7 @@ protected function _junctionTableName(?string $name = null): string /** * Parse extra options passed in the constructor. * - * @param array $options original list of options passed in constructor + * @param array $options original list of options passed in constructor * @return void */ protected function _options(array $options): void diff --git a/Association/DependentDeleteHelper.php b/Association/DependentDeleteHelper.php index 1b7a7d80..7320891b 100644 --- a/Association/DependentDeleteHelper.php +++ b/Association/DependentDeleteHelper.php @@ -33,7 +33,7 @@ class DependentDeleteHelper * * @param \Cake\ORM\Association $association The association callbacks are being cascaded on. * @param \Cake\Datasource\EntityInterface $entity The entity that started the cascaded delete. - * @param array $options The options for the original delete. + * @param array $options The options for the original delete. * @return bool Success. */ public function cascadeDelete(Association $association, EntityInterface $entity, array $options = []): bool diff --git a/Association/HasMany.php b/Association/HasMany.php index 7e91afaa..73c9c775 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -137,7 +137,7 @@ public function getSaveStrategy(): string * `$options` * * @param \Cake\Datasource\EntityInterface $entity an entity from the source table - * @param array $options options to be passed to the save method in the target table + * @param array $options options to be passed to the save method in the target table * @return \Cake\Datasource\EntityInterface|false false if $entity could not be saved, otherwise it returns * the saved entity * @see \Cake\ORM\Table::save() @@ -199,7 +199,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) * entities to be saved. * @param array $entities list of entities * to persist in target table and to link to the parent entity - * @param array $options list of options accepted by `Table::save()`. + * @param array $options list of options accepted by `Table::save()`. * @return bool `true` on success, `false` otherwise. */ protected function _saveTarget( @@ -267,7 +267,7 @@ protected function _saveTarget( * of this association * @param array $targetEntities list of entities belonging to the `target` side * of this association - * @param array $options list of options to be passed to the internal `save` call + * @param array $options list of options to be passed to the internal `save` call * @return bool true on success, false otherwise */ public function link(EntityInterface $sourceEntity, array $targetEntities, array $options = []): bool @@ -424,7 +424,7 @@ function ($assoc) use ($targetEntities) { * @param \Cake\Datasource\EntityInterface $sourceEntity an entity persisted in the source table for * this association * @param array $targetEntities list of entities from the target table to be linked - * @param array $options list of options to be passed to the internal `save`/`delete` calls + * @param array $options list of options to be passed to the internal `save`/`delete` calls * when persisting/updating new links, or deleting existing ones * @throws \InvalidArgumentException if non persisted entities are passed or if * any of them is lacking a primary key value @@ -456,7 +456,7 @@ public function replace(EntityInterface $sourceEntity, array $targetEntities, ar * @param \Cake\Datasource\EntityInterface $entity the entity which should have its associated entities unassigned * @param \Cake\ORM\Table $target The associated table * @param iterable $remainingEntities Entities that should not be deleted - * @param array $options list of options accepted by `Table::delete()` + * @param array $options list of options accepted by `Table::delete()` * @return bool success */ protected function _unlinkAssociated( @@ -504,7 +504,7 @@ function ($v) { * @param array $foreignKey array of foreign key properties * @param \Cake\ORM\Table $target The associated table * @param array $conditions The conditions that specifies what are the objects to be unlinked - * @param array $options list of options accepted by `Table::delete()` + * @param array $options list of options accepted by `Table::delete()` * @return bool success */ protected function _unlink(array $foreignKey, Table $target, array $conditions = [], array $options = []): bool @@ -575,7 +575,7 @@ public function type(): string /** * Whether this association can be expressed directly in a query join * - * @param array $options custom options key that could alter the return value + * @param array $options custom options key that could alter the return value * @return bool if the 'matching' key in $option is true then this function * will return true, false otherwise */ @@ -637,7 +637,7 @@ public function defaultRowValue(array $row, bool $joined): array /** * Parse extra options passed in the constructor. * - * @param array $options original list of options passed in constructor + * @param array $options original list of options passed in constructor * @return void */ protected function _options(array $options): void diff --git a/Association/HasOne.php b/Association/HasOne.php index 77e1e25d..3721722c 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -97,7 +97,7 @@ public function type(): string * `$options` * * @param \Cake\Datasource\EntityInterface $entity an entity from the source table - * @param array $options options to be passed to the save method in the target table + * @param array $options options to be passed to the save method in the target table * @return \Cake\Datasource\EntityInterface|false false if $entity could not be saved, otherwise it returns * the saved entity * @see \Cake\ORM\Table::save() diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 46678001..7826c25b 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -99,7 +99,7 @@ class SelectLoader * Copies the options array to properties in this class. The keys in the array correspond * to properties in this class. * - * @param array $options Properties to be copied to this class + * @param array $options Properties to be copied to this class */ public function __construct(array $options) { @@ -118,7 +118,7 @@ public function __construct(array $options) * Returns a callable that can be used for injecting association results into a given * iterator. The options accepted by this method are the same as `Association::eagerLoader()` * - * @param array $options Same options as `Association::eagerLoader()` + * @param array $options Same options as `Association::eagerLoader()` * @return \Closure */ public function buildEagerLoader(array $options): Closure @@ -151,7 +151,7 @@ protected function _defaultOptions(): array * in the target table that are associated to those specified in $options from * the source table * - * @param array $options options accepted by eagerLoader() + * @param array $options options accepted by eagerLoader() * @return \Cake\ORM\Query * @throws \InvalidArgumentException When a key is required for associations but not selected. */ @@ -359,7 +359,7 @@ protected function _createTupleCondition(Query $query, array $keys, $filter, $op * Generates a string used as a table field that contains the values upon * which the filter should be applied * - * @param array $options The options for getting the link field. + * @param array $options The options for getting the link field. * @return array|string * @throws \RuntimeException */ @@ -457,7 +457,7 @@ protected function _subqueryFields(Query $query): array * the foreignKey value corresponding to this association. * * @param \Cake\ORM\Query $fetchQuery The query to get results from - * @param array $options The options passed to the eager loader + * @param array $options The options passed to the eager loader * @return array */ protected function _buildResultMap(Query $fetchQuery, array $options): array @@ -491,7 +491,7 @@ protected function _buildResultMap(Query $fetchQuery, array $options): array * @param \Cake\ORM\Query $fetchQuery the Query used to fetch results * @param array $resultMap an array with the foreignKey as keys and * the corresponding target table results as value. - * @param array $options The options passed to the eagerLoader method + * @param array $options The options passed to the eagerLoader method * @return \Closure */ protected function _resultInjector(Query $fetchQuery, array $resultMap, array $options): Closure diff --git a/Association/Loader/SelectWithPivotLoader.php b/Association/Loader/SelectWithPivotLoader.php index abe6adde..063bb9b3 100644 --- a/Association/Loader/SelectWithPivotLoader.php +++ b/Association/Loader/SelectWithPivotLoader.php @@ -73,7 +73,7 @@ public function __construct(array $options) * * This is used for eager loading records on the target table based on conditions. * - * @param array $options options accepted by eagerLoader() + * @param array $options options accepted by eagerLoader() * @return \Cake\ORM\Query * @throws \InvalidArgumentException When a key is required for associations but not selected. */ @@ -141,7 +141,7 @@ protected function _assertFieldsPresent(Query $fetchQuery, array $key): void * Generates a string used as a table field that contains the values upon * which the filter should be applied * - * @param array $options the options to use for getting the link field. + * @param array $options the options to use for getting the link field. * @return array|string */ protected function _linkField(array $options) @@ -165,7 +165,7 @@ protected function _linkField(array $options) * the foreignKey value corresponding to this association. * * @param \Cake\ORM\Query $fetchQuery The query to get results from - * @param array $options The options passed to the eager loader + * @param array $options The options passed to the eager loader * @return array * @throws \RuntimeException when the association property is not part of the results set. */ diff --git a/AssociationCollection.php b/AssociationCollection.php index 416221ea..c76b395b 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -79,7 +79,7 @@ public function add(string $alias, Association $association): Association * * @param string $className The name of association class. * @param string $associated The alias for the target table. - * @param array $options List of options to configure the association definition. + * @param array $options List of options to configure the association definition. * @return \Cake\ORM\Association * @throws \InvalidArgumentException */ @@ -209,7 +209,7 @@ public function removeAll(): void * @param \Cake\Datasource\EntityInterface $entity The entity to save associated data for. * @param array $associations The list of associations to save parents from. * associations not in this list will not be saved. - * @param array $options The options for the save operation. + * @param array $options The options for the save operation. * @return bool Success */ public function saveParents(Table $table, EntityInterface $entity, array $associations, array $options = []): bool @@ -231,7 +231,7 @@ public function saveParents(Table $table, EntityInterface $entity, array $associ * @param \Cake\Datasource\EntityInterface $entity The entity to save associated data for. * @param array $associations The list of associations to save children from. * associations not in this list will not be saved. - * @param array $options The options for the save operation. + * @param array $options The options for the save operation. * @return bool Success */ public function saveChildren(Table $table, EntityInterface $entity, array $associations, array $options): bool @@ -249,7 +249,7 @@ public function saveChildren(Table $table, EntityInterface $entity, array $assoc * @param \Cake\ORM\Table $table The table the save is currently operating on * @param \Cake\Datasource\EntityInterface $entity The entity to save * @param array $associations Array of associations to save. - * @param array $options Original options + * @param array $options Original options * @param bool $owningSide Compared with association classes' * isOwningSide method. * @return bool Success @@ -294,7 +294,7 @@ protected function _saveAssociations( * @param \Cake\ORM\Association $association The association object to save with. * @param \Cake\Datasource\EntityInterface $entity The entity to save * @param array $nested Options for deeper associations - * @param array $options Original options + * @param array $options Original options * @return bool Success */ protected function _save( @@ -318,7 +318,7 @@ protected function _save( * Cascade first across associations for which cascadeCallbacks is true. * * @param \Cake\Datasource\EntityInterface $entity The entity to delete associations for. - * @param array $options The options used in the delete operation. + * @param array $options The options used in the delete operation. * @return bool */ public function cascadeDelete(EntityInterface $entity, array $options): bool diff --git a/Behavior/Translate/TranslateStrategyTrait.php b/Behavior/Translate/TranslateStrategyTrait.php index 56e05bec..bdfbb9b8 100644 --- a/Behavior/Translate/TranslateStrategyTrait.php +++ b/Behavior/Translate/TranslateStrategyTrait.php @@ -141,7 +141,7 @@ protected function unsetEmptyFields($entity) * * @param \Cake\ORM\Marshaller $marshaller The marhshaller of the table the behavior is attached to. * @param array $map The property map being built. - * @param array $options The options array used in the marshalling call. + * @param array $options The options array used in the marshalling call. * @return array A map of `[property => callable]` of additional properties to marshal. */ public function buildMarshalMap(Marshaller $marshaller, array $map, array $options): array diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 5daecbb6..68bfdfe3 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -222,7 +222,7 @@ public function implementedEvents(): array * * @param \Cake\ORM\Marshaller $marshaller The marhshaller of the table the behavior is attached to. * @param array $map The property map being built. - * @param array $options The options array used in the marshalling call. + * @param array $options The options array used in the marshalling call. * @return array A map of `[property => callable]` of additional properties to marshal. */ public function buildMarshalMap(Marshaller $marshaller, array $map, array $options): array @@ -306,7 +306,7 @@ public function translationField(string $field): string * for each record. * * @param \Cake\ORM\Query $query The original query to modify - * @param array $options Options + * @param array $options Options * @return \Cake\ORM\Query */ public function findTranslations(Query $query, array $options): Query diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 4f68f7fd..1effd208 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -371,7 +371,7 @@ function ($exp) use ($config) { * is passed in the options containing the id of the node to get its path for. * * @param \Cake\ORM\Query $query The constructed query to modify - * @param array $options the list of options for the query + * @param array $options the list of options for the query * @return \Cake\ORM\Query * @throws \InvalidArgumentException If the 'for' key is missing in options */ @@ -435,7 +435,7 @@ public function childCount(EntityInterface $node, bool $direct = false): int * If the direct option is set to true, only the direct children are returned (based upon the parent_id field) * * @param \Cake\ORM\Query $query Query. - * @param array $options Array of options as described above + * @param array $options Array of options as described above * @return \Cake\ORM\Query * @throws \InvalidArgumentException When the 'for' key is not passed in $options */ @@ -487,7 +487,7 @@ function ($field) { * - spacer: A string to be used as prefix for denoting the depth in the tree for each item * * @param \Cake\ORM\Query $query Query. - * @param array $options Array of options as described above. + * @param array $options Array of options as described above. * @return \Cake\ORM\Query */ public function findTreeList(Query $query, array $options): Query @@ -517,7 +517,7 @@ public function findTreeList(Query $query, array $options): Query * - spacer: A string to be used as prefix for denoting the depth in the tree for each item. * * @param \Cake\ORM\Query $query The query object to format. - * @param array $options Array of options as described above. + * @param array $options Array of options as described above. * @return \Cake\ORM\Query Augmented query. */ public function formatTreeList(Query $query, array $options = []): Query @@ -962,7 +962,7 @@ protected function _ensureFields(EntityInterface $entity): void return; } - $fresh = $this->_table->get($entity->get($this->_getPrimaryKey()), $fields); + $fresh = $this->_table->get($entity->get($this->_getPrimaryKey())); $entity->set($fresh->extract($fields), ['guard' => false]); foreach ($fields as $field) { diff --git a/EagerLoader.php b/EagerLoader.php index 90084867..8f164c56 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -237,7 +237,7 @@ public function isAutoFieldsEnabled(): bool * @param string $associationPath Dot separated association path, 'Name1.Name2.Name3' * @param callable|null $builder the callback function to be used for setting extra * options to the filtering query - * @param array $options Extra options for the association matching. + * @param array $options Extra options for the association matching. * @return $this */ public function setMatching(string $associationPath, ?callable $builder = null, array $options = []) @@ -475,7 +475,7 @@ public function externalAssociations(Table $repository): array * * @param \Cake\ORM\Table $parent owning side of the association * @param string $alias name of the association to be loaded - * @param array $options list of extra options to use for this association + * @param array $options list of extra options to use for this association * @param array $paths An array with two values, the first one is a list of dot * separated strings representing associations that lead to this `$alias` in the * chain of associations to be loaded. The second value is the path to follow in diff --git a/Entity.php b/Entity.php index a7167875..1043720d 100644 --- a/Entity.php +++ b/Entity.php @@ -45,7 +45,7 @@ class Entity implements EntityInterface, InvalidPropertyInterface * ``` * * @param array $properties hash of properties to set in this entity - * @param array $options list of options to use when creating this entity + * @param array $options list of options to use when creating this entity */ public function __construct(array $properties = [], array $options = []) { diff --git a/Locator/LocatorInterface.php b/Locator/LocatorInterface.php index df93bdcc..eb837c07 100644 --- a/Locator/LocatorInterface.php +++ b/Locator/LocatorInterface.php @@ -51,7 +51,7 @@ public function setConfig($alias, $options = null); * Get a table instance from the registry. * * @param string $alias The alias name you want to get. - * @param array $options The options you want to build the table with. + * @param array $options The options you want to build the table with. * @return \Cake\ORM\Table */ public function get(string $alias, array $options = []): Table; diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index eac0b02f..fc693122 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -194,7 +194,7 @@ public function getConfig(?string $alias = null): array * the same alias, the registry will only store the first instance. * * @param string $alias The alias name you want to get. Should be in CamelCase format. - * @param array $options The options you want to build the table with. + * @param array $options The options you want to build the table with. * If a table has already been loaded the options will be ignored. * @return \Cake\ORM\Table * @throws \RuntimeException When you try to configure an alias that already exists. @@ -274,7 +274,7 @@ protected function createInstance(string $alias, array $options) * Gets the table class name. * * @param string $alias The alias name you want to get. Should be in CamelCase format. - * @param array $options Table options array. + * @param array $options Table options array. * @return string|null */ protected function _getClassName(string $alias, array $options = []): ?string @@ -300,7 +300,7 @@ protected function _getClassName(string $alias, array $options = []): ?string /** * Wrapper for creating table instances * - * @param array $options The alias to check for. + * @param array $options The alias to check for. * @return \Cake\ORM\Table */ protected function _create(array $options): Table diff --git a/Marshaller.php b/Marshaller.php index 880c8a24..85796a34 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -62,7 +62,7 @@ public function __construct(Table $table) * Build the map of property => marshalling callable. * * @param array $data The data being marshalled. - * @param array $options List of options containing the 'associated' key. + * @param array $options List of options containing the 'associated' key. * @throws \InvalidArgumentException When associations do not exist. * @return array */ @@ -169,7 +169,7 @@ protected function _buildPropertyMap(array $data, array $options): array * ``` * * @param array $data The data to hydrate. - * @param array $options List of options + * @param array $options List of options * @return \Cake\Datasource\EntityInterface * @see \Cake\ORM\Table::newEntity() * @see \Cake\ORM\Entity::$_accessible @@ -240,7 +240,7 @@ public function one(array $data, array $options = []): EntityInterface * Returns the validation errors for a data set based on the passed options * * @param array $data The data to validate. - * @param array $options The options passed to this marshaller. + * @param array $options The options passed to this marshaller. * @param bool $isNew Whether it is a new entity or one to be updated. * @return array The list of validation errors. * @throws \RuntimeException If no validator can be created. @@ -274,7 +274,7 @@ protected function _validate(array $data, array $options, bool $isNew): array * Returns data and options prepared to validate and marshall. * * @param array $data The data to prepare. - * @param array $options The options passed to this marshaller. + * @param array $options The options passed to this marshaller. * @return array An array containing prepared data and options. */ protected function _prepareDataAndOptions(array $data, array $options): array @@ -299,7 +299,7 @@ protected function _prepareDataAndOptions(array $data, array $options): array * * @param \Cake\ORM\Association $assoc The association to marshall * @param mixed $value The data to hydrate. If not an array, this method will return null. - * @param array $options List of options. + * @param array $options List of options. * @return \Cake\Datasource\EntityInterface|array<\Cake\Datasource\EntityInterface>|null */ protected function _marshalAssociation(Association $assoc, $value, array $options) @@ -349,7 +349,7 @@ protected function _marshalAssociation(Association $assoc, $value, array $option * on missing entities would be ignored. Defaults to false. * * @param array $data The data to hydrate. - * @param array $options List of options + * @param array $options List of options * @return array<\Cake\Datasource\EntityInterface> An array of hydrated records. * @see \Cake\ORM\Table::newEntities() * @see \Cake\ORM\Entity::$_accessible @@ -375,7 +375,7 @@ public function many(array $data, array $options = []): array * * @param \Cake\ORM\Association\BelongsToMany $assoc The association to marshal. * @param array $data The data to convert into entities. - * @param array $options List of options. + * @param array $options List of options. * @return array<\Cake\Datasource\EntityInterface> An array of built entities. * @throws \BadMethodCallException * @throws \InvalidArgumentException @@ -534,7 +534,7 @@ protected function _loadAssociatedByIds(Association $assoc, array $ids): array * @param \Cake\Datasource\EntityInterface $entity the entity that will get the * data merged in * @param array $data key value list of fields to be merged into the entity - * @param array $options List of options. + * @param array $options List of options. * @return \Cake\Datasource\EntityInterface * @see \Cake\ORM\Entity::$_accessible */ @@ -651,7 +651,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) * @param iterable<\Cake\Datasource\EntityInterface> $entities the entities that will get the * data merged in * @param array $data list of arrays to be merged into the entities - * @param array $options List of options. + * @param array $options List of options. * @return array<\Cake\Datasource\EntityInterface> * @see \Cake\ORM\Entity::$_accessible * @psalm-suppress NullArrayOffset @@ -735,7 +735,7 @@ public function mergeMany(iterable $entities, array $data, array $options = []): * @param \Cake\Datasource\EntityInterface|array<\Cake\Datasource\EntityInterface> $original The original entity * @param \Cake\ORM\Association $assoc The association to merge * @param mixed $value The array of data to hydrate. If not an array, this method will return null. - * @param array $options List of options. + * @param array $options List of options. * @return \Cake\Datasource\EntityInterface|array<\Cake\Datasource\EntityInterface>|null */ protected function _mergeAssociation($original, Association $assoc, $value, array $options) @@ -782,7 +782,7 @@ protected function _mergeAssociation($original, Association $assoc, $value, arra * @param array<\Cake\Datasource\EntityInterface> $original The original entities list. * @param \Cake\ORM\Association\BelongsToMany $assoc The association to marshall * @param array $value The data to hydrate - * @param array $options List of options. + * @param array $options List of options. * @return array<\Cake\Datasource\EntityInterface> */ protected function _mergeBelongsToMany(array $original, BelongsToMany $assoc, array $value, array $options): array @@ -812,7 +812,7 @@ protected function _mergeBelongsToMany(array $original, BelongsToMany $assoc, ar * @param array<\Cake\Datasource\EntityInterface> $original The original entities list. * @param \Cake\ORM\Association\BelongsToMany $assoc The association to marshall * @param array $value The data to hydrate - * @param array $options List of options. + * @param array $options List of options. * @return array<\Cake\Datasource\EntityInterface> An array of entities */ protected function _mergeJoinData(array $original, BelongsToMany $assoc, array $value, array $options): array @@ -872,7 +872,7 @@ protected function _mergeJoinData(array $original, BelongsToMany $assoc, array $ * * @param \Cake\Datasource\EntityInterface $entity The entity that was marshaled. * @param array $data readOnly $data to use. - * @param array $options List of options that are readOnly. + * @param array $options List of options that are readOnly. * @return void */ protected function dispatchAfterMarshal(EntityInterface $entity, array $data, array $options = []): void diff --git a/PropertyMarshalInterface.php b/PropertyMarshalInterface.php index f52ce749..8317d9eb 100644 --- a/PropertyMarshalInterface.php +++ b/PropertyMarshalInterface.php @@ -29,7 +29,7 @@ interface PropertyMarshalInterface * * @param \Cake\ORM\Marshaller $marshaller The marhshaller of the table the behavior is attached to. * @param array $map The property map being built. - * @param array $options The options array used in the marshalling call. + * @param array $options The options array used in the marshalling call. * @return array A map of `[property => callable]` of additional properties to marshal. */ public function buildMarshalMap(Marshaller $marshaller, array $map, array $options): array; diff --git a/Query.php b/Query.php index 9ef04ca8..b70478e3 100644 --- a/Query.php +++ b/Query.php @@ -823,7 +823,7 @@ public function notMatching(string $assoc, ?callable $builder = null) * $options = $query->getOptions(); * ``` * - * @param array $options The options to be applied + * @param array $options The options to be applied * @return $this * @see getOptions() */ @@ -1234,7 +1234,7 @@ protected function _addDefaultSelectTypes(): void * {@inheritDoc} * * @param string $finder The finder method to use. - * @param array $options The options for the finder. + * @param array $options The options for the finder. * @return static Returns a modified query. * @psalm-suppress MoreSpecificReturnType */ diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 20dc6312..a945f27e 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -57,7 +57,7 @@ class ExistsIn * @param array|string $fields The field or fields to check existence as primary key. * @param \Cake\ORM\Table|\Cake\ORM\Association|string $repository The repository where the * field will be looked for, or the association name for the repository. - * @param array $options The options that modify the rules behavior. + * @param array $options The options that modify the rules behavior. * Options 'allowNullableNulls' will make the rule pass if given foreign keys are set to `null`. * Notice: allowNullableNulls cannot pass by database columns set to `NOT NULL`. */ @@ -74,7 +74,7 @@ public function __construct($fields, $repository, array $options = []) * Performs the existence check * * @param \Cake\Datasource\EntityInterface $entity The entity from where to extract the fields - * @param array $options Options passed to the check, + * @param array $options Options passed to the check, * where the `repository` key is required. * @throws \RuntimeException When the rule refers to an undefined association. * @return bool diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index b0c80139..1dab30e3 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -48,7 +48,7 @@ class IsUnique * - `allowMultipleNulls` Allows any field to have multiple null values. Defaults to false. * * @param array $fields The list of fields to check uniqueness for - * @param array $options The options for unique checks. + * @param array $options The options for unique checks. */ public function __construct(array $fields, array $options = []) { @@ -61,7 +61,7 @@ public function __construct(array $fields, array $options = []) * * @param \Cake\Datasource\EntityInterface $entity The entity from where to extract the fields * where the `repository` key is required. - * @param array $options Options passed to the check, + * @param array $options Options passed to the check, * @return bool */ public function __invoke(EntityInterface $entity, array $options): bool diff --git a/Rule/LinkConstraint.php b/Rule/LinkConstraint.php index 6a25ba23..587df27e 100644 --- a/Rule/LinkConstraint.php +++ b/Rule/LinkConstraint.php @@ -88,7 +88,7 @@ public function __construct($association, string $requiredLinkStatus) * Performs the actual link check. * * @param \Cake\Datasource\EntityInterface $entity The entity involved in the operation. - * @param array $options Options passed from the rules checker. + * @param array $options Options passed from the rules checker. * @return bool Whether the check was successful. */ public function __invoke(EntityInterface $entity, array $options): bool diff --git a/Rule/ValidCount.php b/Rule/ValidCount.php index f7e23521..89d18d03 100644 --- a/Rule/ValidCount.php +++ b/Rule/ValidCount.php @@ -46,7 +46,7 @@ public function __construct(string $field) * Performs the count check * * @param \Cake\Datasource\EntityInterface $entity The entity from where to extract the fields. - * @param array $options Options passed to the check. + * @param array $options Options passed to the check. * @return bool True if successful, else false. */ public function __invoke(EntityInterface $entity, array $options): bool diff --git a/SaveOptionsBuilder.php b/SaveOptionsBuilder.php index 8da1e144..96023d0f 100644 --- a/SaveOptionsBuilder.php +++ b/SaveOptionsBuilder.php @@ -49,7 +49,7 @@ class SaveOptionsBuilder extends ArrayObject * Constructor. * * @param \Cake\ORM\Table $table A table instance. - * @param array $options Options to parse when instantiating. + * @param array $options Options to parse when instantiating. */ public function __construct(Table $table, array $options = []) { diff --git a/Table.php b/Table.php index 5edb824a..ada1b178 100644 --- a/Table.php +++ b/Table.php @@ -768,7 +768,7 @@ public function setEntityClass(string $name) * Behaviors are generally loaded during Table::initialize(). * * @param string $name The name of the behavior. Can be a short class reference. - * @param array $options The options for the behavior to use. + * @param array $options The options for the behavior to use. * @return $this * @throws \RuntimeException If a behavior is being reloaded. * @see \Cake\ORM\Behavior @@ -1035,7 +1035,7 @@ public function addAssociations(array $params) * * @param string $associated the alias for the target table. This is used to * uniquely identify the association - * @param array $options list of options to configure the association definition + * @param array $options list of options to configure the association definition * @return \Cake\ORM\Association\BelongsTo */ public function belongsTo(string $associated, array $options = []): BelongsTo @@ -1081,7 +1081,7 @@ public function belongsTo(string $associated, array $options = []): BelongsTo * * @param string $associated the alias for the target table. This is used to * uniquely identify the association - * @param array $options list of options to configure the association definition + * @param array $options list of options to configure the association definition * @return \Cake\ORM\Association\HasOne */ public function hasOne(string $associated, array $options = []): HasOne @@ -1133,7 +1133,7 @@ public function hasOne(string $associated, array $options = []): HasOne * * @param string $associated the alias for the target table. This is used to * uniquely identify the association - * @param array $options list of options to configure the association definition + * @param array $options list of options to configure the association definition * @return \Cake\ORM\Association\HasMany */ public function hasMany(string $associated, array $options = []): HasMany @@ -1187,7 +1187,7 @@ public function hasMany(string $associated, array $options = []): HasMany * * @param string $associated the alias for the target table. This is used to * uniquely identify the association - * @param array $options list of options to configure the association definition + * @param array $options list of options to configure the association definition * @return \Cake\ORM\Association\BelongsToMany */ public function belongsToMany(string $associated, array $options = []): BelongsToMany @@ -1255,7 +1255,7 @@ public function belongsToMany(string $associated, array $options = []): BelongsT * Would invoke the `findPublished` method. * * @param string $type the type of query to perform - * @param array $options An array that will be passed to Query::applyOptions() + * @param array $options An array that will be passed to Query::applyOptions() * @return \Cake\ORM\Query The query builder */ public function find(string $type = 'all', array $options = []): Query @@ -1273,7 +1273,7 @@ public function find(string $type = 'all', array $options = []): Query * can override this method in subclasses to modify how `find('all')` works. * * @param \Cake\ORM\Query $query The query to find with - * @param array $options The options to use for the find + * @param array $options The options to use for the find * @return \Cake\ORM\Query The query builder */ public function findAll(Query $query, array $options): Query @@ -1354,7 +1354,7 @@ public function findAll(Query $query, array $options): Query * ``` * * @param \Cake\ORM\Query $query The query to find with - * @param array $options The options for the find + * @param array $options The options for the find * @return \Cake\ORM\Query The query builder */ public function findList(Query $query, array $options): Query @@ -1419,7 +1419,7 @@ public function findList(Query $query, array $options): Query * ``` * * @param \Cake\ORM\Query $query The query to find with - * @param array $options The options to find with + * @param array $options The options to find with * @return \Cake\ORM\Query The query builder */ public function findThreaded(Query $query, array $options): Query @@ -1446,7 +1446,7 @@ public function findThreaded(Query $query, array $options): Query * This is an auxiliary function used for result formatters that can accept * composite keys when comparing values. * - * @param array $options the original options passed to a finder + * @param array $options the original options passed to a finder * @param array $keys the keys to check in $options to build matchers from * the associated value * @return array @@ -1490,7 +1490,7 @@ protected function _setFieldMatchers(array $options, array $keys): array * ``` * * @param mixed $primaryKey primary key value to find - * @param array $options options accepted by `Table::find()` + * @param array $options options accepted by `Table::find()` * @return \Cake\Datasource\EntityInterface * @throws \Cake\Datasource\Exception\RecordNotFoundException if the record with such id * could not be found @@ -1604,7 +1604,7 @@ protected function _transactionCommitted(bool $atomic, bool $primary): bool * @param callable|null $callback A callback that will be invoked for newly * created entities. This callback will be called *before* the entity * is persisted. - * @param array $options The options to use when saving. + * @param array $options The options to use when saving. * @return \Cake\Datasource\EntityInterface An entity. * @throws \Cake\ORM\Exception\PersistenceFailedException When the entity couldn't be saved */ @@ -1634,7 +1634,7 @@ public function findOrCreate($search, ?callable $callback = null, $options = []) * @param callable|null $callback A callback that will be invoked for newly * created entities. This callback will be called *before* the entity * is persisted. - * @param array $options The options to use when saving. + * @param array $options The options to use when saving. * @return \Cake\Datasource\EntityInterface|array An entity. * @throws \Cake\ORM\Exception\PersistenceFailedException When the entity couldn't be saved * @throws \InvalidArgumentException @@ -2507,7 +2507,7 @@ public function hasFinder(string $type): bool * * @param string $type name of the finder to be called * @param \Cake\ORM\Query $query The query object to apply the finder options to - * @param array $options List of options to pass to the finder + * @param array $options List of options to pass to the finder * @return \Cake\ORM\Query * @throws \BadMethodCallException */ @@ -2736,7 +2736,7 @@ public function newEmptyEntity(): EntityInterface * before it is converted into entities. * * @param array $data The data to build an entity with. - * @param array $options A list of options for the object hydration. + * @param array $options A list of options for the object hydration. * @return \Cake\Datasource\EntityInterface * @see \Cake\ORM\Marshaller::one() */ @@ -2777,7 +2777,7 @@ public function newEntity(array $data, array $options = []): EntityInterface * before it is converted into entities. * * @param array $data The data to build an entity with. - * @param array $options A list of options for the objects hydration. + * @param array $options A list of options for the objects hydration. * @return array<\Cake\Datasource\EntityInterface> An array of hydrated records. */ public function newEntities(array $data, array $options = []): array @@ -2835,7 +2835,7 @@ public function newEntities(array $data, array $options = []): array * @param \Cake\Datasource\EntityInterface $entity the entity that will get the * data merged in * @param array $data key value list of fields to be merged into the entity - * @param array $options A list of options for the object hydration. + * @param array $options A list of options for the object hydration. * @return \Cake\Datasource\EntityInterface * @see \Cake\ORM\Marshaller::merge() */ @@ -2875,7 +2875,7 @@ public function patchEntity(EntityInterface $entity, array $data, array $options * @param \Traversable|array<\Cake\Datasource\EntityInterface> $entities the entities that will get the * data merged in * @param array $data list of arrays to be merged into the entities - * @param array $options A list of options for the objects hydration. + * @param array $options A list of options for the objects hydration. * @return array<\Cake\Datasource\EntityInterface> */ public function patchEntities(iterable $entities, array $data, array $options = []): array @@ -2915,7 +2915,7 @@ public function patchEntities(iterable $entities, array $data, array $options = * the data to be validated. * * @param mixed $value The value of column to be checked for uniqueness. - * @param array $options The options array, optionally containing the 'scope' key. + * @param array $options The options array, optionally containing the 'scope' key. * May also be the validation context, if there are no options. * @param array|null $context Either the validation context or null. * @return bool True if the value is unique, or false if a non-scalar, non-unique value was given. @@ -3018,7 +3018,7 @@ public function buildRules(RulesChecker $rules): RulesChecker /** * Gets a SaveOptionsBuilder instance. * - * @param array $options Options to parse by the builder. + * @param array $options Options to parse by the builder. * @return \Cake\ORM\SaveOptionsBuilder */ public function getSaveOptionsBuilder(array $options = []): SaveOptionsBuilder diff --git a/TableRegistry.php b/TableRegistry.php index 52a0c42a..b3a4a7a9 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -86,7 +86,7 @@ public static function setTableLocator(LocatorInterface $tableLocator): void * See options specification in {@link TableLocator::get()}. * * @param string $alias The alias name you want to get. - * @param array $options The options you want to build the table with. + * @param array $options The options you want to build the table with. * @return \Cake\ORM\Table * @deprecated 3.6.0 Use {@link \Cake\ORM\Locator\TableLocator::get()} instead. Will be removed in 5.0. */ From 29abcda8eeb3b7c0a9ec8693840f884a25c70953 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 1 Sep 2021 14:03:01 +0530 Subject: [PATCH 1665/2059] Improve types. --- Query.php | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/Query.php b/Query.php index 6306ae80..d7d687a7 100644 --- a/Query.php +++ b/Query.php @@ -25,7 +25,9 @@ use Cake\Database\ValueBinder; use Cake\Datasource\QueryInterface; use Cake\Datasource\QueryTrait; +use Cake\Datasource\RepositoryInterface; use Cake\Datasource\ResultSetInterface; +use InvalidArgumentException; use JsonSerializable; use RuntimeException; use Traversable; @@ -37,8 +39,6 @@ * required. * * @property \Cake\ORM\Table $_repository Instance of a table object this query is bound to. - * @method \Cake\ORM\Table getRepository() Returns the default table object that will be used by this query, - * that is, the table that will appear in the from clause. */ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface { @@ -144,6 +144,36 @@ public function __construct(Connection $connection, Table $table) $this->addDefaultTypes($table); } + /** + * Set the default Table object that will be used by this query + * and form the `FROM` clause. + * + * @param \Cake\ORM\Table $repository The default table object to use. + * @return $this + * @psalm-suppress MoreSpecificImplementedParamType + */ + public function repository(RepositoryInterface $repository) + { + if (!$repository instanceof Table) { + throw new InvalidArgumentException('$repository must be an instance of Cake\ORM\Table.'); + } + + $this->_repository = $repository; + + return $this; + } + + /** + * Returns the default table object that will be used by this query, + * that is, the table that will appear in the from clause. + * + * @return \Cake\ORM\Table + */ + public function getRepository(): Table + { + return $this->_repository; + } + /** * Adds new fields to be returned by a `SELECT` statement when this query is * executed. Fields can be passed as an array of strings, array of expression From 6c4ff5607f0f3bf1749ce6e1bcdc2c7db450a3bc Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Thu, 2 Sep 2021 17:55:25 -0500 Subject: [PATCH 1666/2059] Make finder functions protected --- Table.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Table.php b/Table.php index 5edb824a..53d598ec 100644 --- a/Table.php +++ b/Table.php @@ -1276,7 +1276,7 @@ public function find(string $type = 'all', array $options = []): Query * @param array $options The options to use for the find * @return \Cake\ORM\Query The query builder */ - public function findAll(Query $query, array $options): Query + protected function findAll(Query $query, array $options): Query { return $query; } @@ -1357,7 +1357,7 @@ public function findAll(Query $query, array $options): Query * @param array $options The options for the find * @return \Cake\ORM\Query The query builder */ - public function findList(Query $query, array $options): Query + protected function findList(Query $query, array $options): Query { $options += [ 'keyField' => $this->getPrimaryKey(), @@ -1422,7 +1422,7 @@ public function findList(Query $query, array $options): Query * @param array $options The options to find with * @return \Cake\ORM\Query The query builder */ - public function findThreaded(Query $query, array $options): Query + protected function findThreaded(Query $query, array $options): Query { $options += [ 'keyField' => $this->getPrimaryKey(), From 360eee5a591543f1a580f236de6e477575ffe948 Mon Sep 17 00:00:00 2001 From: othercorey Date: Fri, 3 Sep 2021 01:18:09 -0500 Subject: [PATCH 1667/2059] Revert "Make finder functions protected" --- Table.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Table.php b/Table.php index 53d598ec..5edb824a 100644 --- a/Table.php +++ b/Table.php @@ -1276,7 +1276,7 @@ public function find(string $type = 'all', array $options = []): Query * @param array $options The options to use for the find * @return \Cake\ORM\Query The query builder */ - protected function findAll(Query $query, array $options): Query + public function findAll(Query $query, array $options): Query { return $query; } @@ -1357,7 +1357,7 @@ protected function findAll(Query $query, array $options): Query * @param array $options The options for the find * @return \Cake\ORM\Query The query builder */ - protected function findList(Query $query, array $options): Query + public function findList(Query $query, array $options): Query { $options += [ 'keyField' => $this->getPrimaryKey(), @@ -1422,7 +1422,7 @@ protected function findList(Query $query, array $options): Query * @param array $options The options to find with * @return \Cake\ORM\Query The query builder */ - protected function findThreaded(Query $query, array $options): Query + public function findThreaded(Query $query, array $options): Query { $options += [ 'keyField' => $this->getPrimaryKey(), From 02625ed516a29ff0f4ad9b639f05a6be8c73c5ae Mon Sep 17 00:00:00 2001 From: mscherer Date: Tue, 31 Aug 2021 22:16:09 +0200 Subject: [PATCH 1668/2059] Start documenting the assoc arrays as per their string key. --- Behavior.php | 2 +- Behavior/TimestampBehavior.php | 2 +- Behavior/TranslateBehavior.php | 2 +- Table.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Behavior.php b/Behavior.php index 1f7939ab..f2695a17 100644 --- a/Behavior.php +++ b/Behavior.php @@ -273,7 +273,7 @@ public function verifyConfig(): void * Override this method if you need to add non-conventional event listeners. * Or if you want your behavior to listen to non-standard events. * - * @return array + * @return array */ public function implementedEvents(): array { diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 6622b11b..41c39c88 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -131,7 +131,7 @@ public function handleEvent(EventInterface $event, EntityInterface $entity): boo * * The implemented events of this behavior depend on configuration * - * @return array + * @return array */ public function implementedEvents(): array { diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 68bfdfe3..e2bccf4b 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -202,7 +202,7 @@ public function setStrategy(TranslateStrategyInterface $strategy) /** * Gets the Model callbacks this behavior is interested in. * - * @return array + * @return array */ public function implementedEvents(): array { diff --git a/Table.php b/Table.php index bf0c543d..45c0dac4 100644 --- a/Table.php +++ b/Table.php @@ -2974,7 +2974,7 @@ public function validateUnique($value, array $options, ?array $context = null): * - Model.beforeRules => beforeRules * - Model.afterRules => afterRules * - * @return array + * @return array */ public function implementedEvents(): array { From c0cc6ec6ca4b92b400752266dda579c3cf7ed2ca Mon Sep 17 00:00:00 2001 From: mscherer Date: Fri, 3 Sep 2021 16:00:27 +0200 Subject: [PATCH 1669/2059] Document the assoc arrays as per their string key. --- Association/Loader/SelectLoader.php | 8 ++++---- Association/Loader/SelectWithPivotLoader.php | 2 +- Behavior.php | 10 +++++----- Behavior/CounterCacheBehavior.php | 4 ++-- Behavior/TimestampBehavior.php | 4 ++-- Behavior/Translate/EavStrategy.php | 4 ++-- Behavior/Translate/ShadowTableStrategy.php | 10 +++++----- Behavior/TranslateBehavior.php | 6 +++--- Behavior/TreeBehavior.php | 2 +- BehaviorRegistry.php | 2 +- EagerLoadable.php | 4 ++-- EagerLoader.php | 2 +- Rule/LinkConstraint.php | 2 +- Table.php | 4 ++-- 14 files changed, 32 insertions(+), 32 deletions(-) diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 7826c25b..d60d95a7 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -238,7 +238,7 @@ protected function _extractFinder($finderData): array * If the required fields are missing, throws an exception. * * @param \Cake\ORM\Query $fetchQuery The association fetching query - * @param array $key The foreign key fields to check + * @param array $key The foreign key fields to check * @return void * @throws \InvalidArgumentException */ @@ -458,7 +458,7 @@ protected function _subqueryFields(Query $query): array * * @param \Cake\ORM\Query $fetchQuery The query to get results from * @param array $options The options passed to the eager loader - * @return array + * @return array */ protected function _buildResultMap(Query $fetchQuery, array $options): array { @@ -489,7 +489,7 @@ protected function _buildResultMap(Query $fetchQuery, array $options): array * for injecting the eager loaded rows * * @param \Cake\ORM\Query $fetchQuery the Query used to fetch results - * @param array $resultMap an array with the foreignKey as keys and + * @param array $resultMap an array with the foreignKey as keys and * the corresponding target table results as value. * @param array $options The options passed to the eagerLoader method * @return \Closure @@ -527,7 +527,7 @@ protected function _resultInjector(Query $fetchQuery, array $resultMap, array $o * for injecting the eager loaded rows when the matching needs to * be done with multiple foreign keys * - * @param array $resultMap A keyed arrays containing the target table + * @param array $resultMap A keyed arrays containing the target table * @param array $sourceKeys An array with aliased keys to match * @param string $nestKey The key under which results should be nested * @return \Closure diff --git a/Association/Loader/SelectWithPivotLoader.php b/Association/Loader/SelectWithPivotLoader.php index 063bb9b3..318a8e45 100644 --- a/Association/Loader/SelectWithPivotLoader.php +++ b/Association/Loader/SelectWithPivotLoader.php @@ -166,7 +166,7 @@ protected function _linkField(array $options) * * @param \Cake\ORM\Query $fetchQuery The query to get results from * @param array $options The options passed to the eager loader - * @return array + * @return array * @throws \RuntimeException when the association property is not part of the results set. */ protected function _buildResultMap(Query $fetchQuery, array $options): array diff --git a/Behavior.php b/Behavior.php index f2695a17..ac049bf1 100644 --- a/Behavior.php +++ b/Behavior.php @@ -138,7 +138,7 @@ class Behavior implements EventListenerInterface * * These are merged with user-provided configuration when the behavior is used. * - * @var array + * @var array */ protected $_defaultConfig = []; @@ -148,7 +148,7 @@ class Behavior implements EventListenerInterface * Merges config with the default and store in the config property * * @param \Cake\ORM\Table $table The table this behavior is attached to. - * @param array $config The config for this behavior. + * @param array $config The config for this behavior. */ public function __construct(Table $table, array $config = []) { @@ -173,7 +173,7 @@ public function __construct(Table $table, array $config = []) * Implement this method to avoid having to overwrite * the constructor and call parent. * - * @param array $config The configuration settings provided to this behavior. + * @param array $config The configuration settings provided to this behavior. * @return void */ public function initialize(array $config): void @@ -207,8 +207,8 @@ public function table(): Table * Removes aliased methods that would otherwise be duplicated by userland configuration. * * @param string $key The key to filter. - * @param array $defaults The default method mappings. - * @param array $config The customized method mappings. + * @param array $defaults The default method mappings. + * @param array $config The customized method mappings. * @return array A de-duped list of config data. */ protected function _resolveMethodAliases(string $key, array $defaults, array $config): array diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 22a8562f..d2cdc5e4 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -208,7 +208,7 @@ protected function _processAssociations(EventInterface $event, EntityInterface $ * @param \Cake\Event\EventInterface $event Event instance. * @param \Cake\Datasource\EntityInterface $entity Entity * @param \Cake\ORM\Association $assoc The association object - * @param array $settings The settings for for counter cache for this association + * @param array $settings The settings for counter cache for this association * @return void * @throws \RuntimeException If invalid callable is passed. */ @@ -289,7 +289,7 @@ protected function _shouldUpdateCount(array $conditions) /** * Fetches and returns the count for a single field in an association * - * @param array $config The counter cache configuration for a single field + * @param array $config The counter cache configuration for a single field * @param array $conditions Additional conditions given to the query * @return int The number of relations matching the given config and conditions */ diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 41c39c88..57e3ec4e 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -44,7 +44,7 @@ class TimestampBehavior extends Behavior * the code is executed, to set to an explicit date time value - set refreshTimetamp to false * and call setTimestamp() on the behavior class before use. * - * @var array + * @var array */ protected $_defaultConfig = [ 'implementedFinders' => [], @@ -74,7 +74,7 @@ class TimestampBehavior extends Behavior * If events are specified - do *not* merge them with existing events, * overwrite the events to listen on * - * @param array $config The config for this behavior. + * @param array $config The config for this behavior. * @return void */ public function initialize(array $config): void diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index 2a9f0e46..4b1e89ec 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -52,7 +52,7 @@ class EavStrategy implements TranslateStrategyInterface * * These are merged with user-provided configuration. * - * @var array + * @var array */ protected $_defaultConfig = [ 'fields' => [], @@ -70,7 +70,7 @@ class EavStrategy implements TranslateStrategyInterface * Constructor * * @param \Cake\ORM\Table $table The table this strategy is attached to. - * @param array $config The config for this strategy. + * @param array $config The config for this strategy. */ public function __construct(Table $table, array $config = []) { diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index f7b6bd2c..328fbcbe 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -45,7 +45,7 @@ class ShadowTableStrategy implements TranslateStrategyInterface * * These are merged with user-provided configuration. * - * @var array + * @var array */ protected $_defaultConfig = [ 'fields' => [], @@ -62,7 +62,7 @@ class ShadowTableStrategy implements TranslateStrategyInterface * Constructor * * @param \Cake\ORM\Table $table Table instance. - * @param array $config Configuration. + * @param array $config Configuration. */ public function __construct(Table $table, array $config = []) { @@ -202,7 +202,7 @@ protected function setupHasOneAssociation(string $locale, ArrayObject $options): * add the locale field though. * * @param \Cake\ORM\Query $query The query to check. - * @param array $config The config to use for adding fields. + * @param array $config The config to use for adding fields. * @return bool Whether a join to the translation table is required. */ protected function addFieldsToQuery($query, array $config) @@ -244,7 +244,7 @@ protected function addFieldsToQuery($query, array $config) * * @param \Cake\ORM\Query $query the query to check. * @param string $name The clause name. - * @param array $config The config to use for adding fields. + * @param array $config The config to use for adding fields. * @return bool Whether a join to the translation table is required. */ protected function iterateClause($query, $name = '', $config = []): bool @@ -290,7 +290,7 @@ function ($c, &$field) use ($fields, $alias, $mainTableAlias, $mainTableFields, * * @param \Cake\ORM\Query $query the query to check. * @param string $name The clause name. - * @param array $config The config to use for adding fields. + * @param array $config The config to use for adding fields. * @return bool Whether a join to the translation table is required. */ protected function traverseClause($query, $name = '', $config = []): bool diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index e2bccf4b..dc0df667 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -45,7 +45,7 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface * * These are merged with user-provided configuration when the behavior is used. * - * @var array + * @var array */ protected $_defaultConfig = [ 'implementedFinders' => ['translations' => 'findTranslations'], @@ -103,7 +103,7 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface * are created/modified. Default `null`. * * @param \Cake\ORM\Table $table The table this behavior is attached to. - * @param array $config The config for this behavior. + * @param array $config The config for this behavior. */ public function __construct(Table $table, array $config = []) { @@ -119,7 +119,7 @@ public function __construct(Table $table, array $config = []) /** * Initialize hook * - * @param array $config The config for this behavior. + * @param array $config The config for this behavior. * @return void */ public function initialize(array $config): void diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 1effd208..62ac3369 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -52,7 +52,7 @@ class TreeBehavior extends Behavior * * These are merged with user-provided configuration when the behavior is used. * - * @var array + * @var array */ protected $_defaultConfig = [ 'implementedFinders' => [ diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 7f7b2c2c..9afbb9a6 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -135,7 +135,7 @@ protected function _throwMissingClassError(string $class, ?string $plugin): void * * @param string $class The classname that is missing. * @param string $alias The alias of the object. - * @param array $config An array of config to use for the behavior. + * @param array $config An array of config to use for the behavior. * @return \Cake\ORM\Behavior The constructed behavior class. * @psalm-suppress MoreSpecificImplementedParamType */ diff --git a/EagerLoadable.php b/EagerLoadable.php index 98aba9c0..0397a09b 100644 --- a/EagerLoadable.php +++ b/EagerLoadable.php @@ -126,7 +126,7 @@ class EagerLoadable * The keys maps to the settable properties in this class. * * @param string $name The Association name. - * @param array $config The list of properties to set. + * @param array $config The list of properties to set. */ public function __construct(string $name, array $config = []) { @@ -236,7 +236,7 @@ public function canBeJoined(): bool * Sets the list of options to pass to the association object for loading * the records. * - * @param array $config The value to set. + * @param array $config The value to set. * @return $this */ public function setConfig(array $config) diff --git a/EagerLoader.php b/EagerLoader.php index 8f164c56..92482d68 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -476,7 +476,7 @@ public function externalAssociations(Table $repository): array * @param \Cake\ORM\Table $parent owning side of the association * @param string $alias name of the association to be loaded * @param array $options list of extra options to use for this association - * @param array $paths An array with two values, the first one is a list of dot + * @param array $paths An array with two values, the first one is a list of dot * separated strings representing associations that lead to this `$alias` in the * chain of associations to be loaded. The second value is the path to follow in * entities' properties to fetch a record of the corresponding association. diff --git a/Rule/LinkConstraint.php b/Rule/LinkConstraint.php index 587df27e..7fa41632 100644 --- a/Rule/LinkConstraint.php +++ b/Rule/LinkConstraint.php @@ -126,7 +126,7 @@ public function __invoke(EntityInterface $entity, array $options): bool /** * Alias fields. * - * @param array $fields The fields that should be aliased. + * @param array $fields The fields that should be aliased. * @param \Cake\ORM\Table $source The object to use for aliasing. * @return array The aliased fields */ diff --git a/Table.php b/Table.php index 70a8e165..e8d5ccdf 100644 --- a/Table.php +++ b/Table.php @@ -273,7 +273,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc * validation set, or an associative array, where key is the name of the * validation set and value the Validator instance. * - * @param array $config List of options for this table + * @param array $config List of options for this table */ public function __construct(array $config = []) { @@ -353,7 +353,7 @@ public static function defaultConnectionName(): string * } * ``` * - * @param array $config Configuration options passed to the constructor + * @param array $config Configuration options passed to the constructor * @return void */ public function initialize(array $config): void From 30cf1cbc968d080f83317a4420a2e213f084e363 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 3 Sep 2021 23:27:26 +0530 Subject: [PATCH 1670/2059] Deprecate passing validator instance for `validate` option. --- Marshaller.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Marshaller.php b/Marshaller.php index 85796a34..4e88bfeb 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -257,6 +257,11 @@ protected function _validate(array $data, array $options, bool $isNew): array } elseif (is_string($options['validate'])) { $validator = $this->_table->getValidator($options['validate']); } elseif (is_object($options['validate'])) { + deprecationWarning( + 'Passing validator instance for the `validate` option is deprecated,' + . ' use `ValidatorAwareTrait::setValidator() instead.`' + ); + /** @var \Cake\Validation\Validator $validator */ $validator = $options['validate']; } From c33be72d5d9976cd1fd9ad5865da935574941483 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Fri, 3 Sep 2021 10:01:23 -0500 Subject: [PATCH 1671/2059] Add native type hints for Database properties --- Query.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Query.php b/Query.php index d7d687a7..636d9469 100644 --- a/Query.php +++ b/Query.php @@ -1058,7 +1058,7 @@ public function isHydrationEnabled(): bool */ public function cache($key, $config = 'default') { - if ($this->_type !== 'select' && $this->_type !== null) { + if ($this->_type !== 'select') { throw new RuntimeException('You cannot cache the results of non-select queries.'); } @@ -1073,7 +1073,7 @@ public function cache($key, $config = 'default') */ public function all(): ResultSetInterface { - if ($this->_type !== 'select' && $this->_type !== null) { + if ($this->_type !== 'select') { throw new RuntimeException( 'You cannot call all() on a non-select query. Use execute() instead.' ); From 1c9aa6db3918760ae96fbb47471bc6965da79279 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 4 Sep 2021 14:13:32 +0530 Subject: [PATCH 1672/2059] Remove deprecated code. --- Marshaller.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 07052dec..2f789ae5 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -256,14 +256,6 @@ protected function _validate(array $data, array $options, bool $isNew): array $validator = $this->_table->getValidator(); } elseif (is_string($options['validate'])) { $validator = $this->_table->getValidator($options['validate']); - } elseif (is_object($options['validate'])) { - deprecationWarning( - 'Passing validator instance for the `validate` option is deprecated,' - . ' use `ValidatorAwareTrait::setValidator() instead.`' - ); - - /** @var \Cake\Validation\Validator $validator */ - $validator = $options['validate']; } if ($validator === null) { From 116312d83cc4f4cc9dee0d0aa1ee8b231c9572db Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 4 Sep 2021 20:03:40 +0530 Subject: [PATCH 1673/2059] Simplify Marshaller::_validate(). --- Marshaller.php | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 2f789ae5..e9194118 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -25,7 +25,6 @@ use Cake\ORM\Association\BelongsToMany; use Cake\Utility\Hash; use InvalidArgumentException; -use RuntimeException; /** * Contains logic to convert array data into entities. @@ -188,7 +187,7 @@ public function one(array $data, array $options = []): EntityInterface $entity->setAccess($key, $value); } } - $errors = $this->_validate($data, $options, true); + $errors = $this->_validate($data, $options['validate'], true); $options['isMerge'] = false; $propertyMap = $this->_buildPropertyMap($data, $options); @@ -240,32 +239,22 @@ public function one(array $data, array $options = []): EntityInterface * Returns the validation errors for a data set based on the passed options * * @param array $data The data to validate. - * @param array $options The options passed to this marshaller. + * @param string|bool $validator Validator name or `true` for default validator. * @param bool $isNew Whether it is a new entity or one to be updated. * @return array The list of validation errors. * @throws \RuntimeException If no validator can be created. */ - protected function _validate(array $data, array $options, bool $isNew): array + protected function _validate(array $data, string|bool $validator, bool $isNew): array { - if (!$options['validate']) { + if (!$validator) { return []; } - $validator = null; - if ($options['validate'] === true) { - $validator = $this->_table->getValidator(); - } elseif (is_string($options['validate'])) { - $validator = $this->_table->getValidator($options['validate']); - } - - if ($validator === null) { - throw new RuntimeException(sprintf( - 'validate must be a boolean, a string or an object. Got %s.', - get_debug_type($options['validate']) - )); + if ($validator === true) { + $validator = null; } - return $validator->validate($data, $isNew); + return $this->_table->getValidator($validator)->validate($data, $isNew); } /** @@ -553,7 +542,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) } } - $errors = $this->_validate($data + $keys, $options, $isNew); + $errors = $this->_validate($data + $keys, $options['validate'], $isNew); $options['isMerge'] = true; $propertyMap = $this->_buildPropertyMap($data, $options); $properties = []; From de0ac1b7af609a5b3e446980da651d2da6b36084 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Sat, 4 Sep 2021 22:53:22 -0500 Subject: [PATCH 1674/2059] Add native type hints for ORM properties --- Association.php | 20 ++++----- Association/BelongsTo.php | 2 +- Association/BelongsToMany.php | 22 +++++----- Association/HasMany.php | 10 ++--- Association/HasOne.php | 2 +- Association/Loader/SelectLoader.php | 20 ++++----- Association/Loader/SelectWithPivotLoader.php | 11 +++-- AssociationCollection.php | 2 +- Behavior.php | 6 +-- Behavior/CounterCacheBehavior.php | 2 +- Behavior/TimestampBehavior.php | 4 +- Behavior/Translate/EavStrategy.php | 2 +- Behavior/Translate/ShadowTableStrategy.php | 2 +- Behavior/Translate/TranslateStrategyTrait.php | 6 +-- Behavior/TranslateBehavior.php | 6 +-- Behavior/TreeBehavior.php | 4 +- BehaviorRegistry.php | 6 +-- EagerLoadable.php | 18 ++++---- EagerLoader.php | 16 +++---- Exception/PersistenceFailedException.php | 2 +- Locator/LocatorAwareTrait.php | 2 +- Locator/TableLocator.php | 10 ++--- Marshaller.php | 2 +- Query.php | 14 +++--- ResultSet.php | 44 ++++++++++--------- Rule/ExistsIn.php | 6 +-- Rule/IsUnique.php | 4 +- Rule/LinkConstraint.php | 4 +- Rule/ValidCount.php | 2 +- SaveOptionsBuilder.php | 4 +- Table.php | 24 +++++----- 31 files changed, 143 insertions(+), 136 deletions(-) diff --git a/Association.php b/Association.php index 70c6a4d1..2b84489c 100644 --- a/Association.php +++ b/Association.php @@ -96,21 +96,21 @@ abstract class Association * * @var string */ - protected $_name; + protected string $_name; /** * The class name of the target table object * * @var string */ - protected $_className; + protected string $_className; /** * The field name in the owning side table that is used to match with the foreignKey * * @var array|string|null */ - protected $_bindingKey; + protected array|string|null $_bindingKey = null; /** * The name of the field representing the foreign key to the table to load @@ -125,7 +125,7 @@ abstract class Association * * @var \Closure|array */ - protected $_conditions = []; + protected Closure|array $_conditions = []; /** * Whether the records on the target table are dependent on the source table, @@ -134,14 +134,14 @@ abstract class Association * * @var bool */ - protected $_dependent = false; + protected bool $_dependent = false; /** * Whether or not cascaded deletes should also fire callbacks. * * @var bool */ - protected $_cascadeCallbacks = false; + protected bool $_cascadeCallbacks = false; /** * Source table instance @@ -162,7 +162,7 @@ abstract class Association * * @var string */ - protected $_joinType = Query::JOIN_TYPE_LEFT; + protected string $_joinType = Query::JOIN_TYPE_LEFT; /** * The property name that should be filled with data from the target table @@ -178,7 +178,7 @@ abstract class Association * * @var string */ - protected $_strategy = self::STRATEGY_JOIN; + protected string $_strategy = self::STRATEGY_JOIN; /** * The default finder name to use for fetching rows from the target table @@ -186,14 +186,14 @@ abstract class Association * * @var array|string */ - protected $_finder = 'all'; + protected array|string $_finder = 'all'; /** * Valid strategies for this association. Subclasses can narrow this down. * * @var array */ - protected $_validStrategies = [ + protected array $_validStrategies = [ self::STRATEGY_JOIN, self::STRATEGY_SELECT, self::STRATEGY_SUBQUERY, diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 73b2d078..70dc971c 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -38,7 +38,7 @@ class BelongsTo extends Association * * @var array */ - protected $_validStrategies = [ + protected array $_validStrategies = [ self::STRATEGY_JOIN, self::STRATEGY_SELECT, ]; diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 71d714ee..686289cb 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -58,14 +58,14 @@ class BelongsToMany extends Association * * @var string */ - protected $_joinType = Query::JOIN_TYPE_INNER; + protected string $_joinType = Query::JOIN_TYPE_INNER; /** * The strategy name to be used to fetch associated records. * * @var string */ - protected $_strategy = self::STRATEGY_SELECT; + protected string $_strategy = self::STRATEGY_SELECT; /** * Junction table instance @@ -79,7 +79,7 @@ class BelongsToMany extends Association * * @var string */ - protected $_junctionTableName; + protected string $_junctionTableName; /** * The name of the hasMany association from the target table @@ -95,21 +95,21 @@ class BelongsToMany extends Association * * @var string */ - protected $_junctionProperty = '_joinData'; + protected string $_junctionProperty = '_joinData'; /** * Saving strategy to be used by this association * * @var string */ - protected $_saveStrategy = self::SAVE_REPLACE; + protected string $_saveStrategy = self::SAVE_REPLACE; /** * The name of the field representing the foreign key to the target table * * @var array|string|null */ - protected $_targetForeignKey; + protected array|string|null $_targetForeignKey = null; /** * The table instance for the junction relation. @@ -123,7 +123,7 @@ class BelongsToMany extends Association * * @var array */ - protected $_validStrategies = [ + protected array $_validStrategies = [ self::STRATEGY_SELECT, self::STRATEGY_SUBQUERY, ]; @@ -136,28 +136,28 @@ class BelongsToMany extends Association * * @var bool */ - protected $_dependent = true; + protected bool $_dependent = true; /** * Filtered conditions that reference the target table. * * @var array|null */ - protected $_targetConditions; + protected ?array $_targetConditions = null; /** * Filtered conditions that reference the junction table. * * @var array|null */ - protected $_junctionConditions; + protected ?array $_junctionConditions = null; /** * Order in which target records should be returned * * @var mixed */ - protected $_sort; + protected mixed $_sort = null; /** * Sets the name of the field representing the foreign key to the target table. diff --git a/Association/HasMany.php b/Association/HasMany.php index 1ea902c4..bc780a0b 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -41,28 +41,28 @@ class HasMany extends Association * * @var mixed */ - protected $_sort; + protected mixed $_sort = null; /** * The type of join to be used when adding the association to a query * * @var string */ - protected $_joinType = Query::JOIN_TYPE_INNER; + protected string $_joinType = Query::JOIN_TYPE_INNER; /** * The strategy name to be used to fetch associated records. * * @var string */ - protected $_strategy = self::STRATEGY_SELECT; + protected string $_strategy = self::STRATEGY_SELECT; /** * Valid strategies for this type of association * * @var array */ - protected $_validStrategies = [ + protected array $_validStrategies = [ self::STRATEGY_SELECT, self::STRATEGY_SUBQUERY, ]; @@ -86,7 +86,7 @@ class HasMany extends Association * * @var string */ - protected $_saveStrategy = self::SAVE_APPEND; + protected string $_saveStrategy = self::SAVE_APPEND; /** * Returns whether or not the passed table is the owning side for this diff --git a/Association/HasOne.php b/Association/HasOne.php index 82e02ce2..9b65b616 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -36,7 +36,7 @@ class HasOne extends Association * * @var array */ - protected $_validStrategies = [ + protected array $_validStrategies = [ self::STRATEGY_JOIN, self::STRATEGY_SELECT, ]; diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index fe34921f..d6e8d855 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -37,42 +37,42 @@ class SelectLoader * * @var string */ - protected $alias; + protected string $alias; /** * The alias of the source association * * @var string */ - protected $sourceAlias; + protected string $sourceAlias; /** * The alias of the target association * * @var string */ - protected $targetAlias; + protected string $targetAlias; /** * The foreignKey to the target association * * @var array|string */ - protected $foreignKey; + protected array|string $foreignKey; /** * The strategy to use for loading, either select or subquery * * @var string */ - protected $strategy; + protected string $strategy; /** * The binding key for the source association. * - * @var string + * @var array|string */ - protected $bindingKey; + protected array|string $bindingKey; /** * A callable that will return a query object used for loading the association results @@ -86,14 +86,14 @@ class SelectLoader * * @var string */ - protected $associationType; + protected string $associationType; /** * The sorting options for loading the association * - * @var string + * @var array|string|null */ - protected $sort; + protected array|string|null $sort = null; /** * Copies the options array to properties in this class. The keys in the array correspond diff --git a/Association/Loader/SelectWithPivotLoader.php b/Association/Loader/SelectWithPivotLoader.php index cf716787..c6bd0b9e 100644 --- a/Association/Loader/SelectWithPivotLoader.php +++ b/Association/Loader/SelectWithPivotLoader.php @@ -16,7 +16,10 @@ */ namespace Cake\ORM\Association\Loader; +use Cake\Database\ExpressionInterface; +use Cake\ORM\Association\HasMany; use Cake\ORM\Query; +use Closure; use RuntimeException; /** @@ -31,28 +34,28 @@ class SelectWithPivotLoader extends SelectLoader * * @var string */ - protected $junctionAssociationName; + protected string $junctionAssociationName; /** * The property name for the junction association, where its results should be nested at. * * @var string */ - protected $junctionProperty; + protected string $junctionProperty; /** * The junction association instance * * @var \Cake\ORM\Association\HasMany */ - protected $junctionAssoc; + protected HasMany $junctionAssoc; /** * Custom conditions for the junction association * * @var \Cake\Database\ExpressionInterface|\Closure|array|string|null */ - protected $junctionConditions; + protected ExpressionInterface|Closure|array|string|null $junctionConditions = null; /** * @inheritDoc diff --git a/AssociationCollection.php b/AssociationCollection.php index 2ca90d74..c8bf1bd1 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -40,7 +40,7 @@ class AssociationCollection implements IteratorAggregate * * @var array<\Cake\ORM\Association> */ - protected $_items = []; + protected array $_items = []; /** * Constructor. diff --git a/Behavior.php b/Behavior.php index 3ae22254..b3583726 100644 --- a/Behavior.php +++ b/Behavior.php @@ -121,7 +121,7 @@ class Behavior implements EventListenerInterface * * @var \Cake\ORM\Table */ - protected $_table; + protected Table $_table; /** * Reflection method cache for behaviors. @@ -131,7 +131,7 @@ class Behavior implements EventListenerInterface * * @var array */ - protected static $_reflectionCache = []; + protected static array $_reflectionCache = []; /** * Default configuration @@ -140,7 +140,7 @@ class Behavior implements EventListenerInterface * * @var array */ - protected $_defaultConfig = []; + protected array $_defaultConfig = []; /** * Constructor diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 972ba6e4..b52e0c02 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -108,7 +108,7 @@ class CounterCacheBehavior extends Behavior * * @var array */ - protected $_ignoreDirty = []; + protected array $_ignoreDirty = []; /** * beforeSave callback. diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index b513d5f3..48494661 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -46,7 +46,7 @@ class TimestampBehavior extends Behavior * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'implementedFinders' => [], 'implementedMethods' => [ 'timestamp' => 'timestamp', @@ -66,7 +66,7 @@ class TimestampBehavior extends Behavior * * @var \Cake\I18n\DateTime|null */ - protected $_ts; + protected ?DateTime $_ts = null; /** * Initialize hook diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index 00708309..fcce4b9d 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -54,7 +54,7 @@ class EavStrategy implements TranslateStrategyInterface * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'fields' => [], 'translationTable' => 'I18n', 'defaultLocale' => null, diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 1c290dcf..68c32f25 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -47,7 +47,7 @@ class ShadowTableStrategy implements TranslateStrategyInterface * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'fields' => [], 'defaultLocale' => null, 'referenceName' => null, diff --git a/Behavior/Translate/TranslateStrategyTrait.php b/Behavior/Translate/TranslateStrategyTrait.php index 1e9ee4d9..0b02c127 100644 --- a/Behavior/Translate/TranslateStrategyTrait.php +++ b/Behavior/Translate/TranslateStrategyTrait.php @@ -32,7 +32,7 @@ trait TranslateStrategyTrait * * @var \Cake\ORM\Table */ - protected $table; + protected Table $table; /** * The locale name that will be used to override fields in the bound table @@ -40,14 +40,14 @@ trait TranslateStrategyTrait * * @var string|null */ - protected $locale; + protected ?string $locale = null; /** * Instance of Table responsible for translating * * @var \Cake\ORM\Table */ - protected $translationTable; + protected Table $translationTable; /** * Return translation table instance. diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 5e4705dc..1416cc9a 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -47,7 +47,7 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'implementedFinders' => ['translations' => 'findTranslations'], 'implementedMethods' => [ 'setLocale' => 'setLocale', @@ -70,14 +70,14 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface * @var string * @psalm-var class-string<\Cake\ORM\Behavior\Translate\TranslateStrategyInterface> */ - protected static $defaultStrategyClass = EavStrategy::class; + protected static string $defaultStrategyClass = EavStrategy::class; /** * Translation strategy instance. * * @var \Cake\ORM\Behavior\Translate\TranslateStrategyInterface|null */ - protected $strategy; + protected ?TranslateStrategyInterface $strategy = null; /** * Constructor diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index bfd522c6..59a3239a 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -45,7 +45,7 @@ class TreeBehavior extends Behavior * * @var string */ - protected $_primaryKey; + protected string $_primaryKey = ''; /** * Default config @@ -54,7 +54,7 @@ class TreeBehavior extends Behavior * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'implementedFinders' => [ 'path' => 'findPath', 'children' => 'findChildren', diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 3c6b40a7..19b16896 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -41,21 +41,21 @@ class BehaviorRegistry extends ObjectRegistry implements EventDispatcherInterfac * * @var \Cake\ORM\Table */ - protected $_table; + protected Table $_table; /** * Method mappings. * * @var array */ - protected $_methodMap = []; + protected array $_methodMap = []; /** * Finder method mappings. * * @var array */ - protected $_finderMap = []; + protected array $_finderMap = []; /** * Constructor diff --git a/EagerLoadable.php b/EagerLoadable.php index 64e7690d..0ce175be 100644 --- a/EagerLoadable.php +++ b/EagerLoadable.php @@ -33,21 +33,21 @@ class EagerLoadable * * @var string */ - protected $_name; + protected string $_name; /** * A list of other associations to load from this level. * * @var array<\Cake\ORM\EagerLoadable> */ - protected $_associations = []; + protected array $_associations = []; /** * The Association class instance to use for loading the records. * * @var \Cake\ORM\Association|null */ - protected $_instance; + protected ?Association $_instance = null; /** * A list of options to pass to the association object for loading @@ -55,7 +55,7 @@ class EagerLoadable * * @var array */ - protected $_config = []; + protected array $_config = []; /** * A dotted separated string representing the path of associations @@ -63,7 +63,7 @@ class EagerLoadable * * @var string */ - protected $_aliasPath; + protected string $_aliasPath; /** * A dotted separated string representing the path of entity properties @@ -79,14 +79,14 @@ class EagerLoadable * * @var string|null */ - protected $_propertyPath; + protected ?string $_propertyPath = null; /** * Whether or not this level can be fetched using a join. * * @var bool */ - protected $_canBeJoined = false; + protected bool $_canBeJoined = false; /** * Whether or not this level was meant for a "matching" fetch @@ -94,7 +94,7 @@ class EagerLoadable * * @var bool|null */ - protected $_forMatching; + protected ?bool $_forMatching = null; /** * The property name where the association result should be nested @@ -110,7 +110,7 @@ class EagerLoadable * * @var string|null */ - protected $_targetProperty; + protected ?string $_targetProperty = null; /** * Constructor. The $config parameter accepts the following array diff --git a/EagerLoader.php b/EagerLoader.php index 712fe118..244b942a 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -36,7 +36,7 @@ class EagerLoader * * @var array */ - protected $_containments = []; + protected array $_containments = []; /** * Contains a nested array with the compiled containments tree @@ -44,7 +44,7 @@ class EagerLoader * * @var \Cake\ORM\EagerLoadable|array<\Cake\ORM\EagerLoadable>|null */ - protected $_normalized; + protected EagerLoadable|array|null $_normalized = null; /** * List of options accepted by associations in contain() @@ -52,7 +52,7 @@ class EagerLoader * * @var array */ - protected $_containOptions = [ + protected array $_containOptions = [ 'associations' => 1, 'foreignKey' => 1, 'conditions' => 1, @@ -71,21 +71,21 @@ class EagerLoader * * @var array<\Cake\ORM\EagerLoadable> */ - protected $_loadExternal = []; + protected array $_loadExternal = []; /** * Contains a list of the association names that are to be eagerly loaded * * @var array */ - protected $_aliasList = []; + protected array $_aliasList = []; /** * Another EagerLoader instance that will be used for 'matching' associations. * * @var \Cake\ORM\EagerLoader|null */ - protected $_matching; + protected ?EagerLoader $_matching = null; /** * A map of table aliases pointing to the association objects they represent @@ -93,7 +93,7 @@ class EagerLoader * * @var array */ - protected $_joinsMap = []; + protected array $_joinsMap = []; /** * Controls whether or not fields from associated tables @@ -102,7 +102,7 @@ class EagerLoader * * @var bool */ - protected $_autoFields = true; + protected bool $_autoFields = true; /** * Sets the list of associations that should be eagerly loaded along for a diff --git a/Exception/PersistenceFailedException.php b/Exception/PersistenceFailedException.php index 870c42e8..d7d5e5cc 100644 --- a/Exception/PersistenceFailedException.php +++ b/Exception/PersistenceFailedException.php @@ -29,7 +29,7 @@ class PersistenceFailedException extends CakeException * * @var \Cake\Datasource\EntityInterface */ - protected $_entity; + protected EntityInterface $_entity; /** * @inheritDoc diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index 3c17d437..f61307d4 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -28,7 +28,7 @@ trait LocatorAwareTrait * * @var \Cake\ORM\Locator\LocatorInterface|null */ - protected $_tableLocator; + protected ?LocatorInterface $_tableLocator = null; /** * Sets the table locator. diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index d3641379..76af3ca0 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -36,14 +36,14 @@ class TableLocator extends AbstractLocator implements LocatorInterface * * @var array */ - protected $locations = []; + protected array $locations = []; /** * Configuration for aliases. * * @var array */ - protected $_config = []; + protected array $_config = []; /** * Instances that belong to the registry. @@ -58,7 +58,7 @@ class TableLocator extends AbstractLocator implements LocatorInterface * * @var array<\Cake\ORM\Table> */ - protected $_fallbacked = []; + protected array $_fallbacked = []; /** * Fallback class to use @@ -66,14 +66,14 @@ class TableLocator extends AbstractLocator implements LocatorInterface * @var string * @psalm-var class-string<\Cake\ORM\Table> */ - protected $fallbackClassName = Table::class; + protected string $fallbackClassName = Table::class; /** * Whether fallback class should be used if a table class could not be found. * * @var bool */ - protected $allowFallbackClass = true; + protected bool $allowFallbackClass = true; /** * Constructor. diff --git a/Marshaller.php b/Marshaller.php index e9194118..37a15040 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -45,7 +45,7 @@ class Marshaller * * @var \Cake\ORM\Table */ - protected $_table; + protected Table $_table; /** * Constructor. diff --git a/Query.php b/Query.php index f3c0e1b9..60dad29f 100644 --- a/Query.php +++ b/Query.php @@ -75,7 +75,7 @@ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface * * @var bool|null */ - protected $_hasFields; + protected ?bool $_hasFields = null; /** * Tracks whether or not the original query should include @@ -83,21 +83,21 @@ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface * * @var bool|null */ - protected $_autoFields; + protected ?bool $_autoFields = null; /** * Whether to hydrate results into entity objects * * @var bool */ - protected $_hydrate = true; + protected bool $_hydrate = true; /** * Whether aliases are generated for fields. * * @var bool */ - protected $aliasingEnabled = true; + protected bool $aliasingEnabled = true; /** * A callable function that can be used to calculate the total amount of @@ -113,14 +113,14 @@ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface * * @var \Cake\ORM\EagerLoader|null */ - protected $_eagerLoader; + protected ?EagerLoader $_eagerLoader = null; /** * True if the beforeFind event has already been triggered for this query * * @var bool */ - protected $_beforeFindFired = false; + protected bool $_beforeFindFired = false; /** * The COUNT(*) for the query. @@ -129,7 +129,7 @@ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface * * @var int|null */ - protected $_resultsCount; + protected ?int $_resultsCount = null; /** * Constructor diff --git a/ResultSet.php b/ResultSet.php index 7dd34685..d3daf538 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -18,6 +18,7 @@ use Cake\Collection\Collection; use Cake\Collection\CollectionTrait; +use Cake\Database\DriverInterface; use Cake\Database\Exception\DatabaseException; use Cake\Database\StatementInterface; use Cake\Datasource\EntityInterface; @@ -37,37 +38,37 @@ class ResultSet implements ResultSetInterface /** * Database statement holding the results * - * @var \Cake\Database\StatementInterface + * @var \Cake\Database\StatementInterface|null */ - protected $_statement; + protected ?StatementInterface $_statement = null; /** * Points to the next record number that should be fetched * * @var int */ - protected $_index = 0; + protected int $_index = 0; /** * Last record fetched from the statement * * @var \Cake\Datasource\EntityInterface|array */ - protected $_current; + protected EntityInterface|array $_current = []; /** * Default table instance * * @var \Cake\ORM\Table */ - protected $_defaultTable; + protected Table $_defaultTable; /** * The default table alias * * @var string */ - protected $_defaultAlias; + protected string $_defaultAlias; /** * List of associations that should be placed under the `_matchingData` @@ -75,14 +76,14 @@ class ResultSet implements ResultSetInterface * * @var array */ - protected $_matchingMap = []; + protected array $_matchingMap = []; /** * List of associations that should be eager loaded. * * @var array */ - protected $_containMap = []; + protected array $_containMap = []; /** * Map of fields that are fetched from the statement with @@ -90,7 +91,7 @@ class ResultSet implements ResultSetInterface * * @var array */ - protected $_map = []; + protected array $_map = []; /** * List of matching associations and the column keys to expect @@ -98,49 +99,49 @@ class ResultSet implements ResultSetInterface * * @var array */ - protected $_matchingMapColumns = []; + protected array $_matchingMapColumns = []; /** * Results that have been fetched or hydrated into the results. * * @var \SplFixedArray|array */ - protected $_results = []; + protected SplFixedArray|array $_results = []; /** * Whether to hydrate results into objects or not * * @var bool */ - protected $_hydrate = true; + protected bool $_hydrate = true; /** * Tracks value of $_autoFields property of $query passed to constructor. * * @var bool|null */ - protected $_autoFields; + protected ?bool $_autoFields = null; /** * The fully namespaced name of the class to use for hydrating results * * @var string */ - protected $_entityClass; + protected string $_entityClass; /** * Whether or not to buffer results fetched from the statement * * @var bool */ - protected $_useBuffering = true; + protected bool $_useBuffering = true; /** * Holds the count of records in this result set * - * @var int + * @var int|null */ - protected $_count; + protected ?int $_count = null; /** * The Database driver object. @@ -149,7 +150,7 @@ class ResultSet implements ResultSetInterface * * @var \Cake\Database\DriverInterface */ - protected $_driver; + protected DriverInterface $_driver; /** * Constructor @@ -257,8 +258,11 @@ public function valid(): bool } } - $this->_current = $this->_fetchResult(); - $valid = $this->_current !== false; + $current = $this->_fetchResult(); + $valid = $current !== false; + if ($valid) { + $this->_current = $current; + } if ($valid && $this->_useBuffering) { $this->_results[$this->_index] = $this->_current; diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 51ffe728..57ad27e4 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -32,21 +32,21 @@ class ExistsIn * * @var array */ - protected $_fields; + protected array $_fields; /** * The repository where the field will be looked for * * @var \Cake\ORM\Table|\Cake\ORM\Association|string */ - protected $_repository; + protected Table|Association|string $_repository; /** * Options for the constructor * * @var array */ - protected $_options = []; + protected array $_options = []; /** * Constructor. diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index 1dab30e3..dd67f044 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -29,14 +29,14 @@ class IsUnique * * @var array */ - protected $_fields; + protected array $_fields; /** * The unique check options * * @var array */ - protected $_options = [ + protected array $_options = [ 'allowMultipleNulls' => false, ]; diff --git a/Rule/LinkConstraint.php b/Rule/LinkConstraint.php index 3833b4c4..193184ca 100644 --- a/Rule/LinkConstraint.php +++ b/Rule/LinkConstraint.php @@ -46,14 +46,14 @@ class LinkConstraint * * @var \Cake\ORM\Association|string */ - protected $_association; + protected Association|string $_association; /** * The link status that is required to be present in order for the check to succeed. * * @var string */ - protected $_requiredLinkState; + protected string $_requiredLinkState; /** * Constructor. diff --git a/Rule/ValidCount.php b/Rule/ValidCount.php index 89d18d03..06722848 100644 --- a/Rule/ValidCount.php +++ b/Rule/ValidCount.php @@ -30,7 +30,7 @@ class ValidCount * * @var string */ - protected $_field; + protected string $_field; /** * Constructor. diff --git a/SaveOptionsBuilder.php b/SaveOptionsBuilder.php index b3a04e29..6a99fd49 100644 --- a/SaveOptionsBuilder.php +++ b/SaveOptionsBuilder.php @@ -36,14 +36,14 @@ class SaveOptionsBuilder extends ArrayObject * * @var array */ - protected $_options = []; + protected array $_options = []; /** * Table object. * * @var \Cake\ORM\Table */ - protected $_table; + protected Table $_table; /** * Constructor. diff --git a/Table.php b/Table.php index ec0c0025..87f6c94a 100644 --- a/Table.php +++ b/Table.php @@ -191,7 +191,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc * * @var string|null */ - protected $_table; + protected ?string $_table = null; /** * Human name giving to this particular instance. Multiple objects representing @@ -199,64 +199,64 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc * * @var string|null */ - protected $_alias; + protected ?string $_alias = null; /** * Connection instance * * @var \Cake\Database\Connection|null */ - protected $_connection; + protected ?Connection $_connection = null; /** * The schema object containing a description of this table fields * * @var \Cake\Database\Schema\TableSchemaInterface|null */ - protected $_schema; + protected ?TableSchemaInterface $_schema = null; /** * The name of the field that represents the primary key in the table * * @var array|string|null */ - protected $_primaryKey; + protected array|string|null $_primaryKey = null; /** * The name of the field that represents a human readable representation of a row * * @var array|string|null */ - protected $_displayField; + protected array|string|null $_displayField = null; /** * The associations container for this Table. * * @var \Cake\ORM\AssociationCollection */ - protected $_associations; + protected AssociationCollection $_associations; /** * BehaviorRegistry for this table * * @var \Cake\ORM\BehaviorRegistry */ - protected $_behaviors; + protected BehaviorRegistry $_behaviors; /** * The name of the class that represent a single row for this table * - * @var string - * @psalm-var class-string<\Cake\Datasource\EntityInterface> + * @var string|null + * @psalm-var class-string<\Cake\Datasource\EntityInterface>|null */ - protected $_entityClass; + protected ?string $_entityClass = null; /** * Registry key used to create this table object * * @var string|null */ - protected $_registryAlias; + protected ?string $_registryAlias = null; /** * Initializes a new instance From f62287980a092819055e0b7fcd0354cc648965b4 Mon Sep 17 00:00:00 2001 From: mscherer Date: Tue, 7 Sep 2021 11:05:23 +0200 Subject: [PATCH 1675/2059] Document the arrays more precisely. --- Association/Loader/SelectLoader.php | 2 +- Rule/LinkConstraint.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index d60d95a7..eb53b02a 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -426,7 +426,7 @@ protected function _buildSubquery(Query $query): Query * that need to be present to ensure the correct association data is loaded. * * @param \Cake\ORM\Query $query The query to get fields from. - * @return array The list of fields for the subquery. + * @return array The list of fields for the subquery. */ protected function _subqueryFields(Query $query): array { diff --git a/Rule/LinkConstraint.php b/Rule/LinkConstraint.php index 7fa41632..37fbdf7d 100644 --- a/Rule/LinkConstraint.php +++ b/Rule/LinkConstraint.php @@ -128,7 +128,7 @@ public function __invoke(EntityInterface $entity, array $options): bool * * @param array $fields The fields that should be aliased. * @param \Cake\ORM\Table $source The object to use for aliasing. - * @return array The aliased fields + * @return array The aliased fields */ protected function _aliasFields(array $fields, Table $source): array { From cda69bb3a15b5694075860b72e8656b951e0a95f Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Wed, 8 Sep 2021 16:26:12 +0200 Subject: [PATCH 1676/2059] Apply suggestions from code review Co-authored-by: ADmad --- Association/Loader/SelectLoader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index eb53b02a..e7ec8ff5 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -426,7 +426,7 @@ protected function _buildSubquery(Query $query): Query * that need to be present to ensure the correct association data is loaded. * * @param \Cake\ORM\Query $query The query to get fields from. - * @return array The list of fields for the subquery. + * @return array The list of fields for the subquery. */ protected function _subqueryFields(Query $query): array { From 8fe540707a0581eeb3f458e8ed3b447a1aa2f3e2 Mon Sep 17 00:00:00 2001 From: mscherer Date: Mon, 13 Sep 2021 10:54:59 +0200 Subject: [PATCH 1677/2059] Fix some grammar topics. --- Association.php | 12 ++++++------ Association/BelongsTo.php | 2 +- Association/BelongsToMany.php | 4 ++-- Association/HasMany.php | 4 ++-- Association/HasOne.php | 2 +- AssociationCollection.php | 2 +- Behavior.php | 2 +- EagerLoadable.php | 10 +++++----- EagerLoader.php | 14 +++++++------- Marshaller.php | 4 ++-- Query.php | 6 +++--- ResultSet.php | 2 +- Rule/ExistsIn.php | 2 +- Table.php | 8 ++++---- 14 files changed, 37 insertions(+), 37 deletions(-) diff --git a/Association.php b/Association.php index e127e84a..e60e223b 100644 --- a/Association.php +++ b/Association.php @@ -135,7 +135,7 @@ abstract class Association protected $_dependent = false; /** - * Whether or not cascaded deletes should also fire callbacks. + * Whether cascaded deletes should also fire callbacks. * * @var bool */ @@ -277,7 +277,7 @@ public function getName(): string } /** - * Sets whether or not cascaded deletes should also fire callbacks. + * Sets whether cascaded deletes should also fire callbacks. * * @param bool $cascadeCallbacks cascade callbacks switch value * @return $this @@ -290,7 +290,7 @@ public function setCascadeCallbacks(bool $cascadeCallbacks) } /** - * Gets whether or not cascaded deletes should also fire callbacks. + * Gets whether cascaded deletes should also fire callbacks. * * @return bool */ @@ -806,7 +806,7 @@ protected function _appendNotMatching(Query $query, array $options): void * @param array $row The row to transform * @param string $nestKey The array key under which the results for this association * should be found - * @param bool $joined Whether or not the row is a result of a direct join + * @param bool $joined Whether the row is a result of a direct join * with this association * @param string|null $targetProperty The property name in the source results where the association * data shuld be nested in. Will use the default one if not provided. @@ -831,7 +831,7 @@ public function transformRow(array $row, string $nestKey, bool $joined, ?string * joined or fetched externally. * * @param array $row The row to set a default on. - * @param bool $joined Whether or not the row is a result of a direct join + * @param bool $joined Whether the row is a result of a direct join * with this association * @return array */ @@ -1231,7 +1231,7 @@ abstract public function eagerLoader(array $options): Closure; abstract public function cascadeDelete(EntityInterface $entity, array $options = []): bool; /** - * Returns whether or not the passed table is the owning side for this + * Returns whether the passed table is the owning side for this * association. This means that rows in the 'target' table would miss important * or required information if the row in 'source' did not exist. * diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index ea8f34fb..1950b256 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -84,7 +84,7 @@ protected function _propertyName(): string } /** - * Returns whether or not the passed table is the owning side for this + * Returns whether the passed table is the owning side for this * association. This means that rows in the 'target' table would miss important * or required information if the row in 'source' did not exist. * diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 29d2cff8..c41d6d89 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -808,7 +808,7 @@ protected function _saveLinks(EntityInterface $sourceEntity, array $targetEntiti ); // Keys were changed, the junction table record _could_ be // new. By clearing the primary key values, and marking the entity - // as new, we let save() sort out whether or not we have a new link + // as new, we let save() sort out whether we have a new link // or if we are updating an existing link. if ($changedKeys) { $joint->setNew(true); @@ -882,7 +882,7 @@ function () use ($sourceEntity, $targetEntities, $options) { * Additionally to the default options accepted by `Table::delete()`, the following * keys are supported: * - * - cleanProperty: Whether or not to remove all the objects in `$targetEntities` that + * - cleanProperty: Whether to remove all the objects in `$targetEntities` that * are stored in `$sourceEntity` (default: true) * * By default this method will unset each of the entity objects stored inside the diff --git a/Association/HasMany.php b/Association/HasMany.php index 73c9c775..adf4341c 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -89,7 +89,7 @@ class HasMany extends Association protected $_saveStrategy = self::SAVE_APPEND; /** - * Returns whether or not the passed table is the owning side for this + * Returns whether the passed table is the owning side for this * association. This means that rows in the 'target' table would miss important * or required information if the row in 'source' did not exist. * @@ -311,7 +311,7 @@ public function link(EntityInterface $sourceEntity, array $targetEntities, array * Additionally to the default options accepted by `Table::delete()`, the following * keys are supported: * - * - cleanProperty: Whether or not to remove all the objects in `$targetEntities` that + * - cleanProperty: Whether to remove all the objects in `$targetEntities` that * are stored in `$sourceEntity` (default: true) * * By default this method will unset each of the entity objects stored inside the diff --git a/Association/HasOne.php b/Association/HasOne.php index 3721722c..b48ea721 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -68,7 +68,7 @@ protected function _propertyName(): string } /** - * Returns whether or not the passed table is the owning side for this + * Returns whether the passed table is the owning side for this * association. This means that rows in the 'target' table would miss important * or required information if the row in 'source' did not exist. * diff --git a/AssociationCollection.php b/AssociationCollection.php index c76b395b..15b6c527 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -134,7 +134,7 @@ public function getByProperty(string $prop): ?Association * Check for an attached association by name. * * @param string $alias The association alias to get. - * @return bool Whether or not the association exists. + * @return bool Whether the association exists. */ public function has(string $alias): bool { diff --git a/Behavior.php b/Behavior.php index ac049bf1..09f13cd4 100644 --- a/Behavior.php +++ b/Behavior.php @@ -54,7 +54,7 @@ * Fired before each find operation. By stopping the event and supplying a * return value you can bypass the find operation entirely. Any changes done * to the $query instance will be retained for the rest of the find. The - * $primary parameter indicates whether or not this is the root query, + * $primary parameter indicates whether this is the root query, * or an associated query. * * - `buildValidator(EventInterface $event, Validator $validator, string $name)` diff --git a/EagerLoadable.php b/EagerLoadable.php index 0397a09b..411f1ebb 100644 --- a/EagerLoadable.php +++ b/EagerLoadable.php @@ -80,14 +80,14 @@ class EagerLoadable protected $_propertyPath; /** - * Whether or not this level can be fetched using a join. + * Whether this level can be fetched using a join. * * @var bool */ protected $_canBeJoined = false; /** - * Whether or not this level was meant for a "matching" fetch + * Whether this level was meant for a "matching" fetch * operation * * @var bool|null @@ -210,7 +210,7 @@ public function propertyPath(): ?string } /** - * Sets whether or not this level can be fetched using a join. + * Sets whether this level can be fetched using a join. * * @param bool $possible The value to set. * @return $this @@ -223,7 +223,7 @@ public function setCanBeJoined(bool $possible) } /** - * Gets whether or not this level can be fetched using a join. + * Gets whether this level can be fetched using a join. * * @return bool */ @@ -258,7 +258,7 @@ public function getConfig(): array } /** - * Gets whether or not this level was meant for a + * Gets whether this level was meant for a * "matching" fetch operation. * * @return bool|null diff --git a/EagerLoader.php b/EagerLoader.php index 92482d68..1cccf411 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -96,7 +96,7 @@ class EagerLoader protected $_joinsMap = []; /** - * Controls whether or not fields from associated tables + * Controls whether fields from associated tables * will be eagerly loaded. When set to false, no fields will * be loaded from associations. * @@ -187,7 +187,7 @@ public function clearContain(): void } /** - * Sets whether or not contained associations will load fields automatically. + * Sets whether contained associations will load fields automatically. * * @param bool $enable The value to set. * @return $this @@ -212,7 +212,7 @@ public function disableAutoFields() } /** - * Gets whether or not contained associations will load fields automatically. + * Gets whether contained associations will load fields automatically. * * @return bool The current value. */ @@ -686,10 +686,10 @@ public function loadExternal(Query $query, StatementInterface $statement): State * * - alias: The association alias * - instance: The association instance - * - canBeJoined: Whether or not the association will be loaded using a JOIN + * - canBeJoined: Whether the association will be loaded using a JOIN * - entityClass: The entity that should be used for hydrating the results * - nestKey: A dotted path that can be used to correctly insert the data into the results. - * - matching: Whether or not it is an association loaded through `matching()`. + * - matching: Whether it is an association loaded through `matching()`. * * @param \Cake\ORM\Table $table The table containing the association that * will be normalized @@ -717,7 +717,7 @@ public function associationsMap(Table $table): array * * @param array $map An initial array for the map. * @param array<\Cake\ORM\EagerLoadable> $level An array of EagerLoadable instances. - * @param bool $matching Whether or not it is an association loaded through `matching()`. + * @param bool $matching Whether it is an association loaded through `matching()`. * @return array */ protected function _buildAssociationsMap(array $map, array $level, bool $matching = false): array @@ -752,7 +752,7 @@ protected function _buildAssociationsMap(array $map, array $level, bool $matchin * @param string $alias The table alias as it appears in the query. * @param \Cake\ORM\Association $assoc The association object the alias represents; * will be normalized - * @param bool $asMatching Whether or not this join results should be treated as a + * @param bool $asMatching Whether this join results should be treated as a * 'matching' association. * @param string $targetProperty The property name where the results of the join should be nested at. * If not passed, the default property for the association will be used. diff --git a/Marshaller.php b/Marshaller.php index 4e88bfeb..3247f969 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -520,7 +520,7 @@ protected function _loadAssociatedByIds(Association $assoc, array $ids): array * ### Options: * * - associated: Associations listed here will be marshalled as well. - * - validate: Whether or not to validate data before hydrating the entities. Can + * - validate: Whether to validate data before hydrating the entities. Can * also be set to a string to use a specific validator. Defaults to true/default. * - fields: An allowed list of fields to be assigned to the entity. If not present * the accessible fields list in the entity will be used. @@ -646,7 +646,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) * * ### Options: * - * - validate: Whether or not to validate data before hydrating the entities. Can + * - validate: Whether to validate data before hydrating the entities. Can * also be set to a string to use a specific validator. Defaults to true/default. * - associated: Associations listed here will be marshalled as well. * - fields: An allowed list of fields to be assigned to the entity. If not present, diff --git a/Query.php b/Query.php index b70478e3..3c94240f 100644 --- a/Query.php +++ b/Query.php @@ -116,7 +116,7 @@ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface protected $_hasFields; /** - * Tracks whether or not the original query should include + * Tracks whether the original query should include * fields from the top level table. * * @var bool|null @@ -1383,7 +1383,7 @@ public function jsonSerialize(): ResultSetInterface } /** - * Sets whether or not the ORM should automatically append fields. + * Sets whether the ORM should automatically append fields. * * By default calling select() will disable auto-fields. You can re-enable * auto-fields with this method. @@ -1411,7 +1411,7 @@ public function disableAutoFields() } /** - * Gets whether or not the ORM should automatically append fields. + * Gets whether the ORM should automatically append fields. * * By default calling select() will disable auto-fields. You can re-enable * auto-fields with enableAutoFields(). diff --git a/ResultSet.php b/ResultSet.php index 1637ae17..cdee2b50 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -130,7 +130,7 @@ class ResultSet implements ResultSetInterface protected $_entityClass; /** - * Whether or not to buffer results fetched from the statement + * Whether to buffer results fetched from the statement * * @var bool */ diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index a945f27e..cc2d68f8 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -147,7 +147,7 @@ function ($key) use ($target) { } /** - * Checks whether or not the given entity fields are nullable and null. + * Checks whether the given entity fields are nullable and null. * * @param \Cake\Datasource\EntityInterface $entity The entity to check. * @param \Cake\ORM\Table $source The table to use schema from. diff --git a/Table.php b/Table.php index e8d5ccdf..d84899fc 100644 --- a/Table.php +++ b/Table.php @@ -89,7 +89,7 @@ * - `Model.beforeFind` Fired before each find operation. By stopping the event and * supplying a return value you can bypass the find operation entirely. Any * changes done to the $query instance will be retained for the rest of the find. The - * `$primary` parameter indicates whether or not this is the root query, or an + * `$primary` parameter indicates whether this is the root query, or an * associated query. * * - `Model.buildValidator` Allows listeners to modify validation rules @@ -868,7 +868,7 @@ public function getBehavior(string $name): Behavior * Check if a behavior with the given alias has been loaded. * * @param string $name The behavior alias to check. - * @return bool Whether or not the behavior exists. + * @return bool Whether the behavior exists. */ public function hasBehavior(string $name): bool { @@ -1766,7 +1766,7 @@ public function exists($conditions): bool * * - atomic: Whether to execute the save and callbacks inside a database * transaction (default: true) - * - checkRules: Whether or not to check the rules on entity before saving, if the checking + * - checkRules: Whether to check the rules on entity before saving, if the checking * fails, it will abort the save operation. (default:true) * - associated: If `true` it will save 1st level associated entities as they are found * in the passed `$entity` whenever the property defined for the association @@ -1774,7 +1774,7 @@ public function exists($conditions): bool * to be saved. It is possible to provide different options for saving on associated * table objects using this key by making the custom options the array value. * If `false` no associated records will be saved. (default: `true`) - * - checkExisting: Whether or not to check if the entity already exists, assuming that the + * - checkExisting: Whether to check if the entity already exists, assuming that the * entity is marked as not new, and the primary key has been set. * * ### Events From bb2ffe892b99290a083d33f7ca0da55509dec3d7 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 13 Sep 2021 22:06:10 +0530 Subject: [PATCH 1678/2059] Use new string functions instead of strpos(). --- Association.php | 2 +- Association/BelongsToMany.php | 4 ++-- AssociationsNormalizerTrait.php | 2 +- Behavior/Translate/ShadowTableStrategy.php | 4 ++-- EagerLoader.php | 6 +++--- Locator/TableLocator.php | 8 ++++---- Table.php | 8 ++++---- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Association.php b/Association.php index 2b84489c..7ec0f07f 100644 --- a/Association.php +++ b/Association.php @@ -381,7 +381,7 @@ public function setTarget(Table $table) public function getTarget(): Table { if ($this->_targetTable === null) { - if (strpos($this->_className, '.')) { + if (str_contains($this->_className, '.')) { [$plugin] = pluginSplit($this->_className, true); $registryAlias = (string)$plugin . $this->_name; } else { diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 686289cb..bb4b4504 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1012,7 +1012,7 @@ protected function targetConditions(): mixed $matching = []; $alias = $this->getAlias() . '.'; foreach ($conditions as $field => $value) { - if (is_string($field) && strpos($field, $alias) === 0) { + if (is_string($field) && str_starts_with($field, $alias)) { $matching[$field] = $value; } elseif (is_int($field) || $value instanceof ExpressionInterface) { $matching[$field] = $value; @@ -1041,7 +1041,7 @@ protected function junctionConditions(): array $alias = $this->_junctionAssociationName() . '.'; foreach ($conditions as $field => $value) { $isString = is_string($field); - if ($isString && strpos($field, $alias) === 0) { + if ($isString && str_starts_with($field, $alias)) { $matching[$field] = $value; } // Assume that operators contain junction conditions. diff --git a/AssociationsNormalizerTrait.php b/AssociationsNormalizerTrait.php index 45a4226e..287927a7 100644 --- a/AssociationsNormalizerTrait.php +++ b/AssociationsNormalizerTrait.php @@ -40,7 +40,7 @@ protected function _normalizeAssociations(array|string $associations): array $options = []; } - if (!strpos($table, '.')) { + if (!str_contains($table, '.')) { $result[$table] = $options; continue; } diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 68c32f25..3505cde2 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -262,7 +262,7 @@ protected function iterateClause(Query $query, string $name = '', array $config $clause->iterateParts( function ($c, &$field) use ($fields, $alias, $mainTableAlias, $mainTableFields, &$joinRequired) { - if (!is_string($field) || strpos($field, '.')) { + if (!is_string($field) || str_contains($field, '.')) { return $c; } @@ -312,7 +312,7 @@ function ($expression) use ($fields, $alias, $mainTableAlias, $mainTableFields, return; } $field = $expression->getField(); - if (!is_string($field) || strpos($field, '.')) { + if (!is_string($field) || str_contains($field, '.')) { return; } diff --git a/EagerLoader.php b/EagerLoader.php index 244b942a..f3b09d97 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -350,7 +350,7 @@ protected function _reformatContain(array $associations, array $original): array continue; } - if (strpos($table, '.')) { + if (str_contains($table, '.')) { $path = explode('.', $table); $table = array_pop($path); foreach ($path as $t) { @@ -548,7 +548,7 @@ protected function _fixStrategies(): void } /** @var \Cake\ORM\EagerLoadable $loadable */ foreach ($configs as $loadable) { - if (strpos($loadable->aliasPath(), '.')) { + if (str_contains($loadable->aliasPath(), '.')) { $this->_correctStrategy($loadable); } } @@ -651,7 +651,7 @@ public function loadExternal(Query $query, StatementInterface $statement): State // Nested paths are not subject to this condition because they could // be attached to joined associations. if ( - strpos($path, '.') === false && + !str_contains($path, '.') && (!array_key_exists($path, $collected) || !array_key_exists($alias, $collected[$path])) ) { $message = "Unable to load `{$path}` association. Ensure foreign key in `{$alias}` is selected."; diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 76af3ca0..6f9288c4 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -210,7 +210,7 @@ public function get(string $alias, array $options = []): Table */ protected function createInstance(string $alias, array $options): Table { - if (strpos($alias, '\\') === false) { + if (!str_contains($alias, '\\')) { [, $classAlias] = pluginSplit($alias); $options = ['alias' => $classAlias] + $options; } elseif (!isset($options['alias'])) { @@ -231,7 +231,7 @@ protected function createInstance(string $alias, array $options): Table if (empty($options['className'])) { $options['className'] = $alias; } - if (!isset($options['table']) && strpos($options['className'], '\\') === false) { + if (!isset($options['table']) && !str_contains($options['className'], '\\')) { [, $table] = pluginSplit($options['className']); $options['table'] = Inflector::underscore($table); } @@ -239,7 +239,7 @@ protected function createInstance(string $alias, array $options): Table } else { $message = $options['className'] ?? $alias; $message = '`' . $message . '`'; - if (strpos($message, '\\') === false) { + if (!str_contains($message, '\\')) { $message = 'for alias ' . $message; } throw new MissingTableClassException([$message]); @@ -283,7 +283,7 @@ protected function _getClassName(string $alias, array $options = []): ?string $options['className'] = $alias; } - if (strpos($options['className'], '\\') !== false && class_exists($options['className'])) { + if (str_contains($options['className'], '\\') && class_exists($options['className'])) { return $options['className']; } diff --git a/Table.php b/Table.php index 87f6c94a..22080207 100644 --- a/Table.php +++ b/Table.php @@ -440,7 +440,7 @@ public function getAlias(): string */ public function aliasField(string $field): string { - if (strpos($field, '.') !== false) { + if (str_contains($field, '.')) { return $field; } @@ -943,7 +943,7 @@ public function hasAssociation(string $name): bool */ protected function findAssociation(string $name): ?Association { - if (strpos($name, '.') === false) { + if (!str_contains($name, '.')) { return $this->_associations->get($name); } @@ -2584,8 +2584,8 @@ protected function _dynamicFinder(string $method, array $args): Query $fields = substr($method, strlen($matches[0])); $findType = Inflector::variable($matches[1]); } - $hasOr = strpos($fields, '_or_'); - $hasAnd = strpos($fields, '_and_'); + $hasOr = str_contains($fields, '_or_'); + $hasAnd = str_contains($fields, '_and_'); $makeConditions = function ($fields, $args) { $conditions = []; From dceaf8e65b30fbd01501738f3f5a28008b8760d5 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 13 Sep 2021 14:20:40 +0530 Subject: [PATCH 1679/2059] Change argument/value type from callable to Closure. It's unlikely these values need elaborate logic to necessitate using a callable besides Closure. It also helps simplying type checks where the value can be a string or callable since strings can be valid callbables too. --- Behavior/TreeBehavior.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 59a3239a..939e9686 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -941,14 +941,11 @@ protected function _scope(Query $query): Query { $scope = $this->getConfig('scope'); - if (is_array($scope)) { - return $query->where($scope); - } - if (is_callable($scope)) { - return $scope($query); + if ($scope === null) { + return $query; } - return $query; + return $query->where($scope); } /** From 62f6ac668aeddef0de089c3b0c587104834eb4f0 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 16 Sep 2021 20:05:20 +0530 Subject: [PATCH 1680/2059] Add missing types for association class properties. --- Association.php | 30 +++++++++++++++--------------- Association/BelongsTo.php | 2 +- Association/BelongsToMany.php | 16 ++++++++-------- Association/HasMany.php | 2 +- Association/HasOne.php | 2 +- 5 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Association.php b/Association.php index 9616c5cc..ef863559 100644 --- a/Association.php +++ b/Association.php @@ -108,16 +108,16 @@ abstract class Association /** * The field name in the owning side table that is used to match with the foreignKey * - * @var array|string|null + * @var array|string */ - protected array|string|null $_bindingKey = null; + protected array|string $_bindingKey; /** * The name of the field representing the foreign key to the table to load * * @var array|string|false */ - protected $_foreignKey; + protected array|string|false $_foreignKey; /** * A list of conditions to be always included when fetching records from @@ -148,14 +148,14 @@ abstract class Association * * @var \Cake\ORM\Table */ - protected $_sourceTable; + protected Table $_sourceTable; /** * Target table instance * * @var \Cake\ORM\Table */ - protected $_targetTable; + protected Table $_targetTable; /** * The type of join to be used when adding the association to a query @@ -170,7 +170,7 @@ abstract class Association * * @var string */ - protected $_propertyName; + protected string $_propertyName; /** * The strategy name to be used to fetch associated records. Some association @@ -251,7 +251,7 @@ public function __construct(string $alias, array $options = []) */ public function setName(string $name) { - if ($this->_targetTable !== null) { + if (isset($this->_targetTable)) { $alias = $this->_targetTable->getAlias(); if ($alias !== $name) { throw new InvalidArgumentException(sprintf( @@ -312,7 +312,7 @@ public function getCascadeCallbacks(): bool public function setClassName(string $className) { if ( - $this->_targetTable !== null && + isset($this->_targetTable) && get_class($this->_targetTable) !== App::className($className, 'Model/Table', 'Table') ) { throw new InvalidArgumentException(sprintf( @@ -380,7 +380,7 @@ public function setTarget(Table $table) */ public function getTarget(): Table { - if ($this->_targetTable === null) { + if (!isset($this->_targetTable)) { if (str_contains($this->_className, '.')) { [$plugin] = pluginSplit($this->_className, true); $registryAlias = (string)$plugin . $this->_name; @@ -407,7 +407,7 @@ public function getTarget(): Table throw new RuntimeException(sprintf( $errorMessage, - $this->_sourceTable === null ? 'null' : get_class($this->_sourceTable), + isset($this->_sourceTable) ? get_class($this->_sourceTable) : 'null', $this->getName(), $this->type(), get_class($this->_targetTable), @@ -469,7 +469,7 @@ public function setBindingKey(array|string $key) */ public function getBindingKey(): array|string { - if ($this->_bindingKey === null) { + if (!isset($this->_bindingKey)) { $this->_bindingKey = $this->isOwningSide($this->getSource()) ? $this->getSource()->getPrimaryKey() : $this->getTarget()->getPrimaryKey(); @@ -590,7 +590,7 @@ public function setProperty(string $name) */ public function getProperty(): string { - if (!$this->_propertyName) { + if (!isset($this->_propertyName)) { $this->_propertyName = $this->_propertyName(); if (in_array($this->_propertyName, $this->_sourceTable->getSchema()->columns(), true)) { $msg = 'Association property name "%s" clashes with field of same name of table "%s".' . @@ -790,7 +790,7 @@ public function attachTo(Query $query, array $options = []): void */ protected function _appendNotMatching(Query $query, array $options): void { - $target = $this->_targetTable; + $target = $this->getTarget(); if (!empty($options['negateMatch'])) { $primaryKey = $query->aliasFields((array)$target->getPrimaryKey(), $this->_name); $query->andWhere(function ($exp) use ($primaryKey) { @@ -969,11 +969,11 @@ protected function _appendFields(Query $query, Query $surrogate, array $options) (empty($fields) && $options['includeFields']) || $surrogate->isAutoFieldsEnabled() ) { - $fields = array_merge($fields, $this->_targetTable->getSchema()->columns()); + $fields = array_merge($fields, $this->getTarget()->getSchema()->columns()); } $query->select($query->aliasFields($fields, $this->_name)); - $query->addDefaultTypes($this->_targetTable); + $query->addDefaultTypes($this->getTarget()); } /** diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 8817956f..6e5d6256 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -48,7 +48,7 @@ class BelongsTo extends Association */ public function getForeignKey(): array|string|false { - if ($this->_foreignKey === null) { + if (!isset($this->_foreignKey)) { $this->_foreignKey = $this->_modelKey($this->getTarget()->getAlias()); } diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 70c12e5c..4ffab97d 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -72,7 +72,7 @@ class BelongsToMany extends Association * * @var \Cake\ORM\Table */ - protected $_junctionTable; + protected Table $_junctionTable; /** * Junction table name @@ -87,7 +87,7 @@ class BelongsToMany extends Association * * @var string */ - protected $_junctionAssociationName; + protected string $_junctionAssociationName; /** * The name of the property to be set containing data from the junction table @@ -116,7 +116,7 @@ class BelongsToMany extends Association * * @var \Cake\ORM\Table|string */ - protected $_through; + protected Table|string $_through; /** * Valid strategies for this type of association @@ -203,7 +203,7 @@ public function canBeJoined(array $options = []): bool */ public function getForeignKey(): array|string|false { - if ($this->_foreignKey === null) { + if (!isset($this->_foreignKey)) { $this->_foreignKey = $this->_modelKey($this->getSource()->getTable()); } @@ -256,12 +256,12 @@ public function defaultRowValue(array $row, bool $joined): array */ public function junction(Table|string|null $table = null): Table { - if ($table === null && $this->_junctionTable !== null) { + if ($table === null && isset($this->_junctionTable)) { return $this->_junctionTable; } $tableLocator = $this->getTableLocator(); - if ($table === null && $this->_through) { + if ($table === null && isset($this->_through)) { $table = $this->_through; } elseif ($table === null) { $tableName = $this->_junctionTableName(); @@ -463,7 +463,7 @@ public function attachTo(Query $query, array $options = []): void $includeFields = $options['includeFields'] ?? null; // Attach the junction table as well we need it to populate _joinData. - $assoc = $this->_targetTable->getAssociation($junction->getAlias()); + $assoc = $this->getTarget()->getAssociation($junction->getAlias()); $newOptions = array_intersect_key($options, ['joinType' => 1, 'fields' => 1]); $newOptions += [ 'conditions' => $cond, @@ -1422,7 +1422,7 @@ protected function _collectJointEntities(EntityInterface $sourceEntity, array $t */ protected function _junctionAssociationName(): string { - if (!$this->_junctionAssociationName) { + if (!isset($this->_junctionAssociationName)) { $this->_junctionAssociationName = $this->getTarget() ->getAssociation($this->junction()->getAlias()) ->getName(); diff --git a/Association/HasMany.php b/Association/HasMany.php index c5d87a54..474d94c8 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -591,7 +591,7 @@ public function canBeJoined(array $options = []): bool */ public function getForeignKey(): array|string|false { - if ($this->_foreignKey === null) { + if (!isset($this->_foreignKey)) { $this->_foreignKey = $this->_modelKey($this->getSource()->getTable()); } diff --git a/Association/HasOne.php b/Association/HasOne.php index 9c0e21ff..a0347171 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -46,7 +46,7 @@ class HasOne extends Association */ public function getForeignKey(): array|string|false { - if ($this->_foreignKey === null) { + if (!isset($this->_foreignKey)) { $this->_foreignKey = $this->_modelKey($this->getSource()->getAlias()); } From 41fe7cec5721e8878af4e4e97b0a963f98d69ebe Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 17 Sep 2021 11:39:53 +0530 Subject: [PATCH 1681/2059] Deprecate Association::setName(). Changing the association name after object creation isn't reflected in the collection nor the target table. Hence it's quite limiting and best to deprecate and remove this method. --- Association.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Association.php b/Association.php index e60e223b..5e0d9e53 100644 --- a/Association.php +++ b/Association.php @@ -246,9 +246,16 @@ public function __construct(string $alias, array $options = []) * * @param string $name Name to be assigned * @return $this + * @deprecated 4.3.0 Changing the association name after object creation is + * no longer supported. The name should only be set through the constructor. */ public function setName(string $name) { + deprecationWarning( + 'Changing the association name after object creation is no longer supported.' + . ' The name should only be set through the constructor' + ); + if ($this->_targetTable !== null) { $alias = $this->_targetTable->getAlias(); if ($alias !== $name) { From 6d2fac50663f8c0cb7bb9d944db5b133f4c10d53 Mon Sep 17 00:00:00 2001 From: mscherer Date: Fri, 17 Sep 2021 15:29:13 +0200 Subject: [PATCH 1682/2059] Fix up types in arrays of var def --- EagerLoader.php | 2 +- Rule/ExistsIn.php | 2 +- Rule/IsUnique.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index 1cccf411..eaf33840 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -50,7 +50,7 @@ class EagerLoader * List of options accepted by associations in contain() * index by key for faster access * - * @var array + * @var array */ protected $_containOptions = [ 'associations' => 1, diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index cc2d68f8..71da04dd 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -44,7 +44,7 @@ class ExistsIn /** * Options for the constructor * - * @var array + * @var array */ protected $_options = []; diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index 1dab30e3..f06f5140 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -34,7 +34,7 @@ class IsUnique /** * The unique check options * - * @var array + * @var array */ protected $_options = [ 'allowMultipleNulls' => false, From b5a6f140294c57af76e69d47141368d03240b544 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 17 Sep 2021 19:31:22 +0530 Subject: [PATCH 1683/2059] Make ShadowTable the default strategy for TranslateBehavior. The EavStrategy which uses one row per field is quite wasteful and can also be a performance killer for large apps. --- Behavior/TranslateBehavior.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 1416cc9a..9a7cf68e 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -18,7 +18,7 @@ use Cake\I18n\I18n; use Cake\ORM\Behavior; -use Cake\ORM\Behavior\Translate\EavStrategy; +use Cake\ORM\Behavior\Translate\ShadowTableStrategy; use Cake\ORM\Behavior\Translate\TranslateStrategyInterface; use Cake\ORM\Marshaller; use Cake\ORM\PropertyMarshalInterface; @@ -62,6 +62,7 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface 'strategy' => 'subquery', 'tableLocator' => null, 'validator' => false, + 'strategyClass' => null, ]; /** @@ -70,7 +71,7 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface * @var string * @psalm-var class-string<\Cake\ORM\Behavior\Translate\TranslateStrategyInterface> */ - protected static string $defaultStrategyClass = EavStrategy::class; + protected static string $defaultStrategyClass = ShadowTableStrategy::class; /** * Translation strategy instance. From 556a079ba29e296f0d60ea00facbcec5306488e1 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 17 Sep 2021 20:06:58 +0530 Subject: [PATCH 1684/2059] Cleanup deprecated code. --- Association.php | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/Association.php b/Association.php index d3498a5f..8dc9c0e8 100644 --- a/Association.php +++ b/Association.php @@ -242,33 +242,6 @@ public function __construct(string $alias, array $options = []) } } - /** - * Sets the name for this association, usually the alias - * assigned to the target associated table - * - * @param string $name Name to be assigned - * @return $this - * @deprecated 4.3.0 Changing the association name after object creation is - * no longer supported. The name should only be set through the constructor. - */ - public function setName(string $name) - { - if (isset($this->_targetTable)) { - $alias = $this->_targetTable->getAlias(); - if ($alias !== $name) { - throw new InvalidArgumentException(sprintf( - 'Association name "%s" does not match target table alias "%s".', - $name, - $alias - )); - } - } - - $this->_name = $name; - - return $this; - } - /** * Gets the name for this association, usually the alias * assigned to the target associated table From 77da875d3a8cf870e693140cb4c831e5231ec1b6 Mon Sep 17 00:00:00 2001 From: mscherer Date: Wed, 22 Sep 2021 17:00:51 +0200 Subject: [PATCH 1685/2059] Make docblock var definitions more concrete. --- Behavior.php | 2 +- Behavior/CounterCacheBehavior.php | 2 +- Locator/TableLocator.php | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Behavior.php b/Behavior.php index 09f13cd4..f9cb14cd 100644 --- a/Behavior.php +++ b/Behavior.php @@ -129,7 +129,7 @@ class Behavior implements EventListenerInterface * Stores the reflected method + finder methods per class. * This prevents reflecting the same class multiple times in a single process. * - * @var array + * @var array */ protected static $_reflectionCache = []; diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index d2cdc5e4..ce851146 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -106,7 +106,7 @@ class CounterCacheBehavior extends Behavior /** * Store the fields which should be ignored * - * @var array + * @var array> */ protected $_ignoreDirty = []; diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index fc693122..091b5415 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -34,14 +34,14 @@ class TableLocator extends AbstractLocator implements LocatorInterface /** * Contains a list of locations where table classes should be looked for. * - * @var array + * @var array */ protected $locations = []; /** * Configuration for aliases. * - * @var array + * @var array */ protected $_config = []; From 3d3d2ba7845f75b7fe19f0733c39c6b7fcb81440 Mon Sep 17 00:00:00 2001 From: mscherer Date: Wed, 22 Sep 2021 17:18:12 +0200 Subject: [PATCH 1686/2059] Move php8 only attributes inside docblock. --- ResultSet.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ResultSet.php b/ResultSet.php index cdee2b50..d81d6351 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -22,7 +22,6 @@ use Cake\Database\StatementInterface; use Cake\Datasource\EntityInterface; use Cake\Datasource\ResultSetInterface; -use ReturnTypeWillChange; use SplFixedArray; /** @@ -185,7 +184,7 @@ public function __construct(Query $query, StatementInterface $statement) * * @return object|array */ - #[ReturnTypeWillChange] + #[\ReturnTypeWillChange] public function current() { return $this->_current; From b6f825058c932e0babd5d98ca64bc6853323dba0 Mon Sep 17 00:00:00 2001 From: mscherer Date: Wed, 22 Sep 2021 22:48:36 +0200 Subject: [PATCH 1687/2059] Make docblock var definitions more concrete. --- Locator/TableLocator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 091b5415..5003c59a 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -41,7 +41,7 @@ class TableLocator extends AbstractLocator implements LocatorInterface /** * Configuration for aliases. * - * @var array + * @var array */ protected $_config = []; From 59d6a2cab8f4459e2af7b6a4dec6e7c0e598363a Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Wed, 22 Sep 2021 19:29:47 -0500 Subject: [PATCH 1688/2059] Set allowMultipleNulls default to true for IsUnique --- Rule/IsUnique.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index 17878d5c..40b6ff91 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -37,7 +37,7 @@ class IsUnique * @var array */ protected array $_options = [ - 'allowMultipleNulls' => false, + 'allowMultipleNulls' => true, ]; /** @@ -45,7 +45,7 @@ class IsUnique * * ### Options * - * - `allowMultipleNulls` Allows any field to have multiple null values. Defaults to false. + * - `allowMultipleNulls` Allows any field to have multiple null values. Defaults to true. * * @param array $fields The list of fields to check uniqueness for * @param array $options The options for unique checks. From 9f0fa433d8df6eeda0d0a08c3bfcd6401604fe49 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 23 Sep 2021 13:36:19 +0530 Subject: [PATCH 1689/2059] Non-numeric array indexes are always coerced to strings. --- Marshaller.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index f79d5569..ad3e1429 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -675,10 +675,8 @@ public function mergeMany(iterable $entities, array $data, array $options = []): }) ->toArray(); - /** @psalm-suppress InvalidArrayOffset */ - $new = $indexed[null] ?? []; - /** @psalm-suppress InvalidArrayOffset */ - unset($indexed[null]); + $new = $indexed[''] ?? []; + unset($indexed['']); $output = []; foreach ($entities as $entity) { From af06eec478254570d1036c73f736373f9040b7ba Mon Sep 17 00:00:00 2001 From: mscherer Date: Sat, 25 Sep 2021 03:16:58 +0200 Subject: [PATCH 1690/2059] Fix IDE found issues/smells. --- Association/BelongsToMany.php | 5 +++-- AssociationCollection.php | 3 +-- EagerLoader.php | 4 ++-- Exception/PersistenceFailedException.php | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index c41d6d89..8ffc172e 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -930,12 +930,13 @@ function () use ($sourceEntity, $targetEntities, $options): void { } ); + /** @var array<\Cake\Datasource\EntityInterface> $existing */ $existing = $sourceEntity->get($property) ?: []; if (!$options['cleanProperty'] || empty($existing)) { return true; } - /** @var \SplObjectStorage<\Cake\Datasource\EntityInterface, null> $storage*/ + /** @var \SplObjectStorage<\Cake\Datasource\EntityInterface, null> $storage */ $storage = new SplObjectStorage(); foreach ($targetEntities as $e) { $storage->attach($e); @@ -993,7 +994,7 @@ public function getThrough() * Any string expressions, or expression objects will * also be returned in this list. * - * @return mixed Generally an array. If the conditions + * @return array|\Closure|null Generally an array. If the conditions * are not an array, the association conditions will be * returned unmodified. */ diff --git a/AssociationCollection.php b/AssociationCollection.php index 15b6c527..c58fec55 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -369,8 +369,7 @@ public function normalizeKeys($keys): array /** * Allow looping through the associations * - * @return array<\Cake\ORM\Association> - * @psalm-return \Traversable + * @return \Traversable */ public function getIterator(): Traversable { diff --git a/EagerLoader.php b/EagerLoader.php index eaf33840..cfdab0bc 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -137,7 +137,7 @@ public function contain($associations, ?callable $queryBuilder = null): array if ($queryBuilder) { if (!is_string($associations)) { throw new InvalidArgumentException( - sprintf('Cannot set containments. To use $queryBuilder, $associations must be a string') + 'Cannot set containments. To use $queryBuilder, $associations must be a string' ); } @@ -754,7 +754,7 @@ protected function _buildAssociationsMap(array $map, array $level, bool $matchin * will be normalized * @param bool $asMatching Whether this join results should be treated as a * 'matching' association. - * @param string $targetProperty The property name where the results of the join should be nested at. + * @param string|null $targetProperty The property name where the results of the join should be nested at. * If not passed, the default property for the association will be used. * @return void */ diff --git a/Exception/PersistenceFailedException.php b/Exception/PersistenceFailedException.php index ef238694..417142ab 100644 --- a/Exception/PersistenceFailedException.php +++ b/Exception/PersistenceFailedException.php @@ -42,7 +42,7 @@ class PersistenceFailedException extends CakeException * @param \Cake\Datasource\EntityInterface $entity The entity on which the persistence operation failed * @param array|string $message Either the string of the error message, or an array of attributes * that are made available in the view, and sprintf()'d into Exception::$_messageTemplate - * @param int $code The code of the error, is also the HTTP status code for the error. + * @param int|null $code The code of the error, is also the HTTP status code for the error. * @param \Throwable|null $previous the previous exception. */ public function __construct(EntityInterface $entity, $message, ?int $code = null, ?Throwable $previous = null) From 5ff886821a002269883ae3d8be0ab944a3b80d4d Mon Sep 17 00:00:00 2001 From: mscherer Date: Sun, 26 Sep 2021 02:10:05 +0200 Subject: [PATCH 1691/2059] Fix up doc typos or missing class links. --- Association.php | 2 +- AssociationCollection.php | 4 ++-- Behavior.php | 2 +- EagerLoader.php | 6 +++--- Locator/TableLocator.php | 2 +- Query.php | 2 +- Table.php | 11 +++++++---- 7 files changed, 16 insertions(+), 13 deletions(-) diff --git a/Association.php b/Association.php index 5e0d9e53..f21de998 100644 --- a/Association.php +++ b/Association.php @@ -1195,7 +1195,7 @@ abstract public function type(): string; /** * Eager loads a list of records in the target table that are related to another - * set of records in the source table. Source records can specified in two ways: + * set of records in the source table. Source records can be specified in two ways: * first one is by passing a Query object setup to find on the source table and * the other way is by explicitly passing an array of primary key values from * the source table. diff --git a/AssociationCollection.php b/AssociationCollection.php index 15b6c527..0a0bcfac 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -175,7 +175,7 @@ public function getByType($class): array /** * Drop/remove an association. * - * Once removed the association will not longer be reachable + * Once removed the association will no longer be reachable * * @param string $alias The alias name. * @return void @@ -188,7 +188,7 @@ public function remove(string $alias): void /** * Remove all registered associations. * - * Once removed associations will not longer be reachable + * Once removed associations will no longer be reachable * * @return void */ diff --git a/Behavior.php b/Behavior.php index f9cb14cd..31ab1f29 100644 --- a/Behavior.php +++ b/Behavior.php @@ -46,7 +46,7 @@ * * ### Callback methods * - * Behaviors can listen to any events fired on a Table. By default + * Behaviors can listen to any events fired on a Table. By default, * CakePHP provides a number of lifecycle events your behaviors can * listen to: * diff --git a/EagerLoader.php b/EagerLoader.php index eaf33840..d522c097 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -284,11 +284,11 @@ public function getMatching(): array * loaded for a table. The normalized array will restructure the original array * by sorting all associations under one key and special options under another. * - * Each of the levels of the associations tree will converted to a Cake\ORM\EagerLoadable + * Each of the levels of the associations tree will be converted to a {@link \Cake\ORM\EagerLoadable} * object, that contains all the information required for the association objects * to load the information from the database. * - * Additionally it will set an 'instance' key per association containing the + * Additionally, it will set an 'instance' key per association containing the * association instance from the corresponding source table * * @param \Cake\ORM\Table $repository The table containing the association that @@ -452,7 +452,7 @@ public function attachableAssociations(Table $repository): array /** * Returns an array with the associations that need to be fetched using a - * separate query, each array value will contain a Cake\ORM\EagerLoadable object. + * separate query, each array value will contain a {@link \Cake\ORM\EagerLoadable} object. * * @param \Cake\ORM\Table $repository The table containing the associations * to be loaded diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 5003c59a..510b221a 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -171,7 +171,7 @@ public function getConfig(?string $alias = null): array * This is important because table associations are resolved at runtime * and cyclic references need to be handled correctly. * - * The options that can be passed are the same as in Cake\ORM\Table::__construct(), but the + * The options that can be passed are the same as in {@link \Cake\ORM\Table::__construct()}, but the * `className` key is also recognized. * * ### Options diff --git a/Query.php b/Query.php index 3c94240f..101c11be 100644 --- a/Query.php +++ b/Query.php @@ -492,7 +492,7 @@ public function clearContain() * * @param \Cake\ORM\Table $table The table instance to pluck associations from. * @param \Cake\Database\TypeMap $typeMap The typemap to check for columns in. - * This typemap is indirectly mutated via Cake\ORM\Query::addDefaultTypes() + * This typemap is indirectly mutated via {@link \Cake\ORM\Query::addDefaultTypes()} * @param array $associations The nested tree of associations to walk. * @return void */ diff --git a/Table.php b/Table.php index d84899fc..eb5694a9 100644 --- a/Table.php +++ b/Table.php @@ -219,7 +219,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc protected $_primaryKey; /** - * The name of the field that represents a human readable representation of a row + * The name of the field that represents a human-readable representation of a row * * @var array|string|null */ @@ -792,7 +792,7 @@ public function addBehavior(string $name, array $options = []) * ]); * ``` * - * @param array $behaviors All of the behaviors to load. + * @param array $behaviors All the behaviors to load. * @return $this * @throws \RuntimeException If a behavior is being reloaded. */ @@ -1287,7 +1287,7 @@ public function findAll(Query $query, array $options): Query * * When calling this finder, the fields passed are used to determine what should * be used as the array key, value and optionally what to group the results by. - * By default the primary key for the model is used for the key, and the display + * By default, the primary key for the model is used for the key, and the display * field as value. * * The results of this finder will be in the following form: @@ -1312,7 +1312,7 @@ public function findAll(Query $query, array $options): Query * ``` * * The `valueField` can also be an array, in which case you can also specify - * the `valueSeparator` option to control how the values will be concatinated: + * the `valueSeparator` option to control how the values will be concatenated: * * ``` * $table->find('list', [ @@ -2510,6 +2510,9 @@ public function hasFinder(string $type): bool * @param array $options List of options to pass to the finder * @return \Cake\ORM\Query * @throws \BadMethodCallException + * @uses findAll() + * @uses findList() + * @uses findThreaded() */ public function callFinder(string $type, Query $query, array $options = []): Query { From 7255a4809c282ea6a39c039738ff45411ac92868 Mon Sep 17 00:00:00 2001 From: mscherer Date: Sun, 26 Sep 2021 05:44:49 +0200 Subject: [PATCH 1692/2059] Clarify assoc array in docblock. --- Association.php | 6 +++--- AssociationCollection.php | 2 +- EagerLoader.php | 4 ++-- Entity.php | 2 +- Query.php | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Association.php b/Association.php index 5e0d9e53..7a1a5b61 100644 --- a/Association.php +++ b/Association.php @@ -877,7 +877,7 @@ public function find($type = null, array $options = []): Query * Proxies the operation to the target table's exists method after * appending the default conditions for this association * - * @param \Cake\Database\ExpressionInterface|\Closure|array $conditions The conditions to use + * @param \Cake\Database\ExpressionInterface|\Closure|array|string|null $conditions The conditions to use * for checking if any record matches. * @see \Cake\ORM\Table::exists() * @return bool @@ -895,7 +895,7 @@ public function exists($conditions): bool * Proxies the update operation to the target table's updateAll method * * @param array $fields A hash of field => new value. - * @param mixed $conditions Conditions to be used, accepts anything Query::where() + * @param \Cake\Database\ExpressionInterface|\Closure|array|string|null $conditions Conditions to be used, accepts anything Query::where() * can take. * @see \Cake\ORM\Table::updateAll() * @return int Count Returns the affected rows. @@ -912,7 +912,7 @@ public function updateAll(array $fields, $conditions): int /** * Proxies the delete operation to the target table's deleteAll method * - * @param mixed $conditions Conditions to be used, accepts anything Query::where() + * @param \Cake\Database\ExpressionInterface|\Closure|array|string|null $conditions Conditions to be used, accepts anything Query::where() * can take. * @return int Returns the number of affected rows. * @see \Cake\ORM\Table::deleteAll() diff --git a/AssociationCollection.php b/AssociationCollection.php index c58fec55..05341781 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -293,7 +293,7 @@ protected function _saveAssociations( * * @param \Cake\ORM\Association $association The association object to save with. * @param \Cake\Datasource\EntityInterface $entity The entity to save - * @param array $nested Options for deeper associations + * @param array $nested Options for deeper associations * @param array $options Original options * @return bool Success */ diff --git a/EagerLoader.php b/EagerLoader.php index cfdab0bc..f6d70f0b 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -34,7 +34,7 @@ class EagerLoader * Nested array describing the association to be fetched * and the options to apply for each of them, if any * - * @var array + * @var array */ protected $_containments = []; @@ -819,7 +819,7 @@ protected function _collectKeys(array $external, Query $query, $statement): arra * defined in $collectKeys * * @param \Cake\Database\Statement\BufferedStatement $statement The statement to read from. - * @param array $collectKeys The keys to collect + * @param array $collectKeys The keys to collect * @return array */ protected function _groupKeys(BufferedStatement $statement, array $collectKeys): array diff --git a/Entity.php b/Entity.php index 1043720d..14e5d57e 100644 --- a/Entity.php +++ b/Entity.php @@ -44,7 +44,7 @@ class Entity implements EntityInterface, InvalidPropertyInterface * $entity = new Entity(['id' => 1, 'name' => 'Andrew']) * ``` * - * @param array $properties hash of properties to set in this entity + * @param array $properties hash of properties to set in this entity * @param array $options list of options to use when creating this entity */ public function __construct(array $properties = [], array $options = []) diff --git a/Query.php b/Query.php index 3c94240f..116d0950 100644 --- a/Query.php +++ b/Query.php @@ -493,7 +493,7 @@ public function clearContain() * @param \Cake\ORM\Table $table The table instance to pluck associations from. * @param \Cake\Database\TypeMap $typeMap The typemap to check for columns in. * This typemap is indirectly mutated via Cake\ORM\Query::addDefaultTypes() - * @param array $associations The nested tree of associations to walk. + * @param array $associations The nested tree of associations to walk. * @return void */ protected function _addAssociationsToTypeMap(Table $table, TypeMap $typeMap, array $associations): void @@ -1306,7 +1306,7 @@ public function delete(?string $table = null) * Can be combined with the where() method to create delete queries. * * @param array $columns The columns to insert into. - * @param array $types A map between columns & their datatypes. + * @param array $types A map between columns & their datatypes. * @return $this */ public function insert(array $columns, array $types = []) From e01305cc78680daee149ee1206d3fbfaa18b2171 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Fri, 24 Sep 2021 12:17:10 -0500 Subject: [PATCH 1693/2059] Allow association binding key to be null during cascadeDelete --- Association/DependentDeleteHelper.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Association/DependentDeleteHelper.php b/Association/DependentDeleteHelper.php index 1b7a7d80..9311eb9e 100644 --- a/Association/DependentDeleteHelper.php +++ b/Association/DependentDeleteHelper.php @@ -45,7 +45,11 @@ public function cascadeDelete(Association $association, EntityInterface $entity, /** @psalm-suppress InvalidArgument */ $foreignKey = array_map([$association, 'aliasField'], (array)$association->getForeignKey()); $bindingKey = (array)$association->getBindingKey(); - $conditions = array_combine($foreignKey, $entity->extract($bindingKey)); + $bindingValue = $entity->extract($bindingKey); + if (in_array(null, $bindingValue, true)) { + return true; + } + $conditions = array_combine($foreignKey, $bindingValue); if ($association->getCascadeCallbacks()) { foreach ($association->find()->where($conditions)->all()->toList() as $related) { From 963e438128681ece1a2cf6f3c1501b6f677ec7b1 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 23 Sep 2021 21:08:09 +0530 Subject: [PATCH 1694/2059] Add convenience method LocatorAwareTrait::getTable(). This will eventually be a replacement for ModelAwareTrait::loadModel(). --- Locator/LocatorAwareTrait.php | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index 3c17d437..8b817917 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -16,13 +16,22 @@ */ namespace Cake\ORM\Locator; +use Cake\Core\Exception\CakeException; use Cake\Datasource\FactoryLocator; +use Cake\ORM\Table; /** * Contains method for setting and accessing LocatorInterface instance */ trait LocatorAwareTrait { + /** + * This object's default table alias. + * + * @var string|null + */ + protected $defaultTable = null; + /** * Table locator instance * @@ -58,4 +67,26 @@ public function getTableLocator(): LocatorInterface /** @var \Cake\ORM\Locator\LocatorInterface */ return $this->_tableLocator; } + + /** + * Convenience method to get a table instance. + * + * @param string|null $alias The alias name you want to get. Should be in CamelCase format. + * If `null` then value of $defaultTable property is used. + * @param array $options The options you want to build the table with. + * If a table has already been loaded the registry options will be ignored. + * @return \Cake\ORM\Table + * @throws \Cake\Core\Exception\CakeException If `$alias` argument and `$defaultTable` property both are `null`. + * @see \Cake\ORM\TableLocator::get() + * @since 4.3.0 + */ + public function getTable(?string $alias = null, array $options = []): Table + { + $alias = $alias ?? $this->defaultTable; + if ($alias === null) { + throw new CakeException('You must provide an `$alias` or set the `$defaultTable` property.'); + } + + return $this->getTableLocator()->get($alias, $options); + } } From bbeee4c93702c4f7de181db54373b12c05bb336d Mon Sep 17 00:00:00 2001 From: Mark Story Date: Wed, 29 Sep 2021 22:05:47 -0400 Subject: [PATCH 1695/2059] Update src/ORM/Locator/LocatorAwareTrait.php --- Locator/LocatorAwareTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index 8b817917..d51b753b 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -72,7 +72,7 @@ public function getTableLocator(): LocatorInterface * Convenience method to get a table instance. * * @param string|null $alias The alias name you want to get. Should be in CamelCase format. - * If `null` then value of $defaultTable property is used. + * If `null` then the value of $defaultTable property is used. * @param array $options The options you want to build the table with. * If a table has already been loaded the registry options will be ignored. * @return \Cake\ORM\Table From 5352e1cc212004becbeb558f3b80863600c31ca5 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 30 Sep 2021 12:39:12 +0530 Subject: [PATCH 1696/2059] Add type for property. --- Locator/LocatorAwareTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index 607b91fd..c9b03b06 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -30,7 +30,7 @@ trait LocatorAwareTrait * * @var string|null */ - protected $defaultTable = null; + protected ?string $defaultTable = null; /** * Table locator instance From e0781eea094445c268fd13ff2da4513d553d9d4f Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 30 Sep 2021 15:54:35 -0400 Subject: [PATCH 1697/2059] Fix belongstomany delete callbacks getting wrong entity type Previously the query used would have the target table as the root table, and *join* the junction table. This would cause the junction table entities to use the target table entity class. Now the junction table is used as the root and the association target is joined on. I've also removed the column subsetting so that entities passed to callbacks include their complete property sets. Fixes #15977 --- Association/BelongsToMany.php | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 8ffc172e..1b305f93 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1193,20 +1193,17 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { $keys[$key] = $junction->aliasField($key); } - // Find existing rows so that we can diff with new entities. - // Only hydrate primary/foreign key columns to save time. - // Attach joins first to ensure where conditions have correct - // column types set. - $existing = $this->_appendJunctionJoin($this->find()) - ->select($keys) + // Find junction records. We join with the association target so that junction + // conditions from `targetConditions()` or the finder work. + $existing = $junction->find() + ->innerJoinWith($target->getAlias()) + ->where($this->targetConditions()) + ->where($this->junctionConditions()) ->where(array_combine($prefixedForeignKey, $primaryValue)); - - // Because we're aliasing key fields to look like they are not - // from joined table we need to overwrite the type map as the junction - // table can have a surrogate primary key that doesn't share a type - // with the target table. - $junctionTypes = array_intersect_key($junction->getSchema()->typeMap(), $keys); - $existing->getSelectTypeMap()->setTypes($junctionTypes); + $finder = $this->getFinder(); + if ($finder) { + $existing = $target->callFinder($finder, $existing); + } $jointEntities = $this->_collectJointEntities($sourceEntity, $targetEntities); $inserts = $this->_diffLinks($existing, $jointEntities, $targetEntities, $options); From 9c908e03e1f03ab88592b47f40da5b913050ce97 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 1 Oct 2021 12:19:17 +0530 Subject: [PATCH 1698/2059] Rename LocatorAwareTrait::getTable() to fetchTable(). This avoid name collisions when the trait is attached to classes which already have a getTable() method, like the Table class. --- Locator/LocatorAwareTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index d51b753b..15713052 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -80,7 +80,7 @@ public function getTableLocator(): LocatorInterface * @see \Cake\ORM\TableLocator::get() * @since 4.3.0 */ - public function getTable(?string $alias = null, array $options = []): Table + public function fetchTable(?string $alias = null, array $options = []): Table { $alias = $alias ?? $this->defaultTable; if ($alias === null) { From ad9fdbfb4ced69df9ffa42af98d62798d0869b30 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Fri, 1 Oct 2021 19:26:02 -0500 Subject: [PATCH 1699/2059] Handle finder array in replaceLinks() --- Association/BelongsToMany.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 1b305f93..e0fd9301 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1202,7 +1202,13 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { ->where(array_combine($prefixedForeignKey, $primaryValue)); $finder = $this->getFinder(); if ($finder) { - $existing = $target->callFinder($finder, $existing); + if (is_array($finder)) { + /** @var array $finderOptions */ + $finderOptions = key($finder); + /** @var string $finder */ + $finder = current($finder); + } + $existing = $target->callFinder($finder, $existing, $finderOptions ?? []); } $jointEntities = $this->_collectJointEntities($sourceEntity, $targetEntities); From 689e960b58cd0735bb094ae7d5e7436d02f31c29 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 1 Oct 2021 22:45:13 -0400 Subject: [PATCH 1700/2059] Use extractFinder() --- Association/BelongsToMany.php | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index e0fd9301..7b62a747 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1200,15 +1200,9 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { ->where($this->targetConditions()) ->where($this->junctionConditions()) ->where(array_combine($prefixedForeignKey, $primaryValue)); - $finder = $this->getFinder(); + [$finder, $finderOptions] = $this->_extractFinder($this->getFinder()); if ($finder) { - if (is_array($finder)) { - /** @var array $finderOptions */ - $finderOptions = key($finder); - /** @var string $finder */ - $finder = current($finder); - } - $existing = $target->callFinder($finder, $existing, $finderOptions ?? []); + $existing = $target->callFinder($finder, $existing, $finderOptions); } $jointEntities = $this->_collectJointEntities($sourceEntity, $targetEntities); From bbbf6d7ceda6861c19b048bb280a8beeb71ee800 Mon Sep 17 00:00:00 2001 From: othercorey Date: Tue, 5 Oct 2021 12:55:05 -0500 Subject: [PATCH 1701/2059] Ignore limit if no order exists when building subquery --- Association/Loader/SelectLoader.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index f3be34f5..b4c88b33 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -404,7 +404,8 @@ protected function _buildSubquery(Query $query): Query $filterQuery->contain([], true); $filterQuery->setValueBinder(new ValueBinder()); - if (!$filterQuery->clause('limit')) { + // Ignore limit if there is no order since we need all rows to find matches + if (!$filterQuery->clause('limit') || !$filterQuery->clause('order')) { $filterQuery->limit(null); $filterQuery->order([], true); $filterQuery->offset(null); From 12dd3df02998096e1c86817da78e634a6703dc89 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Tue, 19 Oct 2021 20:49:29 -0500 Subject: [PATCH 1702/2059] Fix phpdoc annotation formatting --- Association/BelongsToMany.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index f0007441..354a5684 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -997,7 +997,7 @@ public function getThrough(): Table|string * Any string expressions, or expression objects will * also be returned in this list. * - * @return array|\Closure|null Generally an array. If the conditions + * @return \Closure|array|null Generally an array. If the conditions * are not an array, the association conditions will be * returned unmodified. */ From f096730443f16167ce863cb2483cfafe120748c5 Mon Sep 17 00:00:00 2001 From: mscherer Date: Sun, 24 Oct 2021 14:42:50 +0200 Subject: [PATCH 1703/2059] Improve assoc array docs --- Association.php | 2 +- Association/BelongsToMany.php | 2 +- Association/HasMany.php | 2 +- AssociationCollection.php | 2 +- Locator/TableLocator.php | 2 +- Rule/ExistsIn.php | 6 +++--- RulesChecker.php | 4 ++-- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Association.php b/Association.php index 2d89414b..baf40052 100644 --- a/Association.php +++ b/Association.php @@ -857,7 +857,7 @@ public function defaultRowValue(array $row, bool $joined): array * and modifies the query accordingly based of this association * configuration * - * @param array|string|null $type the type of query to perform, if an array is passed, + * @param array|string|null $type the type of query to perform, if an array is passed, * it will be interpreted as the `$options` parameter * @param array $options The options to for the find * @see \Cake\ORM\Table::find() diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 7b62a747..1089d979 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1060,7 +1060,7 @@ protected function junctionConditions(): array * If your association includes conditions or a finder, the junction table will be * included in the query's contained associations. * - * @param array|string|null $type the type of query to perform, if an array is passed, + * @param array|string|null $type the type of query to perform, if an array is passed, * it will be interpreted as the `$options` parameter * @param array $options The options to for the find * @see \Cake\ORM\Table::find() diff --git a/Association/HasMany.php b/Association/HasMany.php index adf4341c..9dbeac72 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -335,7 +335,7 @@ public function link(EntityInterface $sourceEntity, array $targetEntities, array * this association * @param array $targetEntities list of entities persisted in the target table for * this association - * @param array|bool $options list of options to be passed to the internal `delete` call. + * @param array|bool $options list of options to be passed to the internal `delete` call. * If boolean it will be used a value for "cleanProperty" option. * @throws \InvalidArgumentException if non persisted entities are passed or if * any of them is lacking a primary key value diff --git a/AssociationCollection.php b/AssociationCollection.php index d3676928..f73f0592 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -154,7 +154,7 @@ public function keys(): array /** * Get an array of associations matching a specific type. * - * @param array|string $class The type of associations you want. + * @param array|string $class The type of associations you want. * For example 'BelongsTo' or array like ['BelongsTo', 'HasOne'] * @return array<\Cake\ORM\Association> An array of Association objects. * @since 3.5.3 diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 510b221a..7f9fd029 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -78,7 +78,7 @@ class TableLocator extends AbstractLocator implements LocatorInterface /** * Constructor. * - * @param array|null $locations Locations where tables should be looked for. + * @param array|null $locations Locations where tables should be looked for. * If none provided, the default `Model\Table` under your app's namespace is used. */ public function __construct(?array $locations = null) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 71da04dd..912c7199 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -30,7 +30,7 @@ class ExistsIn /** * The list of fields to check * - * @var array + * @var array */ protected $_fields; @@ -54,10 +54,10 @@ class ExistsIn * Available option for $options is 'allowNullableNulls' flag. * Set to true to accept composite foreign keys where one or more nullable columns are null. * - * @param array|string $fields The field or fields to check existence as primary key. + * @param array|string $fields The field or fields to check existence as primary key. * @param \Cake\ORM\Table|\Cake\ORM\Association|string $repository The repository where the * field will be looked for, or the association name for the repository. - * @param array $options The options that modify the rules behavior. + * @param array $options The options that modify the rule's behavior. * Options 'allowNullableNulls' will make the rule pass if given foreign keys are set to `null`. * Notice: allowNullableNulls cannot pass by database columns set to `NOT NULL`. */ diff --git a/RulesChecker.php b/RulesChecker.php index 2784dcc6..8136b6d8 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -48,7 +48,7 @@ class RulesChecker extends BaseRulesChecker * - `allowMultipleNulls` Allows any field to have multiple null values. Defaults to false. * * @param array $fields The list of fields to check for uniqueness. - * @param array|string|null $message The error message to show in case the rule does not pass. Can + * @param array|string|null $message The error message to show in case the rule does not pass. Can * also be an array of options. When an array, the 'message' key can be used to provide a message. * @return \Cake\Datasource\RuleInvoker */ @@ -92,7 +92,7 @@ public function isUnique(array $fields, $message = null): RuleInvoker * @param array|string $field The field or list of fields to check for existence by * primary key lookup in the other table. * @param \Cake\ORM\Table|\Cake\ORM\Association|string $table The table name where the fields existence will be checked. - * @param array|string|null $message The error message to show in case the rule does not pass. Can + * @param array|string|null $message The error message to show in case the rule does not pass. Can * also be an array of options. When an array, the 'message' key can be used to provide a message. * @return \Cake\Datasource\RuleInvoker */ From 1c22bae97c8f27da145b8fd0b8287070ab6561c0 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Sun, 24 Oct 2021 15:02:24 -0500 Subject: [PATCH 1704/2059] Require php 7.4 for packages --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index c0f2675d..d3f1c880 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "source": "https://github.com/cakephp/orm" }, "require": { - "php": ">=7.2.0", + "php": ">=7.4.0", "cakephp/collection": "^4.0", "cakephp/core": "^4.0", "cakephp/datasource": "^4.0", From 37910d6aecd8a2ba8aae91cf4715252385f641c9 Mon Sep 17 00:00:00 2001 From: mark Date: Tue, 26 Oct 2021 22:01:16 +0200 Subject: [PATCH 1705/2059] More assoc array docs. --- Association/BelongsToMany.php | 2 +- Association/Loader/SelectLoader.php | 2 +- Exception/PersistenceFailedException.php | 2 +- Locator/LocatorInterface.php | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 1089d979..ba3503b6 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -902,7 +902,7 @@ function () use ($sourceEntity, $targetEntities, $options) { * this association. * @param array<\Cake\Datasource\EntityInterface> $targetEntities List of entities persisted in the target table for * this association. - * @param array|bool $options List of options to be passed to the internal `delete` call, + * @param array|bool $options List of options to be passed to the internal `delete` call, * or a `boolean` as `cleanProperty` key shortcut. * @throws \InvalidArgumentException If non persisted entities are passed or if * any of them is lacking a primary key value. diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 615543d1..0ccff39a 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -317,7 +317,7 @@ protected function _addFilteringJoin(Query $query, $key, $subquery): Query * target table query given a filter key and some filtering values. * * @param \Cake\ORM\Query $query Target table's query - * @param array|string $key The fields that should be used for filtering + * @param array|string $key The fields that should be used for filtering * @param mixed $filter The value that should be used to match for $key * @return \Cake\ORM\Query */ diff --git a/Exception/PersistenceFailedException.php b/Exception/PersistenceFailedException.php index 417142ab..291d8ced 100644 --- a/Exception/PersistenceFailedException.php +++ b/Exception/PersistenceFailedException.php @@ -40,7 +40,7 @@ class PersistenceFailedException extends CakeException * Constructor. * * @param \Cake\Datasource\EntityInterface $entity The entity on which the persistence operation failed - * @param array|string $message Either the string of the error message, or an array of attributes + * @param array|string $message Either the string of the error message, or an array of attributes * that are made available in the view, and sprintf()'d into Exception::$_messageTemplate * @param int|null $code The code of the error, is also the HTTP status code for the error. * @param \Throwable|null $previous the previous exception. diff --git a/Locator/LocatorInterface.php b/Locator/LocatorInterface.php index eb837c07..24516e8e 100644 --- a/Locator/LocatorInterface.php +++ b/Locator/LocatorInterface.php @@ -38,9 +38,9 @@ public function getConfig(?string $alias = null): array; * Stores a list of options to be used when instantiating an object * with a matching alias. * - * @param array|string $alias Name of the alias or array to completely + * @param array|string $alias Name of the alias or array to completely * overwrite current config. - * @param array|null $options list of options for the alias + * @param array|null $options list of options for the alias * @return $this * @throws \RuntimeException When you attempt to configure an existing * table instance. From 64afd3a4838b11c0a12ee763c21d3841d233c30f Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 29 Oct 2021 18:30:52 +0530 Subject: [PATCH 1706/2059] Fix errors reported by static analyzers. --- Behavior/TreeBehavior.php | 42 +++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 939e9686..6d78299a 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -109,21 +109,21 @@ public function beforeSave(EventInterface $event, EntityInterface $entity): void throw new RuntimeException("Cannot set a node's parent as itself"); } - if ($isNew && $parent) { - $parentNode = $this->_getNode($parent); - $edge = $parentNode->get($config['right']); - $entity->set($config['left'], $edge); - $entity->set($config['right'], $edge + 1); - $this->_sync(2, '+', ">= {$edge}"); + if ($isNew) { + if ($parent) { + $parentNode = $this->_getNode($parent); + $edge = $parentNode->get($config['right']); + $entity->set($config['left'], $edge); + $entity->set($config['right'], $edge + 1); + $this->_sync(2, '+', ">= {$edge}"); - if ($level) { - $entity->set($level, $parentNode[$level] + 1); - } + if ($level) { + $entity->set($level, $parentNode[$level] + 1); + } - return; - } + return; + } - if ($isNew && !$parent) { $edge = $this->_getMax(); $entity->set($config['left'], $edge + 1); $entity->set($config['right'], $edge + 2); @@ -135,18 +135,18 @@ public function beforeSave(EventInterface $event, EntityInterface $entity): void return; } - if ($dirty && $parent) { - $this->_setParent($entity, $parent); + if ($dirty) { + if ($parent) { + $this->_setParent($entity, $parent); - if ($level) { - $parentNode = $this->_getNode($parent); - $entity->set($level, $parentNode[$level] + 1); - } + if ($level) { + $parentNode = $this->_getNode($parent); + $entity->set($level, $parentNode[$level] + 1); + } - return; - } + return; + } - if ($dirty && !$parent) { $this->_setAsRoot($entity); if ($level) { From 8dccd78646529a1f97d70c21b0e602b228b8b2e8 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 30 Oct 2021 19:43:42 +0530 Subject: [PATCH 1707/2059] Fix errors reported by static analyzers. --- Association.php | 2 +- Locator/TableLocator.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Association.php b/Association.php index 5fe6f6d7..da36eb0a 100644 --- a/Association.php +++ b/Association.php @@ -852,7 +852,7 @@ public function find(array|string|null $type = null, array $options = []): Query * @see \Cake\ORM\Table::exists() * @return bool */ - public function exists(ExpressionInterface|Closure|array $conditions): bool + public function exists(ExpressionInterface|Closure|array|string|null $conditions): bool { $conditions = $this->find() ->where($conditions) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index b7898047..9a78a59f 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -49,6 +49,7 @@ class TableLocator extends AbstractLocator implements LocatorInterface * Instances that belong to the registry. * * @var array + * @psalm-suppress NonInvariantDocblockPropertyType */ protected array $instances = []; From d8a084d5c047403ab4a7afdde839b522b4d42675 Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Wed, 10 Nov 2021 04:38:46 +0100 Subject: [PATCH 1708/2059] Doc: update PHPDoc for TreeBehavior moveUp and moveDown (#16104) Adjust phpdoc for TreeBehavior moveUp and moveDown methods --- Behavior/TreeBehavior.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 62ac3369..154d10d2 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -601,12 +601,12 @@ protected function _removeFromTree(EntityInterface $node) * Reorders the node without changing its parent. * * If the node is the first child, or is a top level node with no previous node - * this method will return false + * this method will return the same node without any changes * * @param \Cake\Datasource\EntityInterface $node The node to move * @param int|true $number How many places to move the node, or true to move to first position * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found - * @return \Cake\Datasource\EntityInterface|false $node The node after being moved or false on failure + * @return \Cake\Datasource\EntityInterface|false $node The node after being moved or false if `$number` is < 1 */ public function moveUp(EntityInterface $node, $number = 1) { @@ -626,7 +626,7 @@ public function moveUp(EntityInterface $node, $number = 1) * * @param \Cake\Datasource\EntityInterface $node The node to move * @param int|true $number How many places to move the node, or true to move to first position - * @return \Cake\Datasource\EntityInterface $node The node after being moved or false on failure + * @return \Cake\Datasource\EntityInterface $node The node after being moved * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found */ protected function _moveUp(EntityInterface $node, $number): EntityInterface @@ -693,12 +693,12 @@ protected function _moveUp(EntityInterface $node, $number): EntityInterface * Reorders the node without changing the parent. * * If the node is the last child, or is a top level node with no subsequent node - * this method will return false + * this method will return the same node without any changes * * @param \Cake\Datasource\EntityInterface $node The node to move * @param int|true $number How many places to move the node or true to move to last position * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found - * @return \Cake\Datasource\EntityInterface|false the entity after being moved or false on failure + * @return \Cake\Datasource\EntityInterface|false the entity after being moved or false if `$number` is < 1 */ public function moveDown(EntityInterface $node, $number = 1) { @@ -718,7 +718,7 @@ public function moveDown(EntityInterface $node, $number = 1) * * @param \Cake\Datasource\EntityInterface $node The node to move * @param int|true $number How many places to move the node, or true to move to last position - * @return \Cake\Datasource\EntityInterface $node The node after being moved or false on failure + * @return \Cake\Datasource\EntityInterface $node The node after being moved * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found */ protected function _moveDown(EntityInterface $node, $number): EntityInterface From 5d588c5c2c2a722db988a7e5af6e7c71741a4864 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 14 Nov 2021 22:30:39 -0500 Subject: [PATCH 1709/2059] Update cache key used by Table::get() Make the cache key used by Table::get() to be compatible with FileEngine. While it isn't a great to use local files for ORM query results, it can be useful in local development environments. We shouldn't use cache keys that won't work in all engines. Fixes #16108 --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index eb5694a9..3b32e965 100644 --- a/Table.php +++ b/Table.php @@ -1531,7 +1531,7 @@ public function get($primaryKey, array $options = []): EntityInterface if ($cacheConfig) { if (!$cacheKey) { $cacheKey = sprintf( - 'get:%s.%s%s', + 'get-%s.%s%s', $this->getConnection()->configName(), $this->getTable(), json_encode($primaryKey) From 6c62be5021e6c418691ad33bb6b0255e1f45c706 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 15 Nov 2021 10:42:57 -0500 Subject: [PATCH 1710/2059] Apply suggestions from code review Co-authored-by: othercorey --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 3b32e965..6f545b59 100644 --- a/Table.php +++ b/Table.php @@ -1531,7 +1531,7 @@ public function get($primaryKey, array $options = []): EntityInterface if ($cacheConfig) { if (!$cacheKey) { $cacheKey = sprintf( - 'get-%s.%s%s', + 'get-%s-%s-%s', $this->getConnection()->configName(), $this->getTable(), json_encode($primaryKey) From e947ab5e2fc652c7902e4ac306c901022ffcb78f Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 28 Nov 2021 23:47:36 +0530 Subject: [PATCH 1711/2059] Avoid unneeded variable assignment. --- Table.php | 46 +++++++++++++++++++--------------------------- 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/Table.php b/Table.php index 6f545b59..94480f2b 100644 --- a/Table.php +++ b/Table.php @@ -1260,10 +1260,7 @@ public function belongsToMany(string $associated, array $options = []): BelongsT */ public function find(string $type = 'all', array $options = []): Query { - $query = $this->query(); - $query->select(); - - return $this->callFinder($type, $query, $options); + return $this->callFinder($type, $this->query()->select(), $options); } /** @@ -1718,11 +1715,11 @@ public function subquery(): Query */ public function updateAll($fields, $conditions): int { - $query = $this->query(); - $query->update() + $statement = $this->query() + ->update() ->set($fields) - ->where($conditions); - $statement = $query->execute(); + ->where($conditions) + ->execute(); $statement->closeCursor(); return $statement->rowCount(); @@ -1733,10 +1730,10 @@ public function updateAll($fields, $conditions): int */ public function deleteAll($conditions): int { - $query = $this->query() + $statement = $this->query() ->delete() - ->where($conditions); - $statement = $query->execute(); + ->where($conditions) + ->execute(); $statement->closeCursor(); return $statement->rowCount(); @@ -2066,15 +2063,15 @@ protected function _insert(EntityInterface $entity, array $data) } } - $success = false; if (empty($data)) { - return $success; + return false; } $statement = $this->query()->insert(array_keys($data)) ->values($data) ->execute(); + $success = false; if ($statement->rowCount() !== 0) { $success = $entity; $entity->set($filteredKeys, ['guard' => false]); @@ -2151,16 +2148,13 @@ protected function _update(EntityInterface $entity, array $data) throw new InvalidArgumentException($message); } - $query = $this->query(); - $statement = $query->update() + $statement = $this->query() + ->update() ->set($data) ->where($primaryKey) ->execute(); - $success = false; - if ($statement->errorCode() === '00000') { - $success = $entity; - } + $success = $statement->errorCode() === '00000' ? $entity : false; $statement->closeCursor(); return $success; @@ -2469,15 +2463,13 @@ protected function _processDelete(EntityInterface $entity, ArrayObject $options) return $success; } - $query = $this->query(); - $conditions = $entity->extract($primaryKey); - $statement = $query->delete() - ->where($conditions) + $statement = $this->query() + ->delete() + ->where($entity->extract($primaryKey)) ->execute(); - $success = $statement->rowCount() > 0; - if (!$success) { - return $success; + if ($statement->rowCount() < 1) { + return false; } $this->dispatchEvent('Model.afterDelete', [ @@ -2485,7 +2477,7 @@ protected function _processDelete(EntityInterface $entity, ArrayObject $options) 'options' => $options, ]); - return $success; + return true; } /** From 494d9d477a1153464fce71d4b35066fbda9c372b Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 29 Nov 2021 00:16:40 +0530 Subject: [PATCH 1712/2059] Fix docblock. --- Table.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Table.php b/Table.php index 6f545b59..bddee65a 100644 --- a/Table.php +++ b/Table.php @@ -2502,12 +2502,11 @@ public function hasFinder(string $type): bool } /** - * Calls a finder method directly and applies it to the passed query, - * if no query is passed a new one will be created and returned + * Calls a finder method and applies it to the passed query. * - * @param string $type name of the finder to be called - * @param \Cake\ORM\Query $query The query object to apply the finder options to - * @param array $options List of options to pass to the finder + * @param string $type Name of the finder to be called. + * @param \Cake\ORM\Query $query The query object to apply the finder options to. + * @param array $options List of options to pass to the finder. * @return \Cake\ORM\Query * @throws \BadMethodCallException * @uses findAll() From 2fc31bc65897a9773c24399df672d36999271820 Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Wed, 8 Dec 2021 15:12:08 +0100 Subject: [PATCH 1713/2059] change TableRegistry::get() deprecated phpdoc to LocatorAwareTrait::fetchTable() --- TableRegistry.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TableRegistry.php b/TableRegistry.php index b3a4a7a9..7119be7d 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -88,7 +88,7 @@ public static function setTableLocator(LocatorInterface $tableLocator): void * @param string $alias The alias name you want to get. * @param array $options The options you want to build the table with. * @return \Cake\ORM\Table - * @deprecated 3.6.0 Use {@link \Cake\ORM\Locator\TableLocator::get()} instead. Will be removed in 5.0. + * @deprecated 3.6.0 Use {@link \Cake\ORM\Locator\LocatorAwareTrait::fetchTable()} instead. Will be removed in 5.0. */ public static function get(string $alias, array $options = []): Table { From 458b71a38bff174b253d0e5aec0895e70a1660c3 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 17 Dec 2021 19:58:04 +0530 Subject: [PATCH 1714/2059] Revert "Merge branch '4.x' into 4.next" This reverts commit de4eb4f06bb7a7b8512bb4d7ac939e4f63e9a578, reversing changes made to c2e3db0eef03b730cfc5c41eedf9fae64427e59b. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index d3f1c880..c0f2675d 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "source": "https://github.com/cakephp/orm" }, "require": { - "php": ">=7.4.0", + "php": ">=7.2.0", "cakephp/collection": "^4.0", "cakephp/core": "^4.0", "cakephp/datasource": "^4.0", From 99e08d11f45bbc6674011f60d008fe703e75f47b Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 18 Dec 2021 18:32:04 +0530 Subject: [PATCH 1715/2059] Revert "Revert "Merge branch '4.x' into 4.next"" This reverts commit 2a3c42e6f4a6e3235c089ff440c0920ddbfdfb24. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index c0f2675d..d3f1c880 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "source": "https://github.com/cakephp/orm" }, "require": { - "php": ">=7.2.0", + "php": ">=7.4.0", "cakephp/collection": "^4.0", "cakephp/core": "^4.0", "cakephp/datasource": "^4.0", From bfc37c7dd7a5448aea7274a59644f576a87b2946 Mon Sep 17 00:00:00 2001 From: ndm2 Date: Sat, 29 Jan 2022 17:27:52 +0100 Subject: [PATCH 1716/2059] Fix accepted types for association sorting/ordering. --- Association/BelongsToMany.php | 9 +++++---- Association/HasMany.php | 14 ++++++++------ Association/Loader/SelectLoader.php | 5 +++-- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 874c55b2..afb6114c 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -155,9 +155,9 @@ class BelongsToMany extends Association /** * Order in which target records should be returned * - * @var mixed + * @var \Cake\Database\ExpressionInterface|\Closure|array<\Cake\Database\ExpressionInterface|string>|string|null */ - protected mixed $_sort = null; + protected ExpressionInterface|Closure|array|string|null $_sort = null; /** * Sets the name of the field representing the foreign key to the target table. @@ -213,7 +213,8 @@ public function getForeignKey(): array|string|false /** * Sets the sort order in which target records should be returned. * - * @param \Cake\Database\ExpressionInterface|\Closure|array|string $sort A find() compatible order clause + * @param \Cake\Database\ExpressionInterface|\Closure|array<\Cake\Database\ExpressionInterface|string>|string $sort + * A find() compatible order clause * @return $this */ public function setSort(ExpressionInterface|Closure|array|string $sort) @@ -226,7 +227,7 @@ public function setSort(ExpressionInterface|Closure|array|string $sort) /** * Gets the sort order in which target records should be returned. * - * @return \Cake\Database\ExpressionInterface|\Closure|array|string|null + * @return \Cake\Database\ExpressionInterface|\Closure|array<\Cake\Database\ExpressionInterface|string>|string|null */ public function getSort(): ExpressionInterface|Closure|array|string|null { diff --git a/Association/HasMany.php b/Association/HasMany.php index c512b8fb..c2a56879 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -19,6 +19,7 @@ use Cake\Collection\Collection; use Cake\Database\Expression\FieldInterface; use Cake\Database\Expression\QueryExpression; +use Cake\Database\ExpressionInterface; use Cake\Datasource\EntityInterface; use Cake\Datasource\InvalidPropertyInterface; use Cake\ORM\Association; @@ -39,9 +40,9 @@ class HasMany extends Association /** * Order in which target records should be returned * - * @var mixed + * @var \Cake\Database\ExpressionInterface|\Closure|array<\Cake\Database\ExpressionInterface|string>|string|null */ - protected mixed $_sort = null; + protected ExpressionInterface|Closure|array|string|null $_sort = null; /** * The type of join to be used when adding the association to a query @@ -601,10 +602,11 @@ public function getForeignKey(): array|string|false /** * Sets the sort order in which target records should be returned. * - * @param mixed $sort A find() compatible order clause + * @param \Cake\Database\ExpressionInterface|\Closure|array<\Cake\Database\ExpressionInterface|string>|string $sort + * A find() compatible order clause * @return $this */ - public function setSort(mixed $sort) + public function setSort(ExpressionInterface|Closure|array|string $sort) { $this->_sort = $sort; @@ -614,9 +616,9 @@ public function setSort(mixed $sort) /** * Gets the sort order in which target records should be returned. * - * @return mixed + * @return \Cake\Database\ExpressionInterface|\Closure|array<\Cake\Database\ExpressionInterface|string>|string|null */ - public function getSort(): mixed + public function getSort(): ExpressionInterface|Closure|array|string|null { return $this->_sort; } diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 36ae6041..c9c0d313 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -18,6 +18,7 @@ use Cake\Database\Expression\IdentifierExpression; use Cake\Database\Expression\TupleComparison; +use Cake\Database\ExpressionInterface; use Cake\Database\ValueBinder; use Cake\ORM\Association; use Cake\ORM\Query; @@ -91,9 +92,9 @@ class SelectLoader /** * The sorting options for loading the association * - * @var array|string|null + * @var \Cake\Database\ExpressionInterface|\Closure|array|string|null */ - protected array|string|null $sort = null; + protected ExpressionInterface|Closure|array|string|null $sort = null; /** * Copies the options array to properties in this class. The keys in the array correspond From f9687c4c6d5e20208a5c96a5dd80d34ea7147e40 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 24 Jan 2022 12:53:16 +0530 Subject: [PATCH 1717/2059] StatementDecorator no longer implements Countable. Avoid using StatementInterface::rowCount() for SELECT queries. --- EagerLoader.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/EagerLoader.php b/EagerLoader.php index e7b07bff..b1b4c615 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -633,8 +633,12 @@ public function loadExternal(Query $query, StatementInterface $statement): State $driver = $query->getConnection()->getDriver(); [$collected, $statement] = $this->_collectKeys($external, $query, $statement); + // TODO: The EagerLoader will have to be updated to use the rows fetched by + // StatementInterface::fetchAll() instead of relying on StatementInterface::rowCount() + // to get the rows count. + // No records found, skip trying to attach associations. - if (empty($collected) && $statement->count() === 0) { + if (empty($collected) && $statement->rowCount() === 0) { return $statement; } From 4224d01b03ac53ccc1053ac8fa437c627c945510 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 24 Jan 2022 14:24:06 +0530 Subject: [PATCH 1718/2059] Update ResultSet to not rely on StatementInterface::rowCount(). All results are now prefetched and always buffered. --- ResultSet.php | 137 ++++++++++---------------------------------------- 1 file changed, 26 insertions(+), 111 deletions(-) diff --git a/ResultSet.php b/ResultSet.php index 3b646c6b..70a497e3 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -35,13 +35,6 @@ class ResultSet implements ResultSetInterface { use CollectionTrait; - /** - * Database statement holding the results - * - * @var \Cake\Database\StatementInterface|null - */ - protected ?StatementInterface $_statement = null; - /** * Points to the next record number that should be fetched * @@ -104,9 +97,9 @@ class ResultSet implements ResultSetInterface /** * Results that have been fetched or hydrated into the results. * - * @var \SplFixedArray|array + * @var \SplFixedArray */ - protected SplFixedArray|array $_results = []; + protected SplFixedArray $_results; /** * Whether to hydrate results into objects or not @@ -129,19 +122,12 @@ class ResultSet implements ResultSetInterface */ protected string $_entityClass; - /** - * Whether to buffer results fetched from the statement - * - * @var bool - */ - protected bool $_useBuffering = true; - /** * Holds the count of records in this result set * - * @var int|null + * @var int */ - protected ?int $_count = null; + protected int $_count = 0; /** * The Database driver object. @@ -161,20 +147,32 @@ class ResultSet implements ResultSetInterface public function __construct(Query $query, StatementInterface $statement) { $repository = $query->getRepository(); - $this->_statement = $statement; $this->_driver = $query->getConnection()->getDriver(); $this->_defaultTable = $repository; $this->_calculateAssociationMap($query); $this->_hydrate = $query->isHydrationEnabled(); $this->_entityClass = $repository->getEntityClass(); - $this->_useBuffering = $query->isBufferedResultsEnabled(); $this->_defaultAlias = $this->_defaultTable->getAlias(); $this->_calculateColumnMap($query); $this->_autoFields = $query->isAutoFieldsEnabled(); - if ($this->_useBuffering) { - $count = $this->count(); - $this->_results = new SplFixedArray($count); + $this->fetchResults($statement); + $statement->closeCursor(); + } + + protected function fetchResults(StatementInterface $statement): void + { + $results = $statement->fetchAll('assoc'); + if ($results === false) { + $this->_results = new SplFixedArray(); + + return; + } + + $this->_count = count($results); + $this->_results = new SplFixedArray($this->_count); + foreach ($results as $i => $row) { + $this->_results[$i] = $this->_groupResult($row); } } @@ -224,16 +222,6 @@ public function next(): void */ public function rewind(): void { - if ($this->_index === 0) { - return; - } - - if (!$this->_useBuffering) { - $msg = 'You cannot rewind an un-buffered ResultSet. ' - . 'Use Query::bufferResults() to get a buffered ResultSet.'; - throw new DatabaseException($msg); - } - $this->_index = 0; } @@ -246,48 +234,23 @@ public function rewind(): void */ public function valid(): bool { - if ($this->_useBuffering) { - $valid = $this->_index < $this->_count; - if ($valid && $this->_results[$this->_index] !== null) { - $this->_current = $this->_results[$this->_index]; - - return true; - } - if (!$valid) { - return $valid; - } - } + if ($this->_index < $this->_count) { + $this->_current = $this->_results[$this->_index]; - $current = $this->_fetchResult(); - $valid = $current !== false; - if ($valid) { - $this->_current = $current; + return true; } - if ($valid && $this->_useBuffering) { - $this->_results[$this->_index] = $this->_current; - } - if (!$valid && $this->_statement !== null) { - $this->_statement->closeCursor(); - } - - return $valid; + return false; } /** * Get the first record from a result set. * - * This method will also close the underlying statement cursor. - * * @return object|array|null */ public function first(): object|array|null { foreach ($this as $result) { - if ($this->_statement !== null && !$this->_useBuffering) { - $this->_statement->closeCursor(); - } - return $result; } @@ -301,21 +264,7 @@ public function first(): object|array|null */ public function __serialize(): array { - if (!$this->_useBuffering) { - $msg = 'You cannot serialize an un-buffered ResultSet. ' - . 'Use Query::bufferResults() to get a buffered ResultSet.'; - throw new DatabaseException($msg); - } - - while ($this->valid()) { - $this->next(); - } - - if ($this->_results instanceof SplFixedArray) { - return $this->_results->toArray(); - } - - return $this->_results; + return $this->_results->toArray(); } /** @@ -327,7 +276,6 @@ public function __serialize(): array public function __unserialize(array $data): void { $this->_results = SplFixedArray::fromArray($data); - $this->_useBuffering = true; $this->_count = $this->_results->count(); } @@ -340,19 +288,6 @@ public function __unserialize(array $data): void */ public function count(): int { - if ($this->_count !== null) { - return $this->_count; - } - if ($this->_statement !== null) { - return $this->_count = $this->_statement->rowCount(); - } - - if ($this->_results instanceof SplFixedArray) { - $this->_count = $this->_results->count(); - } else { - $this->_count = count($this->_results); - } - return $this->_count; } @@ -410,26 +345,6 @@ protected function _calculateColumnMap(Query $query): void $this->_map = $map; } - /** - * Helper function to fetch the next result from the statement or - * seeded results. - * - * @return mixed - */ - protected function _fetchResult(): mixed - { - if ($this->_statement === null) { - return false; - } - - $row = $this->_statement->fetch('assoc'); - if ($row === false) { - return $row; - } - - return $this->_groupResult($row); - } - /** * Correctly nests results keys including those coming from associations * From eb34583791f817afb520f3d522df3af57b224b9b Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 24 Jan 2022 18:00:57 +0530 Subject: [PATCH 1719/2059] Fix CS errors --- ResultSet.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ResultSet.php b/ResultSet.php index 70a497e3..d74ec117 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -19,7 +19,6 @@ use Cake\Collection\Collection; use Cake\Collection\CollectionTrait; use Cake\Database\DriverInterface; -use Cake\Database\Exception\DatabaseException; use Cake\Database\StatementInterface; use Cake\Datasource\EntityInterface; use Cake\Datasource\ResultSetInterface; @@ -160,6 +159,12 @@ public function __construct(Query $query, StatementInterface $statement) $statement->closeCursor(); } + /** + * Fetch results. + * + * @param \Cake\Database\StatementInterface $statement The statement to fetch from. + * @return void + */ protected function fetchResults(StatementInterface $statement): void { $results = $statement->fetchAll('assoc'); From 50ee71985b05a834c4abeff5ddbf4002bee62c96 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 24 Jan 2022 19:23:07 +0530 Subject: [PATCH 1720/2059] Construct ResultSet using array of records instead of Statement. --- Query.php | 3 ++- ResultSet.php | 29 ++++------------------------- 2 files changed, 6 insertions(+), 26 deletions(-) diff --git a/Query.php b/Query.php index 7c5c9d3e..79b95af6 100644 --- a/Query.php +++ b/Query.php @@ -1132,8 +1132,9 @@ protected function _execute(): ResultSetInterface } $statement = $this->getEagerLoader()->loadExternal($this, $this->execute()); + $resultset = new ResultSet($this, $statement->fetchAll('assoc') ?: []); - return new ResultSet($this, $statement); + return $resultset; } /** diff --git a/ResultSet.php b/ResultSet.php index d74ec117..c8e8a450 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -19,7 +19,6 @@ use Cake\Collection\Collection; use Cake\Collection\CollectionTrait; use Cake\Database\DriverInterface; -use Cake\Database\StatementInterface; use Cake\Datasource\EntityInterface; use Cake\Datasource\ResultSetInterface; use SplFixedArray; @@ -141,9 +140,9 @@ class ResultSet implements ResultSetInterface * Constructor * * @param \Cake\ORM\Query $query Query from where results come - * @param \Cake\Database\StatementInterface $statement The statement to fetch from + * @param array $results Results array. */ - public function __construct(Query $query, StatementInterface $statement) + public function __construct(Query $query, array $results) { $repository = $query->getRepository(); $this->_driver = $query->getConnection()->getDriver(); @@ -155,28 +154,8 @@ public function __construct(Query $query, StatementInterface $statement) $this->_calculateColumnMap($query); $this->_autoFields = $query->isAutoFieldsEnabled(); - $this->fetchResults($statement); - $statement->closeCursor(); - } - - /** - * Fetch results. - * - * @param \Cake\Database\StatementInterface $statement The statement to fetch from. - * @return void - */ - protected function fetchResults(StatementInterface $statement): void - { - $results = $statement->fetchAll('assoc'); - if ($results === false) { - $this->_results = new SplFixedArray(); - - return; - } - - $this->_count = count($results); - $this->_results = new SplFixedArray($this->_count); - foreach ($results as $i => $row) { + $this->__unserialize($results); + foreach ($this->_results as $i => $row) { $this->_results[$i] = $this->_groupResult($row); } } From 652f7553457af66dc8ad1bc70413c79ad3bd01d9 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 27 Jan 2022 19:49:09 +0530 Subject: [PATCH 1721/2059] Pass results array to Eagerloader instead of statement. --- EagerLoader.php | 52 ++++++++++++++++++------------------------------- Query.php | 10 ++++++++-- 2 files changed, 27 insertions(+), 35 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index b1b4c615..704391fd 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -16,9 +16,6 @@ */ namespace Cake\ORM; -use Cake\Database\Statement\BufferedStatement; -use Cake\Database\Statement\CallbackStatement; -use Cake\Database\StatementInterface; use Closure; use InvalidArgumentException; @@ -618,29 +615,23 @@ protected function _resolveJoins(array $associations, array $matching = []): arr * * @param \Cake\ORM\Query $query The query for which to eager load external * associations - * @param \Cake\Database\StatementInterface $statement The statement created after executing the $query - * @return \Cake\Database\StatementInterface statement modified statement with extra loaders + * @param array $results Results array. + * @return array * @throws \RuntimeException */ - public function loadExternal(Query $query, StatementInterface $statement): StatementInterface + public function loadExternal(Query $query, array $results): array { + if (empty($results)) { + return $results; + } + $table = $query->getRepository(); $external = $this->externalAssociations($table); if (empty($external)) { - return $statement; + return $results; } - $driver = $query->getConnection()->getDriver(); - [$collected, $statement] = $this->_collectKeys($external, $query, $statement); - - // TODO: The EagerLoader will have to be updated to use the rows fetched by - // StatementInterface::fetchAll() instead of relying on StatementInterface::rowCount() - // to get the rows count. - - // No records found, skip trying to attach associations. - if (empty($collected) && $statement->rowCount() === 0) { - return $statement; - } + $collected = $this->_collectKeys($external, $query, $results); foreach ($external as $meta) { $contain = $meta->associations(); @@ -670,7 +661,7 @@ public function loadExternal(Query $query, StatementInterface $statement): State } $keys = $collected[$path][$alias] ?? null; - $f = $instance->eagerLoader( + $callback = $instance->eagerLoader( $config + [ 'query' => $query, 'contain' => $contain, @@ -678,10 +669,10 @@ public function loadExternal(Query $query, StatementInterface $statement): State 'nestKey' => $meta->aliasPath(), ] ); - $statement = new CallbackStatement($statement, $driver, $f); + $results = array_map($callback, $results); } - return $statement; + return $results; } /** @@ -783,10 +774,10 @@ public function addToJoinsMap( * * @param array<\Cake\ORM\EagerLoadable> $external the list of external associations to be loaded * @param \Cake\ORM\Query $query The query from which the results where generated - * @param \Cake\Database\StatementInterface $statement The statement to work on + * @param array $results Results array. * @return array */ - protected function _collectKeys(array $external, Query $query, StatementInterface $statement): array + protected function _collectKeys(array $external, Query $query, array $results): array { $collectKeys = []; foreach ($external as $meta) { @@ -809,28 +800,24 @@ protected function _collectKeys(array $external, Query $query, StatementInterfac $collectKeys[$meta->aliasPath()] = [$alias, $pkFields, count($pkFields) === 1]; } if (empty($collectKeys)) { - return [[], $statement]; - } - - if (!($statement instanceof BufferedStatement)) { - $statement = new BufferedStatement($statement, $query->getConnection()->getDriver()); + return []; } - return [$this->_groupKeys($statement, $collectKeys), $statement]; + return $this->_groupKeys($results, $collectKeys); } /** * Helper function used to iterate a statement and extract the columns * defined in $collectKeys * - * @param \Cake\Database\Statement\BufferedStatement $statement The statement to read from. + * @param array $results Results array. * @param array $collectKeys The keys to collect * @return array */ - protected function _groupKeys(BufferedStatement $statement, array $collectKeys): array + protected function _groupKeys(array $results, array $collectKeys): array { $keys = []; - foreach (($statement->fetchAll('assoc') ?: []) as $result) { + foreach ($results as $result) { foreach ($collectKeys as $nestKey => $parts) { if ($parts[2] === true) { // Missed joins will have null in the results. @@ -857,7 +844,6 @@ protected function _groupKeys(BufferedStatement $statement, array $collectKeys): $keys[$nestKey][$parts[0]][implode(';', $collected)] = $collected; } } - $statement->rewind(); return $keys; } diff --git a/Query.php b/Query.php index 79b95af6..c95e45ee 100644 --- a/Query.php +++ b/Query.php @@ -20,6 +20,7 @@ use Cake\Database\Connection; use Cake\Database\ExpressionInterface; use Cake\Database\Query as DatabaseQuery; +use Cake\Database\StatementInterface; use Cake\Database\TypedResultInterface; use Cake\Database\TypeMap; use Cake\Database\ValueBinder; @@ -1131,8 +1132,13 @@ protected function _execute(): ResultSetInterface return new $decorator($this->_results); } - $statement = $this->getEagerLoader()->loadExternal($this, $this->execute()); - $resultset = new ResultSet($this, $statement->fetchAll('assoc') ?: []); + $results = $this->execute()->fetchAll(StatementInterface::FETCH_TYPE_ASSOC); + if ($results === false) { + $results = []; + } else { + $results = $this->getEagerLoader()->loadExternal($this, $results); + } + $resultset = new ResultSet($this, $results); return $resultset; } From 151bbfb78bc97933b479a99f8021c6383751c584 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 28 Jan 2022 13:32:07 +0530 Subject: [PATCH 1722/2059] Remove results buffering and drop BufferredStatement. --- Behavior/Translate/EavStrategy.php | 4 +--- Behavior/Translate/ShadowTableStrategy.php | 1 - Query.php | 3 +-- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index fcce4b9d..e94190e4 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -294,7 +294,6 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array 'foreign_key' => $key, 'model' => $model, ]) - ->disableBufferedResults() ->all() ->indexBy('field'); } @@ -499,8 +498,7 @@ protected function findExistingTranslations(array $ruleSet): array $query = $association->find() ->select(['id', 'num' => 0]) ->where(current($ruleSet)) - ->disableHydration() - ->disableBufferedResults(); + ->disableHydration(); unset($ruleSet[0]); foreach ($ruleSet as $i => $conditions) { diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 3505cde2..fc893cbf 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -403,7 +403,6 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array $translation = $this->translationTable->find() ->select(array_merge(['id', 'locale'], $fields)) ->where($where) - ->disableBufferedResults() ->first(); } diff --git a/Query.php b/Query.php index c95e45ee..7a8efe6d 100644 --- a/Query.php +++ b/Query.php @@ -1334,7 +1334,6 @@ public function __debugInfo(): array return parent::__debugInfo() + [ 'hydrate' => $this->_hydrate, - 'buffered' => $this->_useBufferedResults, 'formatters' => count($this->_formatters), 'mapReducers' => count($this->_mapReduce), 'contain' => $eagerLoader->getContain(), @@ -1407,7 +1406,7 @@ protected function _decorateResults(Traversable $result): ResultSetInterface { $result = $this->_applyDecorators($result); - if (!($result instanceof ResultSet) && $this->isBufferedResultsEnabled()) { + if (!($result instanceof ResultSet)) { $class = $this->_decoratorClass(); $result = new $class($result->buffered()); } From 607ddb0f568674c32e59777c7ef94966ad859a3b Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 29 Jan 2022 14:22:17 +0530 Subject: [PATCH 1723/2059] Update docblocks and cleanup code. --- EagerLoader.php | 98 ++++++++++++++++++++++++------------------------- Query.php | 2 - ResultSet.php | 64 +++++++++++++------------------- 3 files changed, 74 insertions(+), 90 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index 704391fd..4c56d866 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -45,7 +45,7 @@ class EagerLoader /** * List of options accepted by associations in contain() - * index by key for faster access + * index by key for faster access. * * @var array */ @@ -64,14 +64,14 @@ class EagerLoader ]; /** - * A list of associations that should be loaded with a separate query + * A list of associations that should be loaded with a separate query. * * @var array<\Cake\ORM\EagerLoadable> */ protected array $_loadExternal = []; /** - * Contains a list of the association names that are to be eagerly loaded + * Contains a list of the association names that are to be eagerly loaded. * * @var array */ @@ -93,9 +93,8 @@ class EagerLoader protected array $_joinsMap = []; /** - * Controls whether fields from associated tables - * will be eagerly loaded. When set to false, no fields will - * be loaded from associations. + * Controls whether fields from associated tables will be eagerly loaded. + * When set to false, no fields will be loaded from associations. * * @var bool */ @@ -113,19 +112,19 @@ class EagerLoader * * Accepted options per passed association: * - * - foreignKey: Used to set a different field to match both tables, if set to false + * - `foreignKey`: Used to set a different field to match both tables, if set to false * no join conditions will be generated automatically - * - fields: An array with the fields that should be fetched from the association - * - queryBuilder: Equivalent to passing a callable instead of an options array - * - matching: Whether to inform the association class that it should filter the + * - `fields`: An array with the fields that should be fetched from the association + * - `queryBuilder`: Equivalent to passing a callable instead of an options array + * - `matching`: Whether to inform the association class that it should filter the * main query by the results fetched by that class. - * - joinType: For joinable associations, the SQL join type to use. - * - strategy: The loading strategy to use (join, select, subquery) + * - `joinType`: For joinable associations, the SQL join type to use. + * - `strategy`: The loading strategy to use (join, select, subquery) * - * @param array|string $associations list of table aliases to be queried. + * @param array|string $associations List of table aliases to be queried. * When this method is called multiple times it will merge previous list with * the new one. - * @param callable|null $queryBuilder The query builder callable + * @param callable|null $queryBuilder The query builder callable. * @return array Containments. * @throws \InvalidArgumentException When using $queryBuilder with an array of $associations */ @@ -231,9 +230,9 @@ public function isAutoFieldsEnabled(): bool * - `fields`: Fields to contain * - `negateMatch`: Whether to add conditions negate match on target association * - * @param string $associationPath Dot separated association path, 'Name1.Name2.Name3' + * @param string $associationPath Dot separated association path, 'Name1.Name2.Name3'. * @param callable|null $builder the callback function to be used for setting extra - * options to the filtering query + * options to the filtering query. * @param array $options Extra options for the association matching. * @return $this */ @@ -265,7 +264,7 @@ public function setMatching(string $associationPath, ?callable $builder = null, /** * Returns the current tree of associations to be matched. * - * @return array The resulting containments array + * @return array The resulting containments array. */ public function getMatching(): array { @@ -289,7 +288,7 @@ public function getMatching(): array * association instance from the corresponding source table * * @param \Cake\ORM\Table $repository The table containing the association that - * will be normalized + * will be normalized. * @return array */ public function normalized(Table $repository): array @@ -318,11 +317,11 @@ public function normalized(Table $repository): array /** * Formats the containments array so that associations are always set as keys * in the array. This function merges the original associations array with - * the new associations provided + * the new associations provided. * - * @param array $associations user provided containments array + * @param array $associations User provided containments array. * @param array $original The original containments array to merge - * with the new one + * with the new one. * @return array */ protected function _reformatContain(array $associations, array $original): array @@ -397,11 +396,11 @@ protected function _reformatContain(array $associations, array $original): array * This method will not modify the query for loading external associations, i.e. * those that cannot be loaded without executing a separate query. * - * @param \Cake\ORM\Query $query The query to be modified + * @param \Cake\ORM\Query $query The query to be modified. * @param \Cake\ORM\Table $repository The repository containing the associations * @param bool $includeFields whether to append all fields from the associations * to the passed query. This can be overridden according to the settings defined - * per association in the containments array + * per association in the containments array. * @return void */ public function attachAssociations(Query $query, Table $repository, bool $includeFields): void @@ -434,7 +433,7 @@ public function attachAssociations(Query $query, Table $repository, bool $includ * with Cake\ORM\EagerLoadable objects. * * @param \Cake\ORM\Table $repository The table containing the associations to be - * attached + * attached. * @return array<\Cake\ORM\EagerLoadable> */ public function attachableAssociations(Table $repository): array @@ -452,7 +451,7 @@ public function attachableAssociations(Table $repository): array * separate query, each array value will contain a {@link \Cake\ORM\EagerLoadable} object. * * @param \Cake\ORM\Table $repository The table containing the associations - * to be loaded + * to be loaded. * @return array<\Cake\ORM\EagerLoadable> */ public function externalAssociations(Table $repository): array @@ -468,11 +467,11 @@ public function externalAssociations(Table $repository): array /** * Auxiliary function responsible for fully normalizing deep associations defined - * using `contain()` + * using `contain()`. * - * @param \Cake\ORM\Table $parent owning side of the association - * @param string $alias name of the association to be loaded - * @param array $options list of extra options to use for this association + * @param \Cake\ORM\Table $parent Owning side of the association. + * @param string $alias Name of the association to be loaded. + * @param array $options List of extra options to use for this association. * @param array $paths An array with two values, the first one is a list of dot * separated strings representing associations that lead to this `$alias` in the * chain of associations to be loaded. The second value is the path to follow in @@ -555,9 +554,9 @@ protected function _fixStrategies(): void /** * Changes the association fetching strategy if required because of duplicate - * under the same direct associations chain + * under the same direct associations chain. * - * @param \Cake\ORM\EagerLoadable $loadable The association config + * @param \Cake\ORM\EagerLoadable $loadable The association config. * @return void */ protected function _correctStrategy(EagerLoadable $loadable): void @@ -579,8 +578,8 @@ protected function _correctStrategy(EagerLoadable $loadable): void * Helper function used to compile a list of all associations that can be * joined in the query. * - * @param array<\Cake\ORM\EagerLoadable> $associations list of associations from which to obtain joins. - * @param array<\Cake\ORM\EagerLoadable> $matching list of associations that should be forcibly joined. + * @param array<\Cake\ORM\EagerLoadable> $associations List of associations from which to obtain joins. + * @param array<\Cake\ORM\EagerLoadable> $matching List of associations that should be forcibly joined. * @return array<\Cake\ORM\EagerLoadable> */ protected function _resolveJoins(array $associations, array $matching = []): array @@ -610,11 +609,10 @@ protected function _resolveJoins(array $associations, array $matching = []): arr } /** - * Decorates the passed statement object in order to inject data from associations - * that cannot be joined directly. + * Inject data from associations that cannot be joined directly. * - * @param \Cake\ORM\Query $query The query for which to eager load external - * associations + * @param \Cake\ORM\Query $query The query for which to eager load external. + * associations. * @param array $results Results array. * @return array * @throws \RuntimeException @@ -677,17 +675,17 @@ public function loadExternal(Query $query, array $results): array /** * Returns an array having as keys a dotted path of associations that participate - * in this eager loader. The values of the array will contain the following keys + * in this eager loader. The values of the array will contain the following keys: * - * - alias: The association alias - * - instance: The association instance - * - canBeJoined: Whether the association will be loaded using a JOIN - * - entityClass: The entity that should be used for hydrating the results - * - nestKey: A dotted path that can be used to correctly insert the data into the results. - * - matching: Whether it is an association loaded through `matching()`. + * - `alias`: The association alias + * - `instance`: The association instance + * - `canBeJoined`: Whether the association will be loaded using a JOIN + * - `entityClass`: The entity that should be used for hydrating the results + * - `nestKey`: A dotted path that can be used to correctly insert the data into the results. + * - `matching`: Whether it is an association loaded through `matching()`. * * @param \Cake\ORM\Table $table The table containing the association that - * will be normalized + * will be normalized. * @return array */ public function associationsMap(Table $table): array @@ -746,7 +744,7 @@ protected function _buildAssociationsMap(array $map, array $level, bool $matchin * * @param string $alias The table alias as it appears in the query. * @param \Cake\ORM\Association $assoc The association object the alias represents; - * will be normalized + * will be normalized. * @param bool $asMatching Whether this join results should be treated as a * 'matching' association. * @param string|null $targetProperty The property name where the results of the join should be nested at. @@ -772,8 +770,8 @@ public function addToJoinsMap( * Helper function used to return the keys from the query records that will be used * to eagerly load associations. * - * @param array<\Cake\ORM\EagerLoadable> $external the list of external associations to be loaded - * @param \Cake\ORM\Query $query The query from which the results where generated + * @param array<\Cake\ORM\EagerLoadable> $external The list of external associations to be loaded. + * @param \Cake\ORM\Query $query The query from which the results where generated. * @param array $results Results array. * @return array */ @@ -808,10 +806,10 @@ protected function _collectKeys(array $external, Query $query, array $results): /** * Helper function used to iterate a statement and extract the columns - * defined in $collectKeys + * defined in $collectKeys. * * @param array $results Results array. - * @param array $collectKeys The keys to collect + * @param array $collectKeys The keys to collect. * @return array */ protected function _groupKeys(array $results, array $collectKeys): array diff --git a/Query.php b/Query.php index 7a8efe6d..f291d716 100644 --- a/Query.php +++ b/Query.php @@ -1118,8 +1118,6 @@ public function sql(?ValueBinder $binder = null): string /** * Executes this query and returns a ResultSet object containing the results. - * This will also setup the correct statement class in order to eager load deep - * associations. * * @return \Cake\Datasource\ResultSetInterface */ diff --git a/ResultSet.php b/ResultSet.php index c8e8a450..e5d05395 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -18,7 +18,6 @@ use Cake\Collection\Collection; use Cake\Collection\CollectionTrait; -use Cake\Database\DriverInterface; use Cake\Datasource\EntityInterface; use Cake\Datasource\ResultSetInterface; use SplFixedArray; @@ -26,8 +25,7 @@ /** * Represents the results obtained after executing a query for a specific table * This object is responsible for correctly nesting result keys reported from - * the query, casting each field to the correct type and executing the extra - * queries required for eager loading external associations. + * the query and hydrating entities. */ class ResultSet implements ResultSetInterface { @@ -77,8 +75,7 @@ class ResultSet implements ResultSetInterface protected array $_containMap = []; /** - * Map of fields that are fetched from the statement with - * their type and the table they belong to + * Map of fields that are fetched with their type and the table they belong to. * * @var array */ @@ -100,7 +97,7 @@ class ResultSet implements ResultSetInterface protected SplFixedArray $_results; /** - * Whether to hydrate results into objects or not + * Whether to hydrate results into entities. * * @var bool */ @@ -114,7 +111,7 @@ class ResultSet implements ResultSetInterface protected ?bool $_autoFields = null; /** - * The fully namespaced name of the class to use for hydrating results + * The fully namespaced name of the class to use for hydrating results. * * @var string */ @@ -127,32 +124,23 @@ class ResultSet implements ResultSetInterface */ protected int $_count = 0; - /** - * The Database driver object. - * - * Cached in a property to avoid multiple calls to the same function. - * - * @var \Cake\Database\DriverInterface - */ - protected DriverInterface $_driver; - /** * Constructor * - * @param \Cake\ORM\Query $query Query from where results come + * @param \Cake\ORM\Query $query Query from where results came. * @param array $results Results array. */ public function __construct(Query $query, array $results) { - $repository = $query->getRepository(); - $this->_driver = $query->getConnection()->getDriver(); - $this->_defaultTable = $repository; - $this->_calculateAssociationMap($query); + $this->_defaultTable = $query->getRepository(); $this->_hydrate = $query->isHydrationEnabled(); - $this->_entityClass = $repository->getEntityClass(); + $this->_autoFields = $query->isAutoFieldsEnabled(); + + $this->_entityClass = $this->_defaultTable->getEntityClass(); $this->_defaultAlias = $this->_defaultTable->getAlias(); + + $this->_calculateAssociationMap($query); $this->_calculateColumnMap($query); - $this->_autoFields = $query->isAutoFieldsEnabled(); $this->__unserialize($results); foreach ($this->_results as $i => $row) { @@ -161,7 +149,7 @@ public function __construct(Query $query, array $results) } /** - * Returns the current record in the result iterator + * Returns the current record in the result iterator. * * Part of Iterator interface. * @@ -173,7 +161,7 @@ public function current(): EntityInterface|array } /** - * Returns the key of the current record in the iterator + * Returns the key of the current record in the iterator. * * Part of Iterator interface. * @@ -185,7 +173,7 @@ public function key(): int } /** - * Advances the iterator pointer to the next record + * Advances the iterator pointer to the next record. * * Part of Iterator interface. * @@ -201,7 +189,6 @@ public function next(): void * * Part of Iterator interface. * - * @throws \Cake\Database\Exception\DatabaseException * @return void */ public function rewind(): void @@ -210,7 +197,7 @@ public function rewind(): void } /** - * Whether there are more results to be fetched from the iterator + * Whether there are more results to be fetched from the iterator. * * Part of Iterator interface. * @@ -230,9 +217,9 @@ public function valid(): bool /** * Get the first record from a result set. * - * @return object|array|null + * @return \Cake\Datasource\EntityInterface|array|null */ - public function first(): object|array|null + public function first(): EntityInterface|array|null { foreach ($this as $result) { return $result; @@ -276,10 +263,9 @@ public function count(): int } /** - * Calculates the list of associations that should get eager loaded - * when fetching each record + * Calculates the list of associations that where eager loaded for this query. * - * @param \Cake\ORM\Query $query The query from where to derive the associations + * @param \Cake\ORM\Query $query The query from where to derive the associations. * @return void */ protected function _calculateAssociationMap(Query $query): void @@ -297,10 +283,10 @@ protected function _calculateAssociationMap(Query $query): void } /** - * Creates a map of row keys out of the query select clause that can be + * Creates a map of row keys out of the query's select clause that can be * used to hydrate nested result sets more quickly. * - * @param \Cake\ORM\Query $query The query from where to derive the column map + * @param \Cake\ORM\Query $query The query from where to derive the column map. * @return void */ protected function _calculateColumnMap(Query $query): void @@ -330,10 +316,12 @@ protected function _calculateColumnMap(Query $query): void } /** - * Correctly nests results keys including those coming from associations + * Correctly nests results keys including those coming from associations. + * + * Hyrate row array into entity if hydration is enabled. * - * @param array $row Array containing columns and values or false if there is no results - * @return \Cake\Datasource\EntityInterface|array Results + * @param array $row Array containing columns and values. + * @return \Cake\Datasource\EntityInterface|array */ protected function _groupResult(array $row): EntityInterface|array { From 28ead2bf191fbf9fdb50aa2ec96ee1661f7fba9b Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 29 Jan 2022 13:05:15 +0530 Subject: [PATCH 1724/2059] Change return type of StatementInterface::fetchAll(). PDOStatment::fetchAll() always returns array in PHP 8.0+. --- Query.php | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Query.php b/Query.php index f291d716..204cfd1f 100644 --- a/Query.php +++ b/Query.php @@ -1131,14 +1131,9 @@ protected function _execute(): ResultSetInterface } $results = $this->execute()->fetchAll(StatementInterface::FETCH_TYPE_ASSOC); - if ($results === false) { - $results = []; - } else { - $results = $this->getEagerLoader()->loadExternal($this, $results); - } - $resultset = new ResultSet($this, $results); + $results = $this->getEagerLoader()->loadExternal($this, $results); - return $resultset; + return new ResultSet($this, $results); } /** From 9450ec1263e3a41087d2e4b2582b4af3327a80c3 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 31 Jan 2022 20:02:29 +0530 Subject: [PATCH 1725/2059] Simplify results decoration. --- Query.php | 30 ++++-------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/Query.php b/Query.php index 204cfd1f..fc204e90 100644 --- a/Query.php +++ b/Query.php @@ -31,7 +31,6 @@ use InvalidArgumentException; use JsonSerializable; use RuntimeException; -use Traversable; /** * Extends the base Query class to provide new methods related to association @@ -46,7 +45,6 @@ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface use QueryTrait { cache as private _cache; all as private _all; - _decorateResults as private _applyDecorators; } /** @@ -1117,17 +1115,15 @@ public function sql(?ValueBinder $binder = null): string } /** - * Executes this query and returns a ResultSet object containing the results. + * Executes this query and returns an iterable containing the results. * - * @return \Cake\Datasource\ResultSetInterface + * @return iterable */ - protected function _execute(): ResultSetInterface + protected function _execute(): iterable { $this->triggerBeforeFind(); if ($this->_results) { - $decorator = $this->_decoratorClass(); - - return new $decorator($this->_results); + return $this->_results; } $results = $this->execute()->fetchAll(StatementInterface::FETCH_TYPE_ASSOC); @@ -1388,22 +1384,4 @@ public function isAutoFieldsEnabled(): ?bool { return $this->_autoFields; } - - /** - * Decorates the results iterator with MapReduce routines and formatters - * - * @param \Traversable $result Original results - * @return \Cake\Datasource\ResultSetInterface - */ - protected function _decorateResults(Traversable $result): ResultSetInterface - { - $result = $this->_applyDecorators($result); - - if (!($result instanceof ResultSet)) { - $class = $this->_decoratorClass(); - $result = new $class($result->buffered()); - } - - return $result; - } } From e9f9d897d92af4c014bf62486436e69208b5d9d3 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 3 Feb 2022 19:34:12 +0530 Subject: [PATCH 1726/2059] Optimize count(), countKeys() in ResultSet. --- ResultSet.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ResultSet.php b/ResultSet.php index e5d05395..c6da8c5b 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -262,6 +262,22 @@ public function count(): int return $this->_count; } + /** + * @inheritDoc + */ + public function countKeys(): int + { + return $this->_count; + } + + /** + * @inheritDoc + */ + public function isEmpty(): bool + { + return !$this->_count; + } + /** * Calculates the list of associations that where eager loaded for this query. * From a0893543d6b26c087f29ae406a589c76f55056eb Mon Sep 17 00:00:00 2001 From: ndm2 Date: Thu, 3 Feb 2022 20:12:31 +0100 Subject: [PATCH 1727/2059] Allow disabling foreign key on association level. This is already possible on the fly using the `foreignKey` option for `contain()`, and while not reflected by the method arguments, was technically already possible before native types were introduced. --- Association/BelongsTo.php | 14 ++++++++++++++ Association/HasOne.php | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 6e5d6256..bd818a6b 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -55,6 +55,20 @@ public function getForeignKey(): array|string|false return $this->_foreignKey; } + /** + * Sets the name of the field representing the foreign key to the target table. + * + * @param array|string|false $key the key or keys to be used to link both tables together, if set to `false` + * no join conditions will be generated automatically. + * @return $this + */ + public function setForeignKey(array|string|false $key) + { + $this->_foreignKey = $key; + + return $this; + } + /** * Handle cascading deletes. * diff --git a/Association/HasOne.php b/Association/HasOne.php index a0347171..a3b8826b 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -53,6 +53,20 @@ public function getForeignKey(): array|string|false return $this->_foreignKey; } + /** + * Sets the name of the field representing the foreign key to the target table. + * + * @param array|string|false $key the key or keys to be used to link both tables together, if set to `false` + * no join conditions will be generated automatically. + * @return $this + */ + public function setForeignKey(array|string|false $key) + { + $this->_foreignKey = $key; + + return $this; + } + /** * Returns default property name based on association name. * From 1d78206bee54a725590e74025058afb3754e805f Mon Sep 17 00:00:00 2001 From: mscherer Date: Sat, 5 Feb 2022 01:04:47 +0100 Subject: [PATCH 1728/2059] Fix PHPStan found issues around mixed and object usage. --- Behavior/Translate/ShadowTableStrategy.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index fc893cbf..3677a9aa 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -567,6 +567,7 @@ public function groupTranslations(CollectionInterface $results): CollectionInter */ protected function bundleTranslatedFields(EntityInterface $entity): void { + /** @var array $translations */ $translations = (array)$entity->get('_translations'); if (empty($translations) && !$entity->isDirty('_translations')) { From f9951bb6e26669f34b9225e33c55b00f070f7292 Mon Sep 17 00:00:00 2001 From: mscherer Date: Sat, 5 Feb 2022 01:13:52 +0100 Subject: [PATCH 1729/2059] Fix PHPStan found issues around mixed and object usage. --- Behavior/Translate/ShadowTableStrategy.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 3677a9aa..04322a6f 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -567,7 +567,7 @@ public function groupTranslations(CollectionInterface $results): CollectionInter */ protected function bundleTranslatedFields(EntityInterface $entity): void { - /** @var array $translations */ + /** @var array $translations */ $translations = (array)$entity->get('_translations'); if (empty($translations) && !$entity->isDirty('_translations')) { From d1244cc9bf086fa44cc0d0546f315d3b800b4e18 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 4 Feb 2022 17:44:16 +0530 Subject: [PATCH 1730/2059] Add ResultSetFactory. The code for keys nesting and entities hydration has been extracted from ResultSet into ResultSetFactory. --- Query.php | 23 +++- ResultSet.php | 259 +------------------------------------------ ResultSetFactory.php | 215 +++++++++++++++++++++++++++++++++++ 3 files changed, 242 insertions(+), 255 deletions(-) create mode 100644 ResultSetFactory.php diff --git a/Query.php b/Query.php index fc204e90..5eddf553 100644 --- a/Query.php +++ b/Query.php @@ -130,6 +130,13 @@ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface */ protected ?int $_resultsCount = null; + /** + * Resultset factory + * + * @var \Cake\ORM\ResultSetFactory + */ + protected ResultSetFactory $resultSetFactory; + /** * Constructor * @@ -1129,7 +1136,21 @@ protected function _execute(): iterable $results = $this->execute()->fetchAll(StatementInterface::FETCH_TYPE_ASSOC); $results = $this->getEagerLoader()->loadExternal($this, $results); - return new ResultSet($this, $results); + return $this->resultSetFactory()->createResultSet($this, $results); + } + + /** + * Get resultset factory. + * + * @return \Cake\ORM\ResultSetFactory + */ + protected function resultSetFactory(): ResultSetFactory + { + if (isset($this->resultSetFactory)) { + return $this->resultSetFactory; + } + + return $this->resultSetFactory = new ResultSetFactory(); } /** diff --git a/ResultSet.php b/ResultSet.php index c6da8c5b..c627d192 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -16,16 +16,13 @@ */ namespace Cake\ORM; -use Cake\Collection\Collection; use Cake\Collection\CollectionTrait; use Cake\Datasource\EntityInterface; use Cake\Datasource\ResultSetInterface; use SplFixedArray; /** - * Represents the results obtained after executing a query for a specific table - * This object is responsible for correctly nesting result keys reported from - * the query and hydrating entities. + * Represents the results obtained after executing a query for a specific table. */ class ResultSet implements ResultSetInterface { @@ -46,48 +43,11 @@ class ResultSet implements ResultSetInterface protected EntityInterface|array $_current = []; /** - * Default table instance - * - * @var \Cake\ORM\Table - */ - protected Table $_defaultTable; - - /** - * The default table alias - * - * @var string - */ - protected string $_defaultAlias; - - /** - * List of associations that should be placed under the `_matchingData` - * result key. - * - * @var array - */ - protected array $_matchingMap = []; - - /** - * List of associations that should be eager loaded. - * - * @var array - */ - protected array $_containMap = []; - - /** - * Map of fields that are fetched with their type and the table they belong to. - * - * @var array - */ - protected array $_map = []; - - /** - * List of matching associations and the column keys to expect - * from each of them. + * Holds the count of records in this result set * - * @var array + * @var int */ - protected array $_matchingMapColumns = []; + protected int $_count = 0; /** * Results that have been fetched or hydrated into the results. @@ -96,56 +56,14 @@ class ResultSet implements ResultSetInterface */ protected SplFixedArray $_results; - /** - * Whether to hydrate results into entities. - * - * @var bool - */ - protected bool $_hydrate = true; - - /** - * Tracks value of $_autoFields property of $query passed to constructor. - * - * @var bool|null - */ - protected ?bool $_autoFields = null; - - /** - * The fully namespaced name of the class to use for hydrating results. - * - * @var string - */ - protected string $_entityClass; - - /** - * Holds the count of records in this result set - * - * @var int - */ - protected int $_count = 0; - /** * Constructor * - * @param \Cake\ORM\Query $query Query from where results came. * @param array $results Results array. */ - public function __construct(Query $query, array $results) + public function __construct(array $results) { - $this->_defaultTable = $query->getRepository(); - $this->_hydrate = $query->isHydrationEnabled(); - $this->_autoFields = $query->isAutoFieldsEnabled(); - - $this->_entityClass = $this->_defaultTable->getEntityClass(); - $this->_defaultAlias = $this->_defaultTable->getAlias(); - - $this->_calculateAssociationMap($query); - $this->_calculateColumnMap($query); - $this->__unserialize($results); - foreach ($this->_results as $i => $row) { - $this->_results[$i] = $this->_groupResult($row); - } } /** @@ -278,173 +196,6 @@ public function isEmpty(): bool return !$this->_count; } - /** - * Calculates the list of associations that where eager loaded for this query. - * - * @param \Cake\ORM\Query $query The query from where to derive the associations. - * @return void - */ - protected function _calculateAssociationMap(Query $query): void - { - $map = $query->getEagerLoader()->associationsMap($this->_defaultTable); - $this->_matchingMap = (new Collection($map)) - ->match(['matching' => true]) - ->indexBy('alias') - ->toArray(); - - $this->_containMap = (new Collection(array_reverse($map))) - ->match(['matching' => false]) - ->indexBy('nestKey') - ->toArray(); - } - - /** - * Creates a map of row keys out of the query's select clause that can be - * used to hydrate nested result sets more quickly. - * - * @param \Cake\ORM\Query $query The query from where to derive the column map. - * @return void - */ - protected function _calculateColumnMap(Query $query): void - { - $map = []; - foreach ($query->clause('select') as $key => $field) { - $key = trim($key, '"`[]'); - - if (strpos($key, '__') <= 0) { - $map[$this->_defaultAlias][$key] = $key; - continue; - } - - $parts = explode('__', $key, 2); - $map[$parts[0]][$key] = $parts[1]; - } - - foreach ($this->_matchingMap as $alias => $assoc) { - if (!isset($map[$alias])) { - continue; - } - $this->_matchingMapColumns[$alias] = $map[$alias]; - unset($map[$alias]); - } - - $this->_map = $map; - } - - /** - * Correctly nests results keys including those coming from associations. - * - * Hyrate row array into entity if hydration is enabled. - * - * @param array $row Array containing columns and values. - * @return \Cake\Datasource\EntityInterface|array - */ - protected function _groupResult(array $row): EntityInterface|array - { - $defaultAlias = $this->_defaultAlias; - $results = $presentAliases = []; - $options = [ - 'useSetters' => false, - 'markClean' => true, - 'markNew' => false, - 'guard' => false, - ]; - - foreach ($this->_matchingMapColumns as $alias => $keys) { - $matching = $this->_matchingMap[$alias]; - $results['_matchingData'][$alias] = array_combine( - $keys, - array_intersect_key($row, $keys) - ); - if ($this->_hydrate) { - /** @var \Cake\ORM\Table $table */ - $table = $matching['instance']; - $options['source'] = $table->getRegistryAlias(); - /** @var \Cake\Datasource\EntityInterface $entity */ - $entity = new $matching['entityClass']($results['_matchingData'][$alias], $options); - $results['_matchingData'][$alias] = $entity; - } - } - - foreach ($this->_map as $table => $keys) { - $results[$table] = array_combine($keys, array_intersect_key($row, $keys)); - $presentAliases[$table] = true; - } - - // If the default table is not in the results, set - // it to an empty array so that any contained - // associations hydrate correctly. - $results[$defaultAlias] = $results[$defaultAlias] ?? []; - - unset($presentAliases[$defaultAlias]); - - foreach ($this->_containMap as $assoc) { - $alias = $assoc['nestKey']; - - if ($assoc['canBeJoined'] && empty($this->_map[$alias])) { - continue; - } - - /** @var \Cake\ORM\Association $instance */ - $instance = $assoc['instance']; - - if (!$assoc['canBeJoined'] && !isset($row[$alias])) { - $results = $instance->defaultRowValue($results, $assoc['canBeJoined']); - continue; - } - - if (!$assoc['canBeJoined']) { - $results[$alias] = $row[$alias]; - } - - $target = $instance->getTarget(); - $options['source'] = $target->getRegistryAlias(); - unset($presentAliases[$alias]); - - if ($assoc['canBeJoined'] && $this->_autoFields !== false) { - $hasData = false; - foreach ($results[$alias] as $v) { - if ($v !== null && $v !== []) { - $hasData = true; - break; - } - } - - if (!$hasData) { - $results[$alias] = null; - } - } - - if ($this->_hydrate && $results[$alias] !== null && $assoc['canBeJoined']) { - $entity = new $assoc['entityClass']($results[$alias], $options); - $results[$alias] = $entity; - } - - $results = $instance->transformRow($results, $alias, $assoc['canBeJoined'], $assoc['targetProperty']); - } - - foreach ($presentAliases as $alias => $present) { - if (!isset($results[$alias])) { - continue; - } - $results[$defaultAlias][$alias] = $results[$alias]; - } - - if (isset($results['_matchingData'])) { - $results[$defaultAlias]['_matchingData'] = $results['_matchingData']; - } - - $options['source'] = $this->_defaultTable->getRegistryAlias(); - if (isset($results[$defaultAlias])) { - $results = $results[$defaultAlias]; - } - if ($this->_hydrate && !($results instanceof EntityInterface)) { - $results = new $this->_entityClass($results, $options); - } - - return $results; - } - /** * Returns an array that can be used to describe the internal state of this * object. diff --git a/ResultSetFactory.php b/ResultSetFactory.php new file mode 100644 index 00000000..4697e923 --- /dev/null +++ b/ResultSetFactory.php @@ -0,0 +1,215 @@ +collectData($query); + + foreach ($results as $i => $row) { + $results[$i] = $this->groupResult($row, $data); + } + + return new ResultSet($results); + } + + /** + * Get repository and it's associations data for nesting results key and + * entity hydration. + * + * @param \Cake\ORM\Query $query The query from where to derive the data. + * @return array + */ + protected function collectData(Query $query): array + { + $primaryTable = $query->getRepository(); + $data = [ + 'primaryAlias' => $primaryTable->getAlias(), + 'registryAlias' => $primaryTable->getRegistryAlias(), + 'entityClass' => $primaryTable->getEntityClass(), + 'hydrate' => $query->isHydrationEnabled(), + 'autoFields' => $query->isAutoFieldsEnabled(), + 'matchingColumns' => [], + ]; + + $assocMap = $query->getEagerLoader()->associationsMap($primaryTable); + $data['matchingAssoc'] = (new Collection($assocMap)) + ->match(['matching' => true]) + ->indexBy('alias') + ->toArray(); + + $data['containAssoc'] = (new Collection(array_reverse($assocMap))) + ->match(['matching' => false]) + ->indexBy('nestKey') + ->toArray(); + + $fields = []; + foreach ($query->clause('select') as $key => $field) { + $key = trim($key, '"`[]'); + + if (strpos($key, '__') <= 0) { + $fields[$data['primaryAlias']][$key] = $key; + continue; + } + + $parts = explode('__', $key, 2); + $fields[$parts[0]][$key] = $parts[1]; + } + + foreach ($data['matchingAssoc'] as $alias => $assoc) { + if (!isset($fields[$alias])) { + continue; + } + $data['matchingColumns'][$alias] = $fields[$alias]; + unset($fields[$alias]); + } + + $data['fields'] = $fields; + + return $data; + } + + /** + * Correctly nests results keys including those coming from associations. + * + * Hyrate row array into entity if hydration is enabled. + * + * @param array $row Array containing columns and values. + * @return \Cake\Datasource\EntityInterface|array + */ + protected function groupResult(array $row, array $data): EntityInterface|array + { + $results = $presentAliases = []; + $options = [ + 'useSetters' => false, + 'markClean' => true, + 'markNew' => false, + 'guard' => false, + ]; + + foreach ($data['matchingColumns'] as $alias => $keys) { + $matching = $data['matchingAssoc'][$alias]; + $results['_matchingData'][$alias] = array_combine( + $keys, + array_intersect_key($row, $keys) + ); + if ($data['hydrate']) { + /** @var \Cake\ORM\Table $table */ + $table = $matching['instance']; + $options['source'] = $table->getRegistryAlias(); + /** @var \Cake\Datasource\EntityInterface $entity */ + $entity = new $matching['entityClass']($results['_matchingData'][$alias], $options); + $results['_matchingData'][$alias] = $entity; + } + } + + foreach ($data['fields'] as $table => $keys) { + $results[$table] = array_combine($keys, array_intersect_key($row, $keys)); + $presentAliases[$table] = true; + } + + // If the default table is not in the results, set + // it to an empty array so that any contained + // associations hydrate correctly. + $results[$data['primaryAlias']] = $results[$data['primaryAlias']] ?? []; + + unset($presentAliases[$data['primaryAlias']]); + + foreach ($data['containAssoc'] as $assoc) { + $alias = $assoc['nestKey']; + + if ($assoc['canBeJoined'] && empty($data['fields'][$alias])) { + continue; + } + + /** @var \Cake\ORM\Association $instance */ + $instance = $assoc['instance']; + + if (!$assoc['canBeJoined'] && !isset($row[$alias])) { + $results = $instance->defaultRowValue($results, $assoc['canBeJoined']); + continue; + } + + if (!$assoc['canBeJoined']) { + $results[$alias] = $row[$alias]; + } + + $target = $instance->getTarget(); + $options['source'] = $target->getRegistryAlias(); + unset($presentAliases[$alias]); + + if ($assoc['canBeJoined'] && $data['autoFields'] !== false) { + $hasData = false; + foreach ($results[$alias] as $v) { + if ($v !== null && $v !== []) { + $hasData = true; + break; + } + } + + if (!$hasData) { + $results[$alias] = null; + } + } + + if ($data['hydrate'] && $results[$alias] !== null && $assoc['canBeJoined']) { + $entity = new $assoc['entityClass']($results[$alias], $options); + $results[$alias] = $entity; + } + + $results = $instance->transformRow($results, $alias, $assoc['canBeJoined'], $assoc['targetProperty']); + } + + foreach ($presentAliases as $alias => $present) { + if (!isset($results[$alias])) { + continue; + } + $results[$data['primaryAlias']][$alias] = $results[$alias]; + } + + if (isset($results['_matchingData'])) { + $results[$data['primaryAlias']]['_matchingData'] = $results['_matchingData']; + } + + $options['source'] = $data['registryAlias']; + if (isset($results[$data['primaryAlias']])) { + $results = $results[$data['primaryAlias']]; + } + if ($data['hydrate'] && !($results instanceof EntityInterface)) { + $results = new $data['entityClass']($results, $options); + } + + return $results; + } +} From 2f06d33bbb2af3b567a63979b6445795038117cb Mon Sep 17 00:00:00 2001 From: mscherer Date: Tue, 1 Feb 2022 01:37:48 +0100 Subject: [PATCH 1731/2059] Improve docblocks and PHPStan found issues. --- Locator/TableLocator.php | 6 ++++-- Table.php | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 9a78a59f..75203eeb 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -306,8 +306,10 @@ protected function _getClassName(string $alias, array $options = []): ?string */ protected function _create(array $options): Table { - /** @var \Cake\ORM\Table */ - return new $options['className']($options); + /** @var class-string<\Cake\ORM\Table> $class */ + $class = $options['className']; + + return new $class($options); } /** diff --git a/Table.php b/Table.php index 5fa28a2e..06844eff 100644 --- a/Table.php +++ b/Table.php @@ -863,6 +863,7 @@ public function getBehavior(string $name): Behavior )); } + /** @var \Cake\ORM\Behavior $behavior */ $behavior = $this->_behaviors->get($name); return $behavior; @@ -1535,7 +1536,7 @@ public function get($primaryKey, array $options = []): EntityInterface 'get-%s-%s-%s', $this->getConnection()->configName(), $this->getTable(), - json_encode($primaryKey) + json_encode($primaryKey, JSON_THROW_ON_ERROR) ); } $query->cache($cacheKey, $cacheConfig); From 6cda02f821a3d60aa0cb4fec64cbb05facb912cd Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Wed, 9 Feb 2022 09:17:30 -0600 Subject: [PATCH 1732/2059] Fix return annotation for Association::setConditions() --- Association.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association.php b/Association.php index baf40052..86ef6984 100644 --- a/Association.php +++ b/Association.php @@ -431,7 +431,7 @@ public function getTarget(): Table * * @param \Closure|array $conditions list of conditions to be used * @see \Cake\Database\Query::where() for examples on the format of the array - * @return \Cake\ORM\Association + * @return $this */ public function setConditions($conditions) { From 18947aad2eee3d0005102963f245368ca64e3f60 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Wed, 2 Feb 2022 22:43:19 -0600 Subject: [PATCH 1733/2059] Add Database\Statement and Database\Query::all(). Statements no longer use a nested decorator pattern. Callbacks were removed. --- Query.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Query.php b/Query.php index 5eddf553..63ee788b 100644 --- a/Query.php +++ b/Query.php @@ -20,7 +20,6 @@ use Cake\Database\Connection; use Cake\Database\ExpressionInterface; use Cake\Database\Query as DatabaseQuery; -use Cake\Database\StatementInterface; use Cake\Database\TypedResultInterface; use Cake\Database\TypeMap; use Cake\Database\ValueBinder; @@ -1064,7 +1063,7 @@ public function isHydrationEnabled(): bool */ public function cache($key, $config = 'default') { - if ($this->_type !== 'select') { + if ($this->_type !== self::TYPE_SELECT) { throw new RuntimeException('You cannot cache the results of non-select queries.'); } @@ -1079,7 +1078,7 @@ public function cache($key, $config = 'default') */ public function all(): ResultSetInterface { - if ($this->_type !== 'select') { + if ($this->_type !== self::TYPE_SELECT) { throw new RuntimeException( 'You cannot call all() on a non-select query. Use execute() instead.' ); @@ -1097,7 +1096,7 @@ public function all(): ResultSetInterface */ public function triggerBeforeFind(): void { - if (!$this->_beforeFindFired && $this->_type === 'select') { + if (!$this->_beforeFindFired && $this->_type === self::TYPE_SELECT) { $this->_beforeFindFired = true; $repository = $this->getRepository(); @@ -1133,7 +1132,10 @@ protected function _execute(): iterable return $this->_results; } - $results = $this->execute()->fetchAll(StatementInterface::FETCH_TYPE_ASSOC); + $results = parent::all(); + if (!is_array($results)) { + $results = iterator_to_array($results); + } $results = $this->getEagerLoader()->loadExternal($this, $results); return $this->resultSetFactory()->createResultSet($this, $results); @@ -1167,7 +1169,7 @@ protected function resultSetFactory(): ResultSetFactory */ protected function _transformQuery(): void { - if (!$this->_dirty || $this->_type !== 'select') { + if (!$this->_dirty || $this->_type !== self::TYPE_SELECT) { return; } From 202e92df8bdf1c4c13991b0473ecbc8974bea127 Mon Sep 17 00:00:00 2001 From: mscherer Date: Sat, 19 Feb 2022 22:39:30 +0100 Subject: [PATCH 1734/2059] Fix PHPStan found issues around mixed and object usage. --- Association.php | 13 ++++++++++--- Association/BelongsToMany.php | 2 ++ Behavior/Translate/EavStrategy.php | 9 ++++++--- Behavior/Translate/ShadowTableStrategy.php | 4 +++- Behavior/Translate/TranslateStrategyTrait.php | 2 +- Marshaller.php | 2 ++ Query.php | 1 + Table.php | 4 ++++ 8 files changed, 29 insertions(+), 8 deletions(-) diff --git a/Association.php b/Association.php index da36eb0a..f43ffd67 100644 --- a/Association.php +++ b/Association.php @@ -17,6 +17,7 @@ namespace Cake\ORM; use Cake\Collection\Collection; +use Cake\Collection\CollectionInterface; use Cake\Core\App; use Cake\Core\ConventionsTrait; use Cake\Database\Expression\IdentifierExpression; @@ -974,7 +975,14 @@ protected function _formatAssociationResults(Query $query, Query $surrogate, arr $property = $options['propertyPath']; $propertyPath = explode('.', $property); - $query->formatResults(function ($results, $query) use ($formatters, $property, $propertyPath) { + $query->formatResults(function ( + CollectionInterface $results, + Query $query + ) use ( + $formatters, + $property, + $propertyPath + ) { $extracted = []; foreach ($results as $result) { foreach ($propertyPath as $propertyPathItem) { @@ -991,10 +999,9 @@ protected function _formatAssociationResults(Query $query, Query $surrogate, arr $extracted = new ResultSetDecorator($callable($extracted, $query)); } - /** @var \Cake\Collection\CollectionInterface $results */ $results = $results->insert($property, $extracted); if ($query->isHydrationEnabled()) { - $results = $results->map(function ($result) { + $results = $results->map(function (EntityInterface $result) { $result->clean(); return $result; diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index afb6114c..4b50f59a 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1265,6 +1265,7 @@ protected function _diffLinks( $foreignKey = (array)$this->getForeignKey(); $assocForeignKey = (array)$belongsTo->getForeignKey(); + /** @var array $keys */ $keys = array_merge($foreignKey, $assocForeignKey); $deletes = $indexed = $present = []; @@ -1275,6 +1276,7 @@ protected function _diffLinks( $present[$i] = array_values($entity->extract($assocForeignKey)); } + /** @var \Cake\Datasource\EntityInterface $result */ foreach ($existing as $result) { $fields = $result->extract($keys); $found = false; diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index e94190e4..5a5356dc 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -173,9 +173,10 @@ public function beforeFind(EventInterface $event, Query $query, ArrayObject $opt return; } - $conditions = function ($field, $locale, $query, $select) { - return function ($q) use ($field, $locale, $query, $select) { - $q->where([$q->getRepository()->aliasField('locale') => $locale]); + $conditions = function (string $field, string $locale, Query $query, array $select) { + return function (Query $q) use ($field, $locale, $query, $select) { + $table = $q->getRepository(); + $q->where([$table->aliasField('locale') => $locale]); if ( $query->isAutoFieldsEnabled() || @@ -299,6 +300,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array } $modified = []; + /** @var \Cake\Datasource\EntityInterface $translation */ foreach ($preexistent as $field => $translation) { $translation->set('content', $values[$field]); $modified[$field] = $translation; @@ -439,6 +441,7 @@ public function groupTranslations(CollectionInterface $results): CollectionInter */ protected function bundleTranslatedFields(EntityInterface $entity): void { + /** @var array $translations */ $translations = (array)$entity->get('_translations'); if (empty($translations) && !$entity->isDirty('_translations')) { diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 04322a6f..f50bab80 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -249,6 +249,7 @@ protected function addFieldsToQuery(Query $query, array $config): bool */ protected function iterateClause(Query $query, string $name = '', array $config = []): bool { + /** @var \Cake\Database\Expression\QueryExpression|null $clause */ $clause = $query->clause($name); if (!$clause || !$clause->count()) { return false; @@ -295,6 +296,7 @@ function ($c, &$field) use ($fields, $alias, $mainTableAlias, $mainTableFields, */ protected function traverseClause(Query $query, string $name = '', array $config = []): bool { + /** @var \Cake\Database\Expression\QueryExpression|null $clause */ $clause = $query->clause($name); if (!$clause || !$clause->count()) { return false; @@ -537,7 +539,7 @@ public function groupTranslations(CollectionInterface $results): CollectionInter { return $results->map(function ($row) { $translations = (array)$row['_i18n']; - if (empty($translations) && $row->get('_translations')) { + if (empty($translations) && $row instanceof EntityInterface && $row->get('_translations')) { return $row; } diff --git a/Behavior/Translate/TranslateStrategyTrait.php b/Behavior/Translate/TranslateStrategyTrait.php index 0b02c127..8edbe4ff 100644 --- a/Behavior/Translate/TranslateStrategyTrait.php +++ b/Behavior/Translate/TranslateStrategyTrait.php @@ -151,7 +151,7 @@ public function buildMarshalMap(Marshaller $marshaller, array $map, array $optio } return [ - '_translations' => function ($value, $entity) use ($marshaller, $options) { + '_translations' => function ($value, EntityInterface $entity) use ($marshaller, $options) { if (!is_array($value)) { return null; } diff --git a/Marshaller.php b/Marshaller.php index ae48c989..f4c5c73e 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -413,6 +413,7 @@ protected function _belongsToMany(BelongsToMany $assoc, array $data, array $opti $keyFields = array_keys($primaryKey); $existing = []; + /** @var \Cake\Datasource\EntityInterface $row */ foreach ($query as $row) { $k = implode(';', $row->extract($keyFields)); $existing[$k] = $row; @@ -696,6 +697,7 @@ public function mergeMany(iterable $entities, array $data, array $options = []): $maybeExistentQuery = $this->_table->find()->where($conditions); if (!empty($indexed) && count($maybeExistentQuery->clause('where'))) { + /** @var \Cake\Datasource\EntityInterface $entity */ foreach ($maybeExistentQuery as $entity) { $key = implode(';', $entity->extract($primary)); if (isset($indexed[$key])) { diff --git a/Query.php b/Query.php index 63ee788b..de091db0 100644 --- a/Query.php +++ b/Query.php @@ -955,6 +955,7 @@ protected function _performCount(): int } if (!$complex && $this->_valueBinder !== null) { + /** @var \Cake\Database\Expression\QueryExpression|null $order */ $order = $this->clause('order'); $complex = $order === null ? false : $order->hasNestedExpression(); } diff --git a/Table.php b/Table.php index 06844eff..1dbaf945 100644 --- a/Table.php +++ b/Table.php @@ -2266,6 +2266,10 @@ protected function _saveMany( try { $this->getConnection() ->transactional(function () use ($entities, $options, &$isNew, &$failed) { + /** + * @var string $key + * @var \Cake\Datasource\EntityInterface $entity + */ foreach ($entities as $key => $entity) { $isNew[$key] = $entity->isNew(); if ($this->save($entity, $options) === false) { From 44a362b0d3094cdd42ea79abfaf20f5617cb0dc3 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Mon, 21 Feb 2022 04:26:54 -0600 Subject: [PATCH 1735/2059] Remove ArrayAccess from Table options parameters --- Table.php | 47 +++++++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/Table.php b/Table.php index 1dbaf945..00403761 100644 --- a/Table.php +++ b/Table.php @@ -16,7 +16,6 @@ */ namespace Cake\ORM; -use ArrayAccess; use ArrayObject; use BadMethodCallException; use Cake\Core\App; @@ -1863,19 +1862,19 @@ public function exists(QueryExpression|Closure|array|string|null $conditions): b * ``` * * @param \Cake\Datasource\EntityInterface $entity the entity to be saved - * @param \Cake\ORM\SaveOptionsBuilder|\ArrayAccess|array $options The options to use when saving. + * @param \Cake\ORM\SaveOptionsBuilder|array $options The options to use when saving. * @return \Cake\Datasource\EntityInterface|false * @throws \Cake\ORM\Exception\RolledbackTransactionException If the transaction is aborted in the afterSave event. */ public function save( EntityInterface $entity, - SaveOptionsBuilder|ArrayAccess|array $options = [] + SaveOptionsBuilder|array $options = [] ): EntityInterface|false { if ($options instanceof SaveOptionsBuilder) { $options = $options->toArray(); } - $options = new ArrayObject((array)$options + [ + $options = new ArrayObject($options + [ 'atomic' => true, 'associated' => true, 'checkRules' => true, @@ -1914,12 +1913,12 @@ public function save( * the entity contains errors or the save was aborted by a callback. * * @param \Cake\Datasource\EntityInterface $entity the entity to be saved - * @param \ArrayAccess|array $options The options to use when saving. + * @param array $options The options to use when saving. * @return \Cake\Datasource\EntityInterface * @throws \Cake\ORM\Exception\PersistenceFailedException When the entity couldn't be saved * @see \Cake\ORM\Table::save() */ - public function saveOrFail(EntityInterface $entity, ArrayAccess|array $options = []): EntityInterface + public function saveOrFail(EntityInterface $entity, array $options = []): EntityInterface { $saved = $this->save($entity, $options); if ($saved === false) { @@ -2197,13 +2196,13 @@ protected function _update(EntityInterface $entity, array $data): EntityInterfac * error. * * @param \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface> $entities Entities to save. - * @param \Cake\ORM\SaveOptionsBuilder|\ArrayAccess|array $options Options used when calling Table::save() for each entity. + * @param \Cake\ORM\SaveOptionsBuilder|array $options Options used when calling Table::save() for each entity. * @return \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface>|false False on failure, entities list on success. * @throws \Exception */ public function saveMany( ResultSetInterface|array $entities, - SaveOptionsBuilder|ArrayAccess|array $options = [] + SaveOptionsBuilder|array $options = [] ): ResultSetInterface|array|false { try { return $this->_saveMany($entities, $options); @@ -2220,26 +2219,26 @@ public function saveMany( * error. * * @param \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface> $entities Entities to save. - * @param \ArrayAccess|array $options Options used when calling Table::save() for each entity. + * @param array $options Options used when calling Table::save() for each entity. * @return \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface> Entities list. * @throws \Exception * @throws \Cake\ORM\Exception\PersistenceFailedException If an entity couldn't be saved. */ - public function saveManyOrFail(iterable $entities, ArrayAccess|array $options = []): ResultSetInterface|array + public function saveManyOrFail(iterable $entities, array $options = []): ResultSetInterface|array { return $this->_saveMany($entities, $options); } /** * @param \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface> $entities Entities to save. - * @param \Cake\ORM\SaveOptionsBuilder|\ArrayAccess|array $options Options used when calling Table::save() for each entity. + * @param \Cake\ORM\SaveOptionsBuilder|array $options Options used when calling Table::save() for each entity. * @throws \Cake\ORM\Exception\PersistenceFailedException If an entity couldn't be saved. * @throws \Exception If an entity couldn't be saved. * @return \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface> Entities list. */ protected function _saveMany( ResultSetInterface|array $entities, - SaveOptionsBuilder|ArrayAccess|array $options = [] + SaveOptionsBuilder|array $options = [] ): ResultSetInterface|array { $options = new ArrayObject( (array)$options + [ @@ -2272,7 +2271,7 @@ protected function _saveMany( */ foreach ($entities as $key => $entity) { $isNew[$key] = $entity->isNew(); - if ($this->save($entity, $options) === false) { + if ($this->save($entity, (array)$options) === false) { $failed = $entity; return false; @@ -2327,12 +2326,12 @@ protected function _saveMany( * the options used in the delete operation. * * @param \Cake\Datasource\EntityInterface $entity The entity to remove. - * @param \ArrayAccess|array $options The options for the delete. + * @param array $options The options for the delete. * @return bool success */ public function delete(EntityInterface $entity, $options = []): bool { - $options = new ArrayObject((array)$options + [ + $options = new ArrayObject($options + [ 'atomic' => true, 'checkRules' => true, '_primary' => true, @@ -2360,12 +2359,12 @@ public function delete(EntityInterface $entity, $options = []): bool * error. * * @param \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface> $entities Entities to delete. - * @param \ArrayAccess|array $options Options used when calling Table::save() for each entity. + * @param array $options Options used when calling Table::save() for each entity. * @return \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface>|false Entities list * on success, false on failure. * @see \Cake\ORM\Table::delete() for options and events related to this method. */ - public function deleteMany(iterable $entities, ArrayAccess|array $options = []): ResultSetInterface|array|false + public function deleteMany(iterable $entities, array $options = []): ResultSetInterface|array|false { $failed = $this->_deleteMany($entities, $options); @@ -2384,12 +2383,12 @@ public function deleteMany(iterable $entities, ArrayAccess|array $options = []): * error. * * @param \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface> $entities Entities to delete. - * @param \ArrayAccess|array $options Options used when calling Table::save() for each entity. + * @param array $options Options used when calling Table::save() for each entity. * @return \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface> Entities list. * @throws \Cake\ORM\Exception\PersistenceFailedException * @see \Cake\ORM\Table::delete() for options and events related to this method. */ - public function deleteManyOrFail(iterable $entities, ArrayAccess|array $options = []): iterable + public function deleteManyOrFail(iterable $entities, array $options = []): iterable { $failed = $this->_deleteMany($entities, $options); @@ -2402,12 +2401,12 @@ public function deleteManyOrFail(iterable $entities, ArrayAccess|array $options /** * @param \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface> $entities Entities to delete. - * @param \ArrayAccess|array $options Options used. + * @param array $options Options used. * @return \Cake\Datasource\EntityInterface|null */ - protected function _deleteMany(iterable $entities, ArrayAccess|array $options = []): ?EntityInterface + protected function _deleteMany(iterable $entities, array $options = []): ?EntityInterface { - $options = new ArrayObject((array)$options + [ + $options = new ArrayObject($options + [ 'atomic' => true, 'checkRules' => true, '_primary' => true, @@ -2440,12 +2439,12 @@ protected function _deleteMany(iterable $entities, ArrayAccess|array $options = * has no primary key value, application rules checks failed or the delete was aborted by a callback. * * @param \Cake\Datasource\EntityInterface $entity The entity to remove. - * @param \ArrayAccess|array $options The options for the delete. + * @param array $options The options for the delete. * @return true * @throws \Cake\ORM\Exception\PersistenceFailedException * @see \Cake\ORM\Table::delete() */ - public function deleteOrFail(EntityInterface $entity, ArrayAccess|array $options = []): bool + public function deleteOrFail(EntityInterface $entity, array $options = []): bool { $deleted = $this->delete($entity, $options); if ($deleted === false) { From 3dde4873d8e8b16e0c81848eee79b8e56169e28e Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Mon, 21 Feb 2022 08:00:36 -0600 Subject: [PATCH 1736/2059] Cache options array cast for each entity --- Table.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 00403761..bbab7223 100644 --- a/Table.php +++ b/Table.php @@ -2265,13 +2265,15 @@ protected function _saveMany( try { $this->getConnection() ->transactional(function () use ($entities, $options, &$isNew, &$failed) { + // Cache array cast since options are the same for each entity + $options = (array)$options; /** * @var string $key * @var \Cake\Datasource\EntityInterface $entity */ foreach ($entities as $key => $entity) { $isNew[$key] = $entity->isNew(); - if ($this->save($entity, (array)$options) === false) { + if ($this->save($entity, $options) === false) { $failed = $entity; return false; From 668aa0a18957503dade9fed3eb2781b176b36d3b Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Mon, 21 Feb 2022 08:03:52 -0600 Subject: [PATCH 1737/2059] Convert SaveOptionsBuilder to array properly --- Table.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Table.php b/Table.php index bbab7223..2d38fe13 100644 --- a/Table.php +++ b/Table.php @@ -2240,8 +2240,12 @@ protected function _saveMany( ResultSetInterface|array $entities, SaveOptionsBuilder|array $options = [] ): ResultSetInterface|array { + if ($options instanceof SaveOptionsBuilder) { + $options = $options->toArray(); + } + $options = new ArrayObject( - (array)$options + [ + $options + [ 'atomic' => true, 'checkRules' => true, '_primary' => true, From 9bfa9cc04844c93cf5ecdaac928b6c84c137f374 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Mon, 21 Feb 2022 21:23:47 -0600 Subject: [PATCH 1738/2059] Fix and deprecated SaveOptionsBuilder --- SaveOptionsBuilder.php | 1 + Table.php | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/SaveOptionsBuilder.php b/SaveOptionsBuilder.php index 96023d0f..d53b3019 100644 --- a/SaveOptionsBuilder.php +++ b/SaveOptionsBuilder.php @@ -26,6 +26,7 @@ * you to avoid mistakes by validating the options as you build them. * * @see \Cake\Datasource\RulesChecker + * @deprecated 4.4.4 Use an array of options instead. */ class SaveOptionsBuilder extends ArrayObject { diff --git a/Table.php b/Table.php index 8b0cecbe..788b4db1 100644 --- a/Table.php +++ b/Table.php @@ -1843,6 +1843,7 @@ public function exists($conditions): bool public function save(EntityInterface $entity, $options = []) { if ($options instanceof SaveOptionsBuilder) { + deprecationWarning('SaveOptionsBuilder is deprecated. Use a normal array for options instead.'); $options = $options->toArray(); } @@ -2208,6 +2209,11 @@ public function saveManyOrFail(iterable $entities, $options = []): iterable */ protected function _saveMany(iterable $entities, $options = []): iterable { + if ($options instanceof SaveOptionsBuilder) { + deprecationWarning('SaveOptionsBuilder is deprecated. Use a normal array for options instead.'); + $options = $options->toArray(); + } + $options = new ArrayObject( (array)$options + [ 'atomic' => true, From eafc4e8a3484f2ac26475c121ddf75fe97bd6306 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Tue, 22 Feb 2022 09:38:31 -0600 Subject: [PATCH 1739/2059] Remove deprecated SaveOptionsBuilder --- SaveOptionsBuilder.php | 227 ----------------------------------------- Table.php | 33 ++---- 2 files changed, 6 insertions(+), 254 deletions(-) delete mode 100644 SaveOptionsBuilder.php diff --git a/SaveOptionsBuilder.php b/SaveOptionsBuilder.php deleted file mode 100644 index 3ac5ec53..00000000 --- a/SaveOptionsBuilder.php +++ /dev/null @@ -1,227 +0,0 @@ - $options Options to parse when instantiating. - */ - public function __construct(Table $table, array $options = []) - { - $this->_table = $table; - $this->parseArrayOptions($options); - - parent::__construct(); - } - - /** - * Takes an options array and populates the option object with the data. - * - * This can be used to turn an options array into the object. - * - * @throws \InvalidArgumentException If a given option key does not exist. - * @param array $array Options array. - * @return $this - */ - public function parseArrayOptions(array $array) - { - foreach ($array as $key => $value) { - $this->{$key}($value); - } - - return $this; - } - - /** - * Set associated options. - * - * @param array|string $associated String or array of associations. - * @return $this - */ - public function associated(array|string $associated) - { - $associated = $this->_normalizeAssociations($associated); - $this->_associated($this->_table, $associated); - $this->_options['associated'] = $associated; - - return $this; - } - - /** - * Checks that the associations exists recursively. - * - * @param \Cake\ORM\Table $table Table object. - * @param array $associations An associations array. - * @return void - */ - protected function _associated(Table $table, array $associations): void - { - foreach ($associations as $key => $associated) { - if (is_int($key)) { - $this->_checkAssociation($table, $associated); - continue; - } - $this->_checkAssociation($table, $key); - if (isset($associated['associated'])) { - $this->_associated($table->getAssociation($key)->getTarget(), $associated['associated']); - continue; - } - } - } - - /** - * Checks if an association exists. - * - * @throws \RuntimeException If no such association exists for the given table. - * @param \Cake\ORM\Table $table Table object. - * @param string $association Association name. - * @return void - */ - protected function _checkAssociation(Table $table, string $association): void - { - if (!$table->associations()->has($association)) { - throw new RuntimeException(sprintf( - 'Table `%s` is not associated with `%s`', - get_class($table), - $association - )); - } - } - - /** - * Set the guard option. - * - * @param bool $guard Guard the properties or not. - * @return $this - */ - public function guard(bool $guard) - { - $this->_options['guard'] = $guard; - - return $this; - } - - /** - * Set the validation rule set to use. - * - * @param string $validate Name of the validation rule set to use. - * @return $this - */ - public function validate(string $validate) - { - $this->_table->getValidator($validate); - $this->_options['validate'] = $validate; - - return $this; - } - - /** - * Set check existing option. - * - * @param bool $checkExisting Guard the properties or not. - * @return $this - */ - public function checkExisting(bool $checkExisting) - { - $this->_options['checkExisting'] = $checkExisting; - - return $this; - } - - /** - * Option to check the rules. - * - * @param bool $checkRules Check the rules or not. - * @return $this - */ - public function checkRules(bool $checkRules) - { - $this->_options['checkRules'] = $checkRules; - - return $this; - } - - /** - * Sets the atomic option. - * - * @param bool $atomic Atomic or not. - * @return $this - */ - public function atomic(bool $atomic) - { - $this->_options['atomic'] = $atomic; - - return $this; - } - - /** - * @return array - */ - public function toArray(): array - { - return $this->_options; - } - - /** - * Setting custom options. - * - * @param string $option Option key. - * @param mixed $value Option value. - * @return $this - */ - public function set(string $option, mixed $value) - { - if (method_exists($this, $option)) { - return $this->{$option}($value); - } - $this->_options[$option] = $value; - - return $this; - } -} diff --git a/Table.php b/Table.php index 61201df4..4d7d088e 100644 --- a/Table.php +++ b/Table.php @@ -1862,19 +1862,14 @@ public function exists(QueryExpression|Closure|array|string|null $conditions): b * ``` * * @param \Cake\Datasource\EntityInterface $entity the entity to be saved - * @param \Cake\ORM\SaveOptionsBuilder|array $options The options to use when saving. + * @param array $options The options to use when saving. * @return \Cake\Datasource\EntityInterface|false * @throws \Cake\ORM\Exception\RolledbackTransactionException If the transaction is aborted in the afterSave event. */ public function save( EntityInterface $entity, - SaveOptionsBuilder|array $options = [] + array $options = [] ): EntityInterface|false { - if ($options instanceof SaveOptionsBuilder) { - deprecationWarning('4.4.0', 'SaveOptionsBuilder is deprecated. Use a normal array for options instead.'); - $options = $options->toArray(); - } - $options = new ArrayObject($options + [ 'atomic' => true, 'associated' => true, @@ -2197,13 +2192,13 @@ protected function _update(EntityInterface $entity, array $data): EntityInterfac * error. * * @param \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface> $entities Entities to save. - * @param \Cake\ORM\SaveOptionsBuilder|array $options Options used when calling Table::save() for each entity. + * @param array $options Options used when calling Table::save() for each entity. * @return \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface>|false False on failure, entities list on success. * @throws \Exception */ public function saveMany( ResultSetInterface|array $entities, - SaveOptionsBuilder|array $options = [] + array $options = [] ): ResultSetInterface|array|false { try { return $this->_saveMany($entities, $options); @@ -2232,20 +2227,15 @@ public function saveManyOrFail(iterable $entities, array $options = []): ResultS /** * @param \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface> $entities Entities to save. - * @param \Cake\ORM\SaveOptionsBuilder|array $options Options used when calling Table::save() for each entity. + * @param array $options Options used when calling Table::save() for each entity. * @throws \Cake\ORM\Exception\PersistenceFailedException If an entity couldn't be saved. * @throws \Exception If an entity couldn't be saved. * @return \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface> Entities list. */ protected function _saveMany( ResultSetInterface|array $entities, - SaveOptionsBuilder|array $options = [] + array $options = [] ): ResultSetInterface|array { - if ($options instanceof SaveOptionsBuilder) { - deprecationWarning('4.4.0', 'SaveOptionsBuilder is deprecated. Use a normal array for options instead.'); - $options = $options->toArray(); - } - $options = new ArrayObject( $options + [ 'atomic' => true, @@ -3053,17 +3043,6 @@ public function buildRules(RulesChecker $rules): RulesChecker return $rules; } - /** - * Gets a SaveOptionsBuilder instance. - * - * @param array $options Options to parse by the builder. - * @return \Cake\ORM\SaveOptionsBuilder - */ - public function getSaveOptionsBuilder(array $options = []): SaveOptionsBuilder - { - return new SaveOptionsBuilder($this, $options); - } - /** * Loads the specified associations in the passed entity or list of entities * by executing extra queries in the database and merging the results in the From 4d7577c488129edc2849d52176703951414a9197 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Tue, 22 Feb 2022 23:23:36 -0600 Subject: [PATCH 1740/2059] Add deprecatd annotation to getSaveOptionsBuilder() --- SaveOptionsBuilder.php | 2 +- Table.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/SaveOptionsBuilder.php b/SaveOptionsBuilder.php index d53b3019..1bbf4b2b 100644 --- a/SaveOptionsBuilder.php +++ b/SaveOptionsBuilder.php @@ -26,7 +26,7 @@ * you to avoid mistakes by validating the options as you build them. * * @see \Cake\Datasource\RulesChecker - * @deprecated 4.4.4 Use an array of options instead. + * @deprecated 4.4.0 Use a normal array for options instead. */ class SaveOptionsBuilder extends ArrayObject { diff --git a/Table.php b/Table.php index 788b4db1..8ec4fb37 100644 --- a/Table.php +++ b/Table.php @@ -3020,6 +3020,7 @@ public function buildRules(RulesChecker $rules): RulesChecker * * @param array $options Options to parse by the builder. * @return \Cake\ORM\SaveOptionsBuilder + * @deprecated 4.4.0 Use a normal array for options instead. */ public function getSaveOptionsBuilder(array $options = []): SaveOptionsBuilder { From 38352f79ca1e380a799f3a612fd79696bb042b79 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 1 Mar 2022 01:01:38 -0500 Subject: [PATCH 1741/2059] Fix belongsToMany with finder & contain When a belongsToMany association uses a finder, and that finder applies a contain() the query will fail as the query is rooted in the junction table and not the association target table. This was caused by the changes done in #15977 to fix the wrong entity type being passed to delete callbacks. Restoring the old query fixes this issue, but would break entity delete callbacks. To solve that I've selected all the fields on the junction table and then created the correct entity. This is a bit gross, but trying to use `contain()` on the junction association doesn't work as the table has already been `innerJoinWith()` onto the query. I also attempted to use subqueries and `where exists` but abandoned that as it caused other problems in type mapping parameters. Fixes #16252 --- Association/BelongsToMany.php | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index ba3503b6..e91b7710 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -21,6 +21,7 @@ use Cake\Database\Expression\QueryExpression; use Cake\Database\ExpressionInterface; use Cake\Datasource\EntityInterface; +use Cake\Datasource\ResultSetInterface; use Cake\ORM\Association; use Cake\ORM\Association\Loader\SelectWithPivotLoader; use Cake\ORM\Query; @@ -1183,27 +1184,31 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { $target = $this->getTarget(); $foreignKey = (array)$this->getForeignKey(); - $prefixedForeignKey = array_map([$junction, 'aliasField'], $foreignKey); - - $junctionPrimaryKey = (array)$junction->getPrimaryKey(); $assocForeignKey = (array)$junction->getAssociation($target->getAlias())->getForeignKey(); + $prefixedForeignKey = array_map([$junction, 'aliasField'], $foreignKey); + $junctionPrimaryKey = (array)$junction->getPrimaryKey(); $keys = array_combine($foreignKey, $prefixedForeignKey); foreach (array_merge($assocForeignKey, $junctionPrimaryKey) as $key) { $keys[$key] = $junction->aliasField($key); } - // Find junction records. We join with the association target so that junction - // conditions from `targetConditions()` or the finder work. - $existing = $junction->find() - ->innerJoinWith($target->getAlias()) - ->where($this->targetConditions()) - ->where($this->junctionConditions()) - ->where(array_combine($prefixedForeignKey, $primaryValue)); - [$finder, $finderOptions] = $this->_extractFinder($this->getFinder()); - if ($finder) { - $existing = $target->callFinder($finder, $existing, $finderOptions); - } + $existing = $this->_appendJunctionJoin($this->find()) + ->select($junction) + ->where(array_combine($prefixedForeignKey, $primaryValue)) + ->formatResults(function (ResultSetInterface $results) use ($junction) { + $junctionEntity = $junction->getEntityClass(); + $junctionAlias = $junction->getAlias(); + + // Extract data for the junction entity and map the result + // into junction entity instances so that delete callbacks work correctly. + return $results->map(function ($item) use ($junctionEntity, $junctionAlias) { + return new $junctionEntity( + $item[$junctionAlias], + ['markNew' => false, 'markClean' => true] + ); + }); + }); $jointEntities = $this->_collectJointEntities($sourceEntity, $targetEntities); $inserts = $this->_diffLinks($existing, $jointEntities, $targetEntities, $options); From f89419e42152d60c9fadefc18401f5b163d94ca9 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 1 Mar 2022 22:32:45 -0500 Subject: [PATCH 1742/2059] Refactor belongs to many link query Update the query from #16357 to use a sub-query. This generates more complex SQL but doesn't require using a `formatResults` callback. Thanks to @ndm2 for pointing out that this can be done. The join conditions are enough to limit the results loaded on the junction table. --- Association/BelongsToMany.php | 38 +++++++++++++++++------------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index e91b7710..8ebf9b4b 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -21,7 +21,6 @@ use Cake\Database\Expression\QueryExpression; use Cake\Database\ExpressionInterface; use Cake\Datasource\EntityInterface; -use Cake\Datasource\ResultSetInterface; use Cake\ORM\Association; use Cake\ORM\Association\Loader\SelectWithPivotLoader; use Cake\ORM\Query; @@ -1188,27 +1187,28 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { $prefixedForeignKey = array_map([$junction, 'aliasField'], $foreignKey); $junctionPrimaryKey = (array)$junction->getPrimaryKey(); - $keys = array_combine($foreignKey, $prefixedForeignKey); + + $keys = $matchesConditions = []; foreach (array_merge($assocForeignKey, $junctionPrimaryKey) as $key) { - $keys[$key] = $junction->aliasField($key); + $aliased = $junction->aliasField($key); + $keys[$key] = $aliased; + $matchesConditions[$aliased] = new IdentifierExpression('matches.' . $key); } - $existing = $this->_appendJunctionJoin($this->find()) - ->select($junction) - ->where(array_combine($prefixedForeignKey, $primaryValue)) - ->formatResults(function (ResultSetInterface $results) use ($junction) { - $junctionEntity = $junction->getEntityClass(); - $junctionAlias = $junction->getAlias(); - - // Extract data for the junction entity and map the result - // into junction entity instances so that delete callbacks work correctly. - return $results->map(function ($item) use ($junctionEntity, $junctionAlias) { - return new $junctionEntity( - $item[$junctionAlias], - ['markNew' => false, 'markClean' => true] - ); - }); - }); + // Use association to create row selection + // with finders & association conditions. + $matches = $this->_appendJunctionJoin($this->find()) + ->select($keys) + ->where(array_combine($prefixedForeignKey, $primaryValue)); + + // Create a subquery join to ensure we get + // the correct entity passed to callbacks. + $existing = $junction->query() + ->from(['matches' => $matches]) + ->innerJoin( + [$junction->getAlias() => $junction->getTable()], + $matchesConditions + ); $jointEntities = $this->_collectJointEntities($sourceEntity, $targetEntities); $inserts = $this->_diffLinks($existing, $jointEntities, $targetEntities, $options); From c42fe4db1b53b1047f50177725cb02dd8b2c7171 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 4 Mar 2022 14:52:43 -0500 Subject: [PATCH 1743/2059] Use an alias that is less likely to conflict with application aliases. --- Association/BelongsToMany.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 8ebf9b4b..46f29158 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1187,12 +1187,13 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { $prefixedForeignKey = array_map([$junction, 'aliasField'], $foreignKey); $junctionPrimaryKey = (array)$junction->getPrimaryKey(); + $junctionQueryAlias = $junction->getAlias() . '__matches'; $keys = $matchesConditions = []; foreach (array_merge($assocForeignKey, $junctionPrimaryKey) as $key) { $aliased = $junction->aliasField($key); $keys[$key] = $aliased; - $matchesConditions[$aliased] = new IdentifierExpression('matches.' . $key); + $matchesConditions[$aliased] = new IdentifierExpression($junctionQueryAlias . '.' . $key); } // Use association to create row selection @@ -1204,7 +1205,7 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { // Create a subquery join to ensure we get // the correct entity passed to callbacks. $existing = $junction->query() - ->from(['matches' => $matches]) + ->from([$junctionQueryAlias => $matches]) ->innerJoin( [$junction->getAlias() => $junction->getTable()], $matchesConditions From 46b20e341e8cdbed5275e9f3860cddf98b51abc1 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 7 Mar 2022 14:14:08 +0530 Subject: [PATCH 1744/2059] Use null coalescing assignment. --- Association/BelongsToMany.php | 6 +----- Behavior/Translate/TranslateStrategyTrait.php | 7 ++----- EagerLoader.php | 8 ++------ Locator/LocatorAwareTrait.php | 7 +------ Query.php | 12 ++---------- RulesChecker.php | 5 +---- Table.php | 6 +----- 7 files changed, 10 insertions(+), 41 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 94173096..19ff322f 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -180,11 +180,7 @@ public function setTargetForeignKey(array|string $key) */ public function getTargetForeignKey(): array|string { - if ($this->_targetForeignKey === null) { - $this->_targetForeignKey = $this->_modelKey($this->getTarget()->getAlias()); - } - - return $this->_targetForeignKey; + return $this->_targetForeignKey ??= $this->_modelKey($this->getTarget()->getAlias()); } /** diff --git a/Behavior/Translate/TranslateStrategyTrait.php b/Behavior/Translate/TranslateStrategyTrait.php index 8edbe4ff..fe4a269f 100644 --- a/Behavior/Translate/TranslateStrategyTrait.php +++ b/Behavior/Translate/TranslateStrategyTrait.php @@ -156,11 +156,8 @@ public function buildMarshalMap(Marshaller $marshaller, array $map, array $optio return null; } - /** @var array|null $translations */ - $translations = $entity->get('_translations'); - if ($translations === null) { - $translations = []; - } + /** @var array $translations */ + $translations = $entity->get('_translations') ?? []; $options['validate'] = $this->_config['validator']; $errors = []; diff --git a/EagerLoader.php b/EagerLoader.php index 4c56d866..468e380a 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -238,9 +238,7 @@ public function isAutoFieldsEnabled(): bool */ public function setMatching(string $associationPath, ?callable $builder = null, array $options = []) { - if ($this->_matching === null) { - $this->_matching = new static(); - } + $this->_matching ??= new static(); $options += ['joinType' => Query::JOIN_TYPE_INNER]; $sharedOptions = ['negateMatch' => false, 'matching' => true] + $options; @@ -268,9 +266,7 @@ public function setMatching(string $associationPath, ?callable $builder = null, */ public function getMatching(): array { - if ($this->_matching === null) { - $this->_matching = new static(); - } + $this->_matching ??= new static(); return $this->_matching->getContain(); } diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index dd55bd3d..b9c1b2ef 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -59,13 +59,8 @@ public function setTableLocator(LocatorInterface $tableLocator) */ public function getTableLocator(): LocatorInterface { - if ($this->_tableLocator === null) { - /** @psalm-suppress InvalidPropertyAssignmentValue */ - $this->_tableLocator = FactoryLocator::get('Table'); - } - /** @var \Cake\ORM\Locator\LocatorInterface */ - return $this->_tableLocator; + return $this->_tableLocator ??= FactoryLocator::get('Table'); } /** diff --git a/Query.php b/Query.php index de091db0..3bd9f582 100644 --- a/Query.php +++ b/Query.php @@ -309,11 +309,7 @@ public function setEagerLoader(EagerLoader $instance) */ public function getEagerLoader(): EagerLoader { - if ($this->_eagerLoader === null) { - $this->_eagerLoader = new EagerLoader(); - } - - return $this->_eagerLoader; + return $this->_eagerLoader ??= new EagerLoader(); } /** @@ -915,11 +911,7 @@ public function __clone() */ public function count(): int { - if ($this->_resultsCount === null) { - $this->_resultsCount = $this->_performCount(); - } - - return $this->_resultsCount; + return $this->_resultsCount ??= $this->_performCount(); } /** diff --git a/RulesChecker.php b/RulesChecker.php index 1d0b089a..70726068 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -213,10 +213,7 @@ protected function _addLinkConstraintRule( ): RuleInvoker { if ($association instanceof Association) { $associationAlias = $association->getName(); - - if ($errorField === null) { - $errorField = $association->getProperty(); - } + $errorField ??= $association->getProperty(); } else { $associationAlias = $association; diff --git a/Table.php b/Table.php index 4d7d088e..6115745c 100644 --- a/Table.php +++ b/Table.php @@ -466,11 +466,7 @@ public function setRegistryAlias(string $registryAlias) */ public function getRegistryAlias(): string { - if ($this->_registryAlias === null) { - $this->_registryAlias = $this->getAlias(); - } - - return $this->_registryAlias; + return $this->_registryAlias ??= $this->getAlias(); } /** From 648b8036915aced780696cce1b6af456e591e918 Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Tue, 8 Mar 2022 17:09:00 +0100 Subject: [PATCH 1745/2059] fix CS errors --- Association/BelongsToMany.php | 3 +-- Association/HasMany.php | 3 +-- ResultSetFactory.php | 4 +++- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 94173096..eab37566 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -214,8 +214,7 @@ public function getForeignKey(): array|string|false /** * Sets the sort order in which target records should be returned. * - * @param \Cake\Database\ExpressionInterface|\Closure|array<\Cake\Database\ExpressionInterface|string>|string $sort - * A find() compatible order clause + * @param \Cake\Database\ExpressionInterface|\Closure|array<\Cake\Database\ExpressionInterface|string>|string $sort A find() compatible order clause * @return $this */ public function setSort(ExpressionInterface|Closure|array|string $sort) diff --git a/Association/HasMany.php b/Association/HasMany.php index c2a56879..506d37a8 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -602,8 +602,7 @@ public function getForeignKey(): array|string|false /** * Sets the sort order in which target records should be returned. * - * @param \Cake\Database\ExpressionInterface|\Closure|array<\Cake\Database\ExpressionInterface|string>|string $sort - * A find() compatible order clause + * @param \Cake\Database\ExpressionInterface|\Closure|array<\Cake\Database\ExpressionInterface|string>|string $sort A find() compatible order clause * @return $this */ public function setSort(ExpressionInterface|Closure|array|string $sort) diff --git a/ResultSetFactory.php b/ResultSetFactory.php index 4697e923..5e2ee629 100644 --- a/ResultSetFactory.php +++ b/ResultSetFactory.php @@ -32,6 +32,7 @@ class ResultSetFactory * * @param \Cake\ORM\Query $query Query from where results came. * @param array $results Results array. + * @return \Cake\ORM\ResultSet */ public function createResultSet(Query $query, array $results): ResultSet { @@ -103,9 +104,10 @@ protected function collectData(Query $query): array /** * Correctly nests results keys including those coming from associations. * - * Hyrate row array into entity if hydration is enabled. + * Hydrate row array into entity if hydration is enabled. * * @param array $row Array containing columns and values. + * @param array $data Array containing table and query metadata * @return \Cake\Datasource\EntityInterface|array */ protected function groupResult(array $row, array $data): EntityInterface|array From dcc196645186e3838237c6ef3ba2273ed91c0565 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 11 Mar 2022 23:28:59 +0530 Subject: [PATCH 1746/2059] Fix errors reported by static analyzers --- Association/BelongsToMany.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 15b18c48..6154e54d 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1185,6 +1185,7 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { $junction = $this->junction(); $target = $this->getTarget(); + /** @var array $foreignKey */ $foreignKey = (array)$this->getForeignKey(); $assocForeignKey = (array)$junction->getAssociation($target->getAlias())->getForeignKey(); $prefixedForeignKey = array_map([$junction, 'aliasField'], $foreignKey); @@ -1193,6 +1194,7 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { $junctionQueryAlias = $junction->getAlias() . '__matches'; $keys = $matchesConditions = []; + /** @var string $key */ foreach (array_merge($assocForeignKey, $junctionPrimaryKey) as $key) { $aliased = $junction->aliasField($key); $keys[$key] = $aliased; From be678de7f5bee8b37a986efa39de7cf88a99b7bf Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 12 Mar 2022 23:09:37 -0500 Subject: [PATCH 1747/2059] Start moving to short functions We can start using PHP8 features to simplify the code a bit. --- Behavior/Translate/EavStrategy.php | 4 +-- Behavior/Translate/ShadowTableStrategy.php | 4 +-- Behavior/TreeBehavior.php | 35 ++++++---------------- 3 files changed, 11 insertions(+), 32 deletions(-) diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index 5a5356dc..502820db 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -217,9 +217,7 @@ public function beforeFind(EventInterface $event, Query $query, ArrayObject $opt } $query->contain($contain); - $query->formatResults(function ($results) use ($locale) { - return $this->rowMapper($results, $locale); - }, $query::PREPEND); + $query->formatResults(fn(CollectionInterface $results) => $this->rowMapper($results, $locale), $query::PREPEND); } /** diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index f50bab80..567a730d 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -146,9 +146,7 @@ public function beforeFind(EventInterface $event, Query $query, ArrayObject $opt $query->contain([$config['hasOneAlias']]); - $query->formatResults(function ($results) use ($locale) { - return $this->rowMapper($results, $locale); - }, $query::PREPEND); + $query->formatResults(fn(CollectionInterface $results) => $this->rowMapper($results, $locale), $query::PREPEND); } /** diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index c8e0e330..6fa82d44 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -18,6 +18,7 @@ use Cake\Collection\CollectionInterface; use Cake\Database\Expression\IdentifierExpression; +use Cake\Database\Expression\QueryExpression; use Cake\Datasource\EntityInterface; use Cake\Datasource\Exception\RecordNotFoundException; use Cake\Event\EventInterface; @@ -228,12 +229,9 @@ public function beforeDelete(EventInterface $event, EntityInterface $entity): vo if ($diff > 2) { $query = $this->_scope($this->_table->query()) ->delete() - ->where(function ($exp) use ($config, $left, $right) { - /** @var \Cake\Database\Expression\QueryExpression $exp */ - return $exp - ->gte($config['leftField'], $left + 1) - ->lte($config['leftField'], $right - 1); - }); + ->where(fn(QueryExpression $exp) => $exp + ->gte($config['leftField'], $left + 1) + ->lte($config['leftField'], $right - 1)); $statement = $query->execute(); $statement->closeCursor(); } @@ -358,10 +356,7 @@ function ($exp) use ($config) { ->eq($config['leftField'], $leftInverse->add($config['leftField'])) ->eq($config['rightField'], $rightInverse->add($config['rightField'])); }, - function ($exp) use ($config) { - /** @var \Cake\Database\Expression\QueryExpression $exp */ - return $exp->lt($config['leftField'], 0); - } + fn(QueryExpression $exp) => $exp->lt($config['leftField'], 0) ); } @@ -641,10 +636,7 @@ protected function _moveUp(EntityInterface $node, int|bool $number): EntityInter $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) ->where(["$parent IS" => $nodeParent]) - ->where(function ($exp) use ($config, $nodeLeft) { - /** @var \Cake\Database\Expression\QueryExpression $exp */ - return $exp->lt($config['rightField'], $nodeLeft); - }) + ->where(fn(QueryExpression $exp) => $exp->lt($config['rightField'], $nodeLeft)) ->orderDesc($config['leftField']) ->offset($number - 1) ->limit(1) @@ -655,10 +647,7 @@ protected function _moveUp(EntityInterface $node, int|bool $number): EntityInter $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) ->where(["$parent IS" => $nodeParent]) - ->where(function ($exp) use ($config, $nodeLeft) { - /** @var \Cake\Database\Expression\QueryExpression $exp */ - return $exp->lt($config['rightField'], $nodeLeft); - }) + ->where(fn(QueryExpression $exp) => $exp->lt($config['rightField'], $nodeLeft)) ->orderAsc($config['leftField']) ->limit(1) ->first(); @@ -735,10 +724,7 @@ protected function _moveDown(EntityInterface $node, int|bool $number): EntityInt $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) ->where(["$parent IS" => $nodeParent]) - ->where(function ($exp) use ($config, $nodeRight) { - /** @var \Cake\Database\Expression\QueryExpression $exp */ - return $exp->gt($config['leftField'], $nodeRight); - }) + ->where(fn(QueryExpression $exp) => $exp->gt($config['leftField'], $nodeRight)) ->orderAsc($config['leftField']) ->offset($number - 1) ->limit(1) @@ -749,10 +735,7 @@ protected function _moveDown(EntityInterface $node, int|bool $number): EntityInt $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) ->where(["$parent IS" => $nodeParent]) - ->where(function ($exp) use ($config, $nodeRight) { - /** @var \Cake\Database\Expression\QueryExpression $exp */ - return $exp->gt($config['leftField'], $nodeRight); - }) + ->where(fn(QueryExpression $exp) => $exp->gt($config['leftField'], $nodeRight)) ->orderDesc($config['leftField']) ->limit(1) ->first(); From b287e0afff106c05c38eb8d396241d9f59244760 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 12 Mar 2022 23:45:00 -0500 Subject: [PATCH 1748/2059] Update ORM to short-functions and fix mistakes. --- Association/HasMany.php | 5 +---- EagerLoader.php | 4 +--- LazyEagerLoader.php | 16 ++++------------ Marshaller.php | 14 ++++---------- Rule/ExistsIn.php | 4 +--- Table.php | 37 ++++++++++++++++++------------------- 6 files changed, 29 insertions(+), 51 deletions(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index 506d37a8..47aee00b 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -287,10 +287,7 @@ public function link(EntityInterface $sourceEntity, array $targetEntities, array $sourceEntity->set($property, $currentEntities); - $savedEntity = $this->getConnection()->transactional(function () use ($sourceEntity, $options) { - return $this->saveAssociated($sourceEntity, $options); - }); - + $savedEntity = $this->getConnection()->transactional(fn() => $this->saveAssociated($sourceEntity, $options)); $ok = ($savedEntity instanceof EntityInterface); $this->setSaveStrategy($saveStrategy); diff --git a/EagerLoader.php b/EagerLoader.php index 468e380a..f0495006 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -370,9 +370,7 @@ protected function _reformatContain(array $associations, array $original): array if (isset($options['queryBuilder'], $pointer[$table]['queryBuilder'])) { $first = $pointer[$table]['queryBuilder']; $second = $options['queryBuilder']; - $options['queryBuilder'] = function ($query) use ($first, $second) { - return $second($first($query)); - }; + $options['queryBuilder'] = fn($query) => $second($first($query)); } if (!is_array($options)) { diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 0452692c..4fa34b1a 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -18,6 +18,7 @@ use Cake\Collection\Collection; use Cake\Collection\CollectionInterface; +use Cake\Database\Expression\QueryExpression; use Cake\Database\Expression\TupleComparison; use Cake\Datasource\EntityInterface; @@ -74,18 +75,12 @@ protected function _getQuery(CollectionInterface $objects, array $contain, Table $primaryKey = $source->getPrimaryKey(); $method = is_string($primaryKey) ? 'get' : 'extract'; - $keys = $objects->map(function ($entity) use ($primaryKey, $method) { - return $entity->{$method}($primaryKey); - }); + $keys = $objects->map(fn(EntityInterface $entity) => $entity->{$method}($primaryKey)); $query = $source ->find() ->select((array)$primaryKey) - ->where(function ($exp, $q) use ($primaryKey, $keys, $source) { - /** - * @var \Cake\Database\Expression\QueryExpression $exp - * @var \Cake\ORM\Query $q - */ + ->where(function (QueryExpression $exp, Query $q) use ($primaryKey, $keys, $source) { if (is_array($primaryKey) && count($primaryKey) === 1) { $primaryKey = current($primaryKey); } @@ -148,10 +143,7 @@ protected function _injectResults(iterable $objects, Query $results, array $asso $primaryKey = (array)$source->getPrimaryKey(); $results = $results ->all() - ->indexBy(function ($e) use ($primaryKey) { - /** @var \Cake\Datasource\EntityInterface $e */ - return implode(';', $e->extract($primaryKey)); - }) + ->indexBy(fn(EntityInterface $e) => implode(';', $e->extract($primaryKey))) ->toArray(); foreach ($objects as $k => $object) { diff --git a/Marshaller.php b/Marshaller.php index f4c5c73e..abc87000 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -18,6 +18,7 @@ use ArrayObject; use Cake\Collection\Collection; +use Cake\Database\Expression\QueryExpression; use Cake\Database\Expression\TupleComparison; use Cake\Database\TypeFactory; use Cake\Datasource\EntityInterface; @@ -75,9 +76,7 @@ protected function _buildPropertyMap(array $data, array $options): array $prop = (string)$prop; $columnType = $schema->getColumnType($prop); if ($columnType) { - $map[$prop] = function ($value, $entity) use ($columnType) { - return TypeFactory::build($columnType)->marshal($value); - }; + $map[$prop] = fn($value) => TypeFactory::build($columnType)->marshal($value); } } @@ -405,10 +404,7 @@ protected function _belongsToMany(BelongsToMany $assoc, array $data, array $opti if (!empty($conditions)) { $query = $target->find(); - $query->andWhere(function ($exp) use ($conditions) { - /** @var \Cake\Database\Expression\QueryExpression $exp */ - return $exp->or($conditions); - }); + $query->andWhere(fn(QueryExpression $exp) => $exp->or($conditions)); $keyFields = array_keys($primaryKey); @@ -685,9 +681,7 @@ public function mergeMany(iterable $entities, array $data, array $options = []): ->map(function ($data, $key) { return explode(';', (string)$key); }) - ->filter(function ($keys) use ($primary) { - return count(Hash::filter($keys)) === count($primary); - }) + ->filter(fn ($keys) => count(Hash::filter($keys)) === count($primary)) ->reduce(function ($conditions, $keys) use ($primary) { $fields = array_map([$this->_table, 'aliasField'], $primary); $conditions['OR'][] = array_combine($fields, $keys); diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 92df9caf..98c2975f 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -133,9 +133,7 @@ public function __invoke(EntityInterface $entity, array $options): bool } $primary = array_map( - function ($key) use ($target) { - return $target->aliasField($key) . ' IS'; - }, + fn($key) => $target->aliasField($key) . ' IS', $bindingKey ); $conditions = array_combine( diff --git a/Table.php b/Table.php index 6115745c..1d309900 100644 --- a/Table.php +++ b/Table.php @@ -1385,14 +1385,13 @@ public function findList(Query $query, array $options): Query ['keyField', 'valueField', 'groupField'] ); - return $query->formatResults(function ($results) use ($options) { + return $query->formatResults(fn($results) => /** @var \Cake\Collection\CollectionInterface $results */ - return $results->combine( + $results->combine( $options['keyField'], $options['valueField'], $options['groupField'] - ); - }); + )); } /** @@ -1429,10 +1428,9 @@ public function findThreaded(Query $query, array $options): Query $options = $this->_setFieldMatchers($options, ['keyField', 'parentField']); - return $query->formatResults(function ($results) use ($options) { + return $query->formatResults(fn($results) => /** @var \Cake\Collection\CollectionInterface $results */ - return $results->nest($options['keyField'], $options['parentField'], $options['nestingKey']); - }); + $results->nest($options['keyField'], $options['parentField'], $options['nestingKey'])); } /** @@ -1551,9 +1549,7 @@ public function get($primaryKey, array $options = []): EntityInterface protected function _executeTransaction(callable $worker, bool $atomic = true): mixed { if ($atomic) { - return $this->getConnection()->transactional(function () use ($worker) { - return $worker(); - }); + return $this->getConnection()->transactional(fn () => $worker()); } return $worker(); @@ -1615,9 +1611,10 @@ public function findOrCreate( 'defaults' => true, ]); - $entity = $this->_executeTransaction(function () use ($search, $callback, $options) { - return $this->_processFindOrCreate($search, $callback, $options->getArrayCopy()); - }, $options['atomic']); + $entity = $this->_executeTransaction( + fn() => $this->_processFindOrCreate($search, $callback, $options->getArrayCopy()), + $options['atomic'] + ); if ($entity && $this->_transactionCommitted($options['atomic'], true)) { $this->dispatchEvent('Model.afterSaveCommit', compact('entity', 'options')); @@ -1882,9 +1879,10 @@ public function save( return $entity; } - $success = $this->_executeTransaction(function () use ($entity, $options) { - return $this->_processSave($entity, $options); - }, $options['atomic']); + $success = $this->_executeTransaction( + fn() => $this->_processSave($entity, $options), + $options['atomic'] + ); if ($success) { if ($this->_transactionCommitted($options['atomic'], $options['_primary'])) { @@ -2331,9 +2329,10 @@ public function delete(EntityInterface $entity, $options = []): bool '_primary' => true, ]); - $success = $this->_executeTransaction(function () use ($entity, $options) { - return $this->_processDelete($entity, $options); - }, $options['atomic']); + $success = $this->_executeTransaction( + fn() => $this->_processDelete($entity, $options), + $options['atomic'] + ); if ($success && $this->_transactionCommitted($options['atomic'], $options['_primary'])) { $this->dispatchEvent('Model.afterDeleteCommit', [ From 650de410ff2386c788b5b49cd31eb16d40499471 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 13 Mar 2022 13:33:22 -0400 Subject: [PATCH 1749/2059] Fix phpcs and psalm errors. --- EagerLoader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EagerLoader.php b/EagerLoader.php index f0495006..f6e2b774 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -370,7 +370,7 @@ protected function _reformatContain(array $associations, array $original): array if (isset($options['queryBuilder'], $pointer[$table]['queryBuilder'])) { $first = $pointer[$table]['queryBuilder']; $second = $options['queryBuilder']; - $options['queryBuilder'] = fn($query) => $second($first($query)); + $options['queryBuilder'] = fn($query) => $second($first($query)); } if (!is_array($options)) { From 4732e84bde1d4e374f89048b3ba5ac9a01987548 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 2 Mar 2022 18:00:48 +0530 Subject: [PATCH 1750/2059] Remove uneeded Statement::closeCursor() calls. --- Behavior/TreeBehavior.php | 18 +++++++++--------- Query.php | 1 - Table.php | 4 ---- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 6fa82d44..6588aba5 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -227,13 +227,14 @@ public function beforeDelete(EventInterface $event, EntityInterface $entity): vo $diff = $right - $left + 1; if ($diff > 2) { - $query = $this->_scope($this->_table->query()) + $this->_scope($this->_table->query()) ->delete() - ->where(fn(QueryExpression $exp) => $exp - ->gte($config['leftField'], $left + 1) - ->lte($config['leftField'], $right - 1)); - $statement = $query->execute(); - $statement->closeCursor(); + ->where( + fn (QueryExpression $exp) => $exp + ->gte($config['leftField'], $left + 1) + ->lte($config['leftField'], $right - 1) + ) + ->execute(); } $this->_sync($diff, '-', "> {$right}"); @@ -907,9 +908,8 @@ protected function _sync(int $shift, string $dir, string $conditions, bool $mark $query->update() ->set($exp->eq($field, $movement)) - ->where($where); - - $query->execute()->closeCursor(); + ->where($where) + ->execute(); } } diff --git a/Query.php b/Query.php index 3bd9f582..e6838d2f 100644 --- a/Query.php +++ b/Query.php @@ -968,7 +968,6 @@ protected function _performCount(): int } $result = $statement->fetch('assoc'); - $statement->closeCursor(); if ($result === false) { return 0; diff --git a/Table.php b/Table.php index 1d309900..a6cfaed3 100644 --- a/Table.php +++ b/Table.php @@ -1728,7 +1728,6 @@ public function updateAll( ->set($fields) ->where($conditions) ->execute(); - $statement->closeCursor(); return $statement->rowCount(); } @@ -1753,7 +1752,6 @@ public function deleteAll(QueryExpression|Closure|array|string|null $conditions) ->delete() ->where($conditions) ->execute(); - $statement->closeCursor(); return $statement->rowCount(); } @@ -2105,7 +2103,6 @@ protected function _insert(EntityInterface $entity, array $data): EntityInterfac } } } - $statement->closeCursor(); return $success; } @@ -2173,7 +2170,6 @@ protected function _update(EntityInterface $entity, array $data): EntityInterfac ->execute(); $success = $statement->errorCode() === '00000' ? $entity : false; - $statement->closeCursor(); return $success; } From 908ad366a48ff8671787c1943f08851e6aea336e Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 8 Mar 2022 21:22:43 +0530 Subject: [PATCH 1751/2059] Simplify param types for FactoryLocator. Remove use of TableLocator class of database package from FactoryLocator. --- Locator/LocatorAwareTrait.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index b9c1b2ef..801cad20 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -59,7 +59,10 @@ public function setTableLocator(LocatorInterface $tableLocator) */ public function getTableLocator(): LocatorInterface { - /** @var \Cake\ORM\Locator\LocatorInterface */ + /** + * @var \Cake\ORM\Locator\LocatorInterface + * @psalm-suppress PropertyTypeCoercion + */ return $this->_tableLocator ??= FactoryLocator::get('Table'); } From 46ed7be0bbe689bb485f7cd944522b969439b8a1 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 15 Mar 2022 13:17:13 +0530 Subject: [PATCH 1752/2059] Add bootstrap file for ORM package. It will add the `TableLocator` with the name "Table" to the `FactoryLocator`. --- bootstrap.php | 21 +++++++++++++++++++++ composer.json | 5 ++++- 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 bootstrap.php diff --git a/bootstrap.php b/bootstrap.php new file mode 100644 index 00000000..8b23a2d4 --- /dev/null +++ b/bootstrap.php @@ -0,0 +1,21 @@ + Date: Wed, 16 Mar 2022 14:04:09 +1000 Subject: [PATCH 1753/2059] Update SelectLoader.php --- Association/Loader/SelectLoader.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 0ccff39a..6a2591d4 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -244,6 +244,10 @@ protected function _extractFinder($finderData): array */ protected function _assertFieldsPresent(Query $fetchQuery, array $key): void { + if ($fetchQuery->isAutoFieldsEnabled()) { + return; + } + $select = $fetchQuery->aliasFields($fetchQuery->clause('select')); if (empty($select)) { return; From d38ae0f667235b6bc2bc02cb98f343b3066dce50 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 20 Mar 2022 12:28:57 +0530 Subject: [PATCH 1754/2059] Code cleanup. --- Table.php | 39 ++++++++++++--------------------------- 1 file changed, 12 insertions(+), 27 deletions(-) diff --git a/Table.php b/Table.php index a6cfaed3..eaf502c0 100644 --- a/Table.php +++ b/Table.php @@ -566,10 +566,7 @@ protected function checkAliasLengths(): void throw new RuntimeException("Unable to check max alias lengths for `{$this->getAlias()}` without schema."); } - $maxLength = null; - if (method_exists($this->getConnection()->getDriver(), 'getMaxAliasLength')) { - $maxLength = $this->getConnection()->getDriver()->getMaxAliasLength(); - } + $maxLength = $this->getConnection()->getDriver()->getMaxAliasLength(); if ($maxLength === null) { return; } @@ -858,10 +855,8 @@ public function getBehavior(string $name): Behavior )); } - /** @var \Cake\ORM\Behavior $behavior */ - $behavior = $this->_behaviors->get($name); - - return $behavior; + /** @var \Cake\ORM\Behavior */ + return $this->_behaviors->get($name); } /** @@ -1042,10 +1037,8 @@ public function belongsTo(string $associated, array $options = []): BelongsTo { $options += ['sourceTable' => $this]; - /** @var \Cake\ORM\Association\BelongsTo $association */ - $association = $this->_associations->load(BelongsTo::class, $associated, $options); - - return $association; + /** @var \Cake\ORM\Association\BelongsTo */ + return $this->_associations->load(BelongsTo::class, $associated, $options); } /** @@ -1088,10 +1081,8 @@ public function hasOne(string $associated, array $options = []): HasOne { $options += ['sourceTable' => $this]; - /** @var \Cake\ORM\Association\HasOne $association */ - $association = $this->_associations->load(HasOne::class, $associated, $options); - - return $association; + /** @var \Cake\ORM\Association\HasOne */ + return $this->_associations->load(HasOne::class, $associated, $options); } /** @@ -1140,10 +1131,8 @@ public function hasMany(string $associated, array $options = []): HasMany { $options += ['sourceTable' => $this]; - /** @var \Cake\ORM\Association\HasMany $association */ - $association = $this->_associations->load(HasMany::class, $associated, $options); - - return $association; + /** @var \Cake\ORM\Association\HasMany */ + return $this->_associations->load(HasMany::class, $associated, $options); } /** @@ -1194,10 +1183,8 @@ public function belongsToMany(string $associated, array $options = []): BelongsT { $options += ['sourceTable' => $this]; - /** @var \Cake\ORM\Association\BelongsToMany $association */ - $association = $this->_associations->load(BelongsToMany::class, $associated, $options); - - return $association; + /** @var \Cake\ORM\Association\BelongsToMany */ + return $this->_associations->load(BelongsToMany::class, $associated, $options); } /** @@ -2169,9 +2156,7 @@ protected function _update(EntityInterface $entity, array $data): EntityInterfac ->where($primaryKey) ->execute(); - $success = $statement->errorCode() === '00000' ? $entity : false; - - return $success; + return $statement->errorCode() === '00000' ? $entity : false; } /** From 97f9567528cf4bb859df0d364fd6a37b846d2ab2 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 22 Mar 2022 19:32:24 +0530 Subject: [PATCH 1755/2059] Update psalm's config. --- Association/BelongsToMany.php | 1 + Table.php | 1 + 2 files changed, 2 insertions(+) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index ed94d3f6..ffde7e49 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1225,6 +1225,7 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { $property = $this->getProperty(); if (count($inserts)) { + /** @psalm-suppress RedundantConditionGivenDocblockType */ $inserted = array_combine( array_keys($inserts), (array)$sourceEntity->get($property) diff --git a/Table.php b/Table.php index eaf502c0..d2bd9611 100644 --- a/Table.php +++ b/Table.php @@ -2043,6 +2043,7 @@ protected function _insert(EntityInterface $entity, array $data): EntityInterfac $id = (array)$this->_newId($primary) + $keys; // Generate primary keys preferring values in $data. + /** @psalm-suppress RedundantConditionGivenDocblockType */ $primary = array_combine($primary, $id) ?: []; $primary = array_intersect_key($data, $primary) + $primary; From 68637871f28c8c56fbb907b65a86f3310a24de3c Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Fri, 25 Mar 2022 21:33:57 +0100 Subject: [PATCH 1756/2059] update to PHPStan 1.5 --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 8b0cecbe..1a317ba6 100644 --- a/Table.php +++ b/Table.php @@ -2040,7 +2040,7 @@ protected function _insert(EntityInterface $entity, array $data) $id = (array)$this->_newId($primary) + $keys; // Generate primary keys preferring values in $data. - $primary = array_combine($primary, $id) ?: []; + $primary = array_combine($primary, $id); $primary = array_intersect_key($data, $primary) + $primary; $filteredKeys = array_filter($primary, function ($v) { From aebfb686eaa82721a7dbccf4fbef27053bdb91b0 Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Fri, 25 Mar 2022 21:44:20 +0100 Subject: [PATCH 1757/2059] update to PHPStan 1.5 --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 1a317ba6..8b0cecbe 100644 --- a/Table.php +++ b/Table.php @@ -2040,7 +2040,7 @@ protected function _insert(EntityInterface $entity, array $data) $id = (array)$this->_newId($primary) + $keys; // Generate primary keys preferring values in $data. - $primary = array_combine($primary, $id); + $primary = array_combine($primary, $id) ?: []; $primary = array_intersect_key($data, $primary) + $primary; $filteredKeys = array_filter($primary, function ($v) { From 12e02c6f2a33f985c36351079cd9bd360d2a7677 Mon Sep 17 00:00:00 2001 From: Danial Khoshkhou <31406378+danial-k@users.noreply.github.com> Date: Sun, 27 Mar 2022 18:31:22 +0100 Subject: [PATCH 1758/2059] Fix threaded finder example docs --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 8b0cecbe..859eff21 100644 --- a/Table.php +++ b/Table.php @@ -1410,7 +1410,7 @@ public function findList(Query $query, array $options): Query * ``` * $table->find('threaded', [ * 'keyField' => 'id', - * 'parentField' => 'ancestor_id' + * 'parentField' => 'ancestor_id', * 'nestingKey' => 'children' * ]); * ``` From 93307498925e635d92439a8dcc232d4b7c822897 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 9 Apr 2022 22:27:00 -0400 Subject: [PATCH 1759/2059] Fix missing use of binding key in BelongsToMany Add bindingKey to the generated alias to improve matching query generation. Fix #16432 --- Association/BelongsToMany.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 46f29158..a781e32b 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -426,6 +426,7 @@ protected function _generateJunctionAssociations(Table $junction, Table $source, if (!$junction->hasAssociation($sAlias)) { $junction->belongsTo($sAlias, [ + 'bindingKey' => $this->getBindingKey(), 'foreignKey' => $this->getForeignKey(), 'targetTable' => $source, ]); From cf6e7bbdfa3ffba82dbbafcdf4bfa2e3ace0eef4 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 17 May 2022 18:11:33 +0530 Subject: [PATCH 1760/2059] Fix Model.afterSaveCommit triggering inconsistency. When using Table::saveMany() the entities are now cleaned up after Model.afterSaveCommit event is dispatched, similar to what's done when saving individual entities with Table::save(). Closes #16499 --- Table.php | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/Table.php b/Table.php index 728636eb..3f0ea5fe 100644 --- a/Table.php +++ b/Table.php @@ -1853,6 +1853,7 @@ public function save(EntityInterface $entity, $options = []) 'checkRules' => true, 'checkExisting' => true, '_primary' => true, + '_cleanOnSuccess' => true, ]); if ($entity->hasErrors((bool)$options['associated'])) { @@ -1872,8 +1873,10 @@ public function save(EntityInterface $entity, $options = []) $this->dispatchEvent('Model.afterSaveCommit', compact('entity', 'options')); } if ($options['atomic'] || $options['_primary']) { - $entity->clean(); - $entity->setNew(false); + if ($options['_cleanOnSuccess']) { + $entity->clean(); + $entity->setNew(false); + } $entity->setSource($this->getRegistryAlias()); } } @@ -2221,10 +2224,11 @@ protected function _saveMany(iterable $entities, $options = []): iterable '_primary' => true, ] ); + $options['_cleanOnSuccess'] = false; /** @var array $isNew */ $isNew = []; - $cleanup = function ($entities) use (&$isNew): void { + $cleanupOnFailure = function ($entities) use (&$isNew): void { /** @var array<\Cake\Datasource\EntityInterface> $entities */ foreach ($entities as $key => $entity) { if (isset($isNew[$key]) && $isNew[$key]) { @@ -2249,20 +2253,40 @@ protected function _saveMany(iterable $entities, $options = []): iterable } }); } catch (Exception $e) { - $cleanup($entities); + $cleanupOnFailure($entities); throw $e; } if ($failed !== null) { - $cleanup($entities); + $cleanupOnFailure($entities); throw new PersistenceFailedException($failed, ['saveMany']); } + $cleanupOnSuccess = function (EntityInterface $entity) use (&$cleanupOnSuccess) { + $entity->clean(); + $entity->setNew(false); + + foreach (array_keys($entity->toArray()) as $field) { + $value = $entity->get($field); + + if ($value instanceof EntityInterface) { + $cleanupOnSuccess($value); + } elseif (is_array($value) && current($value) instanceof EntityInterface) { + foreach ($value as $associated) { + $cleanupOnSuccess($associated); + } + } + } + }; + if ($this->_transactionCommitted($options['atomic'], $options['_primary'])) { foreach ($entities as $entity) { $this->dispatchEvent('Model.afterSaveCommit', compact('entity', 'options')); + if ($options['atomic'] || $options['_primary']) { + $cleanupOnSuccess($entity); + } } } From 195fb1f720fe7cfaee9ef6a921184e8b96644d31 Mon Sep 17 00:00:00 2001 From: Robert Gasch Date: Wed, 18 May 2022 20:08:50 +0200 Subject: [PATCH 1761/2059] Fix crash when you have a field which has the same name as your table If you have a column that has the same name as your table, the original version of this code crashes because you're trying to use the "+" operator to perform a merge of a string to an array. Example: Table "AccountName" Fields: - ID - AccountNumber - AccountName In this case, the "isset($data[$tableName])" check is true and the code then tries to use the "+" operator to merge a string to the data array. This change fixes this since it will only perform the "+" based merge if "$data[$tableName]" is an array --- Marshaller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Marshaller.php b/Marshaller.php index 6d52e7e1..7511aafa 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -287,7 +287,7 @@ protected function _prepareDataAndOptions(array $data, array $options): array $options += ['validate' => true]; $tableName = $this->_table->getAlias(); - if (isset($data[$tableName])) { + if (isset($data[$tableName]) && is_array($data[$tableName])) { $data += $data[$tableName]; unset($data[$tableName]); } From dea1c6b8ef289a8317fa9d06ee45aabd26c83c9d Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Sun, 22 May 2022 02:33:54 -0500 Subject: [PATCH 1762/2059] Convert all Query parameters to closures --- EagerLoader.php | 10 +++++----- Query.php | 43 ++++++++++++++++++++++--------------------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index f6e2b774..6fe54c90 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -115,7 +115,7 @@ class EagerLoader * - `foreignKey`: Used to set a different field to match both tables, if set to false * no join conditions will be generated automatically * - `fields`: An array with the fields that should be fetched from the association - * - `queryBuilder`: Equivalent to passing a callable instead of an options array + * - `queryBuilder`: Equivalent to passing a callback instead of an options array * - `matching`: Whether to inform the association class that it should filter the * main query by the results fetched by that class. * - `joinType`: For joinable associations, the SQL join type to use. @@ -124,11 +124,11 @@ class EagerLoader * @param array|string $associations List of table aliases to be queried. * When this method is called multiple times it will merge previous list with * the new one. - * @param callable|null $queryBuilder The query builder callable. + * @param \Closure|null $queryBuilder The query builder callback. * @return array Containments. * @throws \InvalidArgumentException When using $queryBuilder with an array of $associations */ - public function contain(array|string $associations, ?callable $queryBuilder = null): array + public function contain(array|string $associations, ?Closure $queryBuilder = null): array { if ($queryBuilder) { if (!is_string($associations)) { @@ -231,12 +231,12 @@ public function isAutoFieldsEnabled(): bool * - `negateMatch`: Whether to add conditions negate match on target association * * @param string $associationPath Dot separated association path, 'Name1.Name2.Name3'. - * @param callable|null $builder the callback function to be used for setting extra + * @param \Closure|null $builder the callback function to be used for setting extra * options to the filtering query. * @param array $options Extra options for the association matching. * @return $this */ - public function setMatching(string $associationPath, ?callable $builder = null, array $options = []) + public function setMatching(string $associationPath, ?Closure $builder = null, array $options = []) { $this->_matching ??= new static(); diff --git a/Query.php b/Query.php index e6838d2f..985ae792 100644 --- a/Query.php +++ b/Query.php @@ -27,6 +27,7 @@ use Cake\Datasource\QueryTrait; use Cake\Datasource\RepositoryInterface; use Cake\Datasource\ResultSetInterface; +use Closure; use InvalidArgumentException; use JsonSerializable; use RuntimeException; @@ -98,12 +99,12 @@ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface protected bool $aliasingEnabled = true; /** - * A callable function that can be used to calculate the total amount of + * A callback used to calculate the total amount of * records this query will match when not using `limit` * - * @var callable|null + * @var \Closure|null */ - protected $_counter; + protected ?Closure $_counter = null; /** * Instance of a class responsible for storing association containments and @@ -188,7 +189,7 @@ public function getRepository(): Table * real field to be aliased. It is possible to alias strings, Expression objects or * even other Query objects. * - * If a callable function is passed, the returning array of the function will + * If a callback is passed, the returning array of the function will * be used as the list of fields. * * By default this function will append any passed argument to the list of fields @@ -214,13 +215,13 @@ public function getRepository(): Table * all the fields in the schema of the table or the association will be added to * the select clause. * - * @param \Cake\Database\ExpressionInterface|\Cake\ORM\Table|\Cake\ORM\Association|callable|array|string|float|int $fields Fields + * @param \Cake\Database\ExpressionInterface|\Cake\ORM\Table|\Cake\ORM\Association|\Closure|array|string|float|int $fields Fields * to be added to the list. * @param bool $overwrite whether to reset fields with passed list or not * @return $this */ public function select( - ExpressionInterface|Table|Association|callable|array|string|float|int $fields = [], + ExpressionInterface|Table|Association|Closure|array|string|float|int $fields = [], bool $overwrite = false ) { if ($fields instanceof Association) { @@ -368,7 +369,7 @@ public function getEagerLoader(): EagerLoader * - `fields`: An array with the fields that should be fetched from the association. * - `finder`: The finder to use when loading associated records. Either the name of the * finder as a string, or an array to define options to pass to the finder. - * - `queryBuilder`: Equivalent to passing a callable instead of an options array. + * - `queryBuilder`: Equivalent to passing a callback instead of an options array. * * ### Example: * @@ -422,13 +423,13 @@ public function getEagerLoader(): EagerLoader * previous list will be emptied. * * @param array|string $associations List of table aliases to be queried. - * @param callable|bool $override The query builder for the association, or + * @param \Closure|bool $override The query builder for the association, or * if associations is an array, a bool on whether to override previous list * with the one passed * defaults to merging previous list with the new one. * @return $this */ - public function contain(array|string $associations, callable|bool $override = false) + public function contain(array|string $associations, Closure|bool $override = false) { $loader = $this->getEagerLoader(); if ($override === true) { @@ -436,7 +437,7 @@ public function contain(array|string $associations, callable|bool $override = fa } $queryBuilder = null; - if (is_callable($override)) { + if ($override instanceof Closure) { $queryBuilder = $override; } @@ -547,11 +548,11 @@ protected function _addAssociationsToTypeMap(Table $table, TypeMap $typeMap, arr * add more complex clauses you can do it directly in the main query. * * @param string $assoc The association to filter by - * @param callable|null $builder a function that will receive a pre-made query object + * @param \Closure|null $builder a function that will receive a pre-made query object * that can be used to add custom conditions or selecting some fields * @return $this */ - public function matching(string $assoc, ?callable $builder = null) + public function matching(string $assoc, ?Closure $builder = null) { $result = $this->getEagerLoader()->setMatching($assoc, $builder)->getMatching(); $this->_addAssociationsToTypeMap($this->getRepository(), $this->getTypeMap(), $result); @@ -619,11 +620,11 @@ public function matching(string $assoc, ?callable $builder = null) * add more complex clauses you can do it directly in the main query. * * @param string $assoc The association to join with - * @param callable|null $builder a function that will receive a pre-made query object + * @param \Closure|null $builder a function that will receive a pre-made query object * that can be used to add custom conditions or selecting some fields * @return $this */ - public function leftJoinWith(string $assoc, ?callable $builder = null) + public function leftJoinWith(string $assoc, ?Closure $builder = null) { $result = $this->getEagerLoader() ->setMatching($assoc, $builder, [ @@ -667,12 +668,12 @@ public function leftJoinWith(string $assoc, ?callable $builder = null) * will select no fields from the association. * * @param string $assoc The association to join with - * @param callable|null $builder a function that will receive a pre-made query object + * @param \Closure|null $builder a function that will receive a pre-made query object * that can be used to add custom conditions or selecting some fields * @return $this * @see \Cake\ORM\Query::matching() */ - public function innerJoinWith(string $assoc, ?callable $builder = null) + public function innerJoinWith(string $assoc, ?Closure $builder = null) { $result = $this->getEagerLoader() ->setMatching($assoc, $builder, [ @@ -732,11 +733,11 @@ public function innerJoinWith(string $assoc, ?callable $builder = null) * add more complex clauses you can do it directly in the main query. * * @param string $assoc The association to filter by - * @param callable|null $builder a function that will receive a pre-made query object + * @param \Closure|null $builder a function that will receive a pre-made query object * that can be used to add custom conditions or selecting some fields * @return $this */ - public function notMatching(string $assoc, ?callable $builder = null) + public function notMatching(string $assoc, ?Closure $builder = null) { $result = $this->getEagerLoader() ->setMatching($assoc, $builder, [ @@ -977,7 +978,7 @@ protected function _performCount(): int } /** - * Registers a callable function that will be executed when the `count` method in + * Registers a callback that will be executed when the `count` method in * this query is called. The return value for the function will be set as the * return value of the `count` method. * @@ -991,10 +992,10 @@ protected function _performCount(): int * If the first param is a null value, the built-in counter function will be called * instead * - * @param callable|null $counter The counter value + * @param \Closure|null $counter The counter value * @return $this */ - public function counter(?callable $counter) + public function counter(?Closure $counter) { $this->_counter = $counter; From f7841c90365f9f9d8c0a1a4171816011d3d4301c Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Sun, 22 May 2022 02:47:01 -0500 Subject: [PATCH 1763/2059] Use first-class callable syntax --- Association/BelongsTo.php | 2 +- Association/HasMany.php | 2 +- Association/HasOne.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index bd818a6b..294739f8 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -207,7 +207,7 @@ public function eagerLoader(array $options): Closure 'bindingKey' => $this->getBindingKey(), 'strategy' => $this->getStrategy(), 'associationType' => $this->type(), - 'finder' => [$this, 'find'], + 'finder' => $this->find(...), ]); return $loader->buildEagerLoader($options); diff --git a/Association/HasMany.php b/Association/HasMany.php index 47aee00b..2f88a5ce 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -662,7 +662,7 @@ public function eagerLoader(array $options): Closure 'strategy' => $this->getStrategy(), 'associationType' => $this->type(), 'sort' => $this->getSort(), - 'finder' => [$this, 'find'], + 'finder' => $this->find(...), ]); return $loader->buildEagerLoader($options); diff --git a/Association/HasOne.php b/Association/HasOne.php index a3b8826b..ac3fa712 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -150,7 +150,7 @@ public function eagerLoader(array $options): Closure 'bindingKey' => $this->getBindingKey(), 'strategy' => $this->getStrategy(), 'associationType' => $this->type(), - 'finder' => [$this, 'find'], + 'finder' => $this->find(...), ]); return $loader->buildEagerLoader($options); From f0292eb9c31137e97a3dc9f892842abc9cf7c427 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Wed, 25 May 2022 17:31:20 -0500 Subject: [PATCH 1764/2059] Change QueryTrait callable parameters to closures --- Behavior/TranslateBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 9a7cf68e..7ccd0ac1 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -324,7 +324,7 @@ public function findTranslations(Query $query, array $options): Query return $query; }]) - ->formatResults([$this->getStrategy(), 'groupTranslations'], $query::PREPEND); + ->formatResults($this->getStrategy()->groupTranslations(...), $query::PREPEND); } /** From 6587266d6a2eaa874bce44578d18725818da3c56 Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Sat, 4 Jun 2022 22:56:10 +0200 Subject: [PATCH 1765/2059] add ormDelete option to TreeBehavior --- Behavior/TreeBehavior.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 154d10d2..73689f0e 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -75,6 +75,7 @@ class TreeBehavior extends Behavior 'scope' => null, 'level' => null, 'recoverOrder' => null, + 'enableOrmDelete' => false, ]; /** @@ -227,15 +228,22 @@ public function beforeDelete(EventInterface $event, EntityInterface $entity) if ($diff > 2) { $query = $this->_scope($this->_table->query()) - ->delete() ->where(function ($exp) use ($config, $left, $right) { /** @var \Cake\Database\Expression\QueryExpression $exp */ return $exp ->gte($config['leftField'], $left + 1) ->lte($config['leftField'], $right - 1); }); - $statement = $query->execute(); - $statement->closeCursor(); + if($this->getConfig('enableOrmDelete')){ + $result = $query->toArray(); + foreach($result as $entity) { + $this->_table->delete($entity, ['atomic' => false]); + } + } else { + $query->delete(); + $statement = $query->execute(); + $statement->closeCursor(); + } } $this->_sync($diff, '-', "> {$right}"); From 0892e4e89d4b86c13a99f6bf154a774d4c078bc8 Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Sat, 4 Jun 2022 23:03:30 +0200 Subject: [PATCH 1766/2059] fix cs --- Behavior/TreeBehavior.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 73689f0e..38c95f43 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -234,9 +234,9 @@ public function beforeDelete(EventInterface $event, EntityInterface $entity) ->gte($config['leftField'], $left + 1) ->lte($config['leftField'], $right - 1); }); - if($this->getConfig('enableOrmDelete')){ + if ($this->getConfig('enableOrmDelete')) { $result = $query->toArray(); - foreach($result as $entity) { + foreach ($result as $entity) { $this->_table->delete($entity, ['atomic' => false]); } } else { From e5584e0423d5b41f3b630bd986759becf789c499 Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Sat, 4 Jun 2022 23:57:07 +0200 Subject: [PATCH 1767/2059] rename enableOrmDelete to useOrmDelete --- Behavior/TreeBehavior.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 38c95f43..056271b4 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -75,7 +75,7 @@ class TreeBehavior extends Behavior 'scope' => null, 'level' => null, 'recoverOrder' => null, - 'enableOrmDelete' => false, + 'useOrmDelete' => false, ]; /** @@ -234,7 +234,7 @@ public function beforeDelete(EventInterface $event, EntityInterface $entity) ->gte($config['leftField'], $left + 1) ->lte($config['leftField'], $right - 1); }); - if ($this->getConfig('enableOrmDelete')) { + if ($this->getConfig('useOrmDelete')) { $result = $query->toArray(); foreach ($result as $entity) { $this->_table->delete($entity, ['atomic' => false]); From 54539db76185964c725798e706e195018805800a Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Sun, 5 Jun 2022 08:59:40 +0200 Subject: [PATCH 1768/2059] rename variables --- Behavior/TreeBehavior.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 056271b4..6c158a91 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -235,9 +235,9 @@ public function beforeDelete(EventInterface $event, EntityInterface $entity) ->lte($config['leftField'], $right - 1); }); if ($this->getConfig('useOrmDelete')) { - $result = $query->toArray(); - foreach ($result as $entity) { - $this->_table->delete($entity, ['atomic' => false]); + $entities = $query->toArray(); + foreach ($entities as $entityToDelete) { + $this->_table->delete($entityToDelete, ['atomic' => false]); } } else { $query->delete(); From 1f9f9761dcc7f99f007589913291f7de8452ca2d Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Sun, 5 Jun 2022 16:03:50 +0200 Subject: [PATCH 1769/2059] rename useOrmDelete to cascadeCallbacks --- Behavior/TreeBehavior.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 6c158a91..b009d0d5 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -75,7 +75,7 @@ class TreeBehavior extends Behavior 'scope' => null, 'level' => null, 'recoverOrder' => null, - 'useOrmDelete' => false, + 'cascadeCallbacks' => false, ]; /** @@ -234,7 +234,7 @@ public function beforeDelete(EventInterface $event, EntityInterface $entity) ->gte($config['leftField'], $left + 1) ->lte($config['leftField'], $right - 1); }); - if ($this->getConfig('useOrmDelete')) { + if ($this->getConfig('cascadeCallbacks')) { $entities = $query->toArray(); foreach ($entities as $entityToDelete) { $this->_table->delete($entityToDelete, ['atomic' => false]); From 252d13368d866de5dd7bd3ce0b34c1397a195144 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 12 Jun 2022 22:45:21 -0400 Subject: [PATCH 1770/2059] Fix saving belongstomany with complex key values Fix incorrectly persisted data when a belongsToMany association foreignKey/bindingKey fields were modeled as complex types like datetime instances. The strict comparison on the entire key data needed to be replaced with a more nuanced solution. At worst this code will operate in linear time, but I think that N will be <2 for the vast majority of applications. I've attempted to make the solution as efficient as possible and failures should only make one loop iteration. Fixes #16562 --- Association/BelongsToMany.php | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index a781e32b..94758f4b 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -36,6 +36,7 @@ * that contains the association fields between the source and the target table. * * An example of a BelongsToMany association would be Article belongs to many Tags. + * In this example 'Article' is the source table and 'Tags' is the target table. */ class BelongsToMany extends Association { @@ -1277,7 +1278,25 @@ protected function _diffLinks( $fields = $result->extract($keys); $found = false; foreach ($indexed as $i => $data) { - if ($fields === $data) { + $matched = false; + foreach ($keys as $key) { + if (!array_key_exists($key, $data) || !array_key_exists($key, $fields)) { + // Either side missing is no match. + $matched = false; + } elseif (is_object($data[$key]) && is_object($fields[$key])) { + // If both sides are an object then use == so that value objects + // are seen as equivalent. + $matched = $fields[$key] == $data[$key]; + } else { + // Use strict equality for all other values. + $matched = $fields[$key] === $data[$key]; + } + // Stop checks on first failure. + if (!$matched) { + break; + } + } + if ($matched) { unset($indexed[$i]); $found = true; break; From d01fac700a9c6ff9fe231e4ffd2062ba7f7a7d36 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 13 Jun 2022 10:06:48 -0400 Subject: [PATCH 1771/2059] Improve variable naming. --- Association/BelongsToMany.php | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 94758f4b..5cb632ae 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1267,29 +1267,29 @@ protected function _diffLinks( $assocForeignKey = (array)$belongsTo->getForeignKey(); $keys = array_merge($foreignKey, $assocForeignKey); - $deletes = $indexed = $present = []; + $deletes = $unmatchedEntityKeys = $present = []; foreach ($jointEntities as $i => $entity) { - $indexed[$i] = $entity->extract($keys); + $unmatchedEntityKeys[$i] = $entity->extract($keys); $present[$i] = array_values($entity->extract($assocForeignKey)); } - foreach ($existing as $result) { - $fields = $result->extract($keys); + foreach ($existing as $existingLink) { + $existingKeys = $existingLink->extract($keys); $found = false; - foreach ($indexed as $i => $data) { + foreach ($unmatchedEntityKeys as $i => $unmatchedKeys) { $matched = false; foreach ($keys as $key) { - if (!array_key_exists($key, $data) || !array_key_exists($key, $fields)) { + if (!array_key_exists($key, $unmatchedKeys) || !array_key_exists($key, $existingKeys)) { // Either side missing is no match. $matched = false; - } elseif (is_object($data[$key]) && is_object($fields[$key])) { + } elseif (is_object($unmatchedKeys[$key]) && is_object($existingKeys[$key])) { // If both sides are an object then use == so that value objects // are seen as equivalent. - $matched = $fields[$key] == $data[$key]; + $matched = $existingKeys[$key] == $unmatchedKeys[$key]; } else { // Use strict equality for all other values. - $matched = $fields[$key] === $data[$key]; + $matched = $existingKeys[$key] === $unmatchedKeys[$key]; } // Stop checks on first failure. if (!$matched) { @@ -1297,14 +1297,15 @@ protected function _diffLinks( } } if ($matched) { - unset($indexed[$i]); + // Remove the unmatched entity so we don't look at it again. + unset($unmatchedEntityKeys[$i]); $found = true; break; } } if (!$found) { - $deletes[] = $result; + $deletes[] = $existingLink; } } From 1fdfcb91b08cd7c8611293e9bfd7d24ab9d64b46 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 13 Jun 2022 11:05:25 -0400 Subject: [PATCH 1772/2059] Remove redundant case Both arrays will always have all keys because of how EntityTrait::extract() works. --- Association/BelongsToMany.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 5cb632ae..f4340225 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1280,10 +1280,7 @@ protected function _diffLinks( foreach ($unmatchedEntityKeys as $i => $unmatchedKeys) { $matched = false; foreach ($keys as $key) { - if (!array_key_exists($key, $unmatchedKeys) || !array_key_exists($key, $existingKeys)) { - // Either side missing is no match. - $matched = false; - } elseif (is_object($unmatchedKeys[$key]) && is_object($existingKeys[$key])) { + if (is_object($unmatchedKeys[$key]) && is_object($existingKeys[$key])) { // If both sides are an object then use == so that value objects // are seen as equivalent. $matched = $existingKeys[$key] == $unmatchedKeys[$key]; From c521c6a5dff3ca6e2746bbe1d74033242d4a2f46 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 16 Jun 2022 13:19:34 +0530 Subject: [PATCH 1773/2059] Fix CS error --- Association/BelongsToMany.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index cc1f3cca..ae014537 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1266,6 +1266,7 @@ protected function _diffLinks( $target = $this->getTarget(); $belongsTo = $junction->getAssociation($target->getAlias()); $foreignKey = (array)$this->getForeignKey(); + /** @var array $assocForeignKey */ $assocForeignKey = (array)$belongsTo->getForeignKey(); /** @var array $keys */ From 5dfe71539a328931d29c31cd8acd96c5ee9a6fb3 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Wed, 22 Jun 2022 23:22:14 -0500 Subject: [PATCH 1774/2059] Use class-string directly in annotations --- BehaviorRegistry.php | 4 ++-- Table.php | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 19b16896..63735994 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -100,11 +100,11 @@ public static function className(string $class): ?string * Part of the template method for Cake\Core\ObjectRegistry::load() * * @param string $class Partial classname to resolve. - * @return string|null Either the correct class name or null. - * @psalm-return class-string|null + * @return class-string<\Cake\ORM\Behavior>|null Either the correct class name or null. */ protected function _resolveClassName(string $class): ?string { + /** @var class-string<\Cake\ORM\Behavior>|null */ return static::className($class); } diff --git a/Table.php b/Table.php index 0d19ec51..30d52c5a 100644 --- a/Table.php +++ b/Table.php @@ -693,8 +693,7 @@ public function getDisplayField(): array|string|null /** * Returns the class used to hydrate rows for this table. * - * @return string - * @psalm-return class-string<\Cake\Datasource\EntityInterface> + * @return class-string<\Cake\Datasource\EntityInterface> */ public function getEntityClass(): string { From 3646cac84a6be2055bad86973fa57300dff98a2a Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 25 Jun 2022 11:57:37 +0530 Subject: [PATCH 1775/2059] Mimic the exception thrown by ModelAwareTrait::loadModel(). Bake has a feature which specifically checks for the UnexpectedValueException. This accounts for the default table set to an empty string too. --- Locator/LocatorAwareTrait.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index 15713052..1aa4a9e3 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -16,9 +16,9 @@ */ namespace Cake\ORM\Locator; -use Cake\Core\Exception\CakeException; use Cake\Datasource\FactoryLocator; use Cake\ORM\Table; +use UnexpectedValueException; /** * Contains method for setting and accessing LocatorInterface instance @@ -83,8 +83,10 @@ public function getTableLocator(): LocatorInterface public function fetchTable(?string $alias = null, array $options = []): Table { $alias = $alias ?? $this->defaultTable; - if ($alias === null) { - throw new CakeException('You must provide an `$alias` or set the `$defaultTable` property.'); + if (empty($alias)) { + throw new UnexpectedValueException( + 'You must provide an `$alias` or set the `$defaultTable` property to a non empty string.' + ); } return $this->getTableLocator()->get($alias, $options); From d7fd42a38773955b1cc93a2a458336a23c00778c Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 29 Jun 2022 13:28:06 +0530 Subject: [PATCH 1776/2059] Update PHP version constraint in split packages. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 14fbafb7..8f2f0cd2 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "source": "https://github.com/cakephp/orm" }, "require": { - "php": ">=8.0", + "php": ">=8.1", "cakephp/collection": "^5.0", "cakephp/core": "^5.0", "cakephp/datasource": "^5.0", From 72cd5542e45e05af66f79395681fae7b6fe98ce3 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Sat, 9 Jul 2022 01:09:06 -0500 Subject: [PATCH 1777/2059] Replace RuntimeException with CakeException, DatabaseException or InvalidArgumentException --- Association.php | 14 ++++++------- Association/BelongsTo.php | 8 +++---- Association/Loader/SelectLoader.php | 6 +++--- Association/Loader/SelectWithPivotLoader.php | 6 +++--- Behavior/TimestampBehavior.php | 4 ++-- Behavior/TreeBehavior.php | 10 ++++----- EagerLoadable.php | 6 +++--- Locator/TableLocator.php | 4 ++-- Query.php | 10 ++++----- Rule/ExistsIn.php | 6 +++--- Rule/LinkConstraint.php | 4 ++-- Table.php | 22 ++++++++++---------- 12 files changed, 50 insertions(+), 50 deletions(-) diff --git a/Association.php b/Association.php index f43ffd67..f9865488 100644 --- a/Association.php +++ b/Association.php @@ -20,6 +20,7 @@ use Cake\Collection\CollectionInterface; use Cake\Core\App; use Cake\Core\ConventionsTrait; +use Cake\Database\Exception\DatabaseException; use Cake\Database\Expression\IdentifierExpression; use Cake\Database\Expression\QueryExpression; use Cake\Database\ExpressionInterface; @@ -29,7 +30,6 @@ use Cake\Utility\Inflector; use Closure; use InvalidArgumentException; -use RuntimeException; /** * An Association is a relationship established between two tables and is used @@ -381,7 +381,7 @@ public function getTarget(): Table $errorMessage .= 'You can\'t have an association of the same name with a different target '; $errorMessage .= '"className" option anywhere in your app.'; - throw new RuntimeException(sprintf( + throw new DatabaseException(sprintf( $errorMessage, isset($this->_sourceTable) ? get_class($this->_sourceTable) : 'null', $this->getName(), @@ -724,7 +724,7 @@ public function attachTo(Query $query, array $options = []): void if (!empty($options['queryBuilder'])) { $dummy = $options['queryBuilder']($dummy); if (!($dummy instanceof Query)) { - throw new RuntimeException(sprintf( + throw new DatabaseException(sprintf( 'Query builder for association "%s" did not return a query', $this->getName() )); @@ -736,7 +736,7 @@ public function attachTo(Query $query, array $options = []): void $this->_strategy === static::STRATEGY_JOIN && $dummy->getContain() ) { - throw new RuntimeException( + throw new DatabaseException( "`{$this->getName()}` association cannot contain() associations when using JOIN strategy." ); } @@ -1060,7 +1060,7 @@ protected function _bindNewAssociations(Query $query, Query $surrogate, array $o * * @param array $options list of options passed to attachTo method * @return array - * @throws \RuntimeException if the number of columns in the foreignKey do not + * @throws \Cake\Database\Exception\DatabaseException if the number of columns in the foreignKey do not * match the number of columns in the source table primaryKey */ protected function _joinCondition(array $options): array @@ -1078,11 +1078,11 @@ protected function _joinCondition(array $options): array $table = $this->getSource()->getTable(); } $msg = 'The "%s" table does not define a primary key, and cannot have join conditions generated.'; - throw new RuntimeException(sprintf($msg, $table)); + throw new DatabaseException(sprintf($msg, $table)); } $msg = 'Cannot match provided foreignKey for "%s", got "(%s)" but expected foreign key for "(%s)"'; - throw new RuntimeException(sprintf( + throw new DatabaseException(sprintf( $msg, $this->_name, implode(', ', $foreignKey), diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 294739f8..7d7a0286 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -16,6 +16,7 @@ */ namespace Cake\ORM\Association; +use Cake\Database\Exception\DatabaseException; use Cake\Database\Expression\IdentifierExpression; use Cake\Datasource\EntityInterface; use Cake\ORM\Association; @@ -23,7 +24,6 @@ use Cake\ORM\Table; use Cake\Utility\Inflector; use Closure; -use RuntimeException; /** * Represents an 1 - N relationship where the source side of the relation is @@ -159,7 +159,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []): En * * @param array $options list of options passed to attachTo method * @return array<\Cake\Database\Expression\IdentifierExpression> - * @throws \RuntimeException if the number of columns in the foreignKey do not + * @throws \Cake\Database\Exception\DatabaseException if the number of columns in the foreignKey do not * match the number of columns in the target table primaryKey */ protected function _joinCondition(array $options): array @@ -173,11 +173,11 @@ protected function _joinCondition(array $options): array if (count($foreignKey) !== count($bindingKey)) { if (empty($bindingKey)) { $msg = 'The "%s" table does not define a primary key. Please set one.'; - throw new RuntimeException(sprintf($msg, $this->getTarget()->getTable())); + throw new DatabaseException(sprintf($msg, $this->getTarget()->getTable())); } $msg = 'Cannot match provided foreignKey for "%s", got "(%s)" but expected foreign key for "(%s)"'; - throw new RuntimeException(sprintf( + throw new DatabaseException(sprintf( $msg, $this->_name, implode(', ', $foreignKey), diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 41b0b17c..a4d4f274 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -16,6 +16,7 @@ */ namespace Cake\ORM\Association\Loader; +use Cake\Database\Exception\DatabaseException; use Cake\Database\Expression\IdentifierExpression; use Cake\Database\Expression\TupleComparison; use Cake\Database\ExpressionInterface; @@ -24,7 +25,6 @@ use Cake\ORM\Query; use Closure; use InvalidArgumentException; -use RuntimeException; /** * Implements the logic for loading an association using a SELECT query @@ -370,7 +370,7 @@ protected function _createTupleCondition( * * @param array $options The options for getting the link field. * @return array|string - * @throws \RuntimeException + * @throws \Cake\Database\Exception\DatabaseException */ protected function _linkField(array $options): array|string { @@ -380,7 +380,7 @@ protected function _linkField(array $options): array|string if ($options['foreignKey'] === false && $this->associationType === Association::ONE_TO_MANY) { $msg = 'Cannot have foreignKey = false for hasMany associations. ' . 'You must provide a foreignKey column.'; - throw new RuntimeException($msg); + throw new DatabaseException($msg); } $keys = in_array($this->associationType, [Association::ONE_TO_ONE, Association::ONE_TO_MANY], true) ? diff --git a/Association/Loader/SelectWithPivotLoader.php b/Association/Loader/SelectWithPivotLoader.php index c6bd0b9e..d3bf670c 100644 --- a/Association/Loader/SelectWithPivotLoader.php +++ b/Association/Loader/SelectWithPivotLoader.php @@ -16,11 +16,11 @@ */ namespace Cake\ORM\Association\Loader; +use Cake\Database\Exception\DatabaseException; use Cake\Database\ExpressionInterface; use Cake\ORM\Association\HasMany; use Cake\ORM\Query; use Closure; -use RuntimeException; /** * Implements the logic for loading an association using a SELECT query and a pivot table @@ -170,7 +170,7 @@ protected function _linkField(array $options): array|string * @param \Cake\ORM\Query $fetchQuery The query to get results from * @param array $options The options passed to the eager loader * @return array - * @throws \RuntimeException when the association property is not part of the results set. + * @throws \Cake\Database\Exception\DatabaseException when the association property is not part of the results set. */ protected function _buildResultMap(Query $fetchQuery, array $options): array { @@ -179,7 +179,7 @@ protected function _buildResultMap(Query $fetchQuery, array $options): array foreach ($fetchQuery->all() as $result) { if (!isset($result[$this->junctionProperty])) { - throw new RuntimeException(sprintf( + throw new DatabaseException(sprintf( '"%s" is missing from the belongsToMany results. Results cannot be created.', $this->junctionProperty )); diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 48494661..aab2b344 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -23,7 +23,7 @@ use Cake\I18n\DateTime; use Cake\ORM\Behavior; use DateTimeInterface; -use RuntimeException; +use InvalidArgumentException; use UnexpectedValueException; /** @@ -220,7 +220,7 @@ protected function _updateField(EntityInterface $entity, string $field, bool $re $type = TypeFactory::build($columnType); if (!$type instanceof DateTimeType) { - throw new RuntimeException('TimestampBehavior only supports columns of type DateTimeType.'); + throw new InvalidArgumentException('TimestampBehavior only supports columns of type DateTimeType.'); } $class = $type->getDateTimeClassName(); diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index ae7d70bb..f34251d1 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -17,6 +17,7 @@ namespace Cake\ORM\Behavior; use Cake\Collection\CollectionInterface; +use Cake\Database\Exception\DatabaseException; use Cake\Database\Expression\IdentifierExpression; use Cake\Database\Expression\QueryExpression; use Cake\Datasource\EntityInterface; @@ -25,7 +26,6 @@ use Cake\ORM\Behavior; use Cake\ORM\Query; use InvalidArgumentException; -use RuntimeException; /** * Makes the table to which this is attached to behave like a nested set and @@ -96,7 +96,7 @@ public function initialize(array $config): void * @param \Cake\Event\EventInterface $event The beforeSave event that was fired * @param \Cake\Datasource\EntityInterface $entity the entity that is going to be saved * @return void - * @throws \RuntimeException if the parent to set for the node is invalid + * @throws \Cake\Database\Exception\DatabaseException if the parent to set for the node is invalid */ public function beforeSave(EventInterface $event, EntityInterface $entity): void { @@ -108,7 +108,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity): void $level = $config['level']; if ($parent && $entity->get($primaryKey) === $parent) { - throw new RuntimeException("Cannot set a node's parent as itself"); + throw new DatabaseException("Cannot set a node's parent as itself"); } if ($isNew) { @@ -256,7 +256,7 @@ public function beforeDelete(EventInterface $event, EntityInterface $entity): vo * @param \Cake\Datasource\EntityInterface $entity The entity to re-parent * @param mixed $parent the id of the parent to set * @return void - * @throws \RuntimeException if the parent to set to the entity is not valid + * @throws \Cake\Database\Exception\DatabaseException if the parent to set to the entity is not valid */ protected function _setParent(EntityInterface $entity, mixed $parent): void { @@ -269,7 +269,7 @@ protected function _setParent(EntityInterface $entity, mixed $parent): void $left = $entity->get($config['left']); if ($parentLeft > $left && $parentLeft < $right) { - throw new RuntimeException(sprintf( + throw new DatabaseException(sprintf( 'Cannot use node "%s" as parent for entity "%s"', $parent, $entity->get($this->_getPrimaryKey()) diff --git a/EagerLoadable.php b/EagerLoadable.php index 14c5ed54..99e84907 100644 --- a/EagerLoadable.php +++ b/EagerLoadable.php @@ -16,7 +16,7 @@ */ namespace Cake\ORM; -use RuntimeException; +use Cake\Database\Exception\DatabaseException; /** * Represents a single level in the associations tree to be eagerly loaded @@ -170,12 +170,12 @@ public function associations(): array * Gets the Association class instance to use for loading the records. * * @return \Cake\ORM\Association - * @throws \RuntimeException + * @throws \Cake\Database\Exception\DatabaseException */ public function instance(): Association { if ($this->_instance === null) { - throw new RuntimeException('No instance set.'); + throw new DatabaseException('No instance set.'); } return $this->_instance; diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 75203eeb..b9de3d95 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -17,6 +17,7 @@ namespace Cake\ORM\Locator; use Cake\Core\App; +use Cake\Database\Exception\DatabaseException; use Cake\Datasource\ConnectionManager; use Cake\Datasource\Locator\AbstractLocator; use Cake\Datasource\RepositoryInterface; @@ -24,7 +25,6 @@ use Cake\ORM\Exception\MissingTableClassException; use Cake\ORM\Table; use Cake\Utility\Inflector; -use RuntimeException; /** * Provides a default registry/factory for Table objects. @@ -141,7 +141,7 @@ public function setConfig($alias, $options = null) } if (isset($this->instances[$alias])) { - throw new RuntimeException(sprintf( + throw new DatabaseException(sprintf( 'You cannot configure "%s", it has already been constructed.', $alias )); diff --git a/Query.php b/Query.php index 985ae792..eeac8481 100644 --- a/Query.php +++ b/Query.php @@ -18,6 +18,7 @@ use ArrayObject; use Cake\Database\Connection; +use Cake\Database\Exception\DatabaseException; use Cake\Database\ExpressionInterface; use Cake\Database\Query as DatabaseQuery; use Cake\Database\TypedResultInterface; @@ -30,7 +31,6 @@ use Closure; use InvalidArgumentException; use JsonSerializable; -use RuntimeException; /** * Extends the base Query class to provide new methods related to association @@ -1052,12 +1052,12 @@ public function isHydrationEnabled(): bool * @param \Cake\Cache\CacheEngine|string $config Either the name of the cache config to use, or * a cache config instance. * @return $this - * @throws \RuntimeException When you attempt to cache a non-select query. + * @throws \Cake\Database\Exception\DatabaseException When you attempt to cache a non-select query. */ public function cache($key, $config = 'default') { if ($this->_type !== self::TYPE_SELECT) { - throw new RuntimeException('You cannot cache the results of non-select queries.'); + throw new DatabaseException('You cannot cache the results of non-select queries.'); } return $this->_cache($key, $config); @@ -1067,12 +1067,12 @@ public function cache($key, $config = 'default') * {@inheritDoc} * * @return \Cake\Datasource\ResultSetInterface - * @throws \RuntimeException if this method is called on a non-select Query. + * @throws \Cake\Database\Exception\DatabaseException if this method is called on a non-select Query. */ public function all(): ResultSetInterface { if ($this->_type !== self::TYPE_SELECT) { - throw new RuntimeException( + throw new DatabaseException( 'You cannot call all() on a non-select query. Use execute() instead.' ); } diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 98c2975f..ae1c9f39 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -16,10 +16,10 @@ */ namespace Cake\ORM\Rule; +use Cake\Database\Exception\DatabaseException; use Cake\Datasource\EntityInterface; use Cake\ORM\Association; use Cake\ORM\Table; -use RuntimeException; /** * Checks that the value provided in a field exists as the primary key of another @@ -76,14 +76,14 @@ public function __construct(array|string $fields, Table|Association|string $repo * @param \Cake\Datasource\EntityInterface $entity The entity from where to extract the fields * @param array $options Options passed to the check, * where the `repository` key is required. - * @throws \RuntimeException When the rule refers to an undefined association. + * @throws \Cake\Database\Exception\DatabaseException When the rule refers to an undefined association. * @return bool */ public function __invoke(EntityInterface $entity, array $options): bool { if (is_string($this->_repository)) { if (!$options['repository']->hasAssociation($this->_repository)) { - throw new RuntimeException(sprintf( + throw new DatabaseException(sprintf( "ExistsIn rule for '%s' is invalid. '%s' is not associated with '%s'.", implode(', ', $this->_fields), $this->_repository, diff --git a/Rule/LinkConstraint.php b/Rule/LinkConstraint.php index 439f69d3..f0b39954 100644 --- a/Rule/LinkConstraint.php +++ b/Rule/LinkConstraint.php @@ -16,11 +16,11 @@ */ namespace Cake\ORM\Rule; +use Cake\Database\Exception\DatabaseException; use Cake\Datasource\EntityInterface; use Cake\ORM\Association; use Cake\ORM\Table; use InvalidArgumentException; -use RuntimeException; /** * Checks whether links to a given association exist / do not exist. @@ -164,7 +164,7 @@ protected function _countLinks(Association $association, EntityInterface $entity $primaryKey = (array)$source->getPrimaryKey(); if (!$entity->has($primaryKey)) { - throw new RuntimeException(sprintf( + throw new DatabaseException(sprintf( 'LinkConstraint rule on `%s` requires all primary key values for building the counting ' . 'conditions, expected values for `(%s)`, got `(%s)`.', $source->getAlias(), diff --git a/Table.php b/Table.php index 30d52c5a..6191790c 100644 --- a/Table.php +++ b/Table.php @@ -21,6 +21,7 @@ use Cake\Core\App; use Cake\Core\Configure; use Cake\Database\Connection; +use Cake\Database\Exception\DatabaseException; use Cake\Database\Expression\QueryExpression; use Cake\Database\Schema\TableSchemaInterface; use Cake\Database\TypeFactory; @@ -48,7 +49,6 @@ use Closure; use Exception; use InvalidArgumentException; -use RuntimeException; /** * Represents a single database table. @@ -558,12 +558,12 @@ public function setSchema(TableSchemaInterface|array $schema) * queries fit into the max length allowed by database driver. * * @return void - * @throws \RuntimeException When an alias combination is too long + * @throws \Cake\Database\Exception\DatabaseException When an alias combination is too long */ protected function checkAliasLengths(): void { if ($this->_schema === null) { - throw new RuntimeException("Unable to check max alias lengths for `{$this->getAlias()}` without schema."); + throw new DatabaseException("Unable to check max alias lengths for `{$this->getAlias()}` without schema."); } $maxLength = $this->getConnection()->getDriver()->getMaxAliasLength(); @@ -575,7 +575,7 @@ protected function checkAliasLengths(): void foreach ($this->_schema->columns() as $name) { if (strlen($table . '__' . $name) > $maxLength) { $nameLength = $maxLength - 2; - throw new RuntimeException( + throw new DatabaseException( 'ORM queries generate field aliases using the table name/alias and column name. ' . "The table alias `{$table}` and column `{$name}` create an alias longer than ({$nameLength}). " . 'You must change the table schema in the database and shorten either the table or column ' . @@ -1911,7 +1911,7 @@ public function saveOrFail(EntityInterface $entity, array $options = []): Entity * @param \Cake\Datasource\EntityInterface $entity the entity to be saved * @param \ArrayObject $options the options to use for the save operation * @return \Cake\Datasource\EntityInterface|false - * @throws \RuntimeException When an entity is missing some of the primary keys. + * @throws \Cake\Database\Exception\DatabaseException When an entity is missing some of the primary keys. * @throws \Cake\ORM\Exception\RolledbackTransactionException If the transaction * is aborted in the afterSave event. */ @@ -1943,7 +1943,7 @@ protected function _processSave(EntityInterface $entity, ArrayObject $options): } if ($result !== false && !($result instanceof EntityInterface)) { - throw new RuntimeException(sprintf( + throw new DatabaseException(sprintf( 'The beforeSave callback must return `false` or `EntityInterface` instance. Got `%s` instead.', get_debug_type($result) )); @@ -2028,7 +2028,7 @@ protected function _onSaveSuccess(EntityInterface $entity, ArrayObject $options) * @param \Cake\Datasource\EntityInterface $entity the subject entity from were $data was extracted * @param array $data The actual data that needs to be saved * @return \Cake\Datasource\EntityInterface|false - * @throws \RuntimeException if not all the primary keys where supplied or could + * @throws \Cake\Database\Exception\DatabaseException if not all the primary keys where supplied or could * be generated when the table has composite primary keys. Or when the table has no primary key. */ protected function _insert(EntityInterface $entity, array $data): EntityInterface|false @@ -2039,7 +2039,7 @@ protected function _insert(EntityInterface $entity, array $data): EntityInterfac 'Cannot insert row in "%s" table, it has no primary key.', $this->getTable() ); - throw new RuntimeException($msg); + throw new DatabaseException($msg); } $keys = array_fill(0, count($primary), null); $id = (array)$this->_newId($primary) + $keys; @@ -2064,7 +2064,7 @@ protected function _insert(EntityInterface $entity, array $data): EntityInterfac implode(', ', $filteredKeys + $entity->extract(array_keys($primary))), implode(', ', array_keys($primary)) ); - throw new RuntimeException($msg); + throw new DatabaseException($msg); } } } @@ -2653,13 +2653,13 @@ public function __call(string $method, array $args): mixed * * @param string $property the association name * @return \Cake\ORM\Association - * @throws \RuntimeException if no association with such name exists + * @throws \Cake\Database\Exception\DatabaseException if no association with such name exists */ public function __get(string $property): Association { $association = $this->_associations->get($property); if (!$association) { - throw new RuntimeException(sprintf( + throw new DatabaseException(sprintf( 'Undefined property `%s`. ' . 'You have not defined the `%s` association on `%s`.', $property, From b4763a209f78148a4194a468bfbe517529b6e8cd Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 9 Jul 2022 18:11:57 +0530 Subject: [PATCH 1778/2059] Fix array casting of datetime objects. --- Table.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 3f0ea5fe..b2a2fd92 100644 --- a/Table.php +++ b/Table.php @@ -1498,12 +1498,21 @@ protected function _setFieldMatchers(array $options, array $keys): array */ public function get($primaryKey, array $options = []): EntityInterface { + if ($primaryKey === null) { + throw new InvalidPrimaryKeyException(sprintf( + 'Record not found in table "%s" with primary key [NULL]', + $this->getTable() + )); + } + $key = (array)$this->getPrimaryKey(); $alias = $this->getAlias(); foreach ($key as $index => $keyname) { $key[$index] = $alias . '.' . $keyname; } - $primaryKey = (array)$primaryKey; + if (!is_array($primaryKey)) { + $primaryKey = [$primaryKey]; + } if (count($key) !== count($primaryKey)) { $primaryKey = $primaryKey ?: [null]; $primaryKey = array_map(function ($key) { From 1d22fdc9bfdfe1bb15ee1b05e3966f468b33f362 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 14 Jul 2022 22:11:48 +0530 Subject: [PATCH 1779/2059] Remove redundant class type checks. The return type declarations for the methods already ensure a TypeError is thrown with appropriate message in case of type mismatch. --- AssociationCollection.php | 9 +-------- Table.php | 4 +--- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/AssociationCollection.php b/AssociationCollection.php index f73f0592..5e7d6ff2 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -82,6 +82,7 @@ public function add(string $alias, Association $association): Association * @param array $options List of options to configure the association definition. * @return \Cake\ORM\Association * @throws \InvalidArgumentException + * @psalm-param class-string<\Cake\ORM\Association> $className */ public function load(string $className, string $associated, array $options = []): Association { @@ -90,14 +91,6 @@ public function load(string $className, string $associated, array $options = []) ]; $association = new $className($associated, $options); - if (!$association instanceof Association) { - $message = sprintf( - 'The association must extend `%s` class, `%s` given.', - Association::class, - get_class($association) - ); - throw new InvalidArgumentException($message); - } return $this->add($association->getName(), $association); } diff --git a/Table.php b/Table.php index b2a2fd92..6f614487 100644 --- a/Table.php +++ b/Table.php @@ -859,9 +859,7 @@ public function getBehavior(string $name): Behavior )); } - $behavior = $this->_behaviors->get($name); - - return $behavior; + return $this->_behaviors->get($name); } /** From 92ac9bc3d74843b8d1ddb00eb82e80c631eb37c7 Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Fri, 15 Jul 2022 14:40:34 +0200 Subject: [PATCH 1780/2059] fix ResultSet index being changed when an exception is thrown inside a loop --- ResultSet.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ResultSet.php b/ResultSet.php index d81d6351..abf19207 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -572,8 +572,12 @@ protected function _groupResult(array $row) */ public function __debugInfo() { + $currentIndex = $this->_index; + // toArray() adjusts the current index, so we have to reset it + $items = $this->toArray(); + $this->_index = $currentIndex; return [ - 'items' => $this->toArray(), + 'items' => $items, ]; } } From 560cc1bc3fde6755e33b5bae574876d4dae7e812 Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Fri, 15 Jul 2022 14:53:28 +0200 Subject: [PATCH 1781/2059] fix phpcs --- ResultSet.php | 1 + 1 file changed, 1 insertion(+) diff --git a/ResultSet.php b/ResultSet.php index abf19207..d193c0a9 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -576,6 +576,7 @@ public function __debugInfo() // toArray() adjusts the current index, so we have to reset it $items = $this->toArray(); $this->_index = $currentIndex; + return [ 'items' => $items, ]; From dcb8427c53bf60bf0fd40b26bbaa6b7ea32869f5 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 16 Jul 2022 19:10:30 +0530 Subject: [PATCH 1782/2059] Ensure TableLocator returns the same instance when using a table's registry alias. Refs #16419 --- Locator/TableLocator.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 7f9fd029..67a87520 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -215,8 +215,6 @@ protected function createInstance(string $alias, array $options) $options = ['alias' => $classAlias] + $options; } elseif (!isset($options['alias'])) { $options['className'] = $alias; - /** @psalm-suppress PossiblyFalseOperand */ - $alias = substr($alias, strrpos($alias, '\\') + 1, -5); } if (isset($this->_config[$alias])) { From e054c4c6de7d68b19bf8028e5158c5803ade4b0f Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Sat, 16 Jul 2022 23:24:52 +0200 Subject: [PATCH 1783/2059] cleanup variables before return --- EagerLoader.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index 44412c04..dcd89001 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -706,9 +706,8 @@ public function associationsMap(Table $table): array /** @psalm-suppress PossiblyNullReference */ $map = $this->_buildAssociationsMap($map, $this->_matching->normalized($table), true); $map = $this->_buildAssociationsMap($map, $this->normalized($table)); - $map = $this->_buildAssociationsMap($map, $this->_joinsMap); - return $map; + return $this->_buildAssociationsMap($map, $this->_joinsMap); } /** From 6838ecbe3e34a76849cc0d530023cddc6f29b992 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 16 Jul 2022 14:12:09 +0530 Subject: [PATCH 1784/2059] Use assert() for asserting valid class types. This allows the assertion code to be totally skipped in production using the zend.assertions ini config. --- Behavior/TimestampBehavior.php | 8 ++++---- Query.php | 9 ++++----- Table.php | 13 ++++++++----- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index aab2b344..754813f5 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -23,7 +23,6 @@ use Cake\I18n\DateTime; use Cake\ORM\Behavior; use DateTimeInterface; -use InvalidArgumentException; use UnexpectedValueException; /** @@ -219,9 +218,10 @@ protected function _updateField(EntityInterface $entity, string $field, bool $re /** @var \Cake\Database\Type\DateTimeType $type */ $type = TypeFactory::build($columnType); - if (!$type instanceof DateTimeType) { - throw new InvalidArgumentException('TimestampBehavior only supports columns of type DateTimeType.'); - } + assert( + $type instanceof DateTimeType, + 'TimestampBehavior only supports columns of type DateTimeType.' + ); $class = $type->getDateTimeClassName(); diff --git a/Query.php b/Query.php index eeac8481..980c7cee 100644 --- a/Query.php +++ b/Query.php @@ -29,7 +29,6 @@ use Cake\Datasource\RepositoryInterface; use Cake\Datasource\ResultSetInterface; use Closure; -use InvalidArgumentException; use JsonSerializable; /** @@ -156,13 +155,13 @@ public function __construct(Connection $connection, Table $table) * * @param \Cake\ORM\Table $repository The default table object to use. * @return $this - * @psalm-suppress MoreSpecificImplementedParamType */ public function repository(RepositoryInterface $repository) { - if (!$repository instanceof Table) { - throw new InvalidArgumentException('$repository must be an instance of Cake\ORM\Table.'); - } + assert( + $repository instanceof Table, + '$repository must be an instance of Cake\ORM\Table.' + ); $this->_repository = $repository; diff --git a/Table.php b/Table.php index a771accd..6215865a 100644 --- a/Table.php +++ b/Table.php @@ -1951,11 +1951,14 @@ protected function _processSave(EntityInterface $entity, ArrayObject $options): return false; } - if ($result !== false && !($result instanceof EntityInterface)) { - throw new DatabaseException(sprintf( - 'The beforeSave callback must return `false` or `EntityInterface` instance. Got `%s` instead.', - get_debug_type($result) - )); + if ($result !== false) { + assert( + $result instanceof EntityInterface, + sprintf( + 'The beforeSave callback must return `false` or `EntityInterface` instance. Got `%s` instead.', + get_debug_type($result) + ) + ); } return $result; From 1a7e19e3ad3872296212fb28cea4d80a0ceb445f Mon Sep 17 00:00:00 2001 From: mscherer Date: Thu, 28 Jul 2022 10:59:25 +0200 Subject: [PATCH 1785/2059] Fix up generic docblocks. --- LazyEagerLoader.php | 4 ++-- Table.php | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 2fd6cb9e..0e7a0103 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -135,11 +135,11 @@ protected function _getPropertyMap(Table $source, array $associations): array * Injects the results of the eager loader query into the original list of * entities. * - * @param \Traversable|array<\Cake\Datasource\EntityInterface> $objects The original list of entities + * @param iterable<\Cake\Datasource\EntityInterface> $objects The original list of entities * @param \Cake\ORM\Query $results The loaded results * @param array $associations The top level associations that were loaded * @param \Cake\ORM\Table $source The table where the entities came from - * @return array + * @return array<\Cake\Datasource\EntityInterface> */ protected function _injectResults(iterable $objects, $results, array $associations, Table $source): array { diff --git a/Table.php b/Table.php index 6f614487..e96d56b0 100644 --- a/Table.php +++ b/Table.php @@ -2178,9 +2178,9 @@ protected function _update(EntityInterface $entity, array $data) * any one of the records fails to save due to failed validation or database * error. * - * @param \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface> $entities Entities to save. + * @param iterable<\Cake\Datasource\EntityInterface> $entities Entities to save. * @param \Cake\ORM\SaveOptionsBuilder|\ArrayAccess|array $options Options used when calling Table::save() for each entity. - * @return \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface>|false False on failure, entities list on success. + * @return iterable<\Cake\Datasource\EntityInterface>|false False on failure, entities list on success. * @throws \Exception */ public function saveMany(iterable $entities, $options = []) @@ -2199,9 +2199,9 @@ public function saveMany(iterable $entities, $options = []) * any one of the records fails to save due to failed validation or database * error. * - * @param \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface> $entities Entities to save. + * @param iterable<\Cake\Datasource\EntityInterface> $entities Entities to save. * @param \ArrayAccess|array $options Options used when calling Table::save() for each entity. - * @return \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface> Entities list. + * @return iterable<\Cake\Datasource\EntityInterface> Entities list. * @throws \Exception * @throws \Cake\ORM\Exception\PersistenceFailedException If an entity couldn't be saved. */ @@ -2211,11 +2211,11 @@ public function saveManyOrFail(iterable $entities, $options = []): iterable } /** - * @param \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface> $entities Entities to save. + * @param iterable<\Cake\Datasource\EntityInterface> $entities Entities to save. * @param \Cake\ORM\SaveOptionsBuilder|\ArrayAccess|array $options Options used when calling Table::save() for each entity. * @throws \Cake\ORM\Exception\PersistenceFailedException If an entity couldn't be saved. * @throws \Exception If an entity couldn't be saved. - * @return \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface> Entities list. + * @return iterable<\Cake\Datasource\EntityInterface> Entities list. */ protected function _saveMany(iterable $entities, $options = []): iterable { @@ -2359,9 +2359,9 @@ public function delete(EntityInterface $entity, $options = []): bool * any one of the records fails to delete due to failed validation or database * error. * - * @param \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface> $entities Entities to delete. + * @param iterable<\Cake\Datasource\EntityInterface> $entities Entities to delete. * @param \ArrayAccess|array $options Options used when calling Table::save() for each entity. - * @return \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface>|false Entities list + * @return iterable<\Cake\Datasource\EntityInterface>|false Entities list * on success, false on failure. * @see \Cake\ORM\Table::delete() for options and events related to this method. */ @@ -2383,9 +2383,9 @@ public function deleteMany(iterable $entities, $options = []) * any one of the records fails to delete due to failed validation or database * error. * - * @param \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface> $entities Entities to delete. + * @param iterable<\Cake\Datasource\EntityInterface> $entities Entities to delete. * @param \ArrayAccess|array $options Options used when calling Table::save() for each entity. - * @return \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface> Entities list. + * @return iterable<\Cake\Datasource\EntityInterface> Entities list. * @throws \Cake\ORM\Exception\PersistenceFailedException * @see \Cake\ORM\Table::delete() for options and events related to this method. */ @@ -2401,7 +2401,7 @@ public function deleteManyOrFail(iterable $entities, $options = []): iterable } /** - * @param \Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface> $entities Entities to delete. + * @param iterable<\Cake\Datasource\EntityInterface> $entities Entities to delete. * @param \ArrayAccess|array $options Options used. * @return \Cake\Datasource\EntityInterface|null */ @@ -2903,7 +2903,7 @@ public function patchEntity(EntityInterface $entity, array $data, array $options * You can use the `Model.beforeMarshal` event to modify request data * before it is converted into entities. * - * @param \Traversable|array<\Cake\Datasource\EntityInterface> $entities the entities that will get the + * @param iterable<\Cake\Datasource\EntityInterface> $entities the entities that will get the * data merged in * @param array $data list of arrays to be merged into the entities * @param array $options A list of options for the objects hydration. From 607ef19a00ca73590230a0bd6a1f1e8223220f90 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 29 Jul 2022 18:55:46 +0530 Subject: [PATCH 1786/2059] Fix type errors reported by phpstan --- Table.php | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/Table.php b/Table.php index 658865c5..08d09503 100644 --- a/Table.php +++ b/Table.php @@ -29,7 +29,6 @@ use Cake\Datasource\EntityInterface; use Cake\Datasource\Exception\InvalidPrimaryKeyException; use Cake\Datasource\RepositoryInterface; -use Cake\Datasource\ResultSetInterface; use Cake\Datasource\RulesAwareTrait; use Cake\Event\EventDispatcherInterface; use Cake\Event\EventDispatcherTrait; @@ -2187,9 +2186,9 @@ protected function _update(EntityInterface $entity, array $data): EntityInterfac * @throws \Exception */ public function saveMany( - ResultSetInterface|array $entities, + iterable $entities, array $options = [] - ): ResultSetInterface|array|false { + ): iterable|false { try { return $this->_saveMany($entities, $options); } catch (PersistenceFailedException $exception) { @@ -2210,7 +2209,7 @@ public function saveMany( * @throws \Exception * @throws \Cake\ORM\Exception\PersistenceFailedException If an entity couldn't be saved. */ - public function saveManyOrFail(iterable $entities, array $options = []): ResultSetInterface|array + public function saveManyOrFail(iterable $entities, array $options = []): iterable { return $this->_saveMany($entities, $options); } @@ -2223,9 +2222,9 @@ public function saveManyOrFail(iterable $entities, array $options = []): ResultS * @return iterable<\Cake\Datasource\EntityInterface> Entities list. */ protected function _saveMany( - ResultSetInterface|array $entities, + iterable $entities, array $options = [] - ): ResultSetInterface|array { + ): iterable { $options = new ArrayObject( $options + [ 'atomic' => true, @@ -2238,7 +2237,6 @@ protected function _saveMany( /** @var array $isNew */ $isNew = []; $cleanupOnFailure = function ($entities) use (&$isNew): void { - /** @var array<\Cake\Datasource\EntityInterface> $entities */ foreach ($entities as $key => $entity) { if (isset($isNew[$key]) && $isNew[$key]) { $entity->unset($this->getPrimaryKey()); @@ -2247,17 +2245,12 @@ protected function _saveMany( } }; - /** @var \Cake\Datasource\EntityInterface|null $failed */ $failed = null; try { $this->getConnection() ->transactional(function () use ($entities, $options, &$isNew, &$failed) { // Cache array cast since options are the same for each entity $options = (array)$options; - /** - * @var string $key - * @var \Cake\Datasource\EntityInterface $entity - */ foreach ($entities as $key => $entity) { $isNew[$key] = $entity->isNew(); if ($this->save($entity, $options) === false) { @@ -2374,7 +2367,7 @@ public function delete(EntityInterface $entity, $options = []): bool * on success, false on failure. * @see \Cake\ORM\Table::delete() for options and events related to this method. */ - public function deleteMany(iterable $entities, array $options = []): ResultSetInterface|array|false + public function deleteMany(iterable $entities, array $options = []): iterable|false { $failed = $this->_deleteMany($entities, $options); From 6514d52c7de0642c9f5556c268cb516002118be0 Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Wed, 3 Aug 2022 22:05:12 +0200 Subject: [PATCH 1787/2059] use new str_ methods --- Behavior.php | 2 +- Marshaller.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Behavior.php b/Behavior.php index c30122b1..b071c732 100644 --- a/Behavior.php +++ b/Behavior.php @@ -416,7 +416,7 @@ protected function _reflectionCache(): array continue; } - if (substr($methodName, 0, 4) === 'find') { + if (str_starts_with($methodName, 'find')) { $return['finders'][lcfirst(substr($methodName, 4))] = $methodName; } else { $return['methods'][$methodName] = $methodName; diff --git a/Marshaller.php b/Marshaller.php index c68b116a..63ce11f0 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -91,7 +91,7 @@ protected function _buildPropertyMap(array $data, array $options): array // If the key is not a special field like _ids or _joinData // it is a missing association that we should error on. if (!$this->_table->hasAssociation($key)) { - if (substr($key, 0, 1) !== '_') { + if (!str_starts_with($key, '_')) { throw new InvalidArgumentException(sprintf( 'Cannot marshal data for "%s" association. It is not associated with "%s".', (string)$key, From e7b69de8e8c8d3a6322b65611051442deb3d28e1 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Wed, 17 Aug 2022 12:21:45 +0200 Subject: [PATCH 1788/2059] Fix up assoc docblocks. (#16689) Fix up assoc docblocks. Co-authored-by: ADmad --- BehaviorRegistry.php | 4 ++-- EagerLoadable.php | 2 +- EagerLoader.php | 2 +- ResultSet.php | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 9afbb9a6..e4b76e1c 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -46,14 +46,14 @@ class BehaviorRegistry extends ObjectRegistry implements EventDispatcherInterfac /** * Method mappings. * - * @var array + * @var array */ protected $_methodMap = []; /** * Finder method mappings. * - * @var array + * @var array */ protected $_finderMap = []; diff --git a/EagerLoadable.php b/EagerLoadable.php index 411f1ebb..741fa6ce 100644 --- a/EagerLoadable.php +++ b/EagerLoadable.php @@ -51,7 +51,7 @@ class EagerLoadable * A list of options to pass to the association object for loading * the records. * - * @var array + * @var array */ protected $_config = []; diff --git a/EagerLoader.php b/EagerLoader.php index dcd89001..1778c32d 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -91,7 +91,7 @@ class EagerLoader * A map of table aliases pointing to the association objects they represent * for the query. * - * @var array + * @var array */ protected $_joinsMap = []; diff --git a/ResultSet.php b/ResultSet.php index d193c0a9..40b8af9a 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -73,7 +73,7 @@ class ResultSet implements ResultSetInterface * List of associations that should be placed under the `_matchingData` * result key. * - * @var array + * @var array */ protected $_matchingMap = []; @@ -88,7 +88,7 @@ class ResultSet implements ResultSetInterface * Map of fields that are fetched from the statement with * their type and the table they belong to * - * @var array + * @var array */ protected $_map = []; @@ -96,7 +96,7 @@ class ResultSet implements ResultSetInterface * List of matching associations and the column keys to expect * from each of them. * - * @var array + * @var array */ protected $_matchingMapColumns = []; From 2389d50f702ff888c3acf516a729a79eecb54314 Mon Sep 17 00:00:00 2001 From: mscherer Date: Thu, 18 Aug 2022 22:50:50 +0200 Subject: [PATCH 1789/2059] Fix up assoc return docblocks. --- Association.php | 4 ++-- Association/Loader/SelectLoader.php | 2 +- Behavior/Translate/ShadowTableStrategy.php | 4 ++-- EagerLoadable.php | 4 ++-- ResultSet.php | 2 +- Rule/IsUnique.php | 2 +- SaveOptionsBuilder.php | 6 +++--- Table.php | 4 ++-- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Association.php b/Association.php index 86ef6984..7d2cf61c 100644 --- a/Association.php +++ b/Association.php @@ -837,10 +837,10 @@ public function transformRow(array $row, string $nestKey, bool $joined, ?string * with the default empty value according to whether the association was * joined or fetched externally. * - * @param array $row The row to set a default on. + * @param array $row The row to set a default on. * @param bool $joined Whether the row is a result of a direct join * with this association - * @return array + * @return array */ public function defaultRowValue(array $row, bool $joined): array { diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 6a2591d4..dce3b320 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -133,7 +133,7 @@ public function buildEagerLoader(array $options): Closure /** * Returns the default options to use for the eagerLoader * - * @return array + * @return array */ protected function _defaultOptions(): array { diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 328fbcbe..a4ac9764 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -593,7 +593,7 @@ protected function bundleTranslatedFields($entity) /** * Lazy define and return the main table fields. * - * @return array + * @return array */ protected function mainFields() { @@ -613,7 +613,7 @@ protected function mainFields() /** * Lazy define and return the translation table fields. * - * @return array + * @return array */ protected function translatedFields() { diff --git a/EagerLoadable.php b/EagerLoadable.php index 741fa6ce..fc5e226f 100644 --- a/EagerLoadable.php +++ b/EagerLoadable.php @@ -250,7 +250,7 @@ public function setConfig(array $config) * Gets the list of options to pass to the association object for loading * the records. * - * @return array + * @return array */ public function getConfig(): array { @@ -291,7 +291,7 @@ public function targetProperty(): ?string * Returns a representation of this object that can be passed to * Cake\ORM\EagerLoader::contain() * - * @return array + * @return array */ public function asContainArray(): array { diff --git a/ResultSet.php b/ResultSet.php index 40b8af9a..1fe3cf69 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -568,7 +568,7 @@ protected function _groupResult(array $row) * Returns an array that can be used to describe the internal state of this * object. * - * @return array + * @return array */ public function __debugInfo() { diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index f06f5140..a86bfe3e 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -93,7 +93,7 @@ public function __invoke(EntityInterface $entity, array $options): bool * * @param string $alias The alias to add. * @param array $conditions The conditions to alias. - * @return array + * @return array */ protected function _alias(string $alias, array $conditions): array { diff --git a/SaveOptionsBuilder.php b/SaveOptionsBuilder.php index 1bbf4b2b..ad81f905 100644 --- a/SaveOptionsBuilder.php +++ b/SaveOptionsBuilder.php @@ -35,7 +35,7 @@ class SaveOptionsBuilder extends ArrayObject /** * Options * - * @var array + * @var array */ protected $_options = []; @@ -66,7 +66,7 @@ public function __construct(Table $table, array $options = []) * This can be used to turn an options array into the object. * * @throws \InvalidArgumentException If a given option key does not exist. - * @param array $array Options array. + * @param array $array Options array. * @return $this */ public function parseArrayOptions(array $array) @@ -201,7 +201,7 @@ public function atomic(bool $atomic) } /** - * @return array + * @return array */ public function toArray(): array { diff --git a/Table.php b/Table.php index e96d56b0..f3e58492 100644 --- a/Table.php +++ b/Table.php @@ -1444,7 +1444,7 @@ public function findThreaded(Query $query, array $options): Query * @param array $options the original options passed to a finder * @param array $keys the keys to check in $options to build matchers from * the associated value - * @return array + * @return array */ protected function _setFieldMatchers(array $options, array $keys): array { @@ -3105,7 +3105,7 @@ protected function validationMethodExists(string $name): bool * Returns an array that can be used to describe the internal state of this * object. * - * @return array + * @return array */ public function __debugInfo() { From 63d44112e002175d14757a4e88b34f309a71d9c6 Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Fri, 26 Aug 2022 18:42:53 +0200 Subject: [PATCH 1790/2059] add EnumBehavior --- Behavior/EnumBehavior.php | 125 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 Behavior/EnumBehavior.php diff --git a/Behavior/EnumBehavior.php b/Behavior/EnumBehavior.php new file mode 100644 index 00000000..505c2752 --- /dev/null +++ b/Behavior/EnumBehavior.php @@ -0,0 +1,125 @@ +addBehavior('Enum', [ + * 'fieldMap' => [ + * 'published' => ArticleStatus::class + * ] + * ]); + * ``` + */ +class EnumBehavior extends Behavior +{ + /** + * Default config + * + * These are merged with user-provided configuration when the behavior is used. + * + * @var array + */ + protected array $_defaultConfig = [ + 'fieldMap' => [], + ]; + + /** + * Transform array data into entity data + * + * @param \Cake\Event\EventInterface $event The beforeMarshal event that was fired + * @param \ArrayObject $data Data present before converting to an entity + * @param \ArrayObject $options Options passed to the event + * @return void + */ + public function beforeMarshal(EventInterface $event, ArrayObject $data, ArrayObject $options): void + { + $fieldMap = $this->getConfig('fieldMap'); + /** @var \BackedEnum $enumType */ + foreach ($fieldMap as $field => $enumType) { + if ($data->offsetExists($field)) { + $value = $data->offsetGet($field); + if (is_int($value) || is_string($value)) { + $data->offsetSet($field, $enumType::from($value)); + } + } + } + } + + /** + * Transform entity fields into enum instances if they are present. + * + * @param \Cake\Event\EventInterface $event The beforeFind event that was fired + * @param \Cake\ORM\Query $query Query + * @param \ArrayObject $options The options for the query + * @return void + */ + public function beforeFind(EventInterface $event, Query $query, ArrayObject $options): void + { + $query->formatResults(function ($results) { + return $results->map(function (Entity $entity) { + return $this->setEnumField($entity); + }); + }); + } + + /** + * Set enum instances on mapped fields after an entity has been saved + * + * @param \Cake\Event\EventInterface $event The afterSave event that was fired + * @param \Cake\Datasource\EntityInterface $entity The entity being updated + * @param \ArrayObject $options The options for the query + * @return void + */ + public function afterSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void + { + $this->setEnumField($entity); + } + + /** + * @param \Cake\Datasource\EntityInterface $entity The entity to transform data on + * @return \Cake\Datasource\EntityInterface + */ + private function setEnumField(EntityInterface $entity): EntityInterface + { + $fieldMap = $this->getConfig('fieldMap'); + /** @var \BackedEnum $enumType */ + foreach ($fieldMap as $field => $enumType) { + if ($entity->has($field)) { + $dbValue = $entity->get($field); + if (!$dbValue instanceof BackedEnum) { + $entity->set($field, $enumType::from($dbValue)); + } + } + } + + return $entity; + } +} From fec59011979ad14194ae321d2faa76ad239ccf38 Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Fri, 26 Aug 2022 19:52:10 +0200 Subject: [PATCH 1791/2059] Update src/ORM/Behavior/EnumBehavior.php Co-authored-by: ADmad --- Behavior/EnumBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/EnumBehavior.php b/Behavior/EnumBehavior.php index 505c2752..a7565770 100644 --- a/Behavior/EnumBehavior.php +++ b/Behavior/EnumBehavior.php @@ -107,7 +107,7 @@ public function afterSave(EventInterface $event, EntityInterface $entity, ArrayO * @param \Cake\Datasource\EntityInterface $entity The entity to transform data on * @return \Cake\Datasource\EntityInterface */ - private function setEnumField(EntityInterface $entity): EntityInterface + protected function setEnumFields(EntityInterface $entity): EntityInterface { $fieldMap = $this->getConfig('fieldMap'); /** @var \BackedEnum $enumType */ From cb994d20ab8ca92d3b69be715ea869e33d07dca0 Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Sat, 27 Aug 2022 10:21:11 +0200 Subject: [PATCH 1792/2059] add generic EnumType --- Behavior/EnumBehavior.php | 42 +++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/Behavior/EnumBehavior.php b/Behavior/EnumBehavior.php index a7565770..199cc35d 100644 --- a/Behavior/EnumBehavior.php +++ b/Behavior/EnumBehavior.php @@ -23,6 +23,7 @@ use Cake\ORM\Behavior; use Cake\ORM\Entity; use Cake\ORM\Query; +use ValueError; /** * Enum behavior @@ -52,29 +53,54 @@ class EnumBehavior extends Behavior ]; /** - * Transform array data into entity data + * Initialize hook + * + * @param array $config The config for this behavior. + * @return void + */ + public function initialize(array $config): void + { + $schema = $this->_table->getSchema(); + foreach (array_keys($this->getConfig('fieldMap')) as $field) { + $schema->setColumnType($field, 'enum'); + } + $this->_table->setSchema($schema); + } + + /** + * Transform scalar values to enum instances * * @param \Cake\Event\EventInterface $event The beforeMarshal event that was fired - * @param \ArrayObject $data Data present before converting to an entity + * @param \Cake\Datasource\EntityInterface $entity Entity created after marshaling + * @param \ArrayObject $data The original request data * @param \ArrayObject $options Options passed to the event * @return void */ - public function beforeMarshal(EventInterface $event, ArrayObject $data, ArrayObject $options): void - { + public function afterMarshal( + EventInterface $event, + EntityInterface $entity, + ArrayObject $data, + ArrayObject $options + ): void { $fieldMap = $this->getConfig('fieldMap'); /** @var \BackedEnum $enumType */ foreach ($fieldMap as $field => $enumType) { if ($data->offsetExists($field)) { $value = $data->offsetGet($field); if (is_int($value) || is_string($value)) { - $data->offsetSet($field, $enumType::from($value)); + try { + $enumValue = $enumType::from($value); + $entity->set($field, $enumValue); + } catch (ValueError $error) { + $entity->setError($field, __('Given value is not valid')); + } } } } } /** - * Transform entity fields into enum instances if they are present. + * Transform entity fields into enum instances if they are present * * @param \Cake\Event\EventInterface $event The beforeFind event that was fired * @param \Cake\ORM\Query $query Query @@ -85,7 +111,7 @@ public function beforeFind(EventInterface $event, Query $query, ArrayObject $opt { $query->formatResults(function ($results) { return $results->map(function (Entity $entity) { - return $this->setEnumField($entity); + return $this->setEnumFields($entity); }); }); } @@ -100,7 +126,7 @@ public function beforeFind(EventInterface $event, Query $query, ArrayObject $opt */ public function afterSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void { - $this->setEnumField($entity); + $this->setEnumFields($entity); } /** From 84e31d904d3503589780af8f54025ddc96715004 Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Sat, 27 Aug 2022 14:58:04 +0200 Subject: [PATCH 1793/2059] minor changes --- Behavior/EnumBehavior.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Behavior/EnumBehavior.php b/Behavior/EnumBehavior.php index 199cc35d..38f0852c 100644 --- a/Behavior/EnumBehavior.php +++ b/Behavior/EnumBehavior.php @@ -23,7 +23,6 @@ use Cake\ORM\Behavior; use Cake\ORM\Entity; use Cake\ORM\Query; -use ValueError; /** * Enum behavior @@ -88,11 +87,11 @@ public function afterMarshal( if ($data->offsetExists($field)) { $value = $data->offsetGet($field); if (is_int($value) || is_string($value)) { - try { - $enumValue = $enumType::from($value); + $enumValue = $enumType::tryFrom($value); + if ($enumValue !== null) { $entity->set($field, $enumValue); - } catch (ValueError $error) { - $entity->setError($field, __('Given value is not valid')); + } else { + $entity->setError($field, __d('cake', 'Given value is not valid')); } } } From 6f3fe9f04f71b26644fc2e9f592297a9a91ead53 Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Sun, 28 Aug 2022 10:23:26 +0200 Subject: [PATCH 1794/2059] change enum behavior config key to fieldEnums --- Behavior/EnumBehavior.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Behavior/EnumBehavior.php b/Behavior/EnumBehavior.php index 38f0852c..5a35e2d0 100644 --- a/Behavior/EnumBehavior.php +++ b/Behavior/EnumBehavior.php @@ -32,7 +32,7 @@ * * Add behavior + config to connect a field with a backed enum * ``` * $this->addBehavior('Enum', [ - * 'fieldMap' => [ + * 'fieldEnums' => [ * 'published' => ArticleStatus::class * ] * ]); @@ -48,7 +48,7 @@ class EnumBehavior extends Behavior * @var array */ protected array $_defaultConfig = [ - 'fieldMap' => [], + 'fieldEnums' => [], ]; /** @@ -60,7 +60,7 @@ class EnumBehavior extends Behavior public function initialize(array $config): void { $schema = $this->_table->getSchema(); - foreach (array_keys($this->getConfig('fieldMap')) as $field) { + foreach (array_keys($this->getConfig('fieldEnums')) as $field) { $schema->setColumnType($field, 'enum'); } $this->_table->setSchema($schema); @@ -81,9 +81,9 @@ public function afterMarshal( ArrayObject $data, ArrayObject $options ): void { - $fieldMap = $this->getConfig('fieldMap'); + $fieldEnums = $this->getConfig('fieldEnums'); /** @var \BackedEnum $enumType */ - foreach ($fieldMap as $field => $enumType) { + foreach ($fieldEnums as $field => $enumType) { if ($data->offsetExists($field)) { $value = $data->offsetGet($field); if (is_int($value) || is_string($value)) { @@ -134,9 +134,9 @@ public function afterSave(EventInterface $event, EntityInterface $entity, ArrayO */ protected function setEnumFields(EntityInterface $entity): EntityInterface { - $fieldMap = $this->getConfig('fieldMap'); + $fieldEnums = $this->getConfig('fieldEnums'); /** @var \BackedEnum $enumType */ - foreach ($fieldMap as $field => $enumType) { + foreach ($fieldEnums as $field => $enumType) { if ($entity->has($field)) { $dbValue = $entity->get($field); if (!$dbValue instanceof BackedEnum) { From ef02bf351ef2d9de960b1c734aac70bb23301904 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 31 Aug 2022 19:45:16 +0530 Subject: [PATCH 1795/2059] Rename QueryInterface::repository() to QueryInterface::setRepository(). This harmonizes the name as per the naming convention followed throughout the framework where all getter/setter method pairs have get/set prefix. --- Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Query.php b/Query.php index cdba19ae..78df7eaf 100644 --- a/Query.php +++ b/Query.php @@ -178,7 +178,7 @@ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface public function __construct(Connection $connection, Table $table) { parent::__construct($connection); - $this->repository($table); + $this->setRepository($table); if ($this->_repository !== null) { $this->addDefaultTypes($this->_repository); From 121b00e9af5c888b513e44e7de78b0559818a0a3 Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Wed, 31 Aug 2022 19:41:19 +0200 Subject: [PATCH 1796/2059] move logic from EnumBehavior to EnumType --- Behavior/EnumBehavior.php | 150 -------------------------------------- 1 file changed, 150 deletions(-) delete mode 100644 Behavior/EnumBehavior.php diff --git a/Behavior/EnumBehavior.php b/Behavior/EnumBehavior.php deleted file mode 100644 index 5a35e2d0..00000000 --- a/Behavior/EnumBehavior.php +++ /dev/null @@ -1,150 +0,0 @@ -addBehavior('Enum', [ - * 'fieldEnums' => [ - * 'published' => ArticleStatus::class - * ] - * ]); - * ``` - */ -class EnumBehavior extends Behavior -{ - /** - * Default config - * - * These are merged with user-provided configuration when the behavior is used. - * - * @var array - */ - protected array $_defaultConfig = [ - 'fieldEnums' => [], - ]; - - /** - * Initialize hook - * - * @param array $config The config for this behavior. - * @return void - */ - public function initialize(array $config): void - { - $schema = $this->_table->getSchema(); - foreach (array_keys($this->getConfig('fieldEnums')) as $field) { - $schema->setColumnType($field, 'enum'); - } - $this->_table->setSchema($schema); - } - - /** - * Transform scalar values to enum instances - * - * @param \Cake\Event\EventInterface $event The beforeMarshal event that was fired - * @param \Cake\Datasource\EntityInterface $entity Entity created after marshaling - * @param \ArrayObject $data The original request data - * @param \ArrayObject $options Options passed to the event - * @return void - */ - public function afterMarshal( - EventInterface $event, - EntityInterface $entity, - ArrayObject $data, - ArrayObject $options - ): void { - $fieldEnums = $this->getConfig('fieldEnums'); - /** @var \BackedEnum $enumType */ - foreach ($fieldEnums as $field => $enumType) { - if ($data->offsetExists($field)) { - $value = $data->offsetGet($field); - if (is_int($value) || is_string($value)) { - $enumValue = $enumType::tryFrom($value); - if ($enumValue !== null) { - $entity->set($field, $enumValue); - } else { - $entity->setError($field, __d('cake', 'Given value is not valid')); - } - } - } - } - } - - /** - * Transform entity fields into enum instances if they are present - * - * @param \Cake\Event\EventInterface $event The beforeFind event that was fired - * @param \Cake\ORM\Query $query Query - * @param \ArrayObject $options The options for the query - * @return void - */ - public function beforeFind(EventInterface $event, Query $query, ArrayObject $options): void - { - $query->formatResults(function ($results) { - return $results->map(function (Entity $entity) { - return $this->setEnumFields($entity); - }); - }); - } - - /** - * Set enum instances on mapped fields after an entity has been saved - * - * @param \Cake\Event\EventInterface $event The afterSave event that was fired - * @param \Cake\Datasource\EntityInterface $entity The entity being updated - * @param \ArrayObject $options The options for the query - * @return void - */ - public function afterSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void - { - $this->setEnumFields($entity); - } - - /** - * @param \Cake\Datasource\EntityInterface $entity The entity to transform data on - * @return \Cake\Datasource\EntityInterface - */ - protected function setEnumFields(EntityInterface $entity): EntityInterface - { - $fieldEnums = $this->getConfig('fieldEnums'); - /** @var \BackedEnum $enumType */ - foreach ($fieldEnums as $field => $enumType) { - if ($entity->has($field)) { - $dbValue = $entity->get($field); - if (!$dbValue instanceof BackedEnum) { - $entity->set($field, $enumType::from($dbValue)); - } - } - } - - return $entity; - } -} From cbd7ae230f8c54f039159c9b1fb32c5a61daf55e Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 31 Aug 2022 12:59:31 +0530 Subject: [PATCH 1797/2059] Fix array types in docblock. --- Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Query.php b/Query.php index cdba19ae..75318f6f 100644 --- a/Query.php +++ b/Query.php @@ -1306,7 +1306,7 @@ public function delete(?string $table = null) * Can be combined with the where() method to create delete queries. * * @param array $columns The columns to insert into. - * @param array $types A map between columns & their datatypes. + * @param array $types A map between columns & their datatypes. * @return $this */ public function insert(array $columns, array $types = []) From 2d428168e73c3b61d92a3e4d35ff6ad2f324cf04 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 1 Sep 2022 22:36:29 +0530 Subject: [PATCH 1798/2059] Bump psalm to v5 for intersection types support. --- Association/BelongsTo.php | 2 +- Association/BelongsToMany.php | 8 ++++---- Association/HasMany.php | 6 +++--- Association/HasOne.php | 2 +- AssociationCollection.php | 4 +++- Behavior/CounterCacheBehavior.php | 4 ++-- Behavior/Translate/ShadowTableStrategy.php | 2 -- Behavior/TreeBehavior.php | 2 -- BehaviorRegistry.php | 1 - Marshaller.php | 3 --- Query.php | 1 + Table.php | 2 -- 12 files changed, 15 insertions(+), 22 deletions(-) diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 7d7a0286..66866b1e 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -143,7 +143,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []): En return false; } - /** @psalm-suppress InvalidScalarArgument getForeign() returns false */ + /** @psalm-suppress InvalidArgument */ $properties = array_combine( (array)$this->getForeignKey(), $targetEntity->extract((array)$this->getBindingKey()) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index ae014537..9b68d8cc 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -583,7 +583,7 @@ public function cascadeDelete(EntityInterface $entity, array $options = []): boo $conditions = []; if (!empty($bindingKey)) { - /** @psalm-suppress InvalidScalarArgument getForeignKey() returns false */ + /** @psalm-suppress InvalidArgument */ $conditions = array_combine($foreignKey, $entity->extract($bindingKey)); } @@ -798,12 +798,12 @@ protected function _saveLinks(EntityInterface $sourceEntity, array $targetEntiti if (!$joint || !($joint instanceof EntityInterface)) { $joint = new $entityClass([], ['markNew' => true, 'source' => $junctionRegistryAlias]); } - /** @psalm-suppress InvalidScalarArgument getForeignKey() returns false */ + /** @psalm-suppress InvalidArgument */ $sourceKeys = array_combine($foreignKey, $sourceEntity->extract($bindingKey)); - /** @psalm-suppress InvalidScalarArgument getForeignKey() returns false */ + /** @psalm-suppress InvalidArgument */ $targetKeys = array_combine($assocForeignKey, $e->extract($targetBindingKey)); - /** @psalm-suppress InvalidScalarArgument getForeignKey() returns false */ + /** @psalm-suppress InvalidArgument */ $changedKeys = $sourceKeys !== $joint->extract($foreignKey) || $targetKeys !== $joint->extract($assocForeignKey); diff --git a/Association/HasMany.php b/Association/HasMany.php index 2f88a5ce..02810cb2 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -166,7 +166,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []): En throw new InvalidArgumentException($message); } - /** @psalm-suppress InvalidScalarArgument getForeignKey() returns false */ + /** @psalm-suppress InvalidArgument */ $foreignKeyReference = array_combine( (array)$this->getForeignKey(), $entity->extract((array)$this->getBindingKey()) @@ -362,7 +362,7 @@ public function unlink(EntityInterface $sourceEntity, array $targetEntities, arr 'OR' => (new Collection($targetEntities)) ->map(function ($entity) use ($targetPrimaryKey) { /** @var \Cake\Datasource\EntityInterface $entity */ - /** @psalm-suppress InvalidScalarArgument getForeignKey() returns false */ + /** @psalm-suppress InvalidArgument,UnusedPsalmSuppress */ return $entity->extract($targetPrimaryKey); }) ->toList(), @@ -524,7 +524,7 @@ protected function _unlink(array $foreignKey, Table $target, array $conditions = }); $query = $this->find()->where($conditions); $ok = true; - foreach ($query as $assoc) { + foreach ($query->all() as $assoc) { $ok = $ok && $target->delete($assoc, $options); } diff --git a/Association/HasOne.php b/Association/HasOne.php index ac3fa712..dd418f02 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -121,7 +121,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []): En return $entity; } - /** @psalm-suppress InvalidScalarArgument getForeignKey() returns false */ + /** @psalm-suppress InvalidArgument */ $properties = array_combine( (array)$this->getForeignKey(), $entity->extract((array)$this->getBindingKey()) diff --git a/AssociationCollection.php b/AssociationCollection.php index 75234dbe..b8f09001 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -29,6 +29,8 @@ * * Contains methods for managing associations, and * ordering operations around saving and deleting. + * + * @template-implements \IteratorAggregate */ class AssociationCollection implements IteratorAggregate { @@ -38,7 +40,7 @@ class AssociationCollection implements IteratorAggregate /** * Stored associations * - * @var array<\Cake\ORM\Association> + * @var array */ protected array $_items = []; diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 31804523..825d84f2 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -219,7 +219,7 @@ protected function _processAssociation( array $settings ): void { $foreignKeys = (array)$assoc->getForeignKey(); - /** @psalm-suppress InvalidScalarArgument getForeignKey() returns false */ + /** @psalm-suppress InvalidArgument */ $countConditions = $entity->extract($foreignKeys); foreach ($countConditions as $field => $value) { @@ -232,7 +232,7 @@ protected function _processAssociation( $primaryKeys = (array)$assoc->getBindingKey(); $updateConditions = array_combine($primaryKeys, $countConditions); - /** @psalm-suppress InvalidScalarArgument getForeignKey() returns false */ + /** @psalm-suppress InvalidArgument */ $countOriginalConditions = $entity->extractOriginalChanged($foreignKeys); if ($countOriginalConditions !== []) { $updateOriginalConditions = array_combine($primaryKeys, $countOriginalConditions); diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 19738b36..1a82f483 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -265,7 +265,6 @@ function ($c, &$field) use ($fields, $alias, $mainTableAlias, $mainTableFields, return $c; } - /** @psalm-suppress ParadoxicalCondition */ if (in_array($field, $fields, true)) { $joinRequired = true; $field = "$alias.$field"; @@ -323,7 +322,6 @@ function ($expression) use ($fields, $alias, $mainTableAlias, $mainTableFields, return; } - /** @psalm-suppress ParadoxicalCondition */ if (in_array($field, $mainTableFields, true)) { $expression->setField("$mainTableAlias.$field"); } diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index f34251d1..3f57b507 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -783,7 +783,6 @@ protected function _moveDown(EntityInterface $node, int|bool $number): EntityInt * @param mixed $id Record id. * @return \Cake\Datasource\EntityInterface * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found - * @psalm-suppress InvalidReturnType */ protected function _getNode(mixed $id): EntityInterface { @@ -804,7 +803,6 @@ protected function _getNode(mixed $id): EntityInterface throw new RecordNotFoundException("Node \"{$id}\" was not found in the tree."); } - /** @psalm-suppress InvalidReturnStatement */ return $node; } diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 199c21b7..0187c70b 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -137,7 +137,6 @@ protected function _throwMissingClassError(string $class, ?string $plugin): void * @param string $alias The alias of the object. * @param array $config An array of config to use for the behavior. * @return \Cake\ORM\Behavior The constructed behavior class. - * @psalm-suppress MoreSpecificImplementedParamType */ protected function _create(object|string $class, string $alias, array $config): Behavior { diff --git a/Marshaller.php b/Marshaller.php index 63ce11f0..8d7b4ec0 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -639,7 +639,6 @@ public function merge(EntityInterface $entity, array $data, array $options = []) * @param array $options List of options. * @return array<\Cake\Datasource\EntityInterface> * @see \Cake\ORM\Entity::$_accessible - * @psalm-suppress NullArrayOffset */ public function mergeMany(iterable $entities, array $data, array $options = []): array { @@ -738,11 +737,9 @@ protected function _mergeAssociation( $types = [Association::ONE_TO_ONE, Association::MANY_TO_ONE]; $type = $assoc->type(); if (in_array($type, $types, true)) { - /** @psalm-suppress PossiblyInvalidArgument, ArgumentTypeCoercion */ return $marshaller->merge($original, $value, $options); } if ($type === Association::MANY_TO_MANY) { - /** @psalm-suppress PossiblyInvalidArgument, ArgumentTypeCoercion */ return $marshaller->_mergeBelongsToMany($original, $assoc, $value, $options); } diff --git a/Query.php b/Query.php index 980c7cee..36bd9c9b 100644 --- a/Query.php +++ b/Query.php @@ -155,6 +155,7 @@ public function __construct(Connection $connection, Table $table) * * @param \Cake\ORM\Table $repository The default table object to use. * @return $this + * @psalm-suppress MoreSpecificImplementedParamType */ public function repository(RepositoryInterface $repository) { diff --git a/Table.php b/Table.php index 623358db..ef9f556c 100644 --- a/Table.php +++ b/Table.php @@ -1477,7 +1477,6 @@ protected function _setFieldMatchers(array $options, array $keys): array * @throws \Cake\Datasource\Exception\InvalidPrimaryKeyException When $primaryKey has an * incorrect number of elements. * @see \Cake\Datasource\RepositoryInterface::find() - * @psalm-suppress InvalidReturnType */ public function get($primaryKey, array $options = []): EntityInterface { @@ -1529,7 +1528,6 @@ public function get($primaryKey, array $options = []): EntityInterface $query->cache($cacheKey, $cacheConfig); } - /** @psalm-suppress InvalidReturnStatement */ return $query->firstOrFail(); } From 00c0f98f4b7b46c9e5797b4eb20072ed3cad05dd Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 29 Aug 2022 17:24:53 +0530 Subject: [PATCH 1799/2059] Update ORM\Query to extends Database\Query\SelectQuery. Add separate classes for insert, update, delete queries. --- Behavior/TreeBehavior.php | 34 ++++++++++------ Query.php | 86 ++++----------------------------------- Query/DeleteQuery.php | 62 ++++++++++++++++++++++++++++ Query/InsertQuery.php | 62 ++++++++++++++++++++++++++++ Query/UpdateQuery.php | 62 ++++++++++++++++++++++++++++ Table.php | 47 +++++++++++++++++---- 6 files changed, 254 insertions(+), 99 deletions(-) create mode 100644 Query/DeleteQuery.php create mode 100644 Query/InsertQuery.php create mode 100644 Query/UpdateQuery.php diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 3f57b507..f38d0e25 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -20,6 +20,7 @@ use Cake\Database\Exception\DatabaseException; use Cake\Database\Expression\IdentifierExpression; use Cake\Database\Expression\QueryExpression; +use Cake\Database\Query as DbQuery; use Cake\Datasource\EntityInterface; use Cake\Datasource\Exception\RecordNotFoundException; use Cake\Event\EventInterface; @@ -228,19 +229,25 @@ public function beforeDelete(EventInterface $event, EntityInterface $entity): vo $diff = $right - $left + 1; if ($diff > 2) { - $query = $this->_scope($this->_table->query()) - ->where( - fn (QueryExpression $exp) => $exp - ->gte($config['leftField'], $left + 1) - ->lte($config['leftField'], $right - 1) - ); if ($this->getConfig('cascadeCallbacks')) { + $query = $this->_scope($this->_table->query()) + ->where( + fn (QueryExpression $exp) => $exp + ->gte($config['leftField'], $left + 1) + ->lte($config['leftField'], $right - 1) + ); + $entities = $query->toArray(); foreach ($entities as $entityToDelete) { $this->_table->delete($entityToDelete, ['atomic' => false]); } } else { - $query->delete() + $this->_scope($this->_table->deleteQuery()) + ->where( + fn (QueryExpression $exp) => $exp + ->gte($config['leftField'], $left + 1) + ->lte($config['leftField'], $right - 1) + ) ->execute(); } } @@ -898,7 +905,7 @@ protected function _sync(int $shift, string $dir, string $conditions, bool $mark /** @var \Cake\Database\Expression\IdentifierExpression $field */ foreach ([$config['leftField'], $config['rightField']] as $field) { - $query = $this->_scope($this->_table->query()); + $query = $this->_scope($this->_table->updateQuery()); $exp = $query->newExpr(); $movement = clone $exp; @@ -912,7 +919,7 @@ protected function _sync(int $shift, string $dir, string $conditions, bool $mark $where = clone $exp; $where->add($field)->add($conditions)->setConjunction(''); - $query->update() + $query ->set($exp->eq($field, $movement)) ->where($where) ->execute(); @@ -923,10 +930,13 @@ protected function _sync(int $shift, string $dir, string $conditions, bool $mark * Alters the passed query so that it only returns scoped records as defined * in the tree configuration. * - * @param \Cake\ORM\Query $query the Query to modify - * @return \Cake\ORM\Query + * @param \Cake\Database\Query $query the Query to modify + * @return \Cake\Database\Query + * @template T of \Cake\ORM\Query|\Cake\ORM\Query\UpdateQuery|\Cake\ORM\Query\DeleteQuery + * @psalm-param T $query + * @psalm-return T */ - protected function _scope(Query $query): Query + protected function _scope(DbQuery $query): DbQuery { $scope = $this->getConfig('scope'); diff --git a/Query.php b/Query.php index f3d6592f..56655cf4 100644 --- a/Query.php +++ b/Query.php @@ -18,9 +18,8 @@ use ArrayObject; use Cake\Database\Connection; -use Cake\Database\Exception\DatabaseException; use Cake\Database\ExpressionInterface; -use Cake\Database\Query as DatabaseQuery; +use Cake\Database\Query\SelectQuery as DbSelectQuery; use Cake\Database\TypedResultInterface; use Cake\Database\TypeMap; use Cake\Database\ValueBinder; @@ -32,14 +31,14 @@ use JsonSerializable; /** - * Extends the base Query class to provide new methods related to association + * Extends the Cake\Database\Query\SelectQuery class to provide new methods related to association * loading, automatic fields selection, automatic type casting and to wrap results * into a specific iterator that will be responsible for hydrating results if * required. * * @property \Cake\ORM\Table $_repository Instance of a table object this query is bound to. */ -class Query extends DatabaseQuery implements JsonSerializable, QueryInterface +class Query extends DbSelectQuery implements JsonSerializable, QueryInterface { use QueryTrait { cache as private _cache; @@ -628,7 +627,7 @@ public function leftJoinWith(string $assoc, ?Closure $builder = null) { $result = $this->getEagerLoader() ->setMatching($assoc, $builder, [ - 'joinType' => Query::JOIN_TYPE_LEFT, + 'joinType' => static::JOIN_TYPE_LEFT, 'fields' => false, ]) ->getMatching(); @@ -677,7 +676,7 @@ public function innerJoinWith(string $assoc, ?Closure $builder = null) { $result = $this->getEagerLoader() ->setMatching($assoc, $builder, [ - 'joinType' => Query::JOIN_TYPE_INNER, + 'joinType' => static::JOIN_TYPE_INNER, 'fields' => false, ]) ->getMatching(); @@ -741,7 +740,7 @@ public function notMatching(string $assoc, ?Closure $builder = null) { $result = $this->getEagerLoader() ->setMatching($assoc, $builder, [ - 'joinType' => Query::JOIN_TYPE_LEFT, + 'joinType' => static::JOIN_TYPE_LEFT, 'fields' => false, 'negateMatch' => true, ]) @@ -962,7 +961,7 @@ protected function _performCount(): int ->disableAutoFields() ->execute(); } else { - $statement = $this->getConnection()->newQuery() + $statement = $this->getConnection()->newSelectQuery() ->select($count) ->from(['count_source' => $query]) ->execute(); @@ -1056,10 +1055,6 @@ public function isHydrationEnabled(): bool */ public function cache($key, $config = 'default') { - if ($this->_type !== self::TYPE_SELECT) { - throw new DatabaseException('You cannot cache the results of non-select queries.'); - } - return $this->_cache($key, $config); } @@ -1071,12 +1066,6 @@ public function cache($key, $config = 'default') */ public function all(): ResultSetInterface { - if ($this->_type !== self::TYPE_SELECT) { - throw new DatabaseException( - 'You cannot call all() on a non-select query. Use execute() instead.' - ); - } - return $this->_all(); } @@ -1162,7 +1151,7 @@ protected function resultSetFactory(): ResultSetFactory */ protected function _transformQuery(): void { - if (!$this->_dirty || $this->_type !== self::TYPE_SELECT) { + if (!$this->_dirty || $this->_type !== static::TYPE_SELECT) { return; } @@ -1257,65 +1246,6 @@ protected function _dirty(): void parent::_dirty(); } - /** - * Create an update query. - * - * This changes the query type to be 'update'. - * Can be combined with set() and where() methods to create update queries. - * - * @param \Cake\Database\ExpressionInterface|string|null $table Unused parameter. - * @return $this - */ - public function update(ExpressionInterface|string|null $table = null) - { - if (!$table) { - $repository = $this->getRepository(); - $table = $repository->getTable(); - } - - return parent::update($table); - } - - /** - * Create a delete query. - * - * This changes the query type to be 'delete'. - * Can be combined with the where() method to create delete queries. - * - * @param string|null $table Unused parameter. - * @return $this - */ - public function delete(?string $table = null) - { - $repository = $this->getRepository(); - $this->from([$repository->getAlias() => $repository->getTable()]); - - // We do not pass $table to parent class here - return parent::delete(); - } - - /** - * Create an insert query. - * - * This changes the query type to be 'insert'. - * Note calling this method will reset any data previously set - * with Query::values() - * - * Can be combined with the where() method to create delete queries. - * - * @param array $columns The columns to insert into. - * @param array $types A map between columns & their datatypes. - * @return $this - */ - public function insert(array $columns, array $types = []) - { - $repository = $this->getRepository(); - $table = $repository->getTable(); - $this->into($table); - - return parent::insert($columns, $types); - } - /** * Returns a new Query that has automatic field aliasing disabled. * diff --git a/Query/DeleteQuery.php b/Query/DeleteQuery.php new file mode 100644 index 00000000..d72f3b47 --- /dev/null +++ b/Query/DeleteQuery.php @@ -0,0 +1,62 @@ +addDefaultTypes($table); + $this->from([$table->getAlias() => $table->getTable()]); + } + + /** + * Hints this object to associate the correct types when casting conditions + * for the database. This is done by extracting the field types from the schema + * associated to the passed table object. This prevents the user from repeating + * themselves when specifying conditions. + * + * This method returns the same query object for chaining. + * + * @param \Cake\ORM\Table $table The table to pull types from + * @return $this + */ + public function addDefaultTypes(Table $table) + { + $alias = $table->getAlias(); + $map = $table->getSchema()->typeMap(); + $fields = []; + foreach ($map as $f => $type) { + $fields[$f] = $fields[$alias . '.' . $f] = $fields[$alias . '__' . $f] = $type; + } + $this->getTypeMap()->addDefaults($fields); + + return $this; + } +} diff --git a/Query/InsertQuery.php b/Query/InsertQuery.php new file mode 100644 index 00000000..0238ff43 --- /dev/null +++ b/Query/InsertQuery.php @@ -0,0 +1,62 @@ +addDefaultTypes($table); + $this->into($table->getTable()); + } + + /** + * Hints this object to associate the correct types when casting conditions + * for the database. This is done by extracting the field types from the schema + * associated to the passed table object. This prevents the user from repeating + * themselves when specifying conditions. + * + * This method returns the same query object for chaining. + * + * @param \Cake\ORM\Table $table The table to pull types from + * @return $this + */ + public function addDefaultTypes(Table $table) + { + $alias = $table->getAlias(); + $map = $table->getSchema()->typeMap(); + $fields = []; + foreach ($map as $f => $type) { + $fields[$f] = $fields[$alias . '.' . $f] = $fields[$alias . '__' . $f] = $type; + } + $this->getTypeMap()->addDefaults($fields); + + return $this; + } +} diff --git a/Query/UpdateQuery.php b/Query/UpdateQuery.php new file mode 100644 index 00000000..c437cff5 --- /dev/null +++ b/Query/UpdateQuery.php @@ -0,0 +1,62 @@ +addDefaultTypes($table); + $this->update($table->getTable()); + } + + /** + * Hints this object to associate the correct types when casting conditions + * for the database. This is done by extracting the field types from the schema + * associated to the passed table object. This prevents the user from repeating + * themselves when specifying conditions. + * + * This method returns the same query object for chaining. + * + * @param \Cake\ORM\Table $table The table to pull types from + * @return $this + */ + public function addDefaultTypes(Table $table) + { + $alias = $table->getAlias(); + $map = $table->getSchema()->typeMap(); + $fields = []; + foreach ($map as $f => $type) { + $fields[$f] = $fields[$alias . '.' . $f] = $fields[$alias . '__' . $f] = $type; + } + $this->getTypeMap()->addDefaults($fields); + + return $this; + } +} diff --git a/Table.php b/Table.php index ef9f556c..c5257b88 100644 --- a/Table.php +++ b/Table.php @@ -41,6 +41,9 @@ use Cake\ORM\Exception\MissingEntityException; use Cake\ORM\Exception\PersistenceFailedException; use Cake\ORM\Exception\RolledbackTransactionException; +use Cake\ORM\Query\DeleteQuery; +use Cake\ORM\Query\InsertQuery; +use Cake\ORM\Query\UpdateQuery; use Cake\ORM\Rule\IsUnique; use Cake\Utility\Inflector; use Cake\Validation\ValidatorAwareInterface; @@ -1689,6 +1692,36 @@ public function query(): Query return new Query($this->getConnection(), $this); } + /** + * Creates a new insert query + * + * @return \Cake\ORM\Query\InsertQuery + */ + public function insertQuery(): InsertQuery + { + return new InsertQuery($this->getConnection(), $this); + } + + /** + * Creates a new update query + * + * @return \Cake\ORM\Query\UpdateQuery + */ + public function updateQuery(): UpdateQuery + { + return new UpdateQuery($this->getConnection(), $this); + } + + /** + * Creates a new delete query + * + * @return \Cake\ORM\Query\DeleteQuery + */ + public function deleteQuery(): DeleteQuery + { + return new DeleteQuery($this->getConnection(), $this); + } + /** * Creates a new Query::subquery() instance for a table. * @@ -1715,8 +1748,7 @@ public function updateAll( QueryExpression|Closure|array|string $fields, QueryExpression|Closure|array|string|null $conditions ): int { - $statement = $this->query() - ->update() + $statement = $this->updateQuery() ->set($fields) ->where($conditions) ->execute(); @@ -1740,8 +1772,7 @@ public function updateAll( */ public function deleteAll(QueryExpression|Closure|array|string|null $conditions): int { - $statement = $this->query() - ->delete() + $statement = $this->deleteQuery() ->where($conditions) ->execute(); @@ -2082,7 +2113,7 @@ protected function _insert(EntityInterface $entity, array $data): EntityInterfac return false; } - $statement = $this->query()->insert(array_keys($data)) + $statement = $this->insertQuery()->insert(array_keys($data)) ->values($data) ->execute(); @@ -2162,8 +2193,7 @@ protected function _update(EntityInterface $entity, array $data): EntityInterfac throw new InvalidArgumentException($message); } - $statement = $this->query() - ->update() + $statement = $this->updateQuery() ->set($data) ->where($primaryKey) ->execute(); @@ -2500,8 +2530,7 @@ protected function _processDelete(EntityInterface $entity, ArrayObject $options) return $success; } - $statement = $this->query() - ->delete() + $statement = $this->deleteQuery() ->where($entity->extract($primaryKey)) ->execute(); From fa2cccb33a5ae27ec4d505665fd0e2a5aad02a4c Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 31 Aug 2022 14:47:56 +0530 Subject: [PATCH 1800/2059] Move common code into a trait. --- Query/CommonQueryTrait.php | 46 ++++++++++++++++++++++++++++++++++++++ Query/DeleteQuery.php | 26 ++------------------- Query/InsertQuery.php | 26 ++------------------- Query/UpdateQuery.php | 26 ++------------------- 4 files changed, 52 insertions(+), 72 deletions(-) create mode 100644 Query/CommonQueryTrait.php diff --git a/Query/CommonQueryTrait.php b/Query/CommonQueryTrait.php new file mode 100644 index 00000000..72b9cb5b --- /dev/null +++ b/Query/CommonQueryTrait.php @@ -0,0 +1,46 @@ +getAlias(); + $map = $table->getSchema()->typeMap(); + $fields = []; + foreach ($map as $f => $type) { + $fields[$f] = $fields[$alias . '.' . $f] = $fields[$alias . '__' . $f] = $type; + } + $this->getTypeMap()->addDefaults($fields); + + return $this; + } +} diff --git a/Query/DeleteQuery.php b/Query/DeleteQuery.php index d72f3b47..38eb796c 100644 --- a/Query/DeleteQuery.php +++ b/Query/DeleteQuery.php @@ -22,6 +22,8 @@ class DeleteQuery extends DbDeleteQuery { + use CommonQueryTrait; + /** * Constructor * @@ -35,28 +37,4 @@ public function __construct(Connection $connection, Table $table) $this->addDefaultTypes($table); $this->from([$table->getAlias() => $table->getTable()]); } - - /** - * Hints this object to associate the correct types when casting conditions - * for the database. This is done by extracting the field types from the schema - * associated to the passed table object. This prevents the user from repeating - * themselves when specifying conditions. - * - * This method returns the same query object for chaining. - * - * @param \Cake\ORM\Table $table The table to pull types from - * @return $this - */ - public function addDefaultTypes(Table $table) - { - $alias = $table->getAlias(); - $map = $table->getSchema()->typeMap(); - $fields = []; - foreach ($map as $f => $type) { - $fields[$f] = $fields[$alias . '.' . $f] = $fields[$alias . '__' . $f] = $type; - } - $this->getTypeMap()->addDefaults($fields); - - return $this; - } } diff --git a/Query/InsertQuery.php b/Query/InsertQuery.php index 0238ff43..cfcaa570 100644 --- a/Query/InsertQuery.php +++ b/Query/InsertQuery.php @@ -22,6 +22,8 @@ class InsertQuery extends DbInsertQuery { + use CommonQueryTrait; + /** * Constructor * @@ -35,28 +37,4 @@ public function __construct(Connection $connection, Table $table) $this->addDefaultTypes($table); $this->into($table->getTable()); } - - /** - * Hints this object to associate the correct types when casting conditions - * for the database. This is done by extracting the field types from the schema - * associated to the passed table object. This prevents the user from repeating - * themselves when specifying conditions. - * - * This method returns the same query object for chaining. - * - * @param \Cake\ORM\Table $table The table to pull types from - * @return $this - */ - public function addDefaultTypes(Table $table) - { - $alias = $table->getAlias(); - $map = $table->getSchema()->typeMap(); - $fields = []; - foreach ($map as $f => $type) { - $fields[$f] = $fields[$alias . '.' . $f] = $fields[$alias . '__' . $f] = $type; - } - $this->getTypeMap()->addDefaults($fields); - - return $this; - } } diff --git a/Query/UpdateQuery.php b/Query/UpdateQuery.php index c437cff5..9d97647e 100644 --- a/Query/UpdateQuery.php +++ b/Query/UpdateQuery.php @@ -22,6 +22,8 @@ class UpdateQuery extends DbUpdateQuery { + use CommonQueryTrait; + /** * Constructor * @@ -35,28 +37,4 @@ public function __construct(Connection $connection, Table $table) $this->addDefaultTypes($table); $this->update($table->getTable()); } - - /** - * Hints this object to associate the correct types when casting conditions - * for the database. This is done by extracting the field types from the schema - * associated to the passed table object. This prevents the user from repeating - * themselves when specifying conditions. - * - * This method returns the same query object for chaining. - * - * @param \Cake\ORM\Table $table The table to pull types from - * @return $this - */ - public function addDefaultTypes(Table $table) - { - $alias = $table->getAlias(); - $map = $table->getSchema()->typeMap(); - $fields = []; - foreach ($map as $f => $type) { - $fields[$f] = $fields[$alias . '.' . $f] = $fields[$alias . '__' . $f] = $type; - } - $this->getTypeMap()->addDefaults($fields); - - return $this; - } } From ea8e3e019d3c2403ebe81f97b30eaa824925faba Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 31 Aug 2022 19:00:51 +0530 Subject: [PATCH 1801/2059] Remove unrequired checks. --- Query.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Query.php b/Query.php index 56655cf4..4bacabd4 100644 --- a/Query.php +++ b/Query.php @@ -1078,7 +1078,7 @@ public function all(): ResultSetInterface */ public function triggerBeforeFind(): void { - if (!$this->_beforeFindFired && $this->_type === self::TYPE_SELECT) { + if (!$this->_beforeFindFired) { $this->_beforeFindFired = true; $repository = $this->getRepository(); @@ -1151,7 +1151,7 @@ protected function resultSetFactory(): ResultSetFactory */ protected function _transformQuery(): void { - if (!$this->_dirty || $this->_type !== static::TYPE_SELECT) { + if (!$this->_dirty) { return; } From 5b9280a8fab13c30d1ec2b24ad8d86f8dfe82ebe Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 31 Aug 2022 19:19:25 +0530 Subject: [PATCH 1802/2059] Move more methods into CommonQueryTrait --- Query.php | 61 +++----------------------------------- Query/CommonQueryTrait.php | 33 ++++++++++++++++++++- Query/DeleteQuery.php | 16 +++++++++- Query/InsertQuery.php | 16 +++++++++- Query/UpdateQuery.php | 16 +++++++++- 5 files changed, 81 insertions(+), 61 deletions(-) diff --git a/Query.php b/Query.php index 4bacabd4..177a8686 100644 --- a/Query.php +++ b/Query.php @@ -25,8 +25,8 @@ use Cake\Database\ValueBinder; use Cake\Datasource\QueryInterface; use Cake\Datasource\QueryTrait; -use Cake\Datasource\RepositoryInterface; use Cake\Datasource\ResultSetInterface; +use Cake\ORM\Query\CommonQueryTrait; use Closure; use JsonSerializable; @@ -40,7 +40,9 @@ */ class Query extends DbSelectQuery implements JsonSerializable, QueryInterface { - use QueryTrait { + use CommonQueryTrait, QueryTrait { + CommonQueryTrait::setRepository insteadof QueryTrait; + CommonQueryTrait::getRepository insteadof QueryTrait; cache as private _cache; all as private _all; } @@ -148,37 +150,6 @@ public function __construct(Connection $connection, Table $table) $this->addDefaultTypes($table); } - /** - * Set the default Table object that will be used by this query - * and form the `FROM` clause. - * - * @param \Cake\ORM\Table $repository The default table object to use. - * @return $this - * @psalm-suppress MoreSpecificImplementedParamType - */ - public function repository(RepositoryInterface $repository) - { - assert( - $repository instanceof Table, - '$repository must be an instance of Cake\ORM\Table.' - ); - - $this->_repository = $repository; - - return $this; - } - - /** - * Returns the default table object that will be used by this query, - * that is, the table that will appear in the from clause. - * - * @return \Cake\ORM\Table - */ - public function getRepository(): Table - { - return $this->_repository; - } - /** * Adds new fields to be returned by a `SELECT` statement when this query is * executed. Fields can be passed as an array of strings, array of expression @@ -264,30 +235,6 @@ public function selectAllExcept(Table|Association $table, array $excludedFields, return $this->select($fields, $overwrite); } - /** - * Hints this object to associate the correct types when casting conditions - * for the database. This is done by extracting the field types from the schema - * associated to the passed table object. This prevents the user from repeating - * themselves when specifying conditions. - * - * This method returns the same query object for chaining. - * - * @param \Cake\ORM\Table $table The table to pull types from - * @return $this - */ - public function addDefaultTypes(Table $table) - { - $alias = $table->getAlias(); - $map = $table->getSchema()->typeMap(); - $fields = []; - foreach ($map as $f => $type) { - $fields[$f] = $fields[$alias . '.' . $f] = $fields[$alias . '__' . $f] = $type; - } - $this->getTypeMap()->addDefaults($fields); - - return $this; - } - /** * Sets the instance of the eager loader class to use for loading associations * and storing containments. diff --git a/Query/CommonQueryTrait.php b/Query/CommonQueryTrait.php index 72b9cb5b..06c7010c 100644 --- a/Query/CommonQueryTrait.php +++ b/Query/CommonQueryTrait.php @@ -16,8 +16,14 @@ */ namespace Cake\ORM\Query; +use Cake\Datasource\RepositoryInterface; use Cake\ORM\Table; +/** + * Trait with common methods used by all ORM query classes. + * + * @property \Cake\ORM\Table $_repository Instance of a table object this query is bound to. + */ trait CommonQueryTrait { /** @@ -31,7 +37,7 @@ trait CommonQueryTrait * @param \Cake\ORM\Table $table The table to pull types from * @return $this */ - protected function addDefaultTypes(Table $table) + public function addDefaultTypes(Table $table) { $alias = $table->getAlias(); $map = $table->getSchema()->typeMap(); @@ -43,4 +49,29 @@ protected function addDefaultTypes(Table $table) return $this; } + + /** + * Set the default Table object that will be used by this query + * and form the `FROM` clause. + * + * @param \Cake\ORM\Table $repository The default table object to use. + * @return $this + */ + public function setRepository(RepositoryInterface $repository) + { + $this->_repository = $repository; + + return $this; + } + + /** + * Returns the default table object that will be used by this query, + * that is, the table that will appear in the from clause. + * + * @return \Cake\ORM\Table + */ + public function getRepository(): Table + { + return $this->_repository; + } } diff --git a/Query/DeleteQuery.php b/Query/DeleteQuery.php index 38eb796c..d45c1152 100644 --- a/Query/DeleteQuery.php +++ b/Query/DeleteQuery.php @@ -18,6 +18,7 @@ use Cake\Database\Connection; use Cake\Database\Query\DeleteQuery as DbDeleteQuery; +use Cake\Database\ValueBinder; use Cake\ORM\Table; class DeleteQuery extends DbDeleteQuery @@ -34,7 +35,20 @@ public function __construct(Connection $connection, Table $table) { parent::__construct($connection); + $this->setRepository($table); $this->addDefaultTypes($table); - $this->from([$table->getAlias() => $table->getTable()]); + } + + /** + * @inheritDoc + */ + public function sql(?ValueBinder $binder = null): string + { + if (empty($this->_parts['from'])) { + $repository = $this->getRepository(); + $this->from([$repository->getAlias() => $repository->getTable()]); + } + + return parent::sql($binder); } } diff --git a/Query/InsertQuery.php b/Query/InsertQuery.php index cfcaa570..3b17b5c0 100644 --- a/Query/InsertQuery.php +++ b/Query/InsertQuery.php @@ -18,6 +18,7 @@ use Cake\Database\Connection; use Cake\Database\Query\InsertQuery as DbInsertQuery; +use Cake\Database\ValueBinder; use Cake\ORM\Table; class InsertQuery extends DbInsertQuery @@ -34,7 +35,20 @@ public function __construct(Connection $connection, Table $table) { parent::__construct($connection); + $this->setRepository($table); $this->addDefaultTypes($table); - $this->into($table->getTable()); + } + + /** + * @inheritDoc + */ + public function sql(?ValueBinder $binder = null): string + { + if (empty($this->_parts['into'])) { + $repository = $this->getRepository(); + $this->into($repository->getTable()); + } + + return parent::sql($binder); } } diff --git a/Query/UpdateQuery.php b/Query/UpdateQuery.php index 9d97647e..1abceab2 100644 --- a/Query/UpdateQuery.php +++ b/Query/UpdateQuery.php @@ -18,6 +18,7 @@ use Cake\Database\Connection; use Cake\Database\Query\UpdateQuery as DbUpdateQuery; +use Cake\Database\ValueBinder; use Cake\ORM\Table; class UpdateQuery extends DbUpdateQuery @@ -34,7 +35,20 @@ public function __construct(Connection $connection, Table $table) { parent::__construct($connection); + $this->setRepository($table); $this->addDefaultTypes($table); - $this->update($table->getTable()); + } + + /** + * @inheritDoc + */ + public function sql(?ValueBinder $binder = null): string + { + if (empty($this->_parts['update'])) { + $repository = $this->getRepository(); + $this->update($repository->getTable()); + } + + return parent::sql($binder); } } From d0a2ad8ddc484058da7136eb33f9ee31ac3271a1 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 31 Aug 2022 19:31:59 +0530 Subject: [PATCH 1803/2059] Update docblocks --- Query/DeleteQuery.php | 3 +++ Query/InsertQuery.php | 3 +++ Query/UpdateQuery.php | 3 +++ 3 files changed, 9 insertions(+) diff --git a/Query/DeleteQuery.php b/Query/DeleteQuery.php index d45c1152..166bb46d 100644 --- a/Query/DeleteQuery.php +++ b/Query/DeleteQuery.php @@ -21,6 +21,9 @@ use Cake\Database\ValueBinder; use Cake\ORM\Table; +/** + * @inheritDoc + */ class DeleteQuery extends DbDeleteQuery { use CommonQueryTrait; diff --git a/Query/InsertQuery.php b/Query/InsertQuery.php index 3b17b5c0..81732bce 100644 --- a/Query/InsertQuery.php +++ b/Query/InsertQuery.php @@ -21,6 +21,9 @@ use Cake\Database\ValueBinder; use Cake\ORM\Table; +/** + * @inheritDoc + */ class InsertQuery extends DbInsertQuery { use CommonQueryTrait; diff --git a/Query/UpdateQuery.php b/Query/UpdateQuery.php index 1abceab2..fb125d20 100644 --- a/Query/UpdateQuery.php +++ b/Query/UpdateQuery.php @@ -21,6 +21,9 @@ use Cake\Database\ValueBinder; use Cake\ORM\Table; +/** + * @inheritDoc + */ class UpdateQuery extends DbUpdateQuery { use CommonQueryTrait; From cf3f43182ec5f1551154ac08e70bd3753edacfea Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 4 Sep 2022 10:35:28 +0530 Subject: [PATCH 1804/2059] Fix stan errors --- Query/CommonQueryTrait.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Query/CommonQueryTrait.php b/Query/CommonQueryTrait.php index 06c7010c..d837496d 100644 --- a/Query/CommonQueryTrait.php +++ b/Query/CommonQueryTrait.php @@ -56,9 +56,11 @@ public function addDefaultTypes(Table $table) * * @param \Cake\ORM\Table $repository The default table object to use. * @return $this + * @psalm-suppress MoreSpecificImplementedParamType */ public function setRepository(RepositoryInterface $repository) { + /** @psalm-suppress UndefinedThisPropertyAssignment */ $this->_repository = $repository; return $this; @@ -72,6 +74,7 @@ public function setRepository(RepositoryInterface $repository) */ public function getRepository(): Table { + /** @psalm-suppress UndefinedThisPropertyFetch */ return $this->_repository; } } From 8f6dafc948a4f7995dfb01bc00cac857314fe212 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 4 Sep 2022 11:14:29 +0530 Subject: [PATCH 1805/2059] Drop Datasource/QueryTrait --- Query.php | 752 +++++++++++++++++++++++++++++++------ Query/CommonQueryTrait.php | 21 +- 2 files changed, 641 insertions(+), 132 deletions(-) diff --git a/Query.php b/Query.php index 177a8686..9f8d0e35 100644 --- a/Query.php +++ b/Query.php @@ -17,35 +17,33 @@ namespace Cake\ORM; use ArrayObject; +use Cake\Collection\Iterator\MapReduce; use Cake\Database\Connection; use Cake\Database\ExpressionInterface; use Cake\Database\Query\SelectQuery as DbSelectQuery; use Cake\Database\TypedResultInterface; use Cake\Database\TypeMap; use Cake\Database\ValueBinder; +use Cake\Datasource\Exception\RecordNotFoundException; +use Cake\Datasource\QueryCacher; use Cake\Datasource\QueryInterface; -use Cake\Datasource\QueryTrait; +use Cake\Datasource\ResultSetDecorator; use Cake\Datasource\ResultSetInterface; use Cake\ORM\Query\CommonQueryTrait; use Closure; +use InvalidArgumentException; use JsonSerializable; +use Psr\SimpleCache\CacheInterface; /** * Extends the Cake\Database\Query\SelectQuery class to provide new methods related to association * loading, automatic fields selection, automatic type casting and to wrap results * into a specific iterator that will be responsible for hydrating results if * required. - * - * @property \Cake\ORM\Table $_repository Instance of a table object this query is bound to. */ class Query extends DbSelectQuery implements JsonSerializable, QueryInterface { - use CommonQueryTrait, QueryTrait { - CommonQueryTrait::setRepository insteadof QueryTrait; - CommonQueryTrait::getRepository insteadof QueryTrait; - cache as private _cache; - all as private _all; - } + use CommonQueryTrait; /** * Indicates that the operation should append to the list @@ -114,6 +112,13 @@ class Query extends DbSelectQuery implements JsonSerializable, QueryInterface */ protected ?EagerLoader $_eagerLoader = null; + /** + * Whether the query is standalone or the product of an eager load operation. + * + * @var bool + */ + protected bool $_eagerLoaded = false; + /** * True if the beforeFind event has already been triggered for this query * @@ -137,6 +142,54 @@ class Query extends DbSelectQuery implements JsonSerializable, QueryInterface */ protected ResultSetFactory $resultSetFactory; + /** + * A ResultSet. + * + * When set, query execution will be bypassed. + * + * @var iterable|null + * @see \Cake\Datasource\QueryTrait::setResult() + */ + protected ?iterable $_results = null; + + /** + * Instance of a repository object this query is bound to. + * + * @var \Cake\ORM\Table + */ + protected Table $_repository; + + /** + * List of map-reduce routines that should be applied over the query + * result + * + * @var array + */ + protected array $_mapReduce = []; + + /** + * List of formatter classes or callbacks that will post-process the + * results when fetched + * + * @var array<\Closure> + */ + protected array $_formatters = []; + + /** + * A query cacher instance if this query has caching enabled. + * + * @var \Cake\Datasource\QueryCacher|null + */ + protected ?QueryCacher $_cache = null; + + /** + * Holds any custom options passed using applyOptions that could not be processed + * by any method in this class. + * + * @var array + */ + protected array $_options = []; + /** * Constructor * @@ -150,6 +203,571 @@ public function __construct(Connection $connection, Table $table) $this->addDefaultTypes($table); } + /** + * Set the result set for a query. + * + * Setting the resultset of a query will make execute() a no-op. Instead + * of executing the SQL query and fetching results, the ResultSet provided to this + * method will be returned. + * + * This method is most useful when combined with results stored in a persistent cache. + * + * @param iterable $results The results this query should return. + * @return $this + */ + public function setResult(iterable $results) + { + $this->_results = $results; + + return $this; + } + + /** + * Executes this query and returns a results iterator. This function is required + * for implementing the IteratorAggregate interface and allows the query to be + * iterated without having to call execute() manually, thus making it look like + * a result set instead of the query itself. + * + * @return \Cake\Datasource\ResultSetInterface + */ + public function getIterator(): ResultSetInterface + { + return $this->all(); + } + + /** + * Enable result caching for this query. + * + * If a query has caching enabled, it will do the following when executed: + * + * - Check the cache for $key. If there are results no SQL will be executed. + * Instead the cached results will be returned. + * - When the cached data is stale/missing the result set will be cached as the query + * is executed. + * + * ### Usage + * + * ``` + * // Simple string key + config + * $query->cache('my_key', 'db_results'); + * + * // Function to generate key. + * $query->cache(function ($q) { + * $key = serialize($q->clause('select')); + * $key .= serialize($q->clause('where')); + * return md5($key); + * }); + * + * // Using a pre-built cache engine. + * $query->cache('my_key', $engine); + * + * // Disable caching + * $query->cache(false); + * ``` + * + * @param \Closure|string|false $key Either the cache key or a function to generate the cache key. + * When using a function, this query instance will be supplied as an argument. + * @param \Psr\SimpleCache\CacheInterface|string $config Either the name of the cache config to use, or + * a cache engine instance. + * @return $this + */ + public function cache(Closure|string|false $key, CacheInterface|string $config = 'default') + { + if ($key === false) { + $this->_cache = null; + + return $this; + } + + $this->_cache = new QueryCacher($key, $config); + + return $this; + } + + /** + * Returns the current configured query `_eagerLoaded` value + * + * @return bool + */ + public function isEagerLoaded(): bool + { + return $this->_eagerLoaded; + } + + /** + * Sets the query instance to be an eager loaded query. If no argument is + * passed, the current configured query `_eagerLoaded` value is returned. + * + * @param bool $value Whether to eager load. + * @return $this + */ + public function eagerLoaded(bool $value) + { + $this->_eagerLoaded = $value; + + return $this; + } + + /** + * Returns a key => value array representing a single aliased field + * that can be passed directly to the select() method. + * The key will contain the alias and the value the actual field name. + * + * If the field is already aliased, then it will not be changed. + * If no $alias is passed, the default table for this query will be used. + * + * @param string $field The field to alias + * @param string|null $alias the alias used to prefix the field + * @return array + */ + public function aliasField(string $field, ?string $alias = null): array + { + if (str_contains($field, '.')) { + $aliasedField = $field; + [$alias, $field] = explode('.', $field); + } else { + $alias = $alias ?: $this->getRepository()->getAlias(); + $aliasedField = $alias . '.' . $field; + } + + $key = sprintf('%s__%s', $alias, $field); + + return [$key => $aliasedField]; + } + + /** + * Runs `aliasField()` for each field in the provided list and returns + * the result under a single array. + * + * @param array $fields The fields to alias + * @param string|null $defaultAlias The default alias + * @return array + */ + public function aliasFields(array $fields, ?string $defaultAlias = null): array + { + $aliased = []; + foreach ($fields as $alias => $field) { + if (is_numeric($alias) && is_string($field)) { + $aliased += $this->aliasField($field, $defaultAlias); + continue; + } + $aliased[$alias] = $field; + } + + return $aliased; + } + + /** + * Fetch the results for this query. + * + * Will return either the results set through setResult(), or execute this query + * and return the ResultSetDecorator object ready for streaming of results. + * + * ResultSetDecorator is a traversable object that implements the methods found + * on Cake\Collection\Collection. + * + * @return \Cake\Datasource\ResultSetInterface + */ + public function all(): ResultSetInterface + { + if ($this->_results !== null) { + if (!($this->_results instanceof ResultSetInterface)) { + $this->_results = $this->_decorateResults($this->_results); + } + + return $this->_results; + } + + $results = null; + if ($this->_cache) { + $results = $this->_cache->fetch($this); + } + if ($results === null) { + $results = $this->_decorateResults($this->_execute()); + if ($this->_cache) { + $this->_cache->store($this, $results); + } + } + $this->_results = $results; + + return $this->_results; + } + + /** + * Returns an array representation of the results after executing the query. + * + * @return array + */ + public function toArray(): array + { + return $this->all()->toArray(); + } + + /** + * Register a new MapReduce routine to be executed on top of the database results + * + * The MapReduce routing will only be run when the query is executed and the first + * result is attempted to be fetched. + * + * If the third argument is set to true, it will erase previous map reducers + * and replace it with the arguments passed. + * + * @param \Closure|null $mapper The mapper function + * @param \Closure|null $reducer The reducing function + * @param bool $overwrite Set to true to overwrite existing map + reduce functions. + * @return $this + * @see \Cake\Collection\Iterator\MapReduce for details on how to use emit data to the map reducer. + */ + public function mapReduce(?Closure $mapper = null, ?Closure $reducer = null, bool $overwrite = false) + { + if ($overwrite) { + $this->_mapReduce = []; + } + if ($mapper === null) { + if (!$overwrite) { + throw new InvalidArgumentException('$mapper can be null only when $overwrite is true.'); + } + + return $this; + } + $this->_mapReduce[] = compact('mapper', 'reducer'); + + return $this; + } + + /** + * Returns the list of previously registered map reduce routines. + * + * @return array + */ + public function getMapReducers(): array + { + return $this->_mapReduce; + } + + /** + * Registers a new formatter callback function that is to be executed when trying + * to fetch the results from the database. + * + * If the second argument is set to true, it will erase previous formatters + * and replace them with the passed first argument. + * + * Callbacks are required to return an iterator object, which will be used as + * the return value for this query's result. Formatter functions are applied + * after all the `MapReduce` routines for this query have been executed. + * + * Formatting callbacks will receive two arguments, the first one being an object + * implementing `\Cake\Collection\CollectionInterface`, that can be traversed and + * modified at will. The second one being the query instance on which the formatter + * callback is being applied. + * + * Usually the query instance received by the formatter callback is the same query + * instance on which the callback was attached to, except for in a joined + * association, in that case the callback will be invoked on the association source + * side query, and it will receive that query instance instead of the one on which + * the callback was originally attached to - see the examples below! + * + * ### Examples: + * + * Return all results from the table indexed by id: + * + * ``` + * $query->select(['id', 'name'])->formatResults(function ($results) { + * return $results->indexBy('id'); + * }); + * ``` + * + * Add a new column to the ResultSet: + * + * ``` + * $query->select(['name', 'birth_date'])->formatResults(function ($results) { + * return $results->map(function ($row) { + * $row['age'] = $row['birth_date']->diff(new DateTime)->y; + * + * return $row; + * }); + * }); + * ``` + * + * Add a new column to the results with respect to the query's hydration configuration: + * + * ``` + * $query->formatResults(function ($results, $query) { + * return $results->map(function ($row) use ($query) { + * $data = [ + * 'bar' => 'baz', + * ]; + * + * if ($query->isHydrationEnabled()) { + * $row['foo'] = new Foo($data) + * } else { + * $row['foo'] = $data; + * } + * + * return $row; + * }); + * }); + * ``` + * + * Retaining access to the association target query instance of joined associations, + * by inheriting the contain callback's query argument: + * + * ``` + * // Assuming a `Articles belongsTo Authors` association that uses the join strategy + * + * $articlesQuery->contain('Authors', function ($authorsQuery) { + * return $authorsQuery->formatResults(function ($results, $query) use ($authorsQuery) { + * // Here `$authorsQuery` will always be the instance + * // where the callback was attached to. + * + * // The instance passed to the callback in the second + * // argument (`$query`), will be the one where the + * // callback is actually being applied to, in this + * // example that would be `$articlesQuery`. + * + * // ... + * + * return $results; + * }); + * }); + * ``` + * + * @param \Closure|null $formatter The formatting function + * @param int|bool $mode Whether to overwrite, append or prepend the formatter. + * @return $this + * @throws \InvalidArgumentException + */ + public function formatResults(?Closure $formatter = null, int|bool $mode = self::APPEND) + { + if ($mode === self::OVERWRITE) { + $this->_formatters = []; + } + if ($formatter === null) { + /** @psalm-suppress RedundantCondition */ + if ($mode !== self::OVERWRITE) { + throw new InvalidArgumentException('$formatter can be null only when $mode is overwrite.'); + } + + return $this; + } + + if ($mode === self::PREPEND) { + array_unshift($this->_formatters, $formatter); + + return $this; + } + + $this->_formatters[] = $formatter; + + return $this; + } + + /** + * Returns the list of previously registered format routines. + * + * @return array<\Closure> + */ + public function getResultFormatters(): array + { + return $this->_formatters; + } + + /** + * Returns the first result out of executing this query, if the query has not been + * executed before, it will set the limit clause to 1 for performance reasons. + * + * ### Example: + * + * ``` + * $singleUser = $query->select(['id', 'username'])->first(); + * ``` + * + * @return mixed The first result from the ResultSet. + */ + public function first(): mixed + { + if ($this->_dirty) { + $this->limit(1); + } + + return $this->all()->first(); + } + + /** + * Get the first result from the executing query or raise an exception. + * + * @throws \Cake\Datasource\Exception\RecordNotFoundException When there is no first record. + * @return mixed The first result from the ResultSet. + */ + public function firstOrFail(): mixed + { + $entity = $this->first(); + if (!$entity) { + $table = $this->getRepository(); + throw new RecordNotFoundException(sprintf( + 'Record not found in table "%s"', + $table->getTable() + )); + } + + return $entity; + } + + /** + * Returns an array with the custom options that were applied to this query + * and that were not already processed by another method in this class. + * + * ### Example: + * + * ``` + * $query->applyOptions(['doABarrelRoll' => true, 'fields' => ['id', 'name']); + * $query->getOptions(); // Returns ['doABarrelRoll' => true] + * ``` + * + * @see \Cake\Datasource\QueryInterface::applyOptions() to read about the options that will + * be processed by this class and not returned by this function + * @return array + * @see applyOptions() + */ + public function getOptions(): array + { + return $this->_options; + } + + /** + * Populates or adds parts to current query clauses using an array. + * This is handy for passing all query clauses at once. + * + * The method accepts the following query clause related options: + * + * - fields: Maps to the select method + * - conditions: Maps to the where method + * - limit: Maps to the limit method + * - order: Maps to the order method + * - offset: Maps to the offset method + * - group: Maps to the group method + * - having: Maps to the having method + * - contain: Maps to the contain options for eager loading + * - join: Maps to the join method + * - page: Maps to the page method + * + * All other options will not affect the query, but will be stored + * as custom options that can be read via `getOptions()`. Furthermore + * they are automatically passed to `Model.beforeFind`. + * + * ### Example: + * + * ``` + * $query->applyOptions([ + * 'fields' => ['id', 'name'], + * 'conditions' => [ + * 'created >=' => '2013-01-01' + * ], + * 'limit' => 10, + * ]); + * ``` + * + * Is equivalent to: + * + * ``` + * $query + * ->select(['id', 'name']) + * ->where(['created >=' => '2013-01-01']) + * ->limit(10) + * ``` + * + * Custom options can be read via `getOptions()`: + * + * ``` + * $query->applyOptions([ + * 'fields' => ['id', 'name'], + * 'custom' => 'value', + * ]); + * ``` + * + * Here `$options` will hold `['custom' => 'value']` (the `fields` + * option will be applied to the query instead of being stored, as + * it's a query clause related option): + * + * ``` + * $options = $query->getOptions(); + * ``` + * + * @param array $options The options to be applied + * @return $this + * @see getOptions() + */ + public function applyOptions(array $options) + { + $valid = [ + 'fields' => 'select', + 'conditions' => 'where', + 'join' => 'join', + 'order' => 'order', + 'limit' => 'limit', + 'offset' => 'offset', + 'group' => 'group', + 'having' => 'having', + 'contain' => 'contain', + 'page' => 'page', + ]; + + ksort($options); + foreach ($options as $option => $values) { + if (isset($valid[$option], $values)) { + $this->{$valid[$option]}($values); + } else { + $this->_options[$option] = $values; + } + } + + return $this; + } + + /** + * Decorates the results iterator with MapReduce routines and formatters + * + * @param iterable $result Original results + * @return \Cake\Datasource\ResultSetInterface + */ + protected function _decorateResults(iterable $result): ResultSetInterface + { + $decorator = $this->_decoratorClass(); + + if (!empty($this->_mapReduce)) { + foreach ($this->_mapReduce as $functions) { + $result = new MapReduce($result, $functions['mapper'], $functions['reducer']); + } + $result = new $decorator($result); + } + + if (!($result instanceof ResultSetInterface)) { + $result = new $decorator($result); + } + + if (!empty($this->_formatters)) { + foreach ($this->_formatters as $formatter) { + $result = $formatter($result, $this); + } + + if (!($result instanceof ResultSetInterface)) { + $result = new $decorator($result); + } + } + + return $result; + } + + /** + * Returns the name of the class to be used for decorating results + * + * @return class-string<\Cake\Datasource\ResultSetInterface> + */ + protected function _decoratorClass(): string + { + return ResultSetDecorator::class; + } + /** * Adds new fields to be returned by a `SELECT` statement when this query is * executed. Fields can be passed as an array of strings, array of expression @@ -698,96 +1316,6 @@ public function notMatching(string $assoc, ?Closure $builder = null) return $this; } - /** - * Populates or adds parts to current query clauses using an array. - * This is handy for passing all query clauses at once. - * - * The method accepts the following query clause related options: - * - * - fields: Maps to the select method - * - conditions: Maps to the where method - * - limit: Maps to the limit method - * - order: Maps to the order method - * - offset: Maps to the offset method - * - group: Maps to the group method - * - having: Maps to the having method - * - contain: Maps to the contain options for eager loading - * - join: Maps to the join method - * - page: Maps to the page method - * - * All other options will not affect the query, but will be stored - * as custom options that can be read via `getOptions()`. Furthermore - * they are automatically passed to `Model.beforeFind`. - * - * ### Example: - * - * ``` - * $query->applyOptions([ - * 'fields' => ['id', 'name'], - * 'conditions' => [ - * 'created >=' => '2013-01-01' - * ], - * 'limit' => 10, - * ]); - * ``` - * - * Is equivalent to: - * - * ``` - * $query - * ->select(['id', 'name']) - * ->where(['created >=' => '2013-01-01']) - * ->limit(10) - * ``` - * - * Custom options can be read via `getOptions()`: - * - * ``` - * $query->applyOptions([ - * 'fields' => ['id', 'name'], - * 'custom' => 'value', - * ]); - * ``` - * - * Here `$options` will hold `['custom' => 'value']` (the `fields` - * option will be applied to the query instead of being stored, as - * it's a query clause related option): - * - * ``` - * $options = $query->getOptions(); - * ``` - * - * @param array $options The options to be applied - * @return $this - * @see getOptions() - */ - public function applyOptions(array $options) - { - $valid = [ - 'fields' => 'select', - 'conditions' => 'where', - 'join' => 'join', - 'order' => 'order', - 'limit' => 'limit', - 'offset' => 'offset', - 'group' => 'group', - 'having' => 'having', - 'contain' => 'contain', - 'page' => 'page', - ]; - - ksort($options); - foreach ($options as $option => $values) { - if (isset($valid[$option], $values)) { - $this->{$valid[$option]}($values); - } else { - $this->_options[$option] = $values; - } - } - - return $this; - } - /** * Creates a copy of this current query, triggers beforeFind and resets some state. * @@ -990,32 +1518,6 @@ public function isHydrationEnabled(): bool return $this->_hydrate; } - /** - * {@inheritDoc} - * - * @param \Closure|string|false $key Either the cache key or a function to generate the cache key. - * When using a function, this query instance will be supplied as an argument. - * @param \Cake\Cache\CacheEngine|string $config Either the name of the cache config to use, or - * a cache config instance. - * @return $this - * @throws \Cake\Database\Exception\DatabaseException When you attempt to cache a non-select query. - */ - public function cache($key, $config = 'default') - { - return $this->_cache($key, $config); - } - - /** - * {@inheritDoc} - * - * @return \Cake\Datasource\ResultSetInterface - * @throws \Cake\Database\Exception\DatabaseException if this method is called on a non-select Query. - */ - public function all(): ResultSetInterface - { - return $this->_all(); - } - /** * Trigger the beforeFind event on the query's repository object. * diff --git a/Query/CommonQueryTrait.php b/Query/CommonQueryTrait.php index d837496d..0873f77a 100644 --- a/Query/CommonQueryTrait.php +++ b/Query/CommonQueryTrait.php @@ -21,11 +21,16 @@ /** * Trait with common methods used by all ORM query classes. - * - * @property \Cake\ORM\Table $_repository Instance of a table object this query is bound to. */ trait CommonQueryTrait { + /** + * Instance of a repository/table object this query is bound to. + * + * @var \Cake\ORM\Table + */ + protected Table $_repository; + /** * Hints this object to associate the correct types when casting conditions * for the database. This is done by extracting the field types from the schema @@ -54,27 +59,29 @@ public function addDefaultTypes(Table $table) * Set the default Table object that will be used by this query * and form the `FROM` clause. * - * @param \Cake\ORM\Table $repository The default table object to use. + * @param \Cake\Datasource\RepositoryInterface $repository The default table object to use * @return $this - * @psalm-suppress MoreSpecificImplementedParamType */ public function setRepository(RepositoryInterface $repository) { - /** @psalm-suppress UndefinedThisPropertyAssignment */ + assert( + $repository instanceof Table, + '`$repository` must be an instance of Cake\ORM\Table.' + ); + $this->_repository = $repository; return $this; } /** - * Returns the default table object that will be used by this query, + * Returns the default repository object that will be used by this query, * that is, the table that will appear in the from clause. * * @return \Cake\ORM\Table */ public function getRepository(): Table { - /** @psalm-suppress UndefinedThisPropertyFetch */ return $this->_repository; } } From 85a3468baaa574b7ec7e15b2963151adf195a072 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 4 Sep 2022 14:53:33 +0530 Subject: [PATCH 1806/2059] Harmonize method names --- Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Query.php b/Query.php index 9f8d0e35..5c49a52e 100644 --- a/Query.php +++ b/Query.php @@ -1436,7 +1436,7 @@ protected function _performCount(): int ->disableAutoFields() ->execute(); } else { - $statement = $this->getConnection()->newSelectQuery() + $statement = $this->getConnection()->selectQuery() ->select($count) ->from(['count_source' => $query]) ->execute(); From 1e028b726b53acd1eb6b677463a3e2f4eb46477f Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 4 Sep 2022 23:43:26 +0530 Subject: [PATCH 1807/2059] Add ORM\Query\QueryFactory. --- Query.php | 26 +++++++------- Query/QueryFactory.php | 79 ++++++++++++++++++++++++++++++++++++++++++ Table.php | 24 ++++++++----- 3 files changed, 107 insertions(+), 22 deletions(-) create mode 100644 Query/QueryFactory.php diff --git a/Query.php b/Query.php index 5c49a52e..8d77f70d 100644 --- a/Query.php +++ b/Query.php @@ -1682,6 +1682,18 @@ public function find(string $finder, array $options = []): static return $table->callFinder($finder, $this, $options); } + /** + * Disable auto adding table's alias to the fields of SELECT clause. + * + * @return $this + */ + public function disableAutoAliasing() + { + $this->aliasingEnabled = false; + + return $this; + } + /** * Marks a query as dirty, removing any preprocessed information * from in memory caching such as previous results @@ -1695,20 +1707,6 @@ protected function _dirty(): void parent::_dirty(); } - /** - * Returns a new Query that has automatic field aliasing disabled. - * - * @param \Cake\ORM\Table $table The table this query is starting on - * @return static - */ - public static function subquery(Table $table): static - { - $query = new static($table->getConnection(), $table); - $query->aliasingEnabled = false; - - return $query; - } - /** * @inheritDoc */ diff --git a/Query/QueryFactory.php b/Query/QueryFactory.php new file mode 100644 index 00000000..1eb937f4 --- /dev/null +++ b/Query/QueryFactory.php @@ -0,0 +1,79 @@ +connection, $this->table); + } + + /** + * Create a new InsertQuery instance. + * + * @return \Cake\ORM\Query\InsertQuery + */ + public function insert(): InsertQuery + { + return new InsertQuery($this->connection, $this->table); + } + + /** + * Create a new UpdateQuery instance. + * + * @return \Cake\ORM\Query\UpdateQuery + */ + public function update(): UpdateQuery + { + return new UpdateQuery($this->connection, $this->table); + } + + /** + * Create a new DeleteQuery instance. + * + * @return \Cake\ORM\Query\DeleteQuery + */ + public function delete(): DeleteQuery + { + return new DeleteQuery($this->connection, $this->table); + } +} diff --git a/Table.php b/Table.php index c5257b88..be2510ec 100644 --- a/Table.php +++ b/Table.php @@ -43,6 +43,7 @@ use Cake\ORM\Exception\RolledbackTransactionException; use Cake\ORM\Query\DeleteQuery; use Cake\ORM\Query\InsertQuery; +use Cake\ORM\Query\QueryFactory; use Cake\ORM\Query\UpdateQuery; use Cake\ORM\Rule\IsUnique; use Cake\Utility\Inflector; @@ -259,6 +260,8 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc */ protected ?string $_registryAlias = null; + protected QueryFactory $queryFactory; + /** * Initializes a new instance * @@ -278,7 +281,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc * validation set, or an associative array, where key is the name of the * validation set and value the Validator instance. * - * @param array $config List of options for this table + * @param array $config List of options for this table. */ public function __construct(array $config = []) { @@ -294,6 +297,9 @@ public function __construct(array $config = []) if (!empty($config['connection'])) { $this->setConnection($config['connection']); } + if (!empty($config['queryFactory'])) { + $this->queryFactory = $config['queryFactory']; + } if (!empty($config['schema'])) { $this->setSchema($config['schema']); } @@ -323,6 +329,7 @@ public function __construct(array $config = []) $this->_behaviors = $behaviors ?: new BehaviorRegistry(); $this->_behaviors->setTable($this); $this->_associations = $associations ?: new AssociationCollection(); + $this->queryFactory ??= new QueryFactory($this->getConnection(), $this); $this->initialize($config); $this->_eventManager->on($this); @@ -1689,7 +1696,7 @@ protected function _getFindOrCreateQuery(Query|callable|array $search): Query */ public function query(): Query { - return new Query($this->getConnection(), $this); + return $this->queryFactory->select(); } /** @@ -1699,7 +1706,7 @@ public function query(): Query */ public function insertQuery(): InsertQuery { - return new InsertQuery($this->getConnection(), $this); + return $this->queryFactory->insert(); } /** @@ -1709,7 +1716,7 @@ public function insertQuery(): InsertQuery */ public function updateQuery(): UpdateQuery { - return new UpdateQuery($this->getConnection(), $this); + return $this->queryFactory->update(); } /** @@ -1719,18 +1726,19 @@ public function updateQuery(): UpdateQuery */ public function deleteQuery(): DeleteQuery { - return new DeleteQuery($this->getConnection(), $this); + return $this->queryFactory->delete(); } /** - * Creates a new Query::subquery() instance for a table. + * Creates a new Query instance with field auto aliasing disabled. + * + * This is useful for subqueries. * * @return \Cake\ORM\Query - * @see \Cake\ORM\Query::subquery() */ public function subquery(): Query { - return Query::subquery($this); + return $this->queryFactory->select()->disableAutoAliasing(); } /** From b85fa3bb6e00c2b1458a83504568cb30aac5a9dc Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 5 Sep 2022 18:46:32 +0530 Subject: [PATCH 1808/2059] Maintain a common instance of query factory. --- Locator/TableLocator.php | 10 +++++++++- Query/QueryFactory.php | 33 ++++++++++++--------------------- Table.php | 12 ++++++------ 3 files changed, 27 insertions(+), 28 deletions(-) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 862a38c4..0ef53b02 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -23,6 +23,7 @@ use Cake\Datasource\RepositoryInterface; use Cake\ORM\AssociationCollection; use Cake\ORM\Exception\MissingTableClassException; +use Cake\ORM\Query\QueryFactory; use Cake\ORM\Table; use Cake\Utility\Inflector; @@ -76,13 +77,15 @@ class TableLocator extends AbstractLocator implements LocatorInterface */ protected bool $allowFallbackClass = true; + protected QueryFactory $queryFactory; + /** * Constructor. * * @param array|null $locations Locations where tables should be looked for. * If none provided, the default `Model\Table` under your app's namespace is used. */ - public function __construct(?array $locations = null) + public function __construct(?array $locations = null, ?QueryFactory $queryFactory = null) { if ($locations === null) { $locations = [ @@ -93,6 +96,8 @@ public function __construct(?array $locations = null) foreach ($locations as $location) { $this->addLocation($location); } + + $this->queryFactory = $queryFactory ?: new QueryFactory(); } /** @@ -258,6 +263,9 @@ protected function createInstance(string $alias, array $options): Table $associations = new AssociationCollection($this); $options['associations'] = $associations; } + if (empty($options['queryFactory'])) { + $options['queryFactory'] = $this->queryFactory; + } $options['registryAlias'] = $alias; $instance = $this->_create($options); diff --git a/Query/QueryFactory.php b/Query/QueryFactory.php index 1eb937f4..657f6876 100644 --- a/Query/QueryFactory.php +++ b/Query/QueryFactory.php @@ -16,7 +16,6 @@ */ namespace Cake\ORM\Query; -use Cake\Database\Connection; use Cake\ORM\Query; use Cake\ORM\Table; @@ -25,55 +24,47 @@ */ class QueryFactory { - /** - * Constructor - * - * @param \Cake\Database\Connection $connection Connection instance. - * @param \Cake\ORM\Table $table The table the query instanced created will be starting on. - */ - public function __construct( - protected Connection $connection, - protected Table $table - ) { - } - /** * Create a new Query instance. * + * @param \Cake\ORM\Table $table The table this query is starting on. * @return \Cake\ORM\Query */ - public function select(): Query + public function select(Table $table): Query { - return new Query($this->connection, $this->table); + return new Query($table->getConnection(), $table); } /** * Create a new InsertQuery instance. * + * @param \Cake\ORM\Table $table The table this query is starting on. * @return \Cake\ORM\Query\InsertQuery */ - public function insert(): InsertQuery + public function insert(Table $table): InsertQuery { - return new InsertQuery($this->connection, $this->table); + return new InsertQuery($table->getconnection(), $table); } /** * Create a new UpdateQuery instance. * + * @param \Cake\ORM\Table $table The table this query is starting on. * @return \Cake\ORM\Query\UpdateQuery */ - public function update(): UpdateQuery + public function update(Table $table): UpdateQuery { - return new UpdateQuery($this->connection, $this->table); + return new UpdateQuery($table->getconnection(), $table); } /** * Create a new DeleteQuery instance. * + * @param \Cake\ORM\Table $table The table this query is starting on. * @return \Cake\ORM\Query\DeleteQuery */ - public function delete(): DeleteQuery + public function delete(Table $table): DeleteQuery { - return new DeleteQuery($this->connection, $this->table); + return new DeleteQuery($table->getconnection(), $table); } } diff --git a/Table.php b/Table.php index be2510ec..cfaa936c 100644 --- a/Table.php +++ b/Table.php @@ -329,7 +329,7 @@ public function __construct(array $config = []) $this->_behaviors = $behaviors ?: new BehaviorRegistry(); $this->_behaviors->setTable($this); $this->_associations = $associations ?: new AssociationCollection(); - $this->queryFactory ??= new QueryFactory($this->getConnection(), $this); + $this->queryFactory ??= new QueryFactory(); $this->initialize($config); $this->_eventManager->on($this); @@ -1696,7 +1696,7 @@ protected function _getFindOrCreateQuery(Query|callable|array $search): Query */ public function query(): Query { - return $this->queryFactory->select(); + return $this->queryFactory->select($this); } /** @@ -1706,7 +1706,7 @@ public function query(): Query */ public function insertQuery(): InsertQuery { - return $this->queryFactory->insert(); + return $this->queryFactory->insert($this); } /** @@ -1716,7 +1716,7 @@ public function insertQuery(): InsertQuery */ public function updateQuery(): UpdateQuery { - return $this->queryFactory->update(); + return $this->queryFactory->update($this); } /** @@ -1726,7 +1726,7 @@ public function updateQuery(): UpdateQuery */ public function deleteQuery(): DeleteQuery { - return $this->queryFactory->delete(); + return $this->queryFactory->delete($this); } /** @@ -1738,7 +1738,7 @@ public function deleteQuery(): DeleteQuery */ public function subquery(): Query { - return $this->queryFactory->select()->disableAutoAliasing(); + return $this->queryFactory->select($this)->disableAutoAliasing(); } /** From dba77fb7349ce58051e0986b7905215e1d261371 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 7 Sep 2022 19:49:59 +0530 Subject: [PATCH 1809/2059] Fix errors reported by psalm --- Behavior/TimestampBehavior.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 754813f5..4b2c48a7 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -134,6 +134,7 @@ public function handleEvent(EventInterface $event, EntityInterface $entity): boo */ public function implementedEvents(): array { + /** @var array */ return array_fill_keys(array_keys($this->_config['events']), 'handleEvent'); } From d60d4ce8d20536f5063939d3830bde93823341a2 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 9 Sep 2022 00:16:12 +0530 Subject: [PATCH 1810/2059] Remove uneeded constructor args. The ORM queries can get the connection from the table. --- Query.php | 7 +++---- Query/DeleteQuery.php | 6 ++---- Query/InsertQuery.php | 6 ++---- Query/QueryFactory.php | 8 ++++---- Query/UpdateQuery.php | 6 ++---- 5 files changed, 13 insertions(+), 20 deletions(-) diff --git a/Query.php b/Query.php index 8d77f70d..48675e97 100644 --- a/Query.php +++ b/Query.php @@ -18,7 +18,6 @@ use ArrayObject; use Cake\Collection\Iterator\MapReduce; -use Cake\Database\Connection; use Cake\Database\ExpressionInterface; use Cake\Database\Query\SelectQuery as DbSelectQuery; use Cake\Database\TypedResultInterface; @@ -193,12 +192,12 @@ class Query extends DbSelectQuery implements JsonSerializable, QueryInterface /** * Constructor * - * @param \Cake\Database\Connection $connection The connection object * @param \Cake\ORM\Table $table The table this query is starting on */ - public function __construct(Connection $connection, Table $table) + public function __construct(Table $table) { - parent::__construct($connection); + parent::__construct($table->getConnection()); + $this->setRepository($table); $this->addDefaultTypes($table); } diff --git a/Query/DeleteQuery.php b/Query/DeleteQuery.php index 166bb46d..3548d8bf 100644 --- a/Query/DeleteQuery.php +++ b/Query/DeleteQuery.php @@ -16,7 +16,6 @@ */ namespace Cake\ORM\Query; -use Cake\Database\Connection; use Cake\Database\Query\DeleteQuery as DbDeleteQuery; use Cake\Database\ValueBinder; use Cake\ORM\Table; @@ -31,12 +30,11 @@ class DeleteQuery extends DbDeleteQuery /** * Constructor * - * @param \Cake\Database\Connection $connection The connection object * @param \Cake\ORM\Table $table The table this query is starting on */ - public function __construct(Connection $connection, Table $table) + public function __construct(Table $table) { - parent::__construct($connection); + parent::__construct($table->getConnection()); $this->setRepository($table); $this->addDefaultTypes($table); diff --git a/Query/InsertQuery.php b/Query/InsertQuery.php index 81732bce..74ddff67 100644 --- a/Query/InsertQuery.php +++ b/Query/InsertQuery.php @@ -16,7 +16,6 @@ */ namespace Cake\ORM\Query; -use Cake\Database\Connection; use Cake\Database\Query\InsertQuery as DbInsertQuery; use Cake\Database\ValueBinder; use Cake\ORM\Table; @@ -31,12 +30,11 @@ class InsertQuery extends DbInsertQuery /** * Constructor * - * @param \Cake\Database\Connection $connection The connection object * @param \Cake\ORM\Table $table The table this query is starting on */ - public function __construct(Connection $connection, Table $table) + public function __construct(Table $table) { - parent::__construct($connection); + parent::__construct($table->getConnection()); $this->setRepository($table); $this->addDefaultTypes($table); diff --git a/Query/QueryFactory.php b/Query/QueryFactory.php index 657f6876..944f6569 100644 --- a/Query/QueryFactory.php +++ b/Query/QueryFactory.php @@ -32,7 +32,7 @@ class QueryFactory */ public function select(Table $table): Query { - return new Query($table->getConnection(), $table); + return new Query($table); } /** @@ -43,7 +43,7 @@ public function select(Table $table): Query */ public function insert(Table $table): InsertQuery { - return new InsertQuery($table->getconnection(), $table); + return new InsertQuery($table); } /** @@ -54,7 +54,7 @@ public function insert(Table $table): InsertQuery */ public function update(Table $table): UpdateQuery { - return new UpdateQuery($table->getconnection(), $table); + return new UpdateQuery($table); } /** @@ -65,6 +65,6 @@ public function update(Table $table): UpdateQuery */ public function delete(Table $table): DeleteQuery { - return new DeleteQuery($table->getconnection(), $table); + return new DeleteQuery($table); } } diff --git a/Query/UpdateQuery.php b/Query/UpdateQuery.php index fb125d20..7004d7e1 100644 --- a/Query/UpdateQuery.php +++ b/Query/UpdateQuery.php @@ -16,7 +16,6 @@ */ namespace Cake\ORM\Query; -use Cake\Database\Connection; use Cake\Database\Query\UpdateQuery as DbUpdateQuery; use Cake\Database\ValueBinder; use Cake\ORM\Table; @@ -31,12 +30,11 @@ class UpdateQuery extends DbUpdateQuery /** * Constructor * - * @param \Cake\Database\Connection $connection The connection object * @param \Cake\ORM\Table $table The table this query is starting on */ - public function __construct(Connection $connection, Table $table) + public function __construct(Table $table) { - parent::__construct($connection); + parent::__construct($table->getConnection()); $this->setRepository($table); $this->addDefaultTypes($table); From d7f7aa85d490a4cc999112952069276d7d1f9e37 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 11 Sep 2022 00:35:55 +0530 Subject: [PATCH 1811/2059] Remove unneeded method. Users can use Table::initialize() instead to modify the schema. --- Table.php | 32 +++----------------------------- 1 file changed, 3 insertions(+), 29 deletions(-) diff --git a/Table.php b/Table.php index cfaa936c..5f64350f 100644 --- a/Table.php +++ b/Table.php @@ -515,11 +515,9 @@ public function getConnection(): Connection public function getSchema(): TableSchemaInterface { if ($this->_schema === null) { - $this->_schema = $this->_initializeSchema( - $this->getConnection() - ->getSchemaCollection() - ->describe($this->getTable()) - ); + $this->_schema = $this->getConnection() + ->getSchemaCollection() + ->describe($this->getTable()); if (Configure::read('debug')) { $this->checkAliasLengths(); } @@ -594,30 +592,6 @@ protected function checkAliasLengths(): void } } - /** - * Override this function in order to alter the schema used by this table. - * This function is only called after fetching the schema out of the database. - * If you wish to provide your own schema to this table without touching the - * database, you can override schema() or inject the definitions though that - * method. - * - * ### Example: - * - * ``` - * protected function _initializeSchema(\Cake\Database\Schema\TableSchemaInterface $schema) { - * $schema->setColumnType('preferences', 'json'); - * return $schema; - * } - * ``` - * - * @param \Cake\Database\Schema\TableSchemaInterface $schema The table definition fetched from database. - * @return \Cake\Database\Schema\TableSchemaInterface the altered schema - */ - protected function _initializeSchema(TableSchemaInterface $schema): TableSchemaInterface - { - return $schema; - } - /** * Test to see if a Table has a specific field/column. * From 9120bf7c65a0423fa1bb89ada6aa91b3290dd2c8 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Sun, 11 Sep 2022 04:41:02 -0500 Subject: [PATCH 1812/2059] Assert that finder options argument is an associative array --- Table.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Table.php b/Table.php index cfaa936c..44f2ad56 100644 --- a/Table.php +++ b/Table.php @@ -2581,6 +2581,8 @@ public function hasFinder(string $type): bool */ public function callFinder(string $type, Query $query, array $options = []): Query { + assert(empty($options) || !array_is_list($options), 'Finder options should be an associative array not a list'); + $query->applyOptions($options); $options = $query->getOptions(); $finder = 'find' . $type; From 2e67993f2f4a75d33054b97c10462cc991ebdcfd Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 11 Sep 2022 22:41:20 -0400 Subject: [PATCH 1813/2059] Add deprecation for Table::_initializeSchema() This method will be removed in 5.x, as we also have `getSchema()` which can be overridden in application code. Refs #16739 --- Table.php | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Table.php b/Table.php index f3e58492..eb4c811d 100644 --- a/Table.php +++ b/Table.php @@ -45,6 +45,7 @@ use Cake\Validation\ValidatorAwareTrait; use Exception; use InvalidArgumentException; +use ReflectionMethod; use RuntimeException; /** @@ -507,11 +508,17 @@ public function getConnection(): Connection public function getSchema(): TableSchemaInterface { if ($this->_schema === null) { - $this->_schema = $this->_initializeSchema( - $this->getConnection() - ->getSchemaCollection() - ->describe($this->getTable()) - ); + $this->_schema = $this->getConnection() + ->getSchemaCollection() + ->describe($this->getTable()); + + $method = new ReflectionMethod($this, '_initializeSchema'); + if ($method->getDeclaringClass()->getName() != Table::class) { + deprecationWarning( + 'Table::_initializeSchema() is deprecated. Implement `getSchema()` with a parent call instead.' + ); + $this->_schema = $this->_initializeSchema($this->_schema); + } if (Configure::read('debug')) { $this->checkAliasLengths(); } From 5d4d63bcaebdda1876cf9dccd43f9324a94a268c Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 12 Sep 2022 09:58:48 -0400 Subject: [PATCH 1814/2059] Update src/ORM/Table.php Co-authored-by: othercorey --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index eb4c811d..1078f656 100644 --- a/Table.php +++ b/Table.php @@ -515,7 +515,7 @@ public function getSchema(): TableSchemaInterface $method = new ReflectionMethod($this, '_initializeSchema'); if ($method->getDeclaringClass()->getName() != Table::class) { deprecationWarning( - 'Table::_initializeSchema() is deprecated. Implement `getSchema()` with a parent call instead.' + 'Table::_initializeSchema() is deprecated. Override `getSchema()` with a parent call instead.' ); $this->_schema = $this->_initializeSchema($this->_schema); } From a85f90d2539f516244a30da00c364b6bc547cf41 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Wed, 14 Sep 2022 21:56:47 -0400 Subject: [PATCH 1815/2059] CS fixes. --- Table.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/Table.php b/Table.php index c24856f3..deb61afc 100644 --- a/Table.php +++ b/Table.php @@ -52,8 +52,6 @@ use Closure; use Exception; use InvalidArgumentException; -use ReflectionMethod; -use RuntimeException; /** * Represents a single database table. From 29f2ea6f233c341dd3f1aae0691491f164e74548 Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Thu, 15 Sep 2022 17:44:28 +0200 Subject: [PATCH 1816/2059] unify exception string building --- Association.php | 25 ++++++++++---------- Association/BelongsTo.php | 4 ++-- Association/Loader/SelectWithPivotLoader.php | 2 +- AssociationCollection.php | 2 +- Behavior.php | 2 +- Behavior/TimestampBehavior.php | 2 +- Behavior/TreeBehavior.php | 10 ++++---- BehaviorRegistry.php | 6 ++--- Locator/TableLocator.php | 2 +- Marshaller.php | 2 +- Query.php | 2 +- Rule/ExistsIn.php | 2 +- Table.php | 13 ++++++---- 13 files changed, 39 insertions(+), 35 deletions(-) diff --git a/Association.php b/Association.php index f6b35d8f..dab70aa6 100644 --- a/Association.php +++ b/Association.php @@ -292,7 +292,7 @@ public function setClassName(string $className) get_class($this->_targetTable) !== App::className($className, 'Model/Table', 'Table') ) { throw new InvalidArgumentException(sprintf( - 'The class name "%s" doesn\'t match the target table class name of "%s".', + 'The class name `%s` doesn\'t match the target table class name of `%s`.', $className, get_class($this->_targetTable) )); @@ -377,12 +377,12 @@ public function getTarget(): Table $className = App::className($this->_className, 'Model/Table', 'Table') ?: Table::class; if (!$this->_targetTable instanceof $className) { - $errorMessage = '%s association "%s" of type "%s" to "%s" doesn\'t match the expected class "%s". '; - $errorMessage .= 'You can\'t have an association of the same name with a different target '; - $errorMessage .= '"className" option anywhere in your app.'; + $msg = '`%s` association `%s` of type `%s` to `%s` doesn\'t match the expected class `%s`. '; + $msg .= 'You can\'t have an association of the same name with a different target '; + $msg .= '"className" option anywhere in your app.'; throw new DatabaseException(sprintf( - $errorMessage, + $msg, isset($this->_sourceTable) ? get_class($this->_sourceTable) : 'null', $this->getName(), $this->type(), @@ -606,7 +606,7 @@ public function setStrategy(string $name) { if (!in_array($name, $this->_validStrategies, true)) { throw new InvalidArgumentException(sprintf( - 'Invalid strategy "%s" was provided. Valid options are (%s).', + 'Invalid strategy `%s` was provided. Valid options are `(%s)`.', $name, implode(', ', $this->_validStrategies) )); @@ -725,7 +725,7 @@ public function attachTo(Query $query, array $options = []): void $dummy = $options['queryBuilder']($dummy); if (!($dummy instanceof Query)) { throw new DatabaseException(sprintf( - 'Query builder for association "%s" did not return a query', + 'Query builder for association `%s` did not return a query.', $this->getName() )); } @@ -736,9 +736,10 @@ public function attachTo(Query $query, array $options = []): void $this->_strategy === static::STRATEGY_JOIN && $dummy->getContain() ) { - throw new DatabaseException( - "`{$this->getName()}` association cannot contain() associations when using JOIN strategy." - ); + throw new DatabaseException(sprintf( + '`%s` association cannot contain() associations when using JOIN strategy.', + $this->getName() + )); } $dummy->where($options['conditions']); @@ -1077,11 +1078,11 @@ protected function _joinCondition(array $options): array if ($this->isOwningSide($this->getSource())) { $table = $this->getSource()->getTable(); } - $msg = 'The "%s" table does not define a primary key, and cannot have join conditions generated.'; + $msg = 'The `%s` table does not define a primary key, and cannot have join conditions generated.'; throw new DatabaseException(sprintf($msg, $table)); } - $msg = 'Cannot match provided foreignKey for "%s", got "(%s)" but expected foreign key for "(%s)"'; + $msg = 'Cannot match provided foreignKey for `%s`, got `(%s)` but expected foreign key for `(%s)`'; throw new DatabaseException(sprintf( $msg, $this->_name, diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 66866b1e..6a1b6005 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -172,11 +172,11 @@ protected function _joinCondition(array $options): array if (count($foreignKey) !== count($bindingKey)) { if (empty($bindingKey)) { - $msg = 'The "%s" table does not define a primary key. Please set one.'; + $msg = 'The `%s` table does not define a primary key. Please set one.'; throw new DatabaseException(sprintf($msg, $this->getTarget()->getTable())); } - $msg = 'Cannot match provided foreignKey for "%s", got "(%s)" but expected foreign key for "(%s)"'; + $msg = 'Cannot match provided foreignKey for `%s`, got `(%s)` but expected foreign key for `(%s)`.'; throw new DatabaseException(sprintf( $msg, $this->_name, diff --git a/Association/Loader/SelectWithPivotLoader.php b/Association/Loader/SelectWithPivotLoader.php index d3bf670c..f629bf00 100644 --- a/Association/Loader/SelectWithPivotLoader.php +++ b/Association/Loader/SelectWithPivotLoader.php @@ -180,7 +180,7 @@ protected function _buildResultMap(Query $fetchQuery, array $options): array foreach ($fetchQuery->all() as $result) { if (!isset($result[$this->junctionProperty])) { throw new DatabaseException(sprintf( - '"%s" is missing from the belongsToMany results. Results cannot be created.', + '`%s` is missing from the belongsToMany results. Results cannot be created.', $this->junctionProperty )); } diff --git a/AssociationCollection.php b/AssociationCollection.php index b8f09001..25e62537 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -266,7 +266,7 @@ protected function _saveAssociations( $relation = $this->get($alias); if (!$relation) { $msg = sprintf( - 'Cannot save %s, it is not associated to %s', + 'Cannot save `%s`, it is not associated to `%s`.', $alias, $table->getAlias() ); diff --git a/Behavior.php b/Behavior.php index b071c732..9d851cd3 100644 --- a/Behavior.php +++ b/Behavior.php @@ -242,7 +242,7 @@ public function verifyConfig(): void foreach ($this->_config[$key] as $method) { if (!is_callable([$this, $method])) { throw new CakeException(sprintf( - 'The method %s is not callable on class %s', + 'The method `%s` is not callable on class `%s`.', $method, static::class )); diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 4b2c48a7..d2381fb9 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -103,7 +103,7 @@ public function handleEvent(EventInterface $event, EntityInterface $entity): boo foreach ($events[$eventName] as $field => $when) { if (!in_array($when, ['always', 'new', 'existing'], true)) { throw new UnexpectedValueException(sprintf( - 'When should be one of "always", "new" or "existing". The passed value "%s" is invalid', + 'When should be one of "always", "new" or "existing". The passed value `%s` is invalid.', $when )); } diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index f38d0e25..97a3a7fc 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -109,7 +109,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity): void $level = $config['level']; if ($parent && $entity->get($primaryKey) === $parent) { - throw new DatabaseException("Cannot set a node's parent as itself"); + throw new DatabaseException('Cannot set a node\'s parent as itself.'); } if ($isNew) { @@ -277,7 +277,7 @@ protected function _setParent(EntityInterface $entity, mixed $parent): void if ($parentLeft > $left && $parentLeft < $right) { throw new DatabaseException(sprintf( - 'Cannot use node "%s" as parent for entity "%s"', + 'Cannot use node `%s` as parent for entity `%s`.', $parent, $entity->get($this->_getPrimaryKey()) )); @@ -389,7 +389,7 @@ function ($exp) use ($config) { public function findPath(Query $query, array $options): Query { if (empty($options['for'])) { - throw new InvalidArgumentException("The 'for' key is required for find('path')"); + throw new InvalidArgumentException('The "for" key is required for find(\'path\').'); } $config = $this->getConfig(); @@ -464,7 +464,7 @@ function ($field) { [$for, $direct] = [$options['for'], $options['direct']]; if (empty($for)) { - throw new InvalidArgumentException("The 'for' key is required for find('children')"); + throw new InvalidArgumentException('The "for" key is required for find(\'children\').'); } if ($query->clause('order') === null) { @@ -807,7 +807,7 @@ protected function _getNode(mixed $id): EntityInterface ->first(); if (!$node) { - throw new RecordNotFoundException("Node \"{$id}\" was not found in the tree."); + throw new RecordNotFoundException(sprintf('Node `%s` was not found in the tree.', $id)); } return $node; diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 0187c70b..2a348367 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -179,7 +179,7 @@ protected function _getMethods(Behavior $instance, string $class, string $alias) if (isset($this->_finderMap[$finder]) && $this->has($this->_finderMap[$finder][0])) { $duplicate = $this->_finderMap[$finder]; $error = sprintf( - '%s contains duplicate finder "%s" which is already provided by "%s"', + '`%s` contains duplicate finder `%s` which is already provided by `%s`.', $class, $finder, $duplicate[0] @@ -193,7 +193,7 @@ protected function _getMethods(Behavior $instance, string $class, string $alias) if (isset($this->_methodMap[$method]) && $this->has($this->_methodMap[$method][0])) { $duplicate = $this->_methodMap[$method]; $error = sprintf( - '%s contains duplicate method "%s" which is already provided by "%s"', + '`%s` contains duplicate method `%s` which is already provided by `%s`.', $class, $method, $duplicate[0] @@ -256,7 +256,7 @@ public function call(string $method, array $args = []): mixed } throw new BadMethodCallException( - sprintf('Cannot call "%s" it does not belong to any attached behavior.', $method) + sprintf('Cannot call `%s` it does not belong to any attached behavior.', $method) ); } diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 0ef53b02..cc37a754 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -147,7 +147,7 @@ public function setConfig($alias, $options = null) if (isset($this->instances[$alias])) { throw new DatabaseException(sprintf( - 'You cannot configure "%s", it has already been constructed.', + 'You cannot configure `%s`, it has already been constructed.', $alias )); } diff --git a/Marshaller.php b/Marshaller.php index 8d7b4ec0..32f84422 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -93,7 +93,7 @@ protected function _buildPropertyMap(array $data, array $options): array if (!$this->_table->hasAssociation($key)) { if (!str_starts_with($key, '_')) { throw new InvalidArgumentException(sprintf( - 'Cannot marshal data for "%s" association. It is not associated with "%s".', + 'Cannot marshal data for `%s` association. It is not associated with `%s`.', (string)$key, $this->_table->getAlias() )); diff --git a/Query.php b/Query.php index 48675e97..7eba7a11 100644 --- a/Query.php +++ b/Query.php @@ -604,7 +604,7 @@ public function firstOrFail(): mixed if (!$entity) { $table = $this->getRepository(); throw new RecordNotFoundException(sprintf( - 'Record not found in table "%s"', + 'Record not found in table `%s`.', $table->getTable() )); } diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index ae1c9f39..83c4232c 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -84,7 +84,7 @@ public function __invoke(EntityInterface $entity, array $options): bool if (is_string($this->_repository)) { if (!$options['repository']->hasAssociation($this->_repository)) { throw new DatabaseException(sprintf( - "ExistsIn rule for '%s' is invalid. '%s' is not associated with '%s'.", + 'ExistsIn rule for `%s` is invalid. `%s` is not associated with `%s`.', implode(', ', $this->_fields), $this->_repository, get_class($options['repository']) diff --git a/Table.php b/Table.php index deb61afc..55633ae4 100644 --- a/Table.php +++ b/Table.php @@ -570,7 +570,10 @@ public function setSchema(TableSchemaInterface|array $schema) protected function checkAliasLengths(): void { if ($this->_schema === null) { - throw new DatabaseException("Unable to check max alias lengths for `{$this->getAlias()}` without schema."); + throw new DatabaseException(sprintf( + 'Unable to check max alias lengths for `%s` without schema.', + $this->getAlias() + )); } $maxLength = $this->getConnection()->getDriver()->getMaxAliasLength(); @@ -831,7 +834,7 @@ public function getBehavior(string $name): Behavior { if (!$this->_behaviors->has($name)) { throw new InvalidArgumentException(sprintf( - 'The %s behavior is not defined on %s.', + 'The `%s` behavior is not defined on `%s`.', $name, static::class )); @@ -1466,7 +1469,7 @@ public function get($primaryKey, array $options = []): EntityInterface { if ($primaryKey === null) { throw new InvalidPrimaryKeyException(sprintf( - 'Record not found in table "%s" with primary key [NULL]', + 'Record not found in table `%s` with primary key `[NULL]`.', $this->getTable() )); } @@ -1486,7 +1489,7 @@ public function get($primaryKey, array $options = []): EntityInterface }, $primaryKey); throw new InvalidPrimaryKeyException(sprintf( - 'Record not found in table "%s" with primary key [%s]', + 'Record not found in table `%s` with primary key `[%s]`.', $this->getTable(), implode(', ', $primaryKey) )); @@ -2569,7 +2572,7 @@ public function callFinder(string $type, Query $query, array $options = []): Que } throw new BadMethodCallException(sprintf( - 'Unknown finder method "%s" on %s.', + 'Unknown finder method `%s` on `%s`.', $type, static::class )); From fa1d169d9003a34909728477a26e25d7a69ca642 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 16 Sep 2022 00:08:22 +0530 Subject: [PATCH 1817/2059] Add `ORM\Query\SelectQuery`. This makes the namespace and classname for SELECT query consistent with other query types. `ORM\Query` is retained as alias for easier upgrading. --- Association.php | 45 +- Association/BelongsToMany.php | 26 +- Association/HasMany.php | 4 +- Association/Loader/SelectLoader.php | 48 +- Association/Loader/SelectWithPivotLoader.php | 12 +- Behavior.php | 4 +- Behavior/Translate/EavStrategy.php | 16 +- Behavior/Translate/ShadowTableStrategy.php | 18 +- .../Translate/TranslateStrategyInterface.php | 6 +- Behavior/TranslateBehavior.php | 8 +- Behavior/TreeBehavior.php | 47 +- BehaviorRegistry.php | 5 +- EagerLoader.php | 15 +- LazyEagerLoader.php | 17 +- Query.php | 1765 +--------------- Query/QueryFactory.php | 7 +- Query/SelectQuery.php | 1775 +++++++++++++++++ ResultSetFactory.php | 9 +- Table.php | 73 +- 19 files changed, 1966 insertions(+), 1934 deletions(-) create mode 100644 Query/SelectQuery.php diff --git a/Association.php b/Association.php index dab70aa6..f1046fbe 100644 --- a/Association.php +++ b/Association.php @@ -27,6 +27,7 @@ use Cake\Datasource\EntityInterface; use Cake\Datasource\ResultSetDecorator; use Cake\ORM\Locator\LocatorAwareTrait; +use Cake\ORM\Query\SelectQuery; use Cake\Utility\Inflector; use Closure; use InvalidArgumentException; @@ -163,7 +164,7 @@ abstract class Association * * @var string */ - protected string $_joinType = Query::JOIN_TYPE_LEFT; + protected string $_joinType = SelectQuery::JOIN_TYPE_LEFT; /** * The property name that should be filled with data from the target table @@ -683,12 +684,12 @@ protected function _options(array $options): void * - negateMatch: Will append a condition to the passed query for excluding matches. * with this association. * - * @param \Cake\ORM\Query $query the query to be altered to include the target table data + * @param \Cake\ORM\Query\SelectQuery $query the query to be altered to include the target table data * @param array $options Any extra options or overrides to be taken in account * @return void * @throws \RuntimeException Unable to build the query or associations. */ - public function attachTo(Query $query, array $options = []): void + public function attachTo(SelectQuery $query, array $options = []): void { $target = $this->getTarget(); $table = $target->getTable(); @@ -723,7 +724,7 @@ public function attachTo(Query $query, array $options = []): void if (!empty($options['queryBuilder'])) { $dummy = $options['queryBuilder']($dummy); - if (!($dummy instanceof Query)) { + if (!($dummy instanceof SelectQuery)) { throw new DatabaseException(sprintf( 'Query builder for association `%s` did not return a query.', $this->getName() @@ -761,11 +762,11 @@ public function attachTo(Query $query, array $options = []): void * Conditionally adds a condition to the passed Query that will make it find * records where there is no match with this association. * - * @param \Cake\ORM\Query $query The query to modify + * @param \Cake\ORM\Query\SelectQuery $query The query to modify * @param array $options Options array containing the `negateMatch` key. * @return void */ - protected function _appendNotMatching(Query $query, array $options): void + protected function _appendNotMatching(SelectQuery $query, array $options): void { $target = $this->getTarget(); if (!empty($options['negateMatch'])) { @@ -833,9 +834,9 @@ public function defaultRowValue(array $row, bool $joined): array * it will be interpreted as the `$options` parameter * @param array $options The options to for the find * @see \Cake\ORM\Table::find() - * @return \Cake\ORM\Query + * @return \Cake\ORM\Query\SelectQuery */ - public function find(array|string|null $type = null, array $options = []): Query + public function find(array|string|null $type = null, array $options = []): SelectQuery { $type = $type ?: $this->getFinder(); [$type, $opts] = $this->_extractFinder($type); @@ -917,10 +918,10 @@ public function requiresKeys(array $options = []): bool * Triggers beforeFind on the target table for the query this association is * attaching to * - * @param \Cake\ORM\Query $query the query this association is attaching itself to + * @param \Cake\ORM\Query\SelectQuery $query the query this association is attaching itself to * @return void */ - protected function _dispatchBeforeFind(Query $query): void + protected function _dispatchBeforeFind(SelectQuery $query): void { $query->triggerBeforeFind(); } @@ -929,12 +930,12 @@ protected function _dispatchBeforeFind(Query $query): void * Helper function used to conditionally append fields to the select clause of * a query from the fields found in another query object. * - * @param \Cake\ORM\Query $query the query that will get the fields appended to - * @param \Cake\ORM\Query $surrogate the query having the fields to be copied from + * @param \Cake\ORM\Query\SelectQuery $query the query that will get the fields appended to + * @param \Cake\ORM\Query\SelectQuery $surrogate the query having the fields to be copied from * @param array $options options passed to the method `attachTo` * @return void */ - protected function _appendFields(Query $query, Query $surrogate, array $options): void + protected function _appendFields(SelectQuery $query, SelectQuery $surrogate, array $options): void { if ($query->getEagerLoader()->isAutoFieldsEnabled() === false) { return; @@ -960,13 +961,13 @@ protected function _appendFields(Query $query, Query $surrogate, array $options) * applying the surrogate formatters to only the property corresponding to * such table. * - * @param \Cake\ORM\Query $query the query that will get the formatter applied to - * @param \Cake\ORM\Query $surrogate the query having formatters for the associated + * @param \Cake\ORM\Query\SelectQuery $query the query that will get the formatter applied to + * @param \Cake\ORM\Query\SelectQuery $surrogate the query having formatters for the associated * target table. * @param array $options options passed to the method `attachTo` * @return void */ - protected function _formatAssociationResults(Query $query, Query $surrogate, array $options): void + protected function _formatAssociationResults(SelectQuery $query, SelectQuery $surrogate, array $options): void { $formatters = $surrogate->getResultFormatters(); @@ -978,7 +979,7 @@ protected function _formatAssociationResults(Query $query, Query $surrogate, arr $propertyPath = explode('.', $property); $query->formatResults(function ( CollectionInterface $results, - Query $query + SelectQuery $query ) use ( $formatters, $property, @@ -1010,7 +1011,7 @@ protected function _formatAssociationResults(Query $query, Query $surrogate, arr } return $results; - }, Query::PREPEND); + }, SelectQuery::PREPEND); } /** @@ -1021,12 +1022,12 @@ protected function _formatAssociationResults(Query $query, Query $surrogate, arr * passed `$query`. Containments are altered so that they respect the associations * chain from which they originated. * - * @param \Cake\ORM\Query $query the query that will get the associations attached to - * @param \Cake\ORM\Query $surrogate the query having the containments to be attached + * @param \Cake\ORM\Query\SelectQuery $query the query that will get the associations attached to + * @param \Cake\ORM\Query\SelectQuery $surrogate the query having the containments to be attached * @param array $options options passed to the method `attachTo` * @return void */ - protected function _bindNewAssociations(Query $query, Query $surrogate, array $options): void + protected function _bindNewAssociations(SelectQuery $query, SelectQuery $surrogate, array $options): void { $loader = $surrogate->getEagerLoader(); $contain = $loader->getContain(); @@ -1189,7 +1190,7 @@ abstract public function type(): string; * * Options array accepts the following keys: * - * - query: Query object setup to find the source table records + * - query: SelectQuery object setup to find the source table records * - keys: List of primary key values from the source table * - foreignKey: The name of the field used to relate both tables * - conditions: List of conditions to be passed to the query where() method diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 9b68d8cc..3b2f21f2 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -23,7 +23,7 @@ use Cake\Datasource\EntityInterface; use Cake\ORM\Association; use Cake\ORM\Association\Loader\SelectWithPivotLoader; -use Cake\ORM\Query; +use Cake\ORM\Query\SelectQuery; use Cake\ORM\Table; use Cake\Utility\Hash; use Cake\Utility\Inflector; @@ -59,7 +59,7 @@ class BelongsToMany extends Association * * @var string */ - protected string $_joinType = Query::JOIN_TYPE_INNER; + protected string $_joinType = SelectQuery::JOIN_TYPE_INNER; /** * The strategy name to be used to fetch associated records. @@ -441,11 +441,11 @@ protected function _generateJunctionAssociations(Table $junction, Table $source, * - fields: a list of fields in the target table to include in the result * - type: The type of join to be used (e.g. INNER) * - * @param \Cake\ORM\Query $query the query to be altered to include the target table data + * @param \Cake\ORM\Query\SelectQuery $query the query to be altered to include the target table data * @param array $options Any extra options or overrides to be taken in account * @return void */ - public function attachTo(Query $query, array $options = []): void + public function attachTo(SelectQuery $query, array $options = []): void { if (!empty($options['negateMatch'])) { $this->_appendNotMatching($query, $options); @@ -481,7 +481,7 @@ public function attachTo(Query $query, array $options = []): void /** * @inheritDoc */ - protected function _appendNotMatching(Query $query, array $options): void + protected function _appendNotMatching(SelectQuery $query, array $options): void { if (empty($options['negateMatch'])) { return; @@ -1065,9 +1065,9 @@ protected function junctionConditions(): array * it will be interpreted as the `$options` parameter * @param array $options The options to for the find * @see \Cake\ORM\Table::find() - * @return \Cake\ORM\Query + * @return \Cake\ORM\Query\SelectQuery */ - public function find(array|string|null $type = null, array $options = []): Query + public function find(array|string|null $type = null, array $options = []): SelectQuery { $type = $type ?: $this->getFinder(); [$type, $opts] = $this->_extractFinder($type); @@ -1086,11 +1086,11 @@ public function find(array|string|null $type = null, array $options = []): Query /** * Append a join to the junction table. * - * @param \Cake\ORM\Query $query The query to append. + * @param \Cake\ORM\Query\SelectQuery $query The query to append. * @param array|null $conditions The query conditions to use. - * @return \Cake\ORM\Query The modified query. + * @return \Cake\ORM\Query\SelectQuery The modified query. */ - protected function _appendJunctionJoin(Query $query, ?array $conditions = null): Query + protected function _appendJunctionJoin(SelectQuery $query, ?array $conditions = null): SelectQuery { $junctionTable = $this->junction(); if ($conditions === null) { @@ -1108,7 +1108,7 @@ protected function _appendJunctionJoin(Query $query, ?array $conditions = null): $name => [ 'table' => $junctionTable->getTable(), 'conditions' => $conditions, - 'type' => Query::JOIN_TYPE_INNER, + 'type' => SelectQuery::JOIN_TYPE_INNER, ], ]; @@ -1249,7 +1249,7 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { * `$existing` and `$jointEntities`. This method will return the values from * `$targetEntities` that were not deleted from calculating the difference. * - * @param \Cake\ORM\Query $existing a query for getting existing links + * @param \Cake\ORM\Query\SelectQuery $existing a query for getting existing links * @param array<\Cake\Datasource\EntityInterface> $jointEntities link entities that should be persisted * @param array $targetEntities entities in target table that are related to * the `$jointEntities` @@ -1257,7 +1257,7 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { * @return array|false Array of entities not deleted or false in case of deletion failure for atomic saves. */ protected function _diffLinks( - Query $existing, + SelectQuery $existing, array $jointEntities, array $targetEntities, array $options = [] diff --git a/Association/HasMany.php b/Association/HasMany.php index 02810cb2..9fa0fc68 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -24,7 +24,7 @@ use Cake\Datasource\InvalidPropertyInterface; use Cake\ORM\Association; use Cake\ORM\Association\Loader\SelectLoader; -use Cake\ORM\Query; +use Cake\ORM\Query\SelectQuery; use Cake\ORM\Table; use Closure; use InvalidArgumentException; @@ -49,7 +49,7 @@ class HasMany extends Association * * @var string */ - protected string $_joinType = Query::JOIN_TYPE_INNER; + protected string $_joinType = SelectQuery::JOIN_TYPE_INNER; /** * The strategy name to be used to fetch associated records. diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 3b742ebf..c2dc79e0 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -22,7 +22,7 @@ use Cake\Database\ExpressionInterface; use Cake\Database\ValueBinder; use Cake\ORM\Association; -use Cake\ORM\Query; +use Cake\ORM\Query\SelectQuery; use Closure; use InvalidArgumentException; @@ -153,10 +153,10 @@ protected function _defaultOptions(): array * the source table * * @param array $options options accepted by eagerLoader() - * @return \Cake\ORM\Query + * @return \Cake\ORM\Query\SelectQuery * @throws \InvalidArgumentException When a key is required for associations but not selected. */ - protected function _buildQuery(array $options): Query + protected function _buildQuery(array $options): SelectQuery { $key = $this->_linkField($options); $filter = $options['keys']; @@ -164,7 +164,7 @@ protected function _buildQuery(array $options): Query $finder = $this->finder; $options['fields'] = $options['fields'] ?? []; - /** @var \Cake\ORM\Query $query */ + /** @var \Cake\ORM\Query\SelectQuery $query */ $query = $finder(); if (isset($options['finder'])) { [$finderName, $opts] = $this->_extractFinder($options['finder']); @@ -238,12 +238,12 @@ protected function _extractFinder(array|string $finderData): array * has the foreignKey fields selected. * If the required fields are missing, throws an exception. * - * @param \Cake\ORM\Query $fetchQuery The association fetching query + * @param \Cake\ORM\Query\SelectQuery $fetchQuery The association fetching query * @param array $key The foreign key fields to check * @return void * @throws \InvalidArgumentException */ - protected function _assertFieldsPresent(Query $fetchQuery, array $key): void + protected function _assertFieldsPresent(SelectQuery $fetchQuery, array $key): void { if ($fetchQuery->isAutoFieldsEnabled()) { return; @@ -285,12 +285,12 @@ protected function _assertFieldsPresent(Query $fetchQuery, array $key): void * target table query given a filter key and some filtering values when the * filtering needs to be done using a subquery. * - * @param \Cake\ORM\Query $query Target table's query + * @param \Cake\ORM\Query\SelectQuery $query Target table's query * @param array|string $key the fields that should be used for filtering - * @param \Cake\ORM\Query $subquery The Subquery to use for filtering - * @return \Cake\ORM\Query + * @param \Cake\ORM\Query\SelectQuery $subquery The Subquery to use for filtering + * @return \Cake\ORM\Query\SelectQuery */ - protected function _addFilteringJoin(Query $query, array|string $key, Query $subquery): Query + protected function _addFilteringJoin(SelectQuery $query, array|string $key, SelectQuery $subquery): SelectQuery { $filter = []; $aliasedTable = $this->sourceAlias; @@ -321,12 +321,12 @@ protected function _addFilteringJoin(Query $query, array|string $key, Query $sub * Appends any conditions required to load the relevant set of records in the * target table query given a filter key and some filtering values. * - * @param \Cake\ORM\Query $query Target table's query + * @param \Cake\ORM\Query\SelectQuery $query Target table's query * @param array|string $key The fields that should be used for filtering * @param mixed $filter The value that should be used to match for $key - * @return \Cake\ORM\Query + * @return \Cake\ORM\Query\SelectQuery */ - protected function _addFilteringCondition(Query $query, array|string $key, mixed $filter): Query + protected function _addFilteringCondition(SelectQuery $query, array|string $key, mixed $filter): SelectQuery { if (is_array($key)) { $conditions = $this->_createTupleCondition($query, $key, $filter, 'IN'); @@ -341,14 +341,14 @@ protected function _addFilteringCondition(Query $query, array|string $key, mixed * Returns a TupleComparison object that can be used for matching all the fields * from $keys with the tuple values in $filter using the provided operator. * - * @param \Cake\ORM\Query $query Target table's query + * @param \Cake\ORM\Query\SelectQuery $query Target table's query * @param array $keys the fields that should be used for filtering * @param mixed $filter the value that should be used to match for $key * @param string $operator The operator for comparing the tuples * @return \Cake\Database\Expression\TupleComparison */ protected function _createTupleCondition( - Query $query, + SelectQuery $query, array $keys, mixed $filter, string $operator @@ -403,10 +403,10 @@ protected function _linkField(array $options): array|string * target table, it is constructed by cloning the original query that was used * to load records in the source table. * - * @param \Cake\ORM\Query $query the original query used to load source records - * @return \Cake\ORM\Query + * @param \Cake\ORM\Query\SelectQuery $query the original query used to load source records + * @return \Cake\ORM\Query\SelectQuery */ - protected function _buildSubquery(Query $query): Query + protected function _buildSubquery(SelectQuery $query): SelectQuery { $filterQuery = clone $query; $filterQuery->disableAutoFields(); @@ -435,10 +435,10 @@ protected function _buildSubquery(Query $query): Query * those columns are also included as the fields may be calculated or constant values, * that need to be present to ensure the correct association data is loaded. * - * @param \Cake\ORM\Query $query The query to get fields from. + * @param \Cake\ORM\Query\SelectQuery $query The query to get fields from. * @return array The list of fields for the subquery. */ - protected function _subqueryFields(Query $query): array + protected function _subqueryFields(SelectQuery $query): array { $keys = (array)$this->bindingKey; @@ -466,11 +466,11 @@ protected function _subqueryFields(Query $query): array * Builds an array containing the results from fetchQuery indexed by * the foreignKey value corresponding to this association. * - * @param \Cake\ORM\Query $fetchQuery The query to get results from + * @param \Cake\ORM\Query\SelectQuery $fetchQuery The query to get results from * @param array $options The options passed to the eager loader * @return array */ - protected function _buildResultMap(Query $fetchQuery, array $options): array + protected function _buildResultMap(SelectQuery $fetchQuery, array $options): array { $resultMap = []; $singleResult = in_array($this->associationType, [Association::MANY_TO_ONE, Association::ONE_TO_ONE], true); @@ -498,13 +498,13 @@ protected function _buildResultMap(Query $fetchQuery, array $options): array * Returns a callable to be used for each row in a query result set * for injecting the eager loaded rows * - * @param \Cake\ORM\Query $fetchQuery the Query used to fetch results + * @param \Cake\ORM\Query\SelectQuery $fetchQuery the Query used to fetch results * @param array $resultMap an array with the foreignKey as keys and * the corresponding target table results as value. * @param array $options The options passed to the eagerLoader method * @return \Closure */ - protected function _resultInjector(Query $fetchQuery, array $resultMap, array $options): Closure + protected function _resultInjector(SelectQuery $fetchQuery, array $resultMap, array $options): Closure { $keys = $this->associationType === Association::MANY_TO_ONE ? $this->foreignKey : diff --git a/Association/Loader/SelectWithPivotLoader.php b/Association/Loader/SelectWithPivotLoader.php index f629bf00..5b9b8911 100644 --- a/Association/Loader/SelectWithPivotLoader.php +++ b/Association/Loader/SelectWithPivotLoader.php @@ -19,7 +19,7 @@ use Cake\Database\Exception\DatabaseException; use Cake\Database\ExpressionInterface; use Cake\ORM\Association\HasMany; -use Cake\ORM\Query; +use Cake\ORM\Query\SelectQuery; use Closure; /** @@ -77,10 +77,10 @@ public function __construct(array $options) * This is used for eager loading records on the target table based on conditions. * * @param array $options options accepted by eagerLoader() - * @return \Cake\ORM\Query + * @return \Cake\ORM\Query\SelectQuery * @throws \InvalidArgumentException When a key is required for associations but not selected. */ - protected function _buildQuery(array $options): Query + protected function _buildQuery(array $options): SelectQuery { $name = $this->junctionAssociationName; $assoc = $this->junctionAssoc; @@ -135,7 +135,7 @@ protected function _buildQuery(array $options): Query /** * @inheritDoc */ - protected function _assertFieldsPresent(Query $fetchQuery, array $key): void + protected function _assertFieldsPresent(SelectQuery $fetchQuery, array $key): void { // _buildQuery() manually adds in required fields from junction table } @@ -167,12 +167,12 @@ protected function _linkField(array $options): array|string * Builds an array containing the results from fetchQuery indexed by * the foreignKey value corresponding to this association. * - * @param \Cake\ORM\Query $fetchQuery The query to get results from + * @param \Cake\ORM\Query\SelectQuery $fetchQuery The query to get results from * @param array $options The options passed to the eager loader * @return array * @throws \Cake\Database\Exception\DatabaseException when the association property is not part of the results set. */ - protected function _buildResultMap(Query $fetchQuery, array $options): array + protected function _buildResultMap(SelectQuery $fetchQuery, array $options): array { $resultMap = []; $key = (array)$options['foreignKey']; diff --git a/Behavior.php b/Behavior.php index 9d851cd3..bd69926e 100644 --- a/Behavior.php +++ b/Behavior.php @@ -50,7 +50,7 @@ * CakePHP provides a number of lifecycle events your behaviors can * listen to: * - * - `beforeFind(EventInterface $event, Query $query, ArrayObject $options, boolean $primary)` + * - `beforeFind(EventInterface $event, SelectQuery $query, ArrayObject $options, boolean $primary)` * Fired before each find operation. By stopping the event and supplying a * return value you can bypass the find operation entirely. Any changes done * to the $query instance will be retained for the rest of the find. The @@ -106,7 +106,7 @@ * methods should expect the following arguments: * * ``` - * findSlugged(Query $query, array $options) + * findSlugged(SelectQuery $query, array $options) * ``` * * @see \Cake\ORM\Table::addBehavior() diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index 502820db..e2aafbca 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -24,7 +24,7 @@ use Cake\Event\EventInterface; use Cake\ORM\Entity; use Cake\ORM\Locator\LocatorAwareTrait; -use Cake\ORM\Query; +use Cake\ORM\Query\SelectQuery; use Cake\ORM\Table; use Cake\Utility\Hash; @@ -134,7 +134,7 @@ protected function setupAssociations(): void $this->table->hasOne($name, [ 'targetTable' => $fieldTable, 'foreignKey' => 'foreign_key', - 'joinType' => $filter ? Query::JOIN_TYPE_INNER : Query::JOIN_TYPE_LEFT, + 'joinType' => $filter ? SelectQuery::JOIN_TYPE_INNER : SelectQuery ::JOIN_TYPE_LEFT, 'conditions' => $conditions, 'propertyName' => $field . '_translation', ]); @@ -161,11 +161,11 @@ protected function setupAssociations(): void * and adding a formatter to copy the values into the main table records. * * @param \Cake\Event\EventInterface $event The beforeFind event that was fired. - * @param \Cake\ORM\Query $query Query + * @param \Cake\ORM\Query\SelectQuery $query Query * @param \ArrayObject $options The options for the query * @return void */ - public function beforeFind(EventInterface $event, Query $query, ArrayObject $options): void + public function beforeFind(EventInterface $event, SelectQuery $query, ArrayObject $options): void { $locale = Hash::get($options, 'locale', $this->getLocale()); @@ -173,8 +173,8 @@ public function beforeFind(EventInterface $event, Query $query, ArrayObject $opt return; } - $conditions = function (string $field, string $locale, Query $query, array $select) { - return function (Query $q) use ($field, $locale, $query, $select) { + $conditions = function (string $field, string $locale, SelectQuery $query, array $select) { + return function (SelectQuery $q) use ($field, $locale, $query, $select) { $table = $q->getRepository(); $q->where([$table->aliasField('locale') => $locale]); @@ -210,8 +210,8 @@ public function beforeFind(EventInterface $event, Query $query, ArrayObject $opt if ($changeFilter) { $filter = $options['filterByCurrentLocale'] - ? Query::JOIN_TYPE_INNER - : Query::JOIN_TYPE_LEFT; + ? SelectQuery::JOIN_TYPE_INNER + : SelectQuery ::JOIN_TYPE_LEFT; $contain[$name]['joinType'] = $filter; } } diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 1a82f483..924dce2e 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -24,7 +24,7 @@ use Cake\Event\EventInterface; use Cake\ORM\Locator\LocatorAwareTrait; use Cake\ORM\Marshaller; -use Cake\ORM\Query; +use Cake\ORM\Query\SelectQuery; use Cake\ORM\Table; use Cake\Utility\Hash; @@ -118,11 +118,11 @@ protected function setupAssociations(): void * and adding a formatter to copy the values into the main table records. * * @param \Cake\Event\EventInterface $event The beforeFind event that was fired. - * @param \Cake\ORM\Query $query Query. + * @param \Cake\ORM\Query\SelectQuery $query Query. * @param \ArrayObject $options The options for the query. * @return void */ - public function beforeFind(EventInterface $event, Query $query, ArrayObject $options): void + public function beforeFind(EventInterface $event, SelectQuery $query, ArrayObject $options): void { $locale = Hash::get($options, 'locale', $this->getLocale()); $config = $this->getConfig(); @@ -199,11 +199,11 @@ protected function setupHasOneAssociation(string $locale, ArrayObject $options): * Only add translations for fields that are in the main table, always * add the locale field though. * - * @param \Cake\ORM\Query $query The query to check. + * @param \Cake\ORM\Query\SelectQuery $query The query to check. * @param array $config The config to use for adding fields. * @return bool Whether a join to the translation table is required. */ - protected function addFieldsToQuery(Query $query, array $config): bool + protected function addFieldsToQuery(SelectQuery $query, array $config): bool { if ($query->isAutoFieldsEnabled()) { return true; @@ -240,12 +240,12 @@ protected function addFieldsToQuery(Query $query, array $config): bool * prefixing fields with the appropriate table alias. This method currently * expects to receive an order clause only. * - * @param \Cake\ORM\Query $query the query to check. + * @param \Cake\ORM\Query\SelectQuery $query the query to check. * @param string $name The clause name. * @param array $config The config to use for adding fields. * @return bool Whether a join to the translation table is required. */ - protected function iterateClause(Query $query, string $name = '', array $config = []): bool + protected function iterateClause(SelectQuery $query, string $name = '', array $config = []): bool { /** @var \Cake\Database\Expression\QueryExpression|null $clause */ $clause = $query->clause($name); @@ -286,12 +286,12 @@ function ($c, &$field) use ($fields, $alias, $mainTableAlias, $mainTableFields, * prefixing fields with the appropriate table alias. This method currently * expects to receive a where clause only. * - * @param \Cake\ORM\Query $query the query to check. + * @param \Cake\ORM\Query\SelectQuery $query the query to check. * @param string $name The clause name. * @param array $config The config to use for adding fields. * @return bool Whether a join to the translation table is required. */ - protected function traverseClause(Query $query, string $name = '', array $config = []): bool + protected function traverseClause(SelectQuery $query, string $name = '', array $config = []): bool { /** @var \Cake\Database\Expression\QueryExpression|null $clause */ $clause = $query->clause($name); diff --git a/Behavior/Translate/TranslateStrategyInterface.php b/Behavior/Translate/TranslateStrategyInterface.php index d5ce109c..635eab2c 100644 --- a/Behavior/Translate/TranslateStrategyInterface.php +++ b/Behavior/Translate/TranslateStrategyInterface.php @@ -22,7 +22,7 @@ use Cake\Datasource\ResultSetInterface; use Cake\Event\EventInterface; use Cake\ORM\PropertyMarshalInterface; -use Cake\ORM\Query; +use Cake\ORM\Query\SelectQuery; use Cake\ORM\Table; /** @@ -91,11 +91,11 @@ public function groupTranslations(ResultSetInterface $results): CollectionInterf * and adding a formatter to copy the values into the main table records. * * @param \Cake\Event\EventInterface $event The beforeFind event that was fired. - * @param \Cake\ORM\Query $query Query + * @param \Cake\ORM\Query\SelectQuery $query Query * @param \ArrayObject $options The options for the query * @return void */ - public function beforeFind(EventInterface $event, Query $query, ArrayObject $options): void; + public function beforeFind(EventInterface $event, SelectQuery $query, ArrayObject $options): void; /** * Modifies the entity before it is saved so that translated fields are persisted diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 7ccd0ac1..3cf024c7 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -22,7 +22,7 @@ use Cake\ORM\Behavior\Translate\TranslateStrategyInterface; use Cake\ORM\Marshaller; use Cake\ORM\PropertyMarshalInterface; -use Cake\ORM\Query; +use Cake\ORM\Query\SelectQuery; use Cake\ORM\Table; use Cake\Utility\Inflector; @@ -306,11 +306,11 @@ public function translationField(string $field): string * If the `locales` array is not passed, it will bring all translations found * for each record. * - * @param \Cake\ORM\Query $query The original query to modify + * @param \Cake\ORM\Query\SelectQuery $query The original query to modify * @param array $options Options - * @return \Cake\ORM\Query + * @return \Cake\ORM\Query\SelectQuery */ - public function findTranslations(Query $query, array $options): Query + public function findTranslations(SelectQuery $query, array $options): SelectQuery { $locales = $options['locales'] ?? []; $targetAlias = $this->getStrategy()->getTranslationTable()->getAlias(); diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 97a3a7fc..08c6db95 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -20,12 +20,13 @@ use Cake\Database\Exception\DatabaseException; use Cake\Database\Expression\IdentifierExpression; use Cake\Database\Expression\QueryExpression; -use Cake\Database\Query as DbQuery; use Cake\Datasource\EntityInterface; use Cake\Datasource\Exception\RecordNotFoundException; use Cake\Event\EventInterface; use Cake\ORM\Behavior; -use Cake\ORM\Query; +use Cake\ORM\Query\DeleteQuery; +use Cake\ORM\Query\SelectQuery; +use Cake\ORM\Query\UpdateQuery; use InvalidArgumentException; /** @@ -372,7 +373,7 @@ function ($exp) use ($config) { ->eq($config['leftField'], $leftInverse->add($config['leftField'])) ->eq($config['rightField'], $rightInverse->add($config['rightField'])); }, - fn(QueryExpression $exp) => $exp->lt($config['leftField'], 0) + fn (QueryExpression $exp) => $exp->lt($config['leftField'], 0) ); } @@ -381,12 +382,12 @@ function ($exp) use ($config) { * to a specific node in the tree. This custom finder requires that the key 'for' * is passed in the options containing the id of the node to get its path for. * - * @param \Cake\ORM\Query $query The constructed query to modify + * @param \Cake\ORM\Query\SelectQuery $query The constructed query to modify * @param array $options the list of options for the query - * @return \Cake\ORM\Query + * @return \Cake\ORM\Query\SelectQuery * @throws \InvalidArgumentException If the 'for' key is missing in options */ - public function findPath(Query $query, array $options): Query + public function findPath(SelectQuery $query, array $options): SelectQuery { if (empty($options['for'])) { throw new InvalidArgumentException('The "for" key is required for find(\'path\').'); @@ -445,12 +446,12 @@ public function childCount(EntityInterface $node, bool $direct = false): int * * If the direct option is set to true, only the direct children are returned (based upon the parent_id field) * - * @param \Cake\ORM\Query $query Query. + * @param \Cake\ORM\Query\SelectQuery $query Query. * @param array $options Array of options as described above - * @return \Cake\ORM\Query + * @return \Cake\ORM\Query\SelectQuery * @throws \InvalidArgumentException When the 'for' key is not passed in $options */ - public function findChildren(Query $query, array $options): Query + public function findChildren(SelectQuery $query, array $options): SelectQuery { $config = $this->getConfig(); $options += ['for' => null, 'direct' => false]; @@ -497,11 +498,11 @@ function ($field) { * return the value out of the provided row. * - spacer: A string to be used as prefix for denoting the depth in the tree for each item * - * @param \Cake\ORM\Query $query Query. + * @param \Cake\ORM\Query\SelectQuery $query Query. * @param array $options Array of options as described above. - * @return \Cake\ORM\Query + * @return \Cake\ORM\Query\SelectQuery */ - public function findTreeList(Query $query, array $options): Query + public function findTreeList(SelectQuery $query, array $options): SelectQuery { $left = $this->_table->aliasField($this->getConfig('left')); @@ -527,11 +528,11 @@ public function findTreeList(Query $query, array $options): Query * return the value from the provided row. * - spacer: A string to be used as prefix for denoting the depth in the tree for each item. * - * @param \Cake\ORM\Query $query The query object to format. + * @param \Cake\ORM\Query\SelectQuery $query The query object to format. * @param array $options Array of options as described above. - * @return \Cake\ORM\Query Augmented query. + * @return \Cake\ORM\Query\SelectQuery Augmented query. */ - public function formatTreeList(Query $query, array $options = []): Query + public function formatTreeList(SelectQuery $query, array $options = []): SelectQuery { return $query->formatResults(function (CollectionInterface $results) use ($options) { $options += [ @@ -652,7 +653,7 @@ protected function _moveUp(EntityInterface $node, int|bool $number): EntityInter $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) ->where(["$parent IS" => $nodeParent]) - ->where(fn(QueryExpression $exp) => $exp->lt($config['rightField'], $nodeLeft)) + ->where(fn (QueryExpression $exp) => $exp->lt($config['rightField'], $nodeLeft)) ->orderDesc($config['leftField']) ->offset($number - 1) ->limit(1) @@ -663,7 +664,7 @@ protected function _moveUp(EntityInterface $node, int|bool $number): EntityInter $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) ->where(["$parent IS" => $nodeParent]) - ->where(fn(QueryExpression $exp) => $exp->lt($config['rightField'], $nodeLeft)) + ->where(fn (QueryExpression $exp) => $exp->lt($config['rightField'], $nodeLeft)) ->orderAsc($config['leftField']) ->limit(1) ->first(); @@ -740,7 +741,7 @@ protected function _moveDown(EntityInterface $node, int|bool $number): EntityInt $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) ->where(["$parent IS" => $nodeParent]) - ->where(fn(QueryExpression $exp) => $exp->gt($config['leftField'], $nodeRight)) + ->where(fn (QueryExpression $exp) => $exp->gt($config['leftField'], $nodeRight)) ->orderAsc($config['leftField']) ->offset($number - 1) ->limit(1) @@ -751,7 +752,7 @@ protected function _moveDown(EntityInterface $node, int|bool $number): EntityInt $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) ->where(["$parent IS" => $nodeParent]) - ->where(fn(QueryExpression $exp) => $exp->gt($config['leftField'], $nodeRight)) + ->where(fn (QueryExpression $exp) => $exp->gt($config['leftField'], $nodeRight)) ->orderDesc($config['leftField']) ->limit(1) ->first(); @@ -930,13 +931,13 @@ protected function _sync(int $shift, string $dir, string $conditions, bool $mark * Alters the passed query so that it only returns scoped records as defined * in the tree configuration. * - * @param \Cake\Database\Query $query the Query to modify - * @return \Cake\Database\Query - * @template T of \Cake\ORM\Query|\Cake\ORM\Query\UpdateQuery|\Cake\ORM\Query\DeleteQuery + * @param \Cake\ORM\Query\SelectQuery|\Cake\ORM\Query\UpdateQuery|\Cake\ORM\Query\DeleteQuery $query the Query to modify + * @return \Cake\ORM\Query\SelectQuery|\Cake\ORM\Query\UpdateQuery|\Cake\ORM\Query\DeleteQuery + * @template T of \Cake\ORM\Query\SelectQuery|\Cake\ORM\Query\UpdateQuery|\Cake\ORM\Query\DeleteQuery * @psalm-param T $query * @psalm-return T */ - protected function _scope(DbQuery $query): DbQuery + protected function _scope(SelectQuery|UpdateQuery|DeleteQuery $query): SelectQuery|UpdateQuery|DeleteQuery { $scope = $this->getConfig('scope'); diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 2a348367..0fe2dcf7 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -22,6 +22,7 @@ use Cake\Event\EventDispatcherInterface; use Cake\Event\EventDispatcherTrait; use Cake\ORM\Exception\MissingBehaviorException; +use Cake\ORM\Query\SelectQuery; use LogicException; /** @@ -265,10 +266,10 @@ public function call(string $method, array $args = []): mixed * * @param string $type The finder type to invoke. * @param array $args The arguments you want to invoke the method with. - * @return \Cake\ORM\Query The return value depends on the underlying behavior method. + * @return \Cake\ORM\Query\SelectQuery The return value depends on the underlying behavior method. * @throws \BadMethodCallException When the method is unknown. */ - public function callFinder(string $type, array $args = []): Query + public function callFinder(string $type, array $args = []): SelectQuery { $type = strtolower($type); diff --git a/EagerLoader.php b/EagerLoader.php index 20592d35..6a7fd5cb 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -16,6 +16,7 @@ */ namespace Cake\ORM; +use Cake\ORM\Query\SelectQuery; use Closure; use InvalidArgumentException; @@ -240,7 +241,7 @@ public function setMatching(string $associationPath, ?Closure $builder = null, a { $this->_matching ??= new static(); - $options += ['joinType' => Query::JOIN_TYPE_INNER]; + $options += ['joinType' => SelectQuery::JOIN_TYPE_INNER]; $sharedOptions = ['negateMatch' => false, 'matching' => true] + $options; $contains = []; @@ -390,14 +391,14 @@ protected function _reformatContain(array $associations, array $original): array * This method will not modify the query for loading external associations, i.e. * those that cannot be loaded without executing a separate query. * - * @param \Cake\ORM\Query $query The query to be modified. + * @param \Cake\ORM\Query\SelectQuery $query The query to be modified. * @param \Cake\ORM\Table $repository The repository containing the associations * @param bool $includeFields whether to append all fields from the associations * to the passed query. This can be overridden according to the settings defined * per association in the containments array. * @return void */ - public function attachAssociations(Query $query, Table $repository, bool $includeFields): void + public function attachAssociations(SelectQuery $query, Table $repository, bool $includeFields): void { if (empty($this->_containments) && $this->_matching === null) { return; @@ -605,13 +606,13 @@ protected function _resolveJoins(array $associations, array $matching = []): arr /** * Inject data from associations that cannot be joined directly. * - * @param \Cake\ORM\Query $query The query for which to eager load external. + * @param \Cake\ORM\Query\SelectQuery $query The query for which to eager load external. * associations. * @param array $results Results array. * @return array * @throws \RuntimeException */ - public function loadExternal(Query $query, array $results): array + public function loadExternal(SelectQuery $query, array $results): array { if (empty($results)) { return $results; @@ -764,11 +765,11 @@ public function addToJoinsMap( * to eagerly load associations. * * @param array<\Cake\ORM\EagerLoadable> $external The list of external associations to be loaded. - * @param \Cake\ORM\Query $query The query from which the results where generated. + * @param \Cake\ORM\Query\SelectQuery $query The query from which the results where generated. * @param array $results Results array. * @return array */ - protected function _collectKeys(array $external, Query $query, array $results): array + protected function _collectKeys(array $external, SelectQuery $query, array $results): array { $collectKeys = []; foreach ($external as $meta) { diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index feeea795..eb0f3051 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -21,6 +21,7 @@ use Cake\Database\Expression\QueryExpression; use Cake\Database\Expression\TupleComparison; use Cake\Datasource\EntityInterface; +use Cake\ORM\Query\SelectQuery; /** * Contains methods that are capable of injecting eagerly loaded associations into @@ -68,9 +69,9 @@ public function loadInto(EntityInterface|array $entities, array $contain, Table * @param \Cake\Collection\CollectionInterface $objects The original entities * @param array $contain The associations to be loaded * @param \Cake\ORM\Table $source The table to use for fetching the top level entities - * @return \Cake\ORM\Query + * @return \Cake\ORM\Query\SelectQuery */ - protected function _getQuery(CollectionInterface $objects, array $contain, Table $source): Query + protected function _getQuery(CollectionInterface $objects, array $contain, Table $source): SelectQuery { $primaryKey = $source->getPrimaryKey(); $method = is_string($primaryKey) ? 'get' : 'extract'; @@ -80,7 +81,7 @@ protected function _getQuery(CollectionInterface $objects, array $contain, Table $query = $source ->find() ->select((array)$primaryKey) - ->where(function (QueryExpression $exp, Query $q) use ($primaryKey, $keys, $source) { + ->where(function (QueryExpression $exp, SelectQuery $q) use ($primaryKey, $keys, $source) { if (is_array($primaryKey) && count($primaryKey) === 1) { $primaryKey = current($primaryKey); } @@ -131,13 +132,17 @@ protected function _getPropertyMap(Table $source, array $associations): array * entities. * * @param iterable<\Cake\Datasource\EntityInterface> $objects The original list of entities - * @param \Cake\ORM\Query $results The loaded results + * @param \Cake\ORM\Query\SelectQuery $results The loaded results * @param array $associations The top level associations that were loaded * @param \Cake\ORM\Table $source The table where the entities came from * @return array<\Cake\Datasource\EntityInterface> */ - protected function _injectResults(iterable $objects, Query $results, array $associations, Table $source): array - { + protected function _injectResults( + iterable $objects, + SelectQuery $results, + array $associations, + Table $source + ): array { $injected = []; $properties = $this->_getPropertyMap($source, $associations); $primaryKey = (array)$source->getPrimaryKey(); diff --git a/Query.php b/Query.php index 7eba7a11..0aa20623 100644 --- a/Query.php +++ b/Query.php @@ -6,1774 +6,11 @@ * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License - * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice. * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) - * @link https://cakephp.org CakePHP(tm) Project * @since 3.0.0 * @license https://opensource.org/licenses/mit-license.php MIT License */ -namespace Cake\ORM; -use ArrayObject; -use Cake\Collection\Iterator\MapReduce; -use Cake\Database\ExpressionInterface; -use Cake\Database\Query\SelectQuery as DbSelectQuery; -use Cake\Database\TypedResultInterface; -use Cake\Database\TypeMap; -use Cake\Database\ValueBinder; -use Cake\Datasource\Exception\RecordNotFoundException; -use Cake\Datasource\QueryCacher; -use Cake\Datasource\QueryInterface; -use Cake\Datasource\ResultSetDecorator; -use Cake\Datasource\ResultSetInterface; -use Cake\ORM\Query\CommonQueryTrait; -use Closure; -use InvalidArgumentException; -use JsonSerializable; -use Psr\SimpleCache\CacheInterface; - -/** - * Extends the Cake\Database\Query\SelectQuery class to provide new methods related to association - * loading, automatic fields selection, automatic type casting and to wrap results - * into a specific iterator that will be responsible for hydrating results if - * required. - */ -class Query extends DbSelectQuery implements JsonSerializable, QueryInterface -{ - use CommonQueryTrait; - - /** - * Indicates that the operation should append to the list - * - * @var int - */ - public const APPEND = 0; - - /** - * Indicates that the operation should prepend to the list - * - * @var int - */ - public const PREPEND = 1; - - /** - * Indicates that the operation should overwrite the list - * - * @var bool - */ - public const OVERWRITE = true; - - /** - * Whether the user select any fields before being executed, this is used - * to determined if any fields should be automatically be selected. - * - * @var bool|null - */ - protected ?bool $_hasFields = null; - - /** - * Tracks whether the original query should include - * fields from the top level table. - * - * @var bool|null - */ - protected ?bool $_autoFields = null; - - /** - * Whether to hydrate results into entity objects - * - * @var bool - */ - protected bool $_hydrate = true; - - /** - * Whether aliases are generated for fields. - * - * @var bool - */ - protected bool $aliasingEnabled = true; - - /** - * A callback used to calculate the total amount of - * records this query will match when not using `limit` - * - * @var \Closure|null - */ - protected ?Closure $_counter = null; - - /** - * Instance of a class responsible for storing association containments and - * for eager loading them when this query is executed - * - * @var \Cake\ORM\EagerLoader|null - */ - protected ?EagerLoader $_eagerLoader = null; - - /** - * Whether the query is standalone or the product of an eager load operation. - * - * @var bool - */ - protected bool $_eagerLoaded = false; - - /** - * True if the beforeFind event has already been triggered for this query - * - * @var bool - */ - protected bool $_beforeFindFired = false; - - /** - * The COUNT(*) for the query. - * - * When set, count query execution will be bypassed. - * - * @var int|null - */ - protected ?int $_resultsCount = null; - - /** - * Resultset factory - * - * @var \Cake\ORM\ResultSetFactory - */ - protected ResultSetFactory $resultSetFactory; - - /** - * A ResultSet. - * - * When set, query execution will be bypassed. - * - * @var iterable|null - * @see \Cake\Datasource\QueryTrait::setResult() - */ - protected ?iterable $_results = null; - - /** - * Instance of a repository object this query is bound to. - * - * @var \Cake\ORM\Table - */ - protected Table $_repository; - - /** - * List of map-reduce routines that should be applied over the query - * result - * - * @var array - */ - protected array $_mapReduce = []; - - /** - * List of formatter classes or callbacks that will post-process the - * results when fetched - * - * @var array<\Closure> - */ - protected array $_formatters = []; - - /** - * A query cacher instance if this query has caching enabled. - * - * @var \Cake\Datasource\QueryCacher|null - */ - protected ?QueryCacher $_cache = null; - - /** - * Holds any custom options passed using applyOptions that could not be processed - * by any method in this class. - * - * @var array - */ - protected array $_options = []; - - /** - * Constructor - * - * @param \Cake\ORM\Table $table The table this query is starting on - */ - public function __construct(Table $table) - { - parent::__construct($table->getConnection()); - - $this->setRepository($table); - $this->addDefaultTypes($table); - } - - /** - * Set the result set for a query. - * - * Setting the resultset of a query will make execute() a no-op. Instead - * of executing the SQL query and fetching results, the ResultSet provided to this - * method will be returned. - * - * This method is most useful when combined with results stored in a persistent cache. - * - * @param iterable $results The results this query should return. - * @return $this - */ - public function setResult(iterable $results) - { - $this->_results = $results; - - return $this; - } - - /** - * Executes this query and returns a results iterator. This function is required - * for implementing the IteratorAggregate interface and allows the query to be - * iterated without having to call execute() manually, thus making it look like - * a result set instead of the query itself. - * - * @return \Cake\Datasource\ResultSetInterface - */ - public function getIterator(): ResultSetInterface - { - return $this->all(); - } - - /** - * Enable result caching for this query. - * - * If a query has caching enabled, it will do the following when executed: - * - * - Check the cache for $key. If there are results no SQL will be executed. - * Instead the cached results will be returned. - * - When the cached data is stale/missing the result set will be cached as the query - * is executed. - * - * ### Usage - * - * ``` - * // Simple string key + config - * $query->cache('my_key', 'db_results'); - * - * // Function to generate key. - * $query->cache(function ($q) { - * $key = serialize($q->clause('select')); - * $key .= serialize($q->clause('where')); - * return md5($key); - * }); - * - * // Using a pre-built cache engine. - * $query->cache('my_key', $engine); - * - * // Disable caching - * $query->cache(false); - * ``` - * - * @param \Closure|string|false $key Either the cache key or a function to generate the cache key. - * When using a function, this query instance will be supplied as an argument. - * @param \Psr\SimpleCache\CacheInterface|string $config Either the name of the cache config to use, or - * a cache engine instance. - * @return $this - */ - public function cache(Closure|string|false $key, CacheInterface|string $config = 'default') - { - if ($key === false) { - $this->_cache = null; - - return $this; - } - - $this->_cache = new QueryCacher($key, $config); - - return $this; - } - - /** - * Returns the current configured query `_eagerLoaded` value - * - * @return bool - */ - public function isEagerLoaded(): bool - { - return $this->_eagerLoaded; - } - - /** - * Sets the query instance to be an eager loaded query. If no argument is - * passed, the current configured query `_eagerLoaded` value is returned. - * - * @param bool $value Whether to eager load. - * @return $this - */ - public function eagerLoaded(bool $value) - { - $this->_eagerLoaded = $value; - - return $this; - } - - /** - * Returns a key => value array representing a single aliased field - * that can be passed directly to the select() method. - * The key will contain the alias and the value the actual field name. - * - * If the field is already aliased, then it will not be changed. - * If no $alias is passed, the default table for this query will be used. - * - * @param string $field The field to alias - * @param string|null $alias the alias used to prefix the field - * @return array - */ - public function aliasField(string $field, ?string $alias = null): array - { - if (str_contains($field, '.')) { - $aliasedField = $field; - [$alias, $field] = explode('.', $field); - } else { - $alias = $alias ?: $this->getRepository()->getAlias(); - $aliasedField = $alias . '.' . $field; - } - - $key = sprintf('%s__%s', $alias, $field); - - return [$key => $aliasedField]; - } - - /** - * Runs `aliasField()` for each field in the provided list and returns - * the result under a single array. - * - * @param array $fields The fields to alias - * @param string|null $defaultAlias The default alias - * @return array - */ - public function aliasFields(array $fields, ?string $defaultAlias = null): array - { - $aliased = []; - foreach ($fields as $alias => $field) { - if (is_numeric($alias) && is_string($field)) { - $aliased += $this->aliasField($field, $defaultAlias); - continue; - } - $aliased[$alias] = $field; - } - - return $aliased; - } - - /** - * Fetch the results for this query. - * - * Will return either the results set through setResult(), or execute this query - * and return the ResultSetDecorator object ready for streaming of results. - * - * ResultSetDecorator is a traversable object that implements the methods found - * on Cake\Collection\Collection. - * - * @return \Cake\Datasource\ResultSetInterface - */ - public function all(): ResultSetInterface - { - if ($this->_results !== null) { - if (!($this->_results instanceof ResultSetInterface)) { - $this->_results = $this->_decorateResults($this->_results); - } - - return $this->_results; - } - - $results = null; - if ($this->_cache) { - $results = $this->_cache->fetch($this); - } - if ($results === null) { - $results = $this->_decorateResults($this->_execute()); - if ($this->_cache) { - $this->_cache->store($this, $results); - } - } - $this->_results = $results; - - return $this->_results; - } - - /** - * Returns an array representation of the results after executing the query. - * - * @return array - */ - public function toArray(): array - { - return $this->all()->toArray(); - } - - /** - * Register a new MapReduce routine to be executed on top of the database results - * - * The MapReduce routing will only be run when the query is executed and the first - * result is attempted to be fetched. - * - * If the third argument is set to true, it will erase previous map reducers - * and replace it with the arguments passed. - * - * @param \Closure|null $mapper The mapper function - * @param \Closure|null $reducer The reducing function - * @param bool $overwrite Set to true to overwrite existing map + reduce functions. - * @return $this - * @see \Cake\Collection\Iterator\MapReduce for details on how to use emit data to the map reducer. - */ - public function mapReduce(?Closure $mapper = null, ?Closure $reducer = null, bool $overwrite = false) - { - if ($overwrite) { - $this->_mapReduce = []; - } - if ($mapper === null) { - if (!$overwrite) { - throw new InvalidArgumentException('$mapper can be null only when $overwrite is true.'); - } - - return $this; - } - $this->_mapReduce[] = compact('mapper', 'reducer'); - - return $this; - } - - /** - * Returns the list of previously registered map reduce routines. - * - * @return array - */ - public function getMapReducers(): array - { - return $this->_mapReduce; - } - - /** - * Registers a new formatter callback function that is to be executed when trying - * to fetch the results from the database. - * - * If the second argument is set to true, it will erase previous formatters - * and replace them with the passed first argument. - * - * Callbacks are required to return an iterator object, which will be used as - * the return value for this query's result. Formatter functions are applied - * after all the `MapReduce` routines for this query have been executed. - * - * Formatting callbacks will receive two arguments, the first one being an object - * implementing `\Cake\Collection\CollectionInterface`, that can be traversed and - * modified at will. The second one being the query instance on which the formatter - * callback is being applied. - * - * Usually the query instance received by the formatter callback is the same query - * instance on which the callback was attached to, except for in a joined - * association, in that case the callback will be invoked on the association source - * side query, and it will receive that query instance instead of the one on which - * the callback was originally attached to - see the examples below! - * - * ### Examples: - * - * Return all results from the table indexed by id: - * - * ``` - * $query->select(['id', 'name'])->formatResults(function ($results) { - * return $results->indexBy('id'); - * }); - * ``` - * - * Add a new column to the ResultSet: - * - * ``` - * $query->select(['name', 'birth_date'])->formatResults(function ($results) { - * return $results->map(function ($row) { - * $row['age'] = $row['birth_date']->diff(new DateTime)->y; - * - * return $row; - * }); - * }); - * ``` - * - * Add a new column to the results with respect to the query's hydration configuration: - * - * ``` - * $query->formatResults(function ($results, $query) { - * return $results->map(function ($row) use ($query) { - * $data = [ - * 'bar' => 'baz', - * ]; - * - * if ($query->isHydrationEnabled()) { - * $row['foo'] = new Foo($data) - * } else { - * $row['foo'] = $data; - * } - * - * return $row; - * }); - * }); - * ``` - * - * Retaining access to the association target query instance of joined associations, - * by inheriting the contain callback's query argument: - * - * ``` - * // Assuming a `Articles belongsTo Authors` association that uses the join strategy - * - * $articlesQuery->contain('Authors', function ($authorsQuery) { - * return $authorsQuery->formatResults(function ($results, $query) use ($authorsQuery) { - * // Here `$authorsQuery` will always be the instance - * // where the callback was attached to. - * - * // The instance passed to the callback in the second - * // argument (`$query`), will be the one where the - * // callback is actually being applied to, in this - * // example that would be `$articlesQuery`. - * - * // ... - * - * return $results; - * }); - * }); - * ``` - * - * @param \Closure|null $formatter The formatting function - * @param int|bool $mode Whether to overwrite, append or prepend the formatter. - * @return $this - * @throws \InvalidArgumentException - */ - public function formatResults(?Closure $formatter = null, int|bool $mode = self::APPEND) - { - if ($mode === self::OVERWRITE) { - $this->_formatters = []; - } - if ($formatter === null) { - /** @psalm-suppress RedundantCondition */ - if ($mode !== self::OVERWRITE) { - throw new InvalidArgumentException('$formatter can be null only when $mode is overwrite.'); - } - - return $this; - } - - if ($mode === self::PREPEND) { - array_unshift($this->_formatters, $formatter); - - return $this; - } - - $this->_formatters[] = $formatter; - - return $this; - } - - /** - * Returns the list of previously registered format routines. - * - * @return array<\Closure> - */ - public function getResultFormatters(): array - { - return $this->_formatters; - } - - /** - * Returns the first result out of executing this query, if the query has not been - * executed before, it will set the limit clause to 1 for performance reasons. - * - * ### Example: - * - * ``` - * $singleUser = $query->select(['id', 'username'])->first(); - * ``` - * - * @return mixed The first result from the ResultSet. - */ - public function first(): mixed - { - if ($this->_dirty) { - $this->limit(1); - } - - return $this->all()->first(); - } - - /** - * Get the first result from the executing query or raise an exception. - * - * @throws \Cake\Datasource\Exception\RecordNotFoundException When there is no first record. - * @return mixed The first result from the ResultSet. - */ - public function firstOrFail(): mixed - { - $entity = $this->first(); - if (!$entity) { - $table = $this->getRepository(); - throw new RecordNotFoundException(sprintf( - 'Record not found in table `%s`.', - $table->getTable() - )); - } - - return $entity; - } - - /** - * Returns an array with the custom options that were applied to this query - * and that were not already processed by another method in this class. - * - * ### Example: - * - * ``` - * $query->applyOptions(['doABarrelRoll' => true, 'fields' => ['id', 'name']); - * $query->getOptions(); // Returns ['doABarrelRoll' => true] - * ``` - * - * @see \Cake\Datasource\QueryInterface::applyOptions() to read about the options that will - * be processed by this class and not returned by this function - * @return array - * @see applyOptions() - */ - public function getOptions(): array - { - return $this->_options; - } - - /** - * Populates or adds parts to current query clauses using an array. - * This is handy for passing all query clauses at once. - * - * The method accepts the following query clause related options: - * - * - fields: Maps to the select method - * - conditions: Maps to the where method - * - limit: Maps to the limit method - * - order: Maps to the order method - * - offset: Maps to the offset method - * - group: Maps to the group method - * - having: Maps to the having method - * - contain: Maps to the contain options for eager loading - * - join: Maps to the join method - * - page: Maps to the page method - * - * All other options will not affect the query, but will be stored - * as custom options that can be read via `getOptions()`. Furthermore - * they are automatically passed to `Model.beforeFind`. - * - * ### Example: - * - * ``` - * $query->applyOptions([ - * 'fields' => ['id', 'name'], - * 'conditions' => [ - * 'created >=' => '2013-01-01' - * ], - * 'limit' => 10, - * ]); - * ``` - * - * Is equivalent to: - * - * ``` - * $query - * ->select(['id', 'name']) - * ->where(['created >=' => '2013-01-01']) - * ->limit(10) - * ``` - * - * Custom options can be read via `getOptions()`: - * - * ``` - * $query->applyOptions([ - * 'fields' => ['id', 'name'], - * 'custom' => 'value', - * ]); - * ``` - * - * Here `$options` will hold `['custom' => 'value']` (the `fields` - * option will be applied to the query instead of being stored, as - * it's a query clause related option): - * - * ``` - * $options = $query->getOptions(); - * ``` - * - * @param array $options The options to be applied - * @return $this - * @see getOptions() - */ - public function applyOptions(array $options) - { - $valid = [ - 'fields' => 'select', - 'conditions' => 'where', - 'join' => 'join', - 'order' => 'order', - 'limit' => 'limit', - 'offset' => 'offset', - 'group' => 'group', - 'having' => 'having', - 'contain' => 'contain', - 'page' => 'page', - ]; - - ksort($options); - foreach ($options as $option => $values) { - if (isset($valid[$option], $values)) { - $this->{$valid[$option]}($values); - } else { - $this->_options[$option] = $values; - } - } - - return $this; - } - - /** - * Decorates the results iterator with MapReduce routines and formatters - * - * @param iterable $result Original results - * @return \Cake\Datasource\ResultSetInterface - */ - protected function _decorateResults(iterable $result): ResultSetInterface - { - $decorator = $this->_decoratorClass(); - - if (!empty($this->_mapReduce)) { - foreach ($this->_mapReduce as $functions) { - $result = new MapReduce($result, $functions['mapper'], $functions['reducer']); - } - $result = new $decorator($result); - } - - if (!($result instanceof ResultSetInterface)) { - $result = new $decorator($result); - } - - if (!empty($this->_formatters)) { - foreach ($this->_formatters as $formatter) { - $result = $formatter($result, $this); - } - - if (!($result instanceof ResultSetInterface)) { - $result = new $decorator($result); - } - } - - return $result; - } - - /** - * Returns the name of the class to be used for decorating results - * - * @return class-string<\Cake\Datasource\ResultSetInterface> - */ - protected function _decoratorClass(): string - { - return ResultSetDecorator::class; - } - - /** - * Adds new fields to be returned by a `SELECT` statement when this query is - * executed. Fields can be passed as an array of strings, array of expression - * objects, a single expression or a single string. - * - * If an array is passed, keys will be used to alias fields using the value as the - * real field to be aliased. It is possible to alias strings, Expression objects or - * even other Query objects. - * - * If a callback is passed, the returning array of the function will - * be used as the list of fields. - * - * By default this function will append any passed argument to the list of fields - * to be selected, unless the second argument is set to true. - * - * ### Examples: - * - * ``` - * $query->select(['id', 'title']); // Produces SELECT id, title - * $query->select(['author' => 'author_id']); // Appends author: SELECT id, title, author_id as author - * $query->select('id', true); // Resets the list: SELECT id - * $query->select(['total' => $countQuery]); // SELECT id, (SELECT ...) AS total - * $query->select(function ($query) { - * return ['article_id', 'total' => $query->count('*')]; - * }) - * ``` - * - * By default no fields are selected, if you have an instance of `Cake\ORM\Query` and try to append - * fields you should also call `Cake\ORM\Query::enableAutoFields()` to select the default fields - * from the table. - * - * If you pass an instance of a `Cake\ORM\Table` or `Cake\ORM\Association` class, - * all the fields in the schema of the table or the association will be added to - * the select clause. - * - * @param \Cake\Database\ExpressionInterface|\Cake\ORM\Table|\Cake\ORM\Association|\Closure|array|string|float|int $fields Fields - * to be added to the list. - * @param bool $overwrite whether to reset fields with passed list or not - * @return $this - */ - public function select( - ExpressionInterface|Table|Association|Closure|array|string|float|int $fields = [], - bool $overwrite = false - ) { - if ($fields instanceof Association) { - $fields = $fields->getTarget(); - } - - if ($fields instanceof Table) { - if ($this->aliasingEnabled) { - $fields = $this->aliasFields($fields->getSchema()->columns(), $fields->getAlias()); - } else { - $fields = $fields->getSchema()->columns(); - } - } - - return parent::select($fields, $overwrite); - } - - /** - * All the fields associated with the passed table except the excluded - * fields will be added to the select clause of the query. Passed excluded fields should not be aliased. - * After the first call to this method, a second call cannot be used to remove fields that have already - * been added to the query by the first. If you need to change the list after the first call, - * pass overwrite boolean true which will reset the select clause removing all previous additions. - * - * @param \Cake\ORM\Table|\Cake\ORM\Association $table The table to use to get an array of columns - * @param array $excludedFields The un-aliased column names you do not want selected from $table - * @param bool $overwrite Whether to reset/remove previous selected fields - * @return $this - */ - public function selectAllExcept(Table|Association $table, array $excludedFields, bool $overwrite = false) - { - if ($table instanceof Association) { - $table = $table->getTarget(); - } - - $fields = array_diff($table->getSchema()->columns(), $excludedFields); - if ($this->aliasingEnabled) { - $fields = $this->aliasFields($fields); - } - - return $this->select($fields, $overwrite); - } - - /** - * Sets the instance of the eager loader class to use for loading associations - * and storing containments. - * - * @param \Cake\ORM\EagerLoader $instance The eager loader to use. - * @return $this - */ - public function setEagerLoader(EagerLoader $instance) - { - $this->_eagerLoader = $instance; - - return $this; - } - - /** - * Returns the currently configured instance. - * - * @return \Cake\ORM\EagerLoader - */ - public function getEagerLoader(): EagerLoader - { - return $this->_eagerLoader ??= new EagerLoader(); - } - - /** - * Sets the list of associations that should be eagerly loaded along with this - * query. The list of associated tables passed must have been previously set as - * associations using the Table API. - * - * ### Example: - * - * ``` - * // Bring articles' author information - * $query->contain('Author'); - * - * // Also bring the category and tags associated to each article - * $query->contain(['Category', 'Tag']); - * ``` - * - * Associations can be arbitrarily nested using dot notation or nested arrays, - * this allows this object to calculate joins or any additional queries that - * must be executed to bring the required associated data. - * - * ### Example: - * - * ``` - * // Eager load the product info, and for each product load other 2 associations - * $query->contain(['Product' => ['Manufacturer', 'Distributor']); - * - * // Which is equivalent to calling - * $query->contain(['Products.Manufactures', 'Products.Distributors']); - * - * // For an author query, load his region, state and country - * $query->contain('Regions.States.Countries'); - * ``` - * - * It is possible to control the conditions and fields selected for each of the - * contained associations: - * - * ### Example: - * - * ``` - * $query->contain(['Tags' => function ($q) { - * return $q->where(['Tags.is_popular' => true]); - * }]); - * - * $query->contain(['Products.Manufactures' => function ($q) { - * return $q->select(['name'])->where(['Manufactures.active' => true]); - * }]); - * ``` - * - * Each association might define special options when eager loaded, the allowed - * options that can be set per association are: - * - * - `foreignKey`: Used to set a different field to match both tables, if set to false - * no join conditions will be generated automatically. `false` can only be used on - * joinable associations and cannot be used with hasMany or belongsToMany associations. - * - `fields`: An array with the fields that should be fetched from the association. - * - `finder`: The finder to use when loading associated records. Either the name of the - * finder as a string, or an array to define options to pass to the finder. - * - `queryBuilder`: Equivalent to passing a callback instead of an options array. - * - * ### Example: - * - * ``` - * // Set options for the hasMany articles that will be eagerly loaded for an author - * $query->contain([ - * 'Articles' => [ - * 'fields' => ['title', 'author_id'] - * ] - * ]); - * ``` - * - * Finders can be configured to use options. - * - * ``` - * // Retrieve translations for the articles, but only those for the `en` and `es` locales - * $query->contain([ - * 'Articles' => [ - * 'finder' => [ - * 'translations' => [ - * 'locales' => ['en', 'es'] - * ] - * ] - * ] - * ]); - * ``` - * - * When containing associations, it is important to include foreign key columns. - * Failing to do so will trigger exceptions. - * - * ``` - * // Use a query builder to add conditions to the containment - * $query->contain('Authors', function ($q) { - * return $q->where(...); // add conditions - * }); - * // Use special join conditions for multiple containments in the same method call - * $query->contain([ - * 'Authors' => [ - * 'foreignKey' => false, - * 'queryBuilder' => function ($q) { - * return $q->where(...); // Add full filtering conditions - * } - * ], - * 'Tags' => function ($q) { - * return $q->where(...); // add conditions - * } - * ]); - * ``` - * - * If called with an empty first argument and `$override` is set to true, the - * previous list will be emptied. - * - * @param array|string $associations List of table aliases to be queried. - * @param \Closure|bool $override The query builder for the association, or - * if associations is an array, a bool on whether to override previous list - * with the one passed - * defaults to merging previous list with the new one. - * @return $this - */ - public function contain(array|string $associations, Closure|bool $override = false) - { - $loader = $this->getEagerLoader(); - if ($override === true) { - $this->clearContain(); - } - - $queryBuilder = null; - if ($override instanceof Closure) { - $queryBuilder = $override; - } - - if ($associations) { - $loader->contain($associations, $queryBuilder); - } - $this->_addAssociationsToTypeMap( - $this->getRepository(), - $this->getTypeMap(), - $loader->getContain() - ); - - return $this; - } - - /** - * @return array - */ - public function getContain(): array - { - return $this->getEagerLoader()->getContain(); - } - - /** - * Clears the contained associations from the current query. - * - * @return $this - */ - public function clearContain() - { - $this->getEagerLoader()->clearContain(); - $this->_dirty(); - - return $this; - } - - /** - * Used to recursively add contained association column types to - * the query. - * - * @param \Cake\ORM\Table $table The table instance to pluck associations from. - * @param \Cake\Database\TypeMap $typeMap The typemap to check for columns in. - * This typemap is indirectly mutated via {@link \Cake\ORM\Query::addDefaultTypes()} - * @param array $associations The nested tree of associations to walk. - * @return void - */ - protected function _addAssociationsToTypeMap(Table $table, TypeMap $typeMap, array $associations): void - { - foreach ($associations as $name => $nested) { - if (!$table->hasAssociation($name)) { - continue; - } - $association = $table->getAssociation($name); - $target = $association->getTarget(); - $primary = (array)$target->getPrimaryKey(); - if (empty($primary) || $typeMap->type($target->aliasField($primary[0])) === null) { - $this->addDefaultTypes($target); - } - if (!empty($nested)) { - $this->_addAssociationsToTypeMap($target, $typeMap, $nested); - } - } - } - - /** - * Adds filtering conditions to this query to only bring rows that have a relation - * to another from an associated table, based on conditions in the associated table. - * - * This function will add entries in the `contain` graph. - * - * ### Example: - * - * ``` - * // Bring only articles that were tagged with 'cake' - * $query->matching('Tags', function ($q) { - * return $q->where(['name' => 'cake']); - * }); - * ``` - * - * It is possible to filter by deep associations by using dot notation: - * - * ### Example: - * - * ``` - * // Bring only articles that were commented by 'markstory' - * $query->matching('Comments.Users', function ($q) { - * return $q->where(['username' => 'markstory']); - * }); - * ``` - * - * As this function will create `INNER JOIN`, you might want to consider - * calling `distinct` on this query as you might get duplicate rows if - * your conditions don't filter them already. This might be the case, for example, - * of the same user commenting more than once in the same article. - * - * ### Example: - * - * ``` - * // Bring unique articles that were commented by 'markstory' - * $query->distinct(['Articles.id']) - * ->matching('Comments.Users', function ($q) { - * return $q->where(['username' => 'markstory']); - * }); - * ``` - * - * Please note that the query passed to the closure will only accept calling - * `select`, `where`, `andWhere` and `orWhere` on it. If you wish to - * add more complex clauses you can do it directly in the main query. - * - * @param string $assoc The association to filter by - * @param \Closure|null $builder a function that will receive a pre-made query object - * that can be used to add custom conditions or selecting some fields - * @return $this - */ - public function matching(string $assoc, ?Closure $builder = null) - { - $result = $this->getEagerLoader()->setMatching($assoc, $builder)->getMatching(); - $this->_addAssociationsToTypeMap($this->getRepository(), $this->getTypeMap(), $result); - $this->_dirty(); - - return $this; - } - - /** - * Creates a LEFT JOIN with the passed association table while preserving - * the foreign key matching and the custom conditions that were originally set - * for it. - * - * This function will add entries in the `contain` graph. - * - * ### Example: - * - * ``` - * // Get the count of articles per user - * $usersQuery - * ->select(['total_articles' => $query->func()->count('Articles.id')]) - * ->leftJoinWith('Articles') - * ->group(['Users.id']) - * ->enableAutoFields(); - * ``` - * - * You can also customize the conditions passed to the LEFT JOIN: - * - * ``` - * // Get the count of articles per user with at least 5 votes - * $usersQuery - * ->select(['total_articles' => $query->func()->count('Articles.id')]) - * ->leftJoinWith('Articles', function ($q) { - * return $q->where(['Articles.votes >=' => 5]); - * }) - * ->group(['Users.id']) - * ->enableAutoFields(); - * ``` - * - * This will create the following SQL: - * - * ``` - * SELECT COUNT(Articles.id) AS total_articles, Users.* - * FROM users Users - * LEFT JOIN articles Articles ON Articles.user_id = Users.id AND Articles.votes >= 5 - * GROUP BY USers.id - * ``` - * - * It is possible to left join deep associations by using dot notation - * - * ### Example: - * - * ``` - * // Total comments in articles by 'markstory' - * $query - * ->select(['total_comments' => $query->func()->count('Comments.id')]) - * ->leftJoinWith('Comments.Users', function ($q) { - * return $q->where(['username' => 'markstory']); - * }) - * ->group(['Users.id']); - * ``` - * - * Please note that the query passed to the closure will only accept calling - * `select`, `where`, `andWhere` and `orWhere` on it. If you wish to - * add more complex clauses you can do it directly in the main query. - * - * @param string $assoc The association to join with - * @param \Closure|null $builder a function that will receive a pre-made query object - * that can be used to add custom conditions or selecting some fields - * @return $this - */ - public function leftJoinWith(string $assoc, ?Closure $builder = null) - { - $result = $this->getEagerLoader() - ->setMatching($assoc, $builder, [ - 'joinType' => static::JOIN_TYPE_LEFT, - 'fields' => false, - ]) - ->getMatching(); - $this->_addAssociationsToTypeMap($this->getRepository(), $this->getTypeMap(), $result); - $this->_dirty(); - - return $this; - } - - /** - * Creates an INNER JOIN with the passed association table while preserving - * the foreign key matching and the custom conditions that were originally set - * for it. - * - * This function will add entries in the `contain` graph. - * - * ### Example: - * - * ``` - * // Bring only articles that were tagged with 'cake' - * $query->innerJoinWith('Tags', function ($q) { - * return $q->where(['name' => 'cake']); - * }); - * ``` - * - * This will create the following SQL: - * - * ``` - * SELECT Articles.* - * FROM articles Articles - * INNER JOIN tags Tags ON Tags.name = 'cake' - * INNER JOIN articles_tags ArticlesTags ON ArticlesTags.tag_id = Tags.id - * AND ArticlesTags.articles_id = Articles.id - * ``` - * - * This function works the same as `matching()` with the difference that it - * will select no fields from the association. - * - * @param string $assoc The association to join with - * @param \Closure|null $builder a function that will receive a pre-made query object - * that can be used to add custom conditions or selecting some fields - * @return $this - * @see \Cake\ORM\Query::matching() - */ - public function innerJoinWith(string $assoc, ?Closure $builder = null) - { - $result = $this->getEagerLoader() - ->setMatching($assoc, $builder, [ - 'joinType' => static::JOIN_TYPE_INNER, - 'fields' => false, - ]) - ->getMatching(); - $this->_addAssociationsToTypeMap($this->getRepository(), $this->getTypeMap(), $result); - $this->_dirty(); - - return $this; - } - - /** - * Adds filtering conditions to this query to only bring rows that have no match - * to another from an associated table, based on conditions in the associated table. - * - * This function will add entries in the `contain` graph. - * - * ### Example: - * - * ``` - * // Bring only articles that were not tagged with 'cake' - * $query->notMatching('Tags', function ($q) { - * return $q->where(['name' => 'cake']); - * }); - * ``` - * - * It is possible to filter by deep associations by using dot notation: - * - * ### Example: - * - * ``` - * // Bring only articles that weren't commented by 'markstory' - * $query->notMatching('Comments.Users', function ($q) { - * return $q->where(['username' => 'markstory']); - * }); - * ``` - * - * As this function will create a `LEFT JOIN`, you might want to consider - * calling `distinct` on this query as you might get duplicate rows if - * your conditions don't filter them already. This might be the case, for example, - * of the same article having multiple comments. - * - * ### Example: - * - * ``` - * // Bring unique articles that were commented by 'markstory' - * $query->distinct(['Articles.id']) - * ->notMatching('Comments.Users', function ($q) { - * return $q->where(['username' => 'markstory']); - * }); - * ``` - * - * Please note that the query passed to the closure will only accept calling - * `select`, `where`, `andWhere` and `orWhere` on it. If you wish to - * add more complex clauses you can do it directly in the main query. - * - * @param string $assoc The association to filter by - * @param \Closure|null $builder a function that will receive a pre-made query object - * that can be used to add custom conditions or selecting some fields - * @return $this - */ - public function notMatching(string $assoc, ?Closure $builder = null) - { - $result = $this->getEagerLoader() - ->setMatching($assoc, $builder, [ - 'joinType' => static::JOIN_TYPE_LEFT, - 'fields' => false, - 'negateMatch' => true, - ]) - ->getMatching(); - $this->_addAssociationsToTypeMap($this->getRepository(), $this->getTypeMap(), $result); - $this->_dirty(); - - return $this; - } - - /** - * Creates a copy of this current query, triggers beforeFind and resets some state. - * - * The following state will be cleared: - * - * - autoFields - * - limit - * - offset - * - map/reduce functions - * - result formatters - * - order - * - containments - * - * This method creates query clones that are useful when working with subqueries. - * - * @return static - */ - public function cleanCopy(): static - { - $clone = clone $this; - $clone->triggerBeforeFind(); - $clone->disableAutoFields(); - $clone->limit(null); - $clone->order([], true); - $clone->offset(null); - $clone->mapReduce(null, null, true); - $clone->formatResults(null, self::OVERWRITE); - $clone->setSelectTypeMap(new TypeMap()); - $clone->decorateResults(null, true); - - return $clone; - } - - /** - * Clears the internal result cache and the internal count value from the current - * query object. - * - * @return $this - */ - public function clearResult() - { - $this->_dirty(); - - return $this; - } - - /** - * {@inheritDoc} - * - * Handles cloning eager loaders. - */ - public function __clone() - { - parent::__clone(); - if ($this->_eagerLoader !== null) { - $this->_eagerLoader = clone $this->_eagerLoader; - } - } - - /** - * {@inheritDoc} - * - * Returns the COUNT(*) for the query. If the query has not been - * modified, and the count has already been performed the cached - * value is returned - * - * @return int - */ - public function count(): int - { - return $this->_resultsCount ??= $this->_performCount(); - } - - /** - * Performs and returns the COUNT(*) for the query. - * - * @return int - */ - protected function _performCount(): int - { - $query = $this->cleanCopy(); - $counter = $this->_counter; - if ($counter !== null) { - $query->counter(null); - - return (int)$counter($query); - } - - $complex = ( - $query->clause('distinct') || - count($query->clause('group')) || - count($query->clause('union')) || - $query->clause('having') - ); - - if (!$complex) { - // Expression fields could have bound parameters. - foreach ($query->clause('select') as $field) { - if ($field instanceof ExpressionInterface) { - $complex = true; - break; - } - } - } - - if (!$complex && $this->_valueBinder !== null) { - /** @var \Cake\Database\Expression\QueryExpression|null $order */ - $order = $this->clause('order'); - $complex = $order === null ? false : $order->hasNestedExpression(); - } - - $count = ['count' => $query->func()->count('*')]; - - if (!$complex) { - $query->getEagerLoader()->disableAutoFields(); - $statement = $query - ->select($count, true) - ->disableAutoFields() - ->execute(); - } else { - $statement = $this->getConnection()->selectQuery() - ->select($count) - ->from(['count_source' => $query]) - ->execute(); - } - - $result = $statement->fetch('assoc'); - - if ($result === false) { - return 0; - } - - return (int)$result['count']; - } - - /** - * Registers a callback that will be executed when the `count` method in - * this query is called. The return value for the function will be set as the - * return value of the `count` method. - * - * This is particularly useful when you need to optimize a query for returning the - * count, for example removing unnecessary joins, removing group by or just return - * an estimated number of rows. - * - * The callback will receive as first argument a clone of this query and not this - * query itself. - * - * If the first param is a null value, the built-in counter function will be called - * instead - * - * @param \Closure|null $counter The counter value - * @return $this - */ - public function counter(?Closure $counter) - { - $this->_counter = $counter; - - return $this; - } - - /** - * Toggle hydrating entities. - * - * If set to false array results will be returned for the query. - * - * @param bool $enable Use a boolean to set the hydration mode. - * @return $this - */ - public function enableHydration(bool $enable = true) - { - $this->_dirty(); - $this->_hydrate = $enable; - - return $this; - } - - /** - * Disable hydrating entities. - * - * Disabling hydration will cause array results to be returned for the query - * instead of entities. - * - * @return $this - */ - public function disableHydration() - { - $this->_dirty(); - $this->_hydrate = false; - - return $this; - } - - /** - * Returns the current hydration mode. - * - * @return bool - */ - public function isHydrationEnabled(): bool - { - return $this->_hydrate; - } - - /** - * Trigger the beforeFind event on the query's repository object. - * - * Will not trigger more than once, and only for select queries. - * - * @return void - */ - public function triggerBeforeFind(): void - { - if (!$this->_beforeFindFired) { - $this->_beforeFindFired = true; - - $repository = $this->getRepository(); - $repository->dispatchEvent('Model.beforeFind', [ - $this, - new ArrayObject($this->_options), - !$this->isEagerLoaded(), - ]); - } - } - - /** - * @inheritDoc - */ - public function sql(?ValueBinder $binder = null): string - { - $this->triggerBeforeFind(); - - $this->_transformQuery(); - - return parent::sql($binder); - } - - /** - * Executes this query and returns an iterable containing the results. - * - * @return iterable - */ - protected function _execute(): iterable - { - $this->triggerBeforeFind(); - if ($this->_results) { - return $this->_results; - } - - $results = parent::all(); - if (!is_array($results)) { - $results = iterator_to_array($results); - } - $results = $this->getEagerLoader()->loadExternal($this, $results); - - return $this->resultSetFactory()->createResultSet($this, $results); - } - - /** - * Get resultset factory. - * - * @return \Cake\ORM\ResultSetFactory - */ - protected function resultSetFactory(): ResultSetFactory - { - if (isset($this->resultSetFactory)) { - return $this->resultSetFactory; - } - - return $this->resultSetFactory = new ResultSetFactory(); - } - - /** - * Applies some defaults to the query object before it is executed. - * - * Specifically add the FROM clause, adds default table fields if none are - * specified and applies the joins required to eager load associations defined - * using `contain` - * - * It also sets the default types for the columns in the select clause - * - * @see \Cake\Database\Query::execute() - * @return void - */ - protected function _transformQuery(): void - { - if (!$this->_dirty) { - return; - } - - $repository = $this->getRepository(); - - if (empty($this->_parts['from'])) { - $this->from([$repository->getAlias() => $repository->getTable()]); - } - $this->_addDefaultFields(); - $this->getEagerLoader()->attachAssociations($this, $repository, !$this->_hasFields); - $this->_addDefaultSelectTypes(); - } - - /** - * Inspects if there are any set fields for selecting, otherwise adds all - * the fields for the default table. - * - * @return void - */ - protected function _addDefaultFields(): void - { - $select = $this->clause('select'); - $this->_hasFields = true; - - $repository = $this->getRepository(); - - if (!count($select) || $this->_autoFields === true) { - $this->_hasFields = false; - $this->select($repository->getSchema()->columns()); - $select = $this->clause('select'); - } - - if ($this->aliasingEnabled) { - $select = $this->aliasFields($select, $repository->getAlias()); - } - $this->select($select, true); - } - - /** - * Sets the default types for converting the fields in the select clause - * - * @return void - */ - protected function _addDefaultSelectTypes(): void - { - $typeMap = $this->getTypeMap()->getDefaults(); - $select = $this->clause('select'); - $types = []; - - foreach ($select as $alias => $value) { - if ($value instanceof TypedResultInterface) { - $types[$alias] = $value->getReturnType(); - continue; - } - if (isset($typeMap[$alias])) { - $types[$alias] = $typeMap[$alias]; - continue; - } - if (is_string($value) && isset($typeMap[$value])) { - $types[$alias] = $typeMap[$value]; - } - } - $this->getSelectTypeMap()->addDefaults($types); - } - - /** - * {@inheritDoc} - * - * @param string $finder The finder method to use. - * @param array $options The options for the finder. - * @return static Returns a modified query. - * @psalm-suppress MoreSpecificReturnType - */ - public function find(string $finder, array $options = []): static - { - $table = $this->getRepository(); - - /** @psalm-suppress LessSpecificReturnStatement */ - return $table->callFinder($finder, $this, $options); - } - - /** - * Disable auto adding table's alias to the fields of SELECT clause. - * - * @return $this - */ - public function disableAutoAliasing() - { - $this->aliasingEnabled = false; - - return $this; - } - - /** - * Marks a query as dirty, removing any preprocessed information - * from in memory caching such as previous results - * - * @return void - */ - protected function _dirty(): void - { - $this->_results = null; - $this->_resultsCount = null; - parent::_dirty(); - } - - /** - * @inheritDoc - */ - public function __debugInfo(): array - { - $eagerLoader = $this->getEagerLoader(); - - return parent::__debugInfo() + [ - 'hydrate' => $this->_hydrate, - 'formatters' => count($this->_formatters), - 'mapReducers' => count($this->_mapReduce), - 'contain' => $eagerLoader->getContain(), - 'matching' => $eagerLoader->getMatching(), - 'extraOptions' => $this->_options, - 'repository' => $this->_repository, - ]; - } - - /** - * Executes the query and converts the result set into JSON. - * - * Part of JsonSerializable interface. - * - * @return \Cake\Datasource\ResultSetInterface The data to convert to JSON. - */ - public function jsonSerialize(): ResultSetInterface - { - return $this->all(); - } - - /** - * Sets whether the ORM should automatically append fields. - * - * By default calling select() will disable auto-fields. You can re-enable - * auto-fields with this method. - * - * @param bool $value Set true to enable, false to disable. - * @return $this - */ - public function enableAutoFields(bool $value = true) - { - $this->_autoFields = $value; - - return $this; - } - - /** - * Disables automatically appending fields. - * - * @return $this - */ - public function disableAutoFields() - { - $this->_autoFields = false; - - return $this; - } - - /** - * Gets whether the ORM should automatically append fields. - * - * By default calling select() will disable auto-fields. You can re-enable - * auto-fields with enableAutoFields(). - * - * @return bool|null The current value. Returns null if neither enabled or disabled yet. - */ - public function isAutoFieldsEnabled(): ?bool - { - return $this->_autoFields; - } -} +class_alias('Cake\ORM\Query\SelectQuery', 'Cake\ORM\Query'); diff --git a/Query/QueryFactory.php b/Query/QueryFactory.php index 944f6569..b9d04993 100644 --- a/Query/QueryFactory.php +++ b/Query/QueryFactory.php @@ -16,7 +16,6 @@ */ namespace Cake\ORM\Query; -use Cake\ORM\Query; use Cake\ORM\Table; /** @@ -28,11 +27,11 @@ class QueryFactory * Create a new Query instance. * * @param \Cake\ORM\Table $table The table this query is starting on. - * @return \Cake\ORM\Query + * @return \Cake\ORM\Query\SelectQuery */ - public function select(Table $table): Query + public function select(Table $table): SelectQuery { - return new Query($table); + return new SelectQuery($table); } /** diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php new file mode 100644 index 00000000..26ff6ae0 --- /dev/null +++ b/Query/SelectQuery.php @@ -0,0 +1,1775 @@ + + */ + protected array $_formatters = []; + + /** + * A query cacher instance if this query has caching enabled. + * + * @var \Cake\Datasource\QueryCacher|null + */ + protected ?QueryCacher $_cache = null; + + /** + * Holds any custom options passed using applyOptions that could not be processed + * by any method in this class. + * + * @var array + */ + protected array $_options = []; + + /** + * Constructor + * + * @param \Cake\ORM\Table $table The table this query is starting on + */ + public function __construct(Table $table) + { + parent::__construct($table->getConnection()); + + $this->setRepository($table); + $this->addDefaultTypes($table); + } + + /** + * Set the result set for a query. + * + * Setting the resultset of a query will make execute() a no-op. Instead + * of executing the SQL query and fetching results, the ResultSet provided to this + * method will be returned. + * + * This method is most useful when combined with results stored in a persistent cache. + * + * @param iterable $results The results this query should return. + * @return $this + */ + public function setResult(iterable $results) + { + $this->_results = $results; + + return $this; + } + + /** + * Executes this query and returns a results iterator. This function is required + * for implementing the IteratorAggregate interface and allows the query to be + * iterated without having to call execute() manually, thus making it look like + * a result set instead of the query itself. + * + * @return \Cake\Datasource\ResultSetInterface + */ + public function getIterator(): ResultSetInterface + { + return $this->all(); + } + + /** + * Enable result caching for this query. + * + * If a query has caching enabled, it will do the following when executed: + * + * - Check the cache for $key. If there are results no SQL will be executed. + * Instead the cached results will be returned. + * - When the cached data is stale/missing the result set will be cached as the query + * is executed. + * + * ### Usage + * + * ``` + * // Simple string key + config + * $query->cache('my_key', 'db_results'); + * + * // Function to generate key. + * $query->cache(function ($q) { + * $key = serialize($q->clause('select')); + * $key .= serialize($q->clause('where')); + * return md5($key); + * }); + * + * // Using a pre-built cache engine. + * $query->cache('my_key', $engine); + * + * // Disable caching + * $query->cache(false); + * ``` + * + * @param \Closure|string|false $key Either the cache key or a function to generate the cache key. + * When using a function, this query instance will be supplied as an argument. + * @param \Psr\SimpleCache\CacheInterface|string $config Either the name of the cache config to use, or + * a cache engine instance. + * @return $this + */ + public function cache(Closure|string|false $key, CacheInterface|string $config = 'default') + { + if ($key === false) { + $this->_cache = null; + + return $this; + } + + $this->_cache = new QueryCacher($key, $config); + + return $this; + } + + /** + * Returns the current configured query `_eagerLoaded` value + * + * @return bool + */ + public function isEagerLoaded(): bool + { + return $this->_eagerLoaded; + } + + /** + * Sets the query instance to be an eager loaded query. If no argument is + * passed, the current configured query `_eagerLoaded` value is returned. + * + * @param bool $value Whether to eager load. + * @return $this + */ + public function eagerLoaded(bool $value) + { + $this->_eagerLoaded = $value; + + return $this; + } + + /** + * Returns a key => value array representing a single aliased field + * that can be passed directly to the select() method. + * The key will contain the alias and the value the actual field name. + * + * If the field is already aliased, then it will not be changed. + * If no $alias is passed, the default table for this query will be used. + * + * @param string $field The field to alias + * @param string|null $alias the alias used to prefix the field + * @return array + */ + public function aliasField(string $field, ?string $alias = null): array + { + if (str_contains($field, '.')) { + $aliasedField = $field; + [$alias, $field] = explode('.', $field); + } else { + $alias = $alias ?: $this->getRepository()->getAlias(); + $aliasedField = $alias . '.' . $field; + } + + $key = sprintf('%s__%s', $alias, $field); + + return [$key => $aliasedField]; + } + + /** + * Runs `aliasField()` for each field in the provided list and returns + * the result under a single array. + * + * @param array $fields The fields to alias + * @param string|null $defaultAlias The default alias + * @return array + */ + public function aliasFields(array $fields, ?string $defaultAlias = null): array + { + $aliased = []; + foreach ($fields as $alias => $field) { + if (is_numeric($alias) && is_string($field)) { + $aliased += $this->aliasField($field, $defaultAlias); + continue; + } + $aliased[$alias] = $field; + } + + return $aliased; + } + + /** + * Fetch the results for this query. + * + * Will return either the results set through setResult(), or execute this query + * and return the ResultSetDecorator object ready for streaming of results. + * + * ResultSetDecorator is a traversable object that implements the methods found + * on Cake\Collection\Collection. + * + * @return \Cake\Datasource\ResultSetInterface + */ + public function all(): ResultSetInterface + { + if ($this->_results !== null) { + if (!($this->_results instanceof ResultSetInterface)) { + $this->_results = $this->_decorateResults($this->_results); + } + + return $this->_results; + } + + $results = null; + if ($this->_cache) { + $results = $this->_cache->fetch($this); + } + if ($results === null) { + $results = $this->_decorateResults($this->_execute()); + if ($this->_cache) { + $this->_cache->store($this, $results); + } + } + $this->_results = $results; + + return $this->_results; + } + + /** + * Returns an array representation of the results after executing the query. + * + * @return array + */ + public function toArray(): array + { + return $this->all()->toArray(); + } + + /** + * Register a new MapReduce routine to be executed on top of the database results + * + * The MapReduce routing will only be run when the query is executed and the first + * result is attempted to be fetched. + * + * If the third argument is set to true, it will erase previous map reducers + * and replace it with the arguments passed. + * + * @param \Closure|null $mapper The mapper function + * @param \Closure|null $reducer The reducing function + * @param bool $overwrite Set to true to overwrite existing map + reduce functions. + * @return $this + * @see \Cake\Collection\Iterator\MapReduce for details on how to use emit data to the map reducer. + */ + public function mapReduce(?Closure $mapper = null, ?Closure $reducer = null, bool $overwrite = false) + { + if ($overwrite) { + $this->_mapReduce = []; + } + if ($mapper === null) { + if (!$overwrite) { + throw new InvalidArgumentException('$mapper can be null only when $overwrite is true.'); + } + + return $this; + } + $this->_mapReduce[] = compact('mapper', 'reducer'); + + return $this; + } + + /** + * Returns the list of previously registered map reduce routines. + * + * @return array + */ + public function getMapReducers(): array + { + return $this->_mapReduce; + } + + /** + * Registers a new formatter callback function that is to be executed when trying + * to fetch the results from the database. + * + * If the second argument is set to true, it will erase previous formatters + * and replace them with the passed first argument. + * + * Callbacks are required to return an iterator object, which will be used as + * the return value for this query's result. Formatter functions are applied + * after all the `MapReduce` routines for this query have been executed. + * + * Formatting callbacks will receive two arguments, the first one being an object + * implementing `\Cake\Collection\CollectionInterface`, that can be traversed and + * modified at will. The second one being the query instance on which the formatter + * callback is being applied. + * + * Usually the query instance received by the formatter callback is the same query + * instance on which the callback was attached to, except for in a joined + * association, in that case the callback will be invoked on the association source + * side query, and it will receive that query instance instead of the one on which + * the callback was originally attached to - see the examples below! + * + * ### Examples: + * + * Return all results from the table indexed by id: + * + * ``` + * $query->select(['id', 'name'])->formatResults(function ($results) { + * return $results->indexBy('id'); + * }); + * ``` + * + * Add a new column to the ResultSet: + * + * ``` + * $query->select(['name', 'birth_date'])->formatResults(function ($results) { + * return $results->map(function ($row) { + * $row['age'] = $row['birth_date']->diff(new DateTime)->y; + * + * return $row; + * }); + * }); + * ``` + * + * Add a new column to the results with respect to the query's hydration configuration: + * + * ``` + * $query->formatResults(function ($results, $query) { + * return $results->map(function ($row) use ($query) { + * $data = [ + * 'bar' => 'baz', + * ]; + * + * if ($query->isHydrationEnabled()) { + * $row['foo'] = new Foo($data) + * } else { + * $row['foo'] = $data; + * } + * + * return $row; + * }); + * }); + * ``` + * + * Retaining access to the association target query instance of joined associations, + * by inheriting the contain callback's query argument: + * + * ``` + * // Assuming a `Articles belongsTo Authors` association that uses the join strategy + * + * $articlesQuery->contain('Authors', function ($authorsQuery) { + * return $authorsQuery->formatResults(function ($results, $query) use ($authorsQuery) { + * // Here `$authorsQuery` will always be the instance + * // where the callback was attached to. + * + * // The instance passed to the callback in the second + * // argument (`$query`), will be the one where the + * // callback is actually being applied to, in this + * // example that would be `$articlesQuery`. + * + * // ... + * + * return $results; + * }); + * }); + * ``` + * + * @param \Closure|null $formatter The formatting function + * @param int|bool $mode Whether to overwrite, append or prepend the formatter. + * @return $this + * @throws \InvalidArgumentException + */ + public function formatResults(?Closure $formatter = null, int|bool $mode = self::APPEND) + { + if ($mode === self::OVERWRITE) { + $this->_formatters = []; + } + if ($formatter === null) { + /** @psalm-suppress RedundantCondition */ + if ($mode !== self::OVERWRITE) { + throw new InvalidArgumentException('$formatter can be null only when $mode is overwrite.'); + } + + return $this; + } + + if ($mode === self::PREPEND) { + array_unshift($this->_formatters, $formatter); + + return $this; + } + + $this->_formatters[] = $formatter; + + return $this; + } + + /** + * Returns the list of previously registered format routines. + * + * @return array<\Closure> + */ + public function getResultFormatters(): array + { + return $this->_formatters; + } + + /** + * Returns the first result out of executing this query, if the query has not been + * executed before, it will set the limit clause to 1 for performance reasons. + * + * ### Example: + * + * ``` + * $singleUser = $query->select(['id', 'username'])->first(); + * ``` + * + * @return mixed The first result from the ResultSet. + */ + public function first(): mixed + { + if ($this->_dirty) { + $this->limit(1); + } + + return $this->all()->first(); + } + + /** + * Get the first result from the executing query or raise an exception. + * + * @throws \Cake\Datasource\Exception\RecordNotFoundException When there is no first record. + * @return mixed The first result from the ResultSet. + */ + public function firstOrFail(): mixed + { + $entity = $this->first(); + if (!$entity) { + $table = $this->getRepository(); + throw new RecordNotFoundException(sprintf( + 'Record not found in table `%s`.', + $table->getTable() + )); + } + + return $entity; + } + + /** + * Returns an array with the custom options that were applied to this query + * and that were not already processed by another method in this class. + * + * ### Example: + * + * ``` + * $query->applyOptions(['doABarrelRoll' => true, 'fields' => ['id', 'name']); + * $query->getOptions(); // Returns ['doABarrelRoll' => true] + * ``` + * + * @see \Cake\Datasource\QueryInterface::applyOptions() to read about the options that will + * be processed by this class and not returned by this function + * @return array + * @see applyOptions() + */ + public function getOptions(): array + { + return $this->_options; + } + + /** + * Populates or adds parts to current query clauses using an array. + * This is handy for passing all query clauses at once. + * + * The method accepts the following query clause related options: + * + * - fields: Maps to the select method + * - conditions: Maps to the where method + * - limit: Maps to the limit method + * - order: Maps to the order method + * - offset: Maps to the offset method + * - group: Maps to the group method + * - having: Maps to the having method + * - contain: Maps to the contain options for eager loading + * - join: Maps to the join method + * - page: Maps to the page method + * + * All other options will not affect the query, but will be stored + * as custom options that can be read via `getOptions()`. Furthermore + * they are automatically passed to `Model.beforeFind`. + * + * ### Example: + * + * ``` + * $query->applyOptions([ + * 'fields' => ['id', 'name'], + * 'conditions' => [ + * 'created >=' => '2013-01-01' + * ], + * 'limit' => 10, + * ]); + * ``` + * + * Is equivalent to: + * + * ``` + * $query + * ->select(['id', 'name']) + * ->where(['created >=' => '2013-01-01']) + * ->limit(10) + * ``` + * + * Custom options can be read via `getOptions()`: + * + * ``` + * $query->applyOptions([ + * 'fields' => ['id', 'name'], + * 'custom' => 'value', + * ]); + * ``` + * + * Here `$options` will hold `['custom' => 'value']` (the `fields` + * option will be applied to the query instead of being stored, as + * it's a query clause related option): + * + * ``` + * $options = $query->getOptions(); + * ``` + * + * @param array $options The options to be applied + * @return $this + * @see getOptions() + */ + public function applyOptions(array $options) + { + $valid = [ + 'fields' => 'select', + 'conditions' => 'where', + 'join' => 'join', + 'order' => 'order', + 'limit' => 'limit', + 'offset' => 'offset', + 'group' => 'group', + 'having' => 'having', + 'contain' => 'contain', + 'page' => 'page', + ]; + + ksort($options); + foreach ($options as $option => $values) { + if (isset($valid[$option], $values)) { + $this->{$valid[$option]}($values); + } else { + $this->_options[$option] = $values; + } + } + + return $this; + } + + /** + * Decorates the results iterator with MapReduce routines and formatters + * + * @param iterable $result Original results + * @return \Cake\Datasource\ResultSetInterface + */ + protected function _decorateResults(iterable $result): ResultSetInterface + { + $decorator = $this->_decoratorClass(); + + if (!empty($this->_mapReduce)) { + foreach ($this->_mapReduce as $functions) { + $result = new MapReduce($result, $functions['mapper'], $functions['reducer']); + } + $result = new $decorator($result); + } + + if (!($result instanceof ResultSetInterface)) { + $result = new $decorator($result); + } + + if (!empty($this->_formatters)) { + foreach ($this->_formatters as $formatter) { + $result = $formatter($result, $this); + } + + if (!($result instanceof ResultSetInterface)) { + $result = new $decorator($result); + } + } + + return $result; + } + + /** + * Returns the name of the class to be used for decorating results + * + * @return class-string<\Cake\Datasource\ResultSetInterface> + */ + protected function _decoratorClass(): string + { + return ResultSetDecorator::class; + } + + /** + * Adds new fields to be returned by a `SELECT` statement when this query is + * executed. Fields can be passed as an array of strings, array of expression + * objects, a single expression or a single string. + * + * If an array is passed, keys will be used to alias fields using the value as the + * real field to be aliased. It is possible to alias strings, Expression objects or + * even other Query objects. + * + * If a callback is passed, the returning array of the function will + * be used as the list of fields. + * + * By default this function will append any passed argument to the list of fields + * to be selected, unless the second argument is set to true. + * + * ### Examples: + * + * ``` + * $query->select(['id', 'title']); // Produces SELECT id, title + * $query->select(['author' => 'author_id']); // Appends author: SELECT id, title, author_id as author + * $query->select('id', true); // Resets the list: SELECT id + * $query->select(['total' => $countQuery]); // SELECT id, (SELECT ...) AS total + * $query->select(function ($query) { + * return ['article_id', 'total' => $query->count('*')]; + * }) + * ``` + * + * By default no fields are selected, if you have an instance of `Cake\ORM\Query` and try to append + * fields you should also call `Cake\ORM\Query::enableAutoFields()` to select the default fields + * from the table. + * + * If you pass an instance of a `Cake\ORM\Table` or `Cake\ORM\Association` class, + * all the fields in the schema of the table or the association will be added to + * the select clause. + * + * @param \Cake\Database\ExpressionInterface|\Cake\ORM\Table|\Cake\ORM\Association|\Closure|array|string|float|int $fields Fields + * to be added to the list. + * @param bool $overwrite whether to reset fields with passed list or not + * @return $this + */ + public function select( + ExpressionInterface|Table|Association|Closure|array|string|float|int $fields = [], + bool $overwrite = false + ) { + if ($fields instanceof Association) { + $fields = $fields->getTarget(); + } + + if ($fields instanceof Table) { + if ($this->aliasingEnabled) { + $fields = $this->aliasFields($fields->getSchema()->columns(), $fields->getAlias()); + } else { + $fields = $fields->getSchema()->columns(); + } + } + + return parent::select($fields, $overwrite); + } + + /** + * All the fields associated with the passed table except the excluded + * fields will be added to the select clause of the query. Passed excluded fields should not be aliased. + * After the first call to this method, a second call cannot be used to remove fields that have already + * been added to the query by the first. If you need to change the list after the first call, + * pass overwrite boolean true which will reset the select clause removing all previous additions. + * + * @param \Cake\ORM\Table|\Cake\ORM\Association $table The table to use to get an array of columns + * @param array $excludedFields The un-aliased column names you do not want selected from $table + * @param bool $overwrite Whether to reset/remove previous selected fields + * @return $this + */ + public function selectAllExcept(Table|Association $table, array $excludedFields, bool $overwrite = false) + { + if ($table instanceof Association) { + $table = $table->getTarget(); + } + + $fields = array_diff($table->getSchema()->columns(), $excludedFields); + if ($this->aliasingEnabled) { + $fields = $this->aliasFields($fields); + } + + return $this->select($fields, $overwrite); + } + + /** + * Sets the instance of the eager loader class to use for loading associations + * and storing containments. + * + * @param \Cake\ORM\EagerLoader $instance The eager loader to use. + * @return $this + */ + public function setEagerLoader(EagerLoader $instance) + { + $this->_eagerLoader = $instance; + + return $this; + } + + /** + * Returns the currently configured instance. + * + * @return \Cake\ORM\EagerLoader + */ + public function getEagerLoader(): EagerLoader + { + return $this->_eagerLoader ??= new EagerLoader(); + } + + /** + * Sets the list of associations that should be eagerly loaded along with this + * query. The list of associated tables passed must have been previously set as + * associations using the Table API. + * + * ### Example: + * + * ``` + * // Bring articles' author information + * $query->contain('Author'); + * + * // Also bring the category and tags associated to each article + * $query->contain(['Category', 'Tag']); + * ``` + * + * Associations can be arbitrarily nested using dot notation or nested arrays, + * this allows this object to calculate joins or any additional queries that + * must be executed to bring the required associated data. + * + * ### Example: + * + * ``` + * // Eager load the product info, and for each product load other 2 associations + * $query->contain(['Product' => ['Manufacturer', 'Distributor']); + * + * // Which is equivalent to calling + * $query->contain(['Products.Manufactures', 'Products.Distributors']); + * + * // For an author query, load his region, state and country + * $query->contain('Regions.States.Countries'); + * ``` + * + * It is possible to control the conditions and fields selected for each of the + * contained associations: + * + * ### Example: + * + * ``` + * $query->contain(['Tags' => function ($q) { + * return $q->where(['Tags.is_popular' => true]); + * }]); + * + * $query->contain(['Products.Manufactures' => function ($q) { + * return $q->select(['name'])->where(['Manufactures.active' => true]); + * }]); + * ``` + * + * Each association might define special options when eager loaded, the allowed + * options that can be set per association are: + * + * - `foreignKey`: Used to set a different field to match both tables, if set to false + * no join conditions will be generated automatically. `false` can only be used on + * joinable associations and cannot be used with hasMany or belongsToMany associations. + * - `fields`: An array with the fields that should be fetched from the association. + * - `finder`: The finder to use when loading associated records. Either the name of the + * finder as a string, or an array to define options to pass to the finder. + * - `queryBuilder`: Equivalent to passing a callback instead of an options array. + * + * ### Example: + * + * ``` + * // Set options for the hasMany articles that will be eagerly loaded for an author + * $query->contain([ + * 'Articles' => [ + * 'fields' => ['title', 'author_id'] + * ] + * ]); + * ``` + * + * Finders can be configured to use options. + * + * ``` + * // Retrieve translations for the articles, but only those for the `en` and `es` locales + * $query->contain([ + * 'Articles' => [ + * 'finder' => [ + * 'translations' => [ + * 'locales' => ['en', 'es'] + * ] + * ] + * ] + * ]); + * ``` + * + * When containing associations, it is important to include foreign key columns. + * Failing to do so will trigger exceptions. + * + * ``` + * // Use a query builder to add conditions to the containment + * $query->contain('Authors', function ($q) { + * return $q->where(...); // add conditions + * }); + * // Use special join conditions for multiple containments in the same method call + * $query->contain([ + * 'Authors' => [ + * 'foreignKey' => false, + * 'queryBuilder' => function ($q) { + * return $q->where(...); // Add full filtering conditions + * } + * ], + * 'Tags' => function ($q) { + * return $q->where(...); // add conditions + * } + * ]); + * ``` + * + * If called with an empty first argument and `$override` is set to true, the + * previous list will be emptied. + * + * @param array|string $associations List of table aliases to be queried. + * @param \Closure|bool $override The query builder for the association, or + * if associations is an array, a bool on whether to override previous list + * with the one passed + * defaults to merging previous list with the new one. + * @return $this + */ + public function contain(array|string $associations, Closure|bool $override = false) + { + $loader = $this->getEagerLoader(); + if ($override === true) { + $this->clearContain(); + } + + $queryBuilder = null; + if ($override instanceof Closure) { + $queryBuilder = $override; + } + + if ($associations) { + $loader->contain($associations, $queryBuilder); + } + $this->_addAssociationsToTypeMap( + $this->getRepository(), + $this->getTypeMap(), + $loader->getContain() + ); + + return $this; + } + + /** + * @return array + */ + public function getContain(): array + { + return $this->getEagerLoader()->getContain(); + } + + /** + * Clears the contained associations from the current query. + * + * @return $this + */ + public function clearContain() + { + $this->getEagerLoader()->clearContain(); + $this->_dirty(); + + return $this; + } + + /** + * Used to recursively add contained association column types to + * the query. + * + * @param \Cake\ORM\Table $table The table instance to pluck associations from. + * @param \Cake\Database\TypeMap $typeMap The typemap to check for columns in. + * This typemap is indirectly mutated via {@link \Cake\ORM\Query\SelectQuery::addDefaultTypes()} + * @param array $associations The nested tree of associations to walk. + * @return void + */ + protected function _addAssociationsToTypeMap(Table $table, TypeMap $typeMap, array $associations): void + { + foreach ($associations as $name => $nested) { + if (!$table->hasAssociation($name)) { + continue; + } + $association = $table->getAssociation($name); + $target = $association->getTarget(); + $primary = (array)$target->getPrimaryKey(); + if (empty($primary) || $typeMap->type($target->aliasField($primary[0])) === null) { + $this->addDefaultTypes($target); + } + if (!empty($nested)) { + $this->_addAssociationsToTypeMap($target, $typeMap, $nested); + } + } + } + + /** + * Adds filtering conditions to this query to only bring rows that have a relation + * to another from an associated table, based on conditions in the associated table. + * + * This function will add entries in the `contain` graph. + * + * ### Example: + * + * ``` + * // Bring only articles that were tagged with 'cake' + * $query->matching('Tags', function ($q) { + * return $q->where(['name' => 'cake']); + * }); + * ``` + * + * It is possible to filter by deep associations by using dot notation: + * + * ### Example: + * + * ``` + * // Bring only articles that were commented by 'markstory' + * $query->matching('Comments.Users', function ($q) { + * return $q->where(['username' => 'markstory']); + * }); + * ``` + * + * As this function will create `INNER JOIN`, you might want to consider + * calling `distinct` on this query as you might get duplicate rows if + * your conditions don't filter them already. This might be the case, for example, + * of the same user commenting more than once in the same article. + * + * ### Example: + * + * ``` + * // Bring unique articles that were commented by 'markstory' + * $query->distinct(['Articles.id']) + * ->matching('Comments.Users', function ($q) { + * return $q->where(['username' => 'markstory']); + * }); + * ``` + * + * Please note that the query passed to the closure will only accept calling + * `select`, `where`, `andWhere` and `orWhere` on it. If you wish to + * add more complex clauses you can do it directly in the main query. + * + * @param string $assoc The association to filter by + * @param \Closure|null $builder a function that will receive a pre-made query object + * that can be used to add custom conditions or selecting some fields + * @return $this + */ + public function matching(string $assoc, ?Closure $builder = null) + { + $result = $this->getEagerLoader()->setMatching($assoc, $builder)->getMatching(); + $this->_addAssociationsToTypeMap($this->getRepository(), $this->getTypeMap(), $result); + $this->_dirty(); + + return $this; + } + + /** + * Creates a LEFT JOIN with the passed association table while preserving + * the foreign key matching and the custom conditions that were originally set + * for it. + * + * This function will add entries in the `contain` graph. + * + * ### Example: + * + * ``` + * // Get the count of articles per user + * $usersQuery + * ->select(['total_articles' => $query->func()->count('Articles.id')]) + * ->leftJoinWith('Articles') + * ->group(['Users.id']) + * ->enableAutoFields(); + * ``` + * + * You can also customize the conditions passed to the LEFT JOIN: + * + * ``` + * // Get the count of articles per user with at least 5 votes + * $usersQuery + * ->select(['total_articles' => $query->func()->count('Articles.id')]) + * ->leftJoinWith('Articles', function ($q) { + * return $q->where(['Articles.votes >=' => 5]); + * }) + * ->group(['Users.id']) + * ->enableAutoFields(); + * ``` + * + * This will create the following SQL: + * + * ``` + * SELECT COUNT(Articles.id) AS total_articles, Users.* + * FROM users Users + * LEFT JOIN articles Articles ON Articles.user_id = Users.id AND Articles.votes >= 5 + * GROUP BY USers.id + * ``` + * + * It is possible to left join deep associations by using dot notation + * + * ### Example: + * + * ``` + * // Total comments in articles by 'markstory' + * $query + * ->select(['total_comments' => $query->func()->count('Comments.id')]) + * ->leftJoinWith('Comments.Users', function ($q) { + * return $q->where(['username' => 'markstory']); + * }) + * ->group(['Users.id']); + * ``` + * + * Please note that the query passed to the closure will only accept calling + * `select`, `where`, `andWhere` and `orWhere` on it. If you wish to + * add more complex clauses you can do it directly in the main query. + * + * @param string $assoc The association to join with + * @param \Closure|null $builder a function that will receive a pre-made query object + * that can be used to add custom conditions or selecting some fields + * @return $this + */ + public function leftJoinWith(string $assoc, ?Closure $builder = null) + { + $result = $this->getEagerLoader() + ->setMatching($assoc, $builder, [ + 'joinType' => static::JOIN_TYPE_LEFT, + 'fields' => false, + ]) + ->getMatching(); + $this->_addAssociationsToTypeMap($this->getRepository(), $this->getTypeMap(), $result); + $this->_dirty(); + + return $this; + } + + /** + * Creates an INNER JOIN with the passed association table while preserving + * the foreign key matching and the custom conditions that were originally set + * for it. + * + * This function will add entries in the `contain` graph. + * + * ### Example: + * + * ``` + * // Bring only articles that were tagged with 'cake' + * $query->innerJoinWith('Tags', function ($q) { + * return $q->where(['name' => 'cake']); + * }); + * ``` + * + * This will create the following SQL: + * + * ``` + * SELECT Articles.* + * FROM articles Articles + * INNER JOIN tags Tags ON Tags.name = 'cake' + * INNER JOIN articles_tags ArticlesTags ON ArticlesTags.tag_id = Tags.id + * AND ArticlesTags.articles_id = Articles.id + * ``` + * + * This function works the same as `matching()` with the difference that it + * will select no fields from the association. + * + * @param string $assoc The association to join with + * @param \Closure|null $builder a function that will receive a pre-made query object + * that can be used to add custom conditions or selecting some fields + * @return $this + * @see \Cake\ORM\Query\SeletQuery::matching() + */ + public function innerJoinWith(string $assoc, ?Closure $builder = null) + { + $result = $this->getEagerLoader() + ->setMatching($assoc, $builder, [ + 'joinType' => static::JOIN_TYPE_INNER, + 'fields' => false, + ]) + ->getMatching(); + $this->_addAssociationsToTypeMap($this->getRepository(), $this->getTypeMap(), $result); + $this->_dirty(); + + return $this; + } + + /** + * Adds filtering conditions to this query to only bring rows that have no match + * to another from an associated table, based on conditions in the associated table. + * + * This function will add entries in the `contain` graph. + * + * ### Example: + * + * ``` + * // Bring only articles that were not tagged with 'cake' + * $query->notMatching('Tags', function ($q) { + * return $q->where(['name' => 'cake']); + * }); + * ``` + * + * It is possible to filter by deep associations by using dot notation: + * + * ### Example: + * + * ``` + * // Bring only articles that weren't commented by 'markstory' + * $query->notMatching('Comments.Users', function ($q) { + * return $q->where(['username' => 'markstory']); + * }); + * ``` + * + * As this function will create a `LEFT JOIN`, you might want to consider + * calling `distinct` on this query as you might get duplicate rows if + * your conditions don't filter them already. This might be the case, for example, + * of the same article having multiple comments. + * + * ### Example: + * + * ``` + * // Bring unique articles that were commented by 'markstory' + * $query->distinct(['Articles.id']) + * ->notMatching('Comments.Users', function ($q) { + * return $q->where(['username' => 'markstory']); + * }); + * ``` + * + * Please note that the query passed to the closure will only accept calling + * `select`, `where`, `andWhere` and `orWhere` on it. If you wish to + * add more complex clauses you can do it directly in the main query. + * + * @param string $assoc The association to filter by + * @param \Closure|null $builder a function that will receive a pre-made query object + * that can be used to add custom conditions or selecting some fields + * @return $this + */ + public function notMatching(string $assoc, ?Closure $builder = null) + { + $result = $this->getEagerLoader() + ->setMatching($assoc, $builder, [ + 'joinType' => static::JOIN_TYPE_LEFT, + 'fields' => false, + 'negateMatch' => true, + ]) + ->getMatching(); + $this->_addAssociationsToTypeMap($this->getRepository(), $this->getTypeMap(), $result); + $this->_dirty(); + + return $this; + } + + /** + * Creates a copy of this current query, triggers beforeFind and resets some state. + * + * The following state will be cleared: + * + * - autoFields + * - limit + * - offset + * - map/reduce functions + * - result formatters + * - order + * - containments + * + * This method creates query clones that are useful when working with subqueries. + * + * @return static + */ + public function cleanCopy(): static + { + $clone = clone $this; + $clone->triggerBeforeFind(); + $clone->disableAutoFields(); + $clone->limit(null); + $clone->order([], true); + $clone->offset(null); + $clone->mapReduce(null, null, true); + $clone->formatResults(null, self::OVERWRITE); + $clone->setSelectTypeMap(new TypeMap()); + $clone->decorateResults(null, true); + + return $clone; + } + + /** + * Clears the internal result cache and the internal count value from the current + * query object. + * + * @return $this + */ + public function clearResult() + { + $this->_dirty(); + + return $this; + } + + /** + * {@inheritDoc} + * + * Handles cloning eager loaders. + */ + public function __clone() + { + parent::__clone(); + if ($this->_eagerLoader !== null) { + $this->_eagerLoader = clone $this->_eagerLoader; + } + } + + /** + * {@inheritDoc} + * + * Returns the COUNT(*) for the query. If the query has not been + * modified, and the count has already been performed the cached + * value is returned + * + * @return int + */ + public function count(): int + { + return $this->_resultsCount ??= $this->_performCount(); + } + + /** + * Performs and returns the COUNT(*) for the query. + * + * @return int + */ + protected function _performCount(): int + { + $query = $this->cleanCopy(); + $counter = $this->_counter; + if ($counter !== null) { + $query->counter(null); + + return (int)$counter($query); + } + + $complex = ( + $query->clause('distinct') || + count($query->clause('group')) || + count($query->clause('union')) || + $query->clause('having') + ); + + if (!$complex) { + // Expression fields could have bound parameters. + foreach ($query->clause('select') as $field) { + if ($field instanceof ExpressionInterface) { + $complex = true; + break; + } + } + } + + if (!$complex && $this->_valueBinder !== null) { + /** @var \Cake\Database\Expression\QueryExpression|null $order */ + $order = $this->clause('order'); + $complex = $order === null ? false : $order->hasNestedExpression(); + } + + $count = ['count' => $query->func()->count('*')]; + + if (!$complex) { + $query->getEagerLoader()->disableAutoFields(); + $statement = $query + ->select($count, true) + ->disableAutoFields() + ->execute(); + } else { + $statement = $this->getConnection()->selectQuery() + ->select($count) + ->from(['count_source' => $query]) + ->execute(); + } + + $result = $statement->fetch('assoc'); + + if ($result === false) { + return 0; + } + + return (int)$result['count']; + } + + /** + * Registers a callback that will be executed when the `count` method in + * this query is called. The return value for the function will be set as the + * return value of the `count` method. + * + * This is particularly useful when you need to optimize a query for returning the + * count, for example removing unnecessary joins, removing group by or just return + * an estimated number of rows. + * + * The callback will receive as first argument a clone of this query and not this + * query itself. + * + * If the first param is a null value, the built-in counter function will be called + * instead + * + * @param \Closure|null $counter The counter value + * @return $this + */ + public function counter(?Closure $counter) + { + $this->_counter = $counter; + + return $this; + } + + /** + * Toggle hydrating entities. + * + * If set to false array results will be returned for the query. + * + * @param bool $enable Use a boolean to set the hydration mode. + * @return $this + */ + public function enableHydration(bool $enable = true) + { + $this->_dirty(); + $this->_hydrate = $enable; + + return $this; + } + + /** + * Disable hydrating entities. + * + * Disabling hydration will cause array results to be returned for the query + * instead of entities. + * + * @return $this + */ + public function disableHydration() + { + $this->_dirty(); + $this->_hydrate = false; + + return $this; + } + + /** + * Returns the current hydration mode. + * + * @return bool + */ + public function isHydrationEnabled(): bool + { + return $this->_hydrate; + } + + /** + * Trigger the beforeFind event on the query's repository object. + * + * Will not trigger more than once, and only for select queries. + * + * @return void + */ + public function triggerBeforeFind(): void + { + if (!$this->_beforeFindFired) { + $this->_beforeFindFired = true; + + $repository = $this->getRepository(); + $repository->dispatchEvent('Model.beforeFind', [ + $this, + new ArrayObject($this->_options), + !$this->isEagerLoaded(), + ]); + } + } + + /** + * @inheritDoc + */ + public function sql(?ValueBinder $binder = null): string + { + $this->triggerBeforeFind(); + + $this->_transformQuery(); + + return parent::sql($binder); + } + + /** + * Executes this query and returns an iterable containing the results. + * + * @return iterable + */ + protected function _execute(): iterable + { + $this->triggerBeforeFind(); + if ($this->_results) { + return $this->_results; + } + + $results = parent::all(); + if (!is_array($results)) { + $results = iterator_to_array($results); + } + $results = $this->getEagerLoader()->loadExternal($this, $results); + + return $this->resultSetFactory()->createResultSet($this, $results); + } + + /** + * Get resultset factory. + * + * @return \Cake\ORM\ResultSetFactory + */ + protected function resultSetFactory(): ResultSetFactory + { + if (isset($this->resultSetFactory)) { + return $this->resultSetFactory; + } + + return $this->resultSetFactory = new ResultSetFactory(); + } + + /** + * Applies some defaults to the query object before it is executed. + * + * Specifically add the FROM clause, adds default table fields if none are + * specified and applies the joins required to eager load associations defined + * using `contain` + * + * It also sets the default types for the columns in the select clause + * + * @see \Cake\Database\Query::execute() + * @return void + */ + protected function _transformQuery(): void + { + if (!$this->_dirty) { + return; + } + + $repository = $this->getRepository(); + + if (empty($this->_parts['from'])) { + $this->from([$repository->getAlias() => $repository->getTable()]); + } + $this->_addDefaultFields(); + $this->getEagerLoader()->attachAssociations($this, $repository, !$this->_hasFields); + $this->_addDefaultSelectTypes(); + } + + /** + * Inspects if there are any set fields for selecting, otherwise adds all + * the fields for the default table. + * + * @return void + */ + protected function _addDefaultFields(): void + { + $select = $this->clause('select'); + $this->_hasFields = true; + + $repository = $this->getRepository(); + + if (!count($select) || $this->_autoFields === true) { + $this->_hasFields = false; + $this->select($repository->getSchema()->columns()); + $select = $this->clause('select'); + } + + if ($this->aliasingEnabled) { + $select = $this->aliasFields($select, $repository->getAlias()); + } + $this->select($select, true); + } + + /** + * Sets the default types for converting the fields in the select clause + * + * @return void + */ + protected function _addDefaultSelectTypes(): void + { + $typeMap = $this->getTypeMap()->getDefaults(); + $select = $this->clause('select'); + $types = []; + + foreach ($select as $alias => $value) { + if ($value instanceof TypedResultInterface) { + $types[$alias] = $value->getReturnType(); + continue; + } + if (isset($typeMap[$alias])) { + $types[$alias] = $typeMap[$alias]; + continue; + } + if (is_string($value) && isset($typeMap[$value])) { + $types[$alias] = $typeMap[$value]; + } + } + $this->getSelectTypeMap()->addDefaults($types); + } + + /** + * {@inheritDoc} + * + * @param string $finder The finder method to use. + * @param array $options The options for the finder. + * @return static Returns a modified query. + * @psalm-suppress MoreSpecificReturnType + */ + public function find(string $finder, array $options = []): static + { + $table = $this->getRepository(); + + /** @psalm-suppress LessSpecificReturnStatement */ + return $table->callFinder($finder, $this, $options); + } + + /** + * Disable auto adding table's alias to the fields of SELECT clause. + * + * @return $this + */ + public function disableAutoAliasing() + { + $this->aliasingEnabled = false; + + return $this; + } + + /** + * Marks a query as dirty, removing any preprocessed information + * from in memory caching such as previous results + * + * @return void + */ + protected function _dirty(): void + { + $this->_results = null; + $this->_resultsCount = null; + parent::_dirty(); + } + + /** + * @inheritDoc + */ + public function __debugInfo(): array + { + $eagerLoader = $this->getEagerLoader(); + + return parent::__debugInfo() + [ + 'hydrate' => $this->_hydrate, + 'formatters' => count($this->_formatters), + 'mapReducers' => count($this->_mapReduce), + 'contain' => $eagerLoader->getContain(), + 'matching' => $eagerLoader->getMatching(), + 'extraOptions' => $this->_options, + 'repository' => $this->_repository, + ]; + } + + /** + * Executes the query and converts the result set into JSON. + * + * Part of JsonSerializable interface. + * + * @return \Cake\Datasource\ResultSetInterface The data to convert to JSON. + */ + public function jsonSerialize(): ResultSetInterface + { + return $this->all(); + } + + /** + * Sets whether the ORM should automatically append fields. + * + * By default calling select() will disable auto-fields. You can re-enable + * auto-fields with this method. + * + * @param bool $value Set true to enable, false to disable. + * @return $this + */ + public function enableAutoFields(bool $value = true) + { + $this->_autoFields = $value; + + return $this; + } + + /** + * Disables automatically appending fields. + * + * @return $this + */ + public function disableAutoFields() + { + $this->_autoFields = false; + + return $this; + } + + /** + * Gets whether the ORM should automatically append fields. + * + * By default calling select() will disable auto-fields. You can re-enable + * auto-fields with enableAutoFields(). + * + * @return bool|null The current value. Returns null if neither enabled or disabled yet. + */ + public function isAutoFieldsEnabled(): ?bool + { + return $this->_autoFields; + } +} diff --git a/ResultSetFactory.php b/ResultSetFactory.php index 5e2ee629..84e655ba 100644 --- a/ResultSetFactory.php +++ b/ResultSetFactory.php @@ -18,6 +18,7 @@ use Cake\Collection\Collection; use Cake\Datasource\EntityInterface; +use Cake\ORM\Query\SelectQuery; /** * Factory class for generation ResulSet instances. @@ -30,11 +31,11 @@ class ResultSetFactory /** * Constructor * - * @param \Cake\ORM\Query $query Query from where results came. + * @param \Cake\ORM\Query\SelectQuery $query Query from where results came. * @param array $results Results array. * @return \Cake\ORM\ResultSet */ - public function createResultSet(Query $query, array $results): ResultSet + public function createResultSet(SelectQuery $query, array $results): ResultSet { $data = $this->collectData($query); @@ -49,10 +50,10 @@ public function createResultSet(Query $query, array $results): ResultSet * Get repository and it's associations data for nesting results key and * entity hydration. * - * @param \Cake\ORM\Query $query The query from where to derive the data. + * @param \Cake\ORM\Query\SelectQuery $query The query from where to derive the data. * @return array */ - protected function collectData(Query $query): array + protected function collectData(SelectQuery $query): array { $primaryTable = $query->getRepository(); $data = [ diff --git a/Table.php b/Table.php index 55633ae4..c4dda6b9 100644 --- a/Table.php +++ b/Table.php @@ -44,6 +44,7 @@ use Cake\ORM\Query\DeleteQuery; use Cake\ORM\Query\InsertQuery; use Cake\ORM\Query\QueryFactory; +use Cake\ORM\Query\SelectQuery; use Cake\ORM\Query\UpdateQuery; use Cake\ORM\Rule\IsUnique; use Cake\Utility\Inflector; @@ -130,7 +131,7 @@ * You can subscribe to the events listed above in your table classes by implementing the * lifecycle methods below: * - * - `beforeFind(EventInterface $event, Query $query, ArrayObject $options, boolean $primary)` + * - `beforeFind(EventInterface $event, SelectQuery $query, ArrayObject $options, boolean $primary)` * - `beforeMarshal(EventInterface $event, ArrayObject $data, ArrayObject $options)` * - `afterMarshal(EventInterface $event, EntityInterface $entity, ArrayObject $options)` * - `buildValidator(EventInterface $event, Validator $validator, string $name)` @@ -1228,11 +1229,11 @@ public function belongsToMany(string $associated, array $options = []): BelongsT * * @param string $type the type of query to perform * @param array $options An array that will be passed to Query::applyOptions() - * @return \Cake\ORM\Query The query builder + * @return \Cake\ORM\Query\SelectQuery The query builder */ - public function find(string $type = 'all', array $options = []): Query + public function find(string $type = 'all', array $options = []): SelectQuery { - return $this->callFinder($type, $this->query()->select(), $options); + return $this->callFinder($type, $this->selectQuery(), $options); } /** @@ -1241,11 +1242,11 @@ public function find(string $type = 'all', array $options = []): Query * By default findAll() applies no conditions, you * can override this method in subclasses to modify how `find('all')` works. * - * @param \Cake\ORM\Query $query The query to find with + * @param \Cake\ORM\Query\SelectQuery $query The query to find with * @param array $options The options to use for the find - * @return \Cake\ORM\Query The query builder + * @return \Cake\ORM\Query\SelectQuery The query builder */ - public function findAll(Query $query, array $options): Query + public function findAll(SelectQuery $query, array $options): SelectQuery { return $query; } @@ -1322,11 +1323,11 @@ public function findAll(Query $query, array $options): Query * ] * ``` * - * @param \Cake\ORM\Query $query The query to find with + * @param \Cake\ORM\Query\SelectQuery $query The query to find with * @param array $options The options for the find - * @return \Cake\ORM\Query The query builder + * @return \Cake\ORM\Query\SelectQuery The query builder */ - public function findList(Query $query, array $options): Query + public function findList(SelectQuery $query, array $options): SelectQuery { $options += [ 'keyField' => $this->getPrimaryKey(), @@ -1386,11 +1387,11 @@ public function findList(Query $query, array $options): Query * ]); * ``` * - * @param \Cake\ORM\Query $query The query to find with + * @param \Cake\ORM\Query\SelectQuery $query The query to find with * @param array $options The options to find with - * @return \Cake\ORM\Query The query builder + * @return \Cake\ORM\Query\SelectQuery The query builder */ - public function findThreaded(Query $query, array $options): Query + public function findThreaded(SelectQuery $query, array $options): SelectQuery { $options += [ 'keyField' => $this->getPrimaryKey(), @@ -1559,7 +1560,7 @@ protected function _transactionCommitted(bool $atomic, bool $primary): bool * entity will be saved and returned. * * If your find conditions require custom order, associations or conditions, then the $search - * parameter can be a callable that takes the Query as the argument, or a \Cake\ORM\Query object passed + * parameter can be a callable that takes the Query as the argument, or a \Cake\ORM\Query\SelectQuery object passed * as the $search parameter. Allowing you to customize the find results. * * ### Options @@ -1570,7 +1571,7 @@ protected function _transactionCommitted(bool $atomic, bool $primary): bool * transaction (default: true) * - defaults: Whether to use the search criteria as default values for the new entity (default: true) * - * @param \Cake\ORM\Query|callable|array $search The criteria to find existing + * @param \Cake\ORM\Query\SelectQuery |callable|array $search The criteria to find existing * records by. Note that when you pass a query object you'll have to use * the 2nd arg of the method to modify the entity data before saving. * @param callable|null $callback A callback that will be invoked for newly @@ -1581,7 +1582,7 @@ protected function _transactionCommitted(bool $atomic, bool $primary): bool * @throws \Cake\ORM\Exception\PersistenceFailedException When the entity couldn't be saved */ public function findOrCreate( - Query|callable|array $search, + SelectQuery|callable|array $search, ?callable $callback = null, array $options = [] ): EntityInterface { @@ -1605,7 +1606,7 @@ public function findOrCreate( /** * Performs the actual find and/or create of an entity based on the passed options. * - * @param \Cake\ORM\Query|callable|array $search The criteria to find an existing record by, or a callable tha will + * @param \Cake\ORM\Query\SelectQuery |callable|array $search The criteria to find an existing record by, or a callable tha will * customize the find query. * @param callable|null $callback A callback that will be invoked for newly * created entities. This callback will be called *before* the entity @@ -1616,7 +1617,7 @@ public function findOrCreate( * @throws \InvalidArgumentException */ protected function _processFindOrCreate( - Query|callable|array $search, + SelectQuery|callable|array $search, ?callable $callback = null, array $options = [] ): EntityInterface|array { @@ -1649,10 +1650,10 @@ protected function _processFindOrCreate( /** * Gets the query object for findOrCreate(). * - * @param \Cake\ORM\Query|callable|array $search The criteria to find existing records by. - * @return \Cake\ORM\Query + * @param \Cake\ORM\Query\SelectQuery |callable|array $search The criteria to find existing records by. + * @return \Cake\ORM\Query\SelectQuery */ - protected function _getFindOrCreateQuery(Query|callable|array $search): Query + protected function _getFindOrCreateQuery(SelectQuery|callable|array $search): SelectQuery { if (is_callable($search)) { $query = $this->find(); @@ -1667,11 +1668,21 @@ protected function _getFindOrCreateQuery(Query|callable|array $search): Query } /** - * Creates a new Query instance for a table. + * Creates a new SelectQuery instance for a table. * - * @return \Cake\ORM\Query + * @return \Cake\ORM\Query\SelectQuery */ - public function query(): Query + public function query(): SelectQuery + { + return $this->selectQuery(); + } + + /** + * Creates a new select query + * + * @return \Cake\ORM\Query\SelectQuery + */ + public function selectQuery(): SelectQuery { return $this->queryFactory->select($this); } @@ -1711,9 +1722,9 @@ public function deleteQuery(): DeleteQuery * * This is useful for subqueries. * - * @return \Cake\ORM\Query + * @return \Cake\ORM\Query\SelectQuery */ - public function subquery(): Query + public function subquery(): SelectQuery { return $this->queryFactory->select($this)->disableAutoAliasing(); } @@ -2548,15 +2559,15 @@ public function hasFinder(string $type): bool * Calls a finder method and applies it to the passed query. * * @param string $type Name of the finder to be called. - * @param \Cake\ORM\Query $query The query object to apply the finder options to. + * @param \Cake\ORM\Query\SelectQuery $query The query object to apply the finder options to. * @param array $options List of options to pass to the finder. - * @return \Cake\ORM\Query + * @return \Cake\ORM\Query\SelectQuery * @throws \BadMethodCallException * @uses findAll() * @uses findList() * @uses findThreaded() */ - public function callFinder(string $type, Query $query, array $options = []): Query + public function callFinder(string $type, SelectQuery $query, array $options = []): SelectQuery { assert(empty($options) || !array_is_list($options), 'Finder options should be an associative array not a list'); @@ -2583,11 +2594,11 @@ public function callFinder(string $type, Query $query, array $options = []): Que * * @param string $method The method name that was fired. * @param array $args List of arguments passed to the function. - * @return \Cake\ORM\Query + * @return \Cake\ORM\Query\SelectQuery * @throws \BadMethodCallException when there are missing arguments, or when * and & or are combined. */ - protected function _dynamicFinder(string $method, array $args): Query + protected function _dynamicFinder(string $method, array $args): SelectQuery { $method = Inflector::underscore($method); preg_match('/^find_([\w]+)_by_/', $method, $matches); From 795a49c4f8bdd598dca1e89d739ca0d13824bae8 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 19 Sep 2022 08:35:26 +0530 Subject: [PATCH 1818/2059] Fix errors reported by phpstan. --- Marshaller.php | 1 + Table.php | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 32f84422..e1018b86 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -740,6 +740,7 @@ protected function _mergeAssociation( return $marshaller->merge($original, $value, $options); } if ($type === Association::MANY_TO_MANY) { + /** @phpstan-ignore-next-line */ return $marshaller->_mergeBelongsToMany($original, $assoc, $value, $options); } diff --git a/Table.php b/Table.php index c4dda6b9..a02aab73 100644 --- a/Table.php +++ b/Table.php @@ -178,7 +178,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc /** * The rules class name that is used. * - * @var string + * @var class-string<\Cake\ORM\RulesChecker> */ public const RULES_CLASS = RulesChecker::class; @@ -2081,8 +2081,7 @@ protected function _insert(EntityInterface $entity, array $data): EntityInterfac $id = (array)$this->_newId($primary) + $keys; // Generate primary keys preferring values in $data. - /** @psalm-suppress RedundantConditionGivenDocblockType */ - $primary = array_combine($primary, $id) ?: []; + $primary = array_combine($primary, $id); $primary = array_intersect_key($data, $primary) + $primary; $filteredKeys = array_filter($primary, function ($v) { From 931e21ca9b2ed77c68d6327041ec8c8e1fbe872f Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 19 Sep 2022 11:13:11 +0530 Subject: [PATCH 1819/2059] Add missing type declarations. --- Locator/TableLocator.php | 2 +- Table.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index cc37a754..eb42aa7c 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -137,7 +137,7 @@ public function setFallbackClassName(string $className) /** * @inheritDoc */ - public function setConfig($alias, $options = null) + public function setConfig(array|string $alias, ?array $options = null) { if (!is_string($alias)) { $this->_config = $alias; diff --git a/Table.php b/Table.php index a02aab73..a01191fd 100644 --- a/Table.php +++ b/Table.php @@ -1466,7 +1466,7 @@ protected function _setFieldMatchers(array $options, array $keys): array * incorrect number of elements. * @see \Cake\Datasource\RepositoryInterface::find() */ - public function get($primaryKey, array $options = []): EntityInterface + public function get(mixed $primaryKey, array $options = []): EntityInterface { if ($primaryKey === null) { throw new InvalidPrimaryKeyException(sprintf( @@ -2354,7 +2354,7 @@ protected function _saveMany( * @param array $options The options for the delete. * @return bool success */ - public function delete(EntityInterface $entity, $options = []): bool + public function delete(EntityInterface $entity, array $options = []): bool { $options = new ArrayObject($options + [ 'atomic' => true, From a84eec484a1d5b3bcafd90e690a011f77782ec44 Mon Sep 17 00:00:00 2001 From: Arhell Date: Sun, 25 Sep 2022 10:14:39 +0300 Subject: [PATCH 1820/2059] update src folder links --- Association/DependentDeleteHelper.php | 10 +++++----- Behavior/Translate/TranslateStrategyInterface.php | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Association/DependentDeleteHelper.php b/Association/DependentDeleteHelper.php index 52b6289b..e965d5de 100644 --- a/Association/DependentDeleteHelper.php +++ b/Association/DependentDeleteHelper.php @@ -2,17 +2,17 @@ declare(strict_types=1); /** - * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) - * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) + * CakePHP(tm) : Rapid Development Framework (https://cakephp.org) + * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice. * - * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) - * @link http://cakephp.org CakePHP(tm) Project + * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) + * @link https://cakephp.org CakePHP(tm) Project * @since 3.5.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://www.opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM\Association; diff --git a/Behavior/Translate/TranslateStrategyInterface.php b/Behavior/Translate/TranslateStrategyInterface.php index 9f8d9a20..41536e6c 100644 --- a/Behavior/Translate/TranslateStrategyInterface.php +++ b/Behavior/Translate/TranslateStrategyInterface.php @@ -2,17 +2,17 @@ declare(strict_types=1); /** - * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) - * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) + * CakePHP(tm) : Rapid Development Framework (https://cakephp.org) + * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice. * - * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) - * @link http://cakephp.org CakePHP(tm) Project + * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) + * @link https://cakephp.org CakePHP(tm) Project * @since 4.0.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @license https://www.opensource.org/licenses/mit-license.php MIT License */ namespace Cake\ORM\Behavior\Translate; From 15b989b513566cf0274b47ea18f06267f8e0a15a Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 26 Sep 2022 13:04:49 +0530 Subject: [PATCH 1821/2059] Use ::class instead of get_class(). --- AssociationCollection.php | 2 +- Behavior/TranslateBehavior.php | 2 +- Table.php | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/AssociationCollection.php b/AssociationCollection.php index 25e62537..330666dd 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -159,7 +159,7 @@ public function getByType(array|string $class): array $class = array_map('strtolower', (array)$class); $out = array_filter($this->_items, function ($assoc) use ($class) { - [, $name] = namespaceSplit(get_class($assoc)); + [, $name] = namespaceSplit($assoc::class); return in_array(strtolower($name), $class, true); }); diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 3cf024c7..037e10fa 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -352,7 +352,7 @@ public function __call(string $method, array $args): mixed */ protected function referenceName(Table $table): string { - $name = namespaceSplit(get_class($table)); + $name = namespaceSplit($table::class); $name = substr(end($name), 0, -5); if (empty($name)) { $name = $table->getTable() ?: $table->getAlias(); diff --git a/Table.php b/Table.php index a01191fd..c9a217b2 100644 --- a/Table.php +++ b/Table.php @@ -2176,7 +2176,7 @@ protected function _update(EntityInterface $entity, array $data): EntityInterfac } if (count($primaryColumns) === 0) { - $entityClass = get_class($entity); + $entityClass = $entity::class; $table = $this->getTable(); $message = "Cannot update `$entityClass`. The `$table` has no primary key."; throw new InvalidArgumentException($message); @@ -2184,7 +2184,7 @@ protected function _update(EntityInterface $entity, array $data): EntityInterfac if (!$entity->has($primaryColumns)) { $message = 'All primary key value(s) are needed for updating, '; - $message .= get_class($entity) . ' is missing ' . implode(', ', $primaryColumns); + $message .= $entity::class . ' is missing ' . implode(', ', $primaryColumns); throw new InvalidArgumentException($message); } From ed38d21cdbe4ade86b698fdfc9fab8b3730ddea3 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 6 Oct 2022 00:47:26 +0530 Subject: [PATCH 1822/2059] Add comment to clarify method's purpose. --- ResultSet.php | 1 + 1 file changed, 1 insertion(+) diff --git a/ResultSet.php b/ResultSet.php index 8737cd6a..c084bcea 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -185,6 +185,7 @@ public function count(): int */ public function countKeys(): int { + // This is an optimization over the implementation provided by CollectionTrait::countKeys() return $this->_count; } From 9d21477360553e1dd244dc1f5033a35ebdd27ac7 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 7 Oct 2022 14:30:43 -0700 Subject: [PATCH 1823/2059] Start to replace `@var` with assert() While `@var` annotations are good for convincing static analyzers of types, we could still encounter runtime warnings. Using assert() will convert those warnings/notices into exceptions. If folks like this approach I can apply it to the remaining classes in managable chunks. --- Table.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Table.php b/Table.php index c9a217b2..bf70128b 100644 --- a/Table.php +++ b/Table.php @@ -185,7 +185,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc /** * The IsUnique class name that is used. * - * @var string + * @var class-string<\Cake\ORM\Rule\IsUnique> */ public const IS_UNIQUE_CLASS = IsUnique::class; @@ -2121,8 +2121,8 @@ protected function _insert(EntityInterface $entity, array $data): EntityInterfac foreach ($primary as $key => $v) { if (!isset($data[$key])) { $id = $statement->lastInsertId($this->getTable(), $key); - /** @var string $type */ $type = $schema->getColumnType($key); + assert($type !== null); $entity->set($key, TypeFactory::build($type)->toPHP($id, $driver)); break; } @@ -2150,8 +2150,8 @@ protected function _newId(array $primary): ?string if (!$primary || count($primary) > 1) { return null; } - /** @var string $typeName */ $typeName = $this->getSchema()->getColumnType($primary[0]); + assert($typeName !== null); $type = TypeFactory::build($typeName); return $type->newId(); From 3b1f090ae7017534cc0b722eeccfd2069318caf3 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 8 Oct 2022 23:51:33 -0400 Subject: [PATCH 1824/2059] First pass at DeleteQuery This subclass of ORM\Query will enable userland code to adapt to the 5.x API more easily. We can combine these new classes with deprecations to encourage the use of new Table methods. --- Query.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Query.php b/Query.php index 26c06d4d..600ec2ad 100644 --- a/Query.php +++ b/Query.php @@ -29,6 +29,7 @@ use Cake\Datasource\ResultSetInterface; use InvalidArgumentException; use JsonSerializable; +use PHPUnit\Framework\MockObject\InvalidMethodNameException; use RuntimeException; use Traversable; @@ -1440,4 +1441,18 @@ protected function _decorateResults(Traversable $result): ResultSetInterface return $result; } + + /** + * Helper for ORM\Query exceptions + * + * @param string $method The method that is invalid. + * @param string $message An additional message. + * @internal + */ + protected function _deprecatedException($method, $message = '') + { + $class = get_class($this); + $text = "Calling {$method}() on {$class} is deprecated. " . $message; + throw new InvalidMethodNameException($text); + } } From 93e6bba329992a45c6d16d9998966597e265af33 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 9 Oct 2022 00:09:48 -0400 Subject: [PATCH 1825/2059] Add Table::deleteQuery() Soon, we'll be adding a deprecation to query() and update all of the core usage of the ORM to use the new [verb]Query() methods. This will result in all user-land code using query() directly to start throwing errors. --- Table.php | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/Table.php b/Table.php index 1078f656..c53afb95 100644 --- a/Table.php +++ b/Table.php @@ -40,6 +40,7 @@ use Cake\ORM\Exception\PersistenceFailedException; use Cake\ORM\Exception\RolledbackTransactionException; use Cake\ORM\Rule\IsUnique; +use Cake\ORM\Query\DeleteQuery; use Cake\Utility\Inflector; use Cake\Validation\ValidatorAwareInterface; use Cake\Validation\ValidatorAwareTrait; @@ -1710,9 +1711,22 @@ protected function _getFindOrCreateQuery($search): Query */ public function query(): Query { + // TODO(markstory) Add deprecation here to encourage new methods. + // Remember to remind people implementing this method to + // implement the new ones instead. return new Query($this->getConnection(), $this); } + /** + * Creates a new DeleteQuery instance for a table. + * + * @return \Cake\ORM\DeleteQuery + */ + public function deleteQuery(): Query + { + return new DeleteQuery($this->getConnection(), $this); + } + /** * Creates a new Query::subquery() instance for a table. * @@ -1744,8 +1758,7 @@ public function updateAll($fields, $conditions): int */ public function deleteAll($conditions): int { - $statement = $this->query() - ->delete() + $statement = $this->deleteQuery() ->where($conditions) ->execute(); $statement->closeCursor(); @@ -2507,8 +2520,7 @@ protected function _processDelete(EntityInterface $entity, ArrayObject $options) return $success; } - $statement = $this->query() - ->delete() + $statement = $this->deleteQuery() ->where($entity->extract($primaryKey)) ->execute(); From 5356614d1ce2c9412c7d3fa2e8bd44f831b3a180 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 9 Oct 2022 00:16:26 -0400 Subject: [PATCH 1826/2059] Add DeleteQuery --- Query/DeleteQuery.php | 223 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 Query/DeleteQuery.php diff --git a/Query/DeleteQuery.php b/Query/DeleteQuery.php new file mode 100644 index 00000000..9023eda3 --- /dev/null +++ b/Query/DeleteQuery.php @@ -0,0 +1,223 @@ +from([$table->getAlias() => $table->getTable()]); + } + + /** + * @inheritDoc + */ + public function delete(?string $table = null) + { + $this->_deprecatedException('delete()', 'Remove this method call.'); + } + + /** + * @inheritDoc + */ + public function cache($key, $config = 'default') + { + $this->_deprecatedException('cache()', 'Use execute() instead.'); + } + + /** + * @inheritDoc + */ + public function all(): ResultSetInterface + { + $this->_deprecatedException('all()', 'Use execute() instead.'); + } + + /** + * @inheritDoc + */ + public function select($fields = [], bool $overwrite = false) + { + $this->_deprecatedException('select()'); + } + + /** + * @inheritDoc + */ + public function distinct($on = [], $overwrite = false) + { + $this->_deprecatedException('distinct()'); + } + + /** + * @inheritDoc + */ + public function modifier($modifiers, $overwrite = false) + { + $this->_deprecatedException('modifier()'); + } + + /** + * @inheritDoc + */ + public function join($tables, $types = [], $overwrite = false) + { + $this->_deprecatedException('join()'); + } + + /** + * @inheritDoc + */ + public function removeJoin(string $name) + { + $this->_deprecatedException('removeJoin()'); + } + + /** + * @inheritDoc + */ + public function leftJoin($table, $conditions = [], $types = []) + { + $this->_deprecatedException('leftJoin()'); + } + + /** + * @inheritDoc + */ + public function rightJoin($table, $conditions = [], $types = []) + { + $this->_deprecatedException('rightJoin()'); + } + + /** + * @inheritDoc + */ + public function innerJoin($table, $conditions = [], $types = []) + { + $this->_deprecatedException('innerJoin()'); + } + + /** + * @inheritDoc + */ + public function group($fields, $overwrite = false) + { + $this->_deprecatedException('group()'); + } + + /** + * @inheritDoc + */ + public function having($conditions = null, $types = [], $overwrite = false) + { + $this->_deprecatedException('having()'); + } + + /** + * @inheritDoc + */ + public function andHaving($conditions, $types = []) + { + $this->_deprecatedException('andHaving()'); + } + + /** + * @inheritDoc + */ + public function page(int $num, ?int $limit = null) + { + $this->_deprecatedException('page()'); + } + + /** + * @inheritDoc + */ + public function union($query, $overwrite = false) + { + $this->_deprecatedException('union()'); + } + + /** + * @inheritDoc + */ + public function unionAll($query, $overwrite = false) + { + $this->_deprecatedException('union()'); + } + + /** + * @inheritDoc + */ + public function insert(array $columns, array $types = []) + { + $this->_deprecatedException('insert()'); + } + + /** + * @inheritDoc + */ + public function into(string $table) + { + $this->_deprecatedException('into()', 'Use from() instead.'); + } + + /** + * @inheritDoc + */ + public function values($data) + { + $this->_deprecatedException('values()'); + } + + /** + * @inheritDoc + */ + public function update($table = null) + { + $this->_deprecatedException('update()'); + } + + /** + * @inheritDoc + */ + public function set($key, $value = null, $types = []) + { + $this->_deprecatedException('set()'); + } +} From 18532e6488da489b2798fb13c9357179462565dd Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 9 Oct 2022 00:21:05 -0400 Subject: [PATCH 1827/2059] Improve deprecation exception Using a class in our package that has some additional formatting seems wise. --- Exception/DeprecationException.php | 14 ++++++++++++++ Query.php | 7 ++++--- 2 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 Exception/DeprecationException.php diff --git a/Exception/DeprecationException.php b/Exception/DeprecationException.php new file mode 100644 index 00000000..d2c9a9f3 --- /dev/null +++ b/Exception/DeprecationException.php @@ -0,0 +1,14 @@ + Date: Sun, 9 Oct 2022 16:51:59 -0400 Subject: [PATCH 1828/2059] 5.x Update remaining var annotation to use assert() There are still a bunch of `@var` annotations left, but they are for types more complex than PHP's typesystem can currently handle or were inline hints for factory methods. --- Association/BelongsToMany.php | 2 +- Association/HasMany.php | 7 ++----- Association/Loader/SelectLoader.php | 2 +- AssociationsNormalizerTrait.php | 3 ++- Behavior.php | 2 +- Behavior/TimestampBehavior.php | 2 -- Behavior/Translate/EavStrategy.php | 3 ++- Behavior/Translate/ShadowTableStrategy.php | 5 +++-- Behavior/TranslateBehavior.php | 4 ++-- Behavior/TreeBehavior.php | 14 ++++++-------- BehaviorRegistry.php | 3 ++- EagerLoader.php | 2 +- LazyEagerLoader.php | 2 +- Marshaller.php | 9 ++++----- Query/SelectQuery.php | 3 ++- ResultSetFactory.php | 8 +++++--- Table.php | 9 ++++----- 17 files changed, 39 insertions(+), 41 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 3b2f21f2..2689894e 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1102,8 +1102,8 @@ protected function _appendJunctionJoin(SelectQuery $query, ?array $conditions = } $name = $this->_junctionAssociationName(); - /** @var array $joins */ $joins = $query->clause('join'); + assert(is_array($joins)); $matching = [ $name => [ 'table' => $junctionTable->getTable(), diff --git a/Association/HasMany.php b/Association/HasMany.php index 9fa0fc68..f2389cd7 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -360,9 +360,7 @@ public function unlink(EntityInterface $sourceEntity, array $targetEntities, arr $conditions = [ 'OR' => (new Collection($targetEntities)) - ->map(function ($entity) use ($targetPrimaryKey) { - /** @var \Cake\Datasource\EntityInterface $entity */ - /** @psalm-suppress InvalidArgument,UnusedPsalmSuppress */ + ->map(function (EntityInterface $entity) use ($targetPrimaryKey) { return $entity->extract($targetPrimaryKey); }) ->toList(), @@ -469,8 +467,7 @@ protected function _unlinkAssociated( $primaryKey = (array)$target->getPrimaryKey(); $exclusions = new Collection($remainingEntities); $exclusions = $exclusions->map( - function ($ent) use ($primaryKey) { - /** @var \Cake\Datasource\EntityInterface $ent */ + function (EntityInterface $ent) use ($primaryKey) { return $ent->extract($primaryKey); } ) diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index c2dc79e0..f8ec7adf 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -164,8 +164,8 @@ protected function _buildQuery(array $options): SelectQuery $finder = $this->finder; $options['fields'] = $options['fields'] ?? []; - /** @var \Cake\ORM\Query\SelectQuery $query */ $query = $finder(); + assert($query instanceof SelectQuery); if (isset($options['finder'])) { [$finderName, $opts] = $this->_extractFinder($options['finder']); $query = $query->find($finderName, $opts); diff --git a/AssociationsNormalizerTrait.php b/AssociationsNormalizerTrait.php index 287927a7..6f869d21 100644 --- a/AssociationsNormalizerTrait.php +++ b/AssociationsNormalizerTrait.php @@ -47,8 +47,9 @@ protected function _normalizeAssociations(array|string $associations): array $path = explode('.', $table); $table = array_pop($path); - /** @var string $first */ $first = array_shift($path); + assert(is_string($first)); + $pointer += [$first => []]; $pointer = &$pointer[$first]; $pointer += ['associated' => []]; diff --git a/Behavior.php b/Behavior.php index bd69926e..99878599 100644 --- a/Behavior.php +++ b/Behavior.php @@ -385,8 +385,8 @@ protected function _reflectionCache(): array $eventMethods = []; foreach ($events as $binding) { if (is_array($binding) && isset($binding['callable'])) { - /** @var string $callable */ $callable = $binding['callable']; + assert(is_string($callable)); $binding = $callable; } $eventMethods[$binding] = true; diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index d2381fb9..a9232f24 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -216,9 +216,7 @@ protected function _updateField(EntityInterface $entity, string $field, bool $re return; } - /** @var \Cake\Database\Type\DateTimeType $type */ $type = TypeFactory::build($columnType); - assert( $type instanceof DateTimeType, 'TimestampBehavior only supports columns of type DateTimeType.' diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index e2aafbca..9d228e48 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -298,8 +298,9 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array } $modified = []; - /** @var \Cake\Datasource\EntityInterface $translation */ foreach ($preexistent as $field => $translation) { + assert($translation instanceof EntityInterface); + $translation->set('content', $values[$field]); $modified[$field] = $translation; } diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 924dce2e..539a821a 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -20,6 +20,7 @@ use Cake\Collection\CollectionInterface; use Cake\Core\InstanceConfigTrait; use Cake\Database\Expression\FieldInterface; +use Cake\Database\Expression\QueryExpression; use Cake\Datasource\EntityInterface; use Cake\Event\EventInterface; use Cake\ORM\Locator\LocatorAwareTrait; @@ -247,11 +248,11 @@ protected function addFieldsToQuery(SelectQuery $query, array $config): bool */ protected function iterateClause(SelectQuery $query, string $name = '', array $config = []): bool { - /** @var \Cake\Database\Expression\QueryExpression|null $clause */ $clause = $query->clause($name); if (!$clause || !$clause->count()) { return false; } + assert($clause instanceof QueryExpression); $alias = $config['hasOneAlias']; $fields = $this->translatedFields(); @@ -397,7 +398,6 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array if ($id) { $where['id'] = $id; - /** @var \Cake\Datasource\EntityInterface|null $translation */ $translation = $this->translationTable->find() ->select(array_merge(['id', 'locale'], $fields)) ->where($where) @@ -415,6 +415,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array ] ); } + assert($translation instanceof EntityInterface); $entity->set('_i18n', array_merge($bundled, [$translation])); $entity->set('_locale', $locale, ['setter' => false]); diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 037e10fa..23a4fe7c 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -17,6 +17,7 @@ namespace Cake\ORM\Behavior; use Cake\I18n\I18n; +use Cake\Datasource\QueryInterface; use Cake\ORM\Behavior; use Cake\ORM\Behavior\Translate\ShadowTableStrategy; use Cake\ORM\Behavior\Translate\TranslateStrategyInterface; @@ -316,8 +317,7 @@ public function findTranslations(SelectQuery $query, array $options): SelectQuer $targetAlias = $this->getStrategy()->getTranslationTable()->getAlias(); return $query - ->contain([$targetAlias => function ($query) use ($locales, $targetAlias) { - /** @var \Cake\Datasource\QueryInterface $query */ + ->contain([$targetAlias => function (QueryInterface $query) use ($locales, $targetAlias) { if ($locales) { $query->where(["$targetAlias.locale IN" => $locales]); } diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 08c6db95..0606f912 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -17,6 +17,7 @@ namespace Cake\ORM\Behavior; use Cake\Collection\CollectionInterface; +use Cake\Collection\Iterator\TreeIterator; use Cake\Database\Exception\DatabaseException; use Cake\Database\Expression\IdentifierExpression; use Cake\Database\Expression\QueryExpression; @@ -201,8 +202,8 @@ protected function _setChildrenLevel(EntityInterface $entity): void 'order' => $config['left'], ]); - /** @var \Cake\Datasource\EntityInterface $node */ foreach ($children as $node) { + assert($node instanceof EntityInterface); $parentIdValue = $node->get($config['parent']); $depth = $depths[$parentIdValue] + 1; $depths[$node->get($primaryKey)] = $depth; @@ -363,8 +364,7 @@ protected function _unmarkInternalTree(): void { $config = $this->getConfig(); $this->_table->updateAll( - function ($exp) use ($config) { - /** @var \Cake\Database\Expression\QueryExpression $exp */ + function (QueryExpression $exp) use ($config) { $leftInverse = clone $exp; $leftInverse->setConjunction('*')->add('-1'); $rightInverse = clone $leftInverse; @@ -541,8 +541,8 @@ public function formatTreeList(SelectQuery $query, array $options = []): SelectQ 'spacer' => '_', ]; - /** @var \Cake\Collection\Iterator\TreeIterator $nested */ $nested = $results->listNested(); + assert($nested instanceof TreeIterator); return $nested->printer($options['valuePath'], $options['keyPath'], $options['spacer']); }); @@ -737,7 +737,6 @@ protected function _moveDown(EntityInterface $node, int|bool $number): EntityInt $targetNode = null; if ($number !== true) { - /** @var \Cake\Datasource\EntityInterface|null $targetNode */ $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) ->where(["$parent IS" => $nodeParent]) @@ -748,7 +747,6 @@ protected function _moveDown(EntityInterface $node, int|bool $number): EntityInt ->first(); } if (!$targetNode) { - /** @var \Cake\Datasource\EntityInterface|null $targetNode */ $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) ->where(["$parent IS" => $nodeParent]) @@ -761,6 +759,7 @@ protected function _moveDown(EntityInterface $node, int|bool $number): EntityInt return $node; } } + assert($targetNode instanceof EntityInterface); [, $targetRight] = array_values($targetNode->extract([$left, $right])); $edge = $this->_getMax(); @@ -774,9 +773,8 @@ protected function _moveDown(EntityInterface $node, int|bool $number): EntityInt $this->_sync($shift, '-', "BETWEEN {$leftBoundary} AND {$rightBoundary}"); $this->_sync($nodeToHole, '-', "> {$edge}"); - /** @var string $left */ + assert(is_string($left) && is_string($right)); $node->set($left, $targetRight - ($nodeRight - $nodeLeft)); - /** @var string $right */ $node->set($right, $targetRight); $node->setDirty($left, false); diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 0fe2dcf7..19769c5a 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -145,8 +145,9 @@ protected function _create(object|string $class, string $alias, array $config): return $class; } - /** @var \Cake\ORM\Behavior $instance */ $instance = new $class($this->_table, $config); + assert($instance instanceof Behavior); + $enable = $config['enabled'] ?? true; if ($enable) { $this->getEventManager()->on($instance); diff --git a/EagerLoader.php b/EagerLoader.php index 6a7fd5cb..cad157b5 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -537,8 +537,8 @@ protected function _fixStrategies(): void if (count($configs) < 2) { continue; } - /** @var \Cake\ORM\EagerLoadable $loadable */ foreach ($configs as $loadable) { + assert($loadable instanceof EagerLoadable); if (str_contains($loadable->aliasPath(), '.')) { $this->_correctStrategy($loadable); } diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index eb0f3051..39d7fd2e 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -158,8 +158,8 @@ protected function _injectResults( continue; } - /** @var \Cake\Datasource\EntityInterface $loaded */ $loaded = $results[$key]; + assert($loaded instanceof EntityInterface); foreach ($associations as $assoc) { $property = $properties[$assoc]; $object->set($property, $loaded->get($property), ['useSetters' => false]); diff --git a/Marshaller.php b/Marshaller.php index e1018b86..73873f4e 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -106,8 +106,7 @@ protected function _buildPropertyMap(array $data, array $options): array $nested['forceNew'] = $options['forceNew']; } if (isset($options['isMerge'])) { - $callback = function ($value, $entity) use ($assoc, $nested) { - /** @var \Cake\Datasource\EntityInterface $entity */ + $callback = function ($value, EntityInterface $entity) use ($assoc, $nested) { $options = $nested + ['associated' => [], 'association' => $assoc]; return $this->_mergeAssociation($entity->get($assoc->getProperty()), $assoc, $value, $options); @@ -409,8 +408,8 @@ protected function _belongsToMany(BelongsToMany $assoc, array $data, array $opti $keyFields = array_keys($primaryKey); $existing = []; - /** @var \Cake\Datasource\EntityInterface $row */ foreach ($query as $row) { + assert($row instanceof EntityInterface); $k = implode(';', $row->extract($keyFields)); $existing[$k] = $row; } @@ -595,7 +594,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) } foreach ((array)$options['fields'] as $field) { - /** @var string $field */ + assert(is_string($field)); if (!array_key_exists($field, $properties)) { continue; } @@ -690,8 +689,8 @@ public function mergeMany(iterable $entities, array $data, array $options = []): $maybeExistentQuery = $this->_table->find()->where($conditions); if (!empty($indexed) && count($maybeExistentQuery->clause('where'))) { - /** @var \Cake\Datasource\EntityInterface $entity */ foreach ($maybeExistentQuery as $entity) { + assert($entity instanceof EntityInterface); $key = implode(';', $entity->extract($primary)); if (isset($indexed[$key])) { $output[] = $this->merge($entity, $indexed[$key], $options); diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index 26ff6ae0..15494182 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -19,6 +19,7 @@ use ArrayObject; use Cake\Collection\Iterator\MapReduce; use Cake\Database\ExpressionInterface; +use Cake\Database\Expression\QueryExpression; use Cake\Database\Query\SelectQuery as DbSelectQuery; use Cake\Database\TypedResultInterface; use Cake\Database\TypeMap; @@ -1417,8 +1418,8 @@ protected function _performCount(): int } if (!$complex && $this->_valueBinder !== null) { - /** @var \Cake\Database\Expression\QueryExpression|null $order */ $order = $this->clause('order'); + assert($order === null || $order instanceof QueryExpression); $complex = $order === null ? false : $order->hasNestedExpression(); } diff --git a/ResultSetFactory.php b/ResultSetFactory.php index 84e655ba..709f630b 100644 --- a/ResultSetFactory.php +++ b/ResultSetFactory.php @@ -128,11 +128,13 @@ protected function groupResult(array $row, array $data): EntityInterface|array array_intersect_key($row, $keys) ); if ($data['hydrate']) { - /** @var \Cake\ORM\Table $table */ $table = $matching['instance']; + assert($table instanceof Table || $table instanceof Association); + $options['source'] = $table->getRegistryAlias(); - /** @var \Cake\Datasource\EntityInterface $entity */ $entity = new $matching['entityClass']($results['_matchingData'][$alias], $options); + assert($entity instanceof EntityInterface); + $results['_matchingData'][$alias] = $entity; } } @@ -156,8 +158,8 @@ protected function groupResult(array $row, array $data): EntityInterface|array continue; } - /** @var \Cake\ORM\Association $instance */ $instance = $assoc['instance']; + assert($instance instanceof Association); if (!$assoc['canBeJoined'] && !isset($row[$alias])) { $results = $instance->defaultRowValue($results, $assoc['canBeJoined']); diff --git a/Table.php b/Table.php index bf70128b..124031e4 100644 --- a/Table.php +++ b/Table.php @@ -18,6 +18,7 @@ use ArrayObject; use BadMethodCallException; +use Cake\Collection\CollectionInterface; use Cake\Core\App; use Cake\Core\Configure; use Cake\Database\Connection; @@ -500,8 +501,8 @@ public function setConnection(Connection $connection) public function getConnection(): Connection { if (!$this->_connection) { - /** @var \Cake\Database\Connection $connection */ $connection = ConnectionManager::get(static::defaultConnectionName()); + assert($connection instanceof Connection); $this->_connection = $connection; } @@ -1358,8 +1359,7 @@ public function findList(SelectQuery $query, array $options): SelectQuery ['keyField', 'valueField', 'groupField'] ); - return $query->formatResults(fn($results) => - /** @var \Cake\Collection\CollectionInterface $results */ + return $query->formatResults(fn(CollectionInterface $results) => $results->combine( $options['keyField'], $options['valueField'], @@ -1401,8 +1401,7 @@ public function findThreaded(SelectQuery $query, array $options): SelectQuery $options = $this->_setFieldMatchers($options, ['keyField', 'parentField']); - return $query->formatResults(fn($results) => - /** @var \Cake\Collection\CollectionInterface $results */ + return $query->formatResults(fn(CollectionInterface $results) => $results->nest($options['keyField'], $options['parentField'], $options['nestingKey'])); } From c41e6e80af9b39c3874e46cf947fabac707cfac5 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 9 Oct 2022 23:40:41 -0400 Subject: [PATCH 1829/2059] Appease the linting tool gods. Humbly accept more metadata on what is going on as you're narrowing types a bit too much or not enough. --- Association/HasMany.php | 2 ++ Behavior/TranslateBehavior.php | 2 +- Query/SelectQuery.php | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index f2389cd7..432fa011 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -361,6 +361,8 @@ public function unlink(EntityInterface $sourceEntity, array $targetEntities, arr $conditions = [ 'OR' => (new Collection($targetEntities)) ->map(function (EntityInterface $entity) use ($targetPrimaryKey) { + /** @psalm-suppress InvalidArgument,UnusedPsalmSuppress */ + /** @var array $targetPrimaryKey */ return $entity->extract($targetPrimaryKey); }) ->toList(), diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 23a4fe7c..d1f9446a 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -16,8 +16,8 @@ */ namespace Cake\ORM\Behavior; -use Cake\I18n\I18n; use Cake\Datasource\QueryInterface; +use Cake\I18n\I18n; use Cake\ORM\Behavior; use Cake\ORM\Behavior\Translate\ShadowTableStrategy; use Cake\ORM\Behavior\Translate\TranslateStrategyInterface; diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index 15494182..2ec710e8 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -18,8 +18,8 @@ use ArrayObject; use Cake\Collection\Iterator\MapReduce; -use Cake\Database\ExpressionInterface; use Cake\Database\Expression\QueryExpression; +use Cake\Database\ExpressionInterface; use Cake\Database\Query\SelectQuery as DbSelectQuery; use Cake\Database\TypedResultInterface; use Cake\Database\TypeMap; From a2014115aa62375a501561027dd8bbc60b4ac27f Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 9 Oct 2022 14:59:07 -0400 Subject: [PATCH 1830/2059] Make exceptions into deprecations While using exceptions would give these changes high visibility, it also is not backwards compatible and forces an early upgrade. Using deprecationWarning() allows userland code to silence these errors until they can be addressed. Fix phpcs, psalm and tests. --- Exception/DeprecationException.php | 14 ----- Query.php | 9 ++- Query/DeleteQuery.php | 88 ++++++++++++++++++++++-------- Table.php | 6 +- 4 files changed, 73 insertions(+), 44 deletions(-) delete mode 100644 Exception/DeprecationException.php diff --git a/Exception/DeprecationException.php b/Exception/DeprecationException.php deleted file mode 100644 index d2c9a9f3..00000000 --- a/Exception/DeprecationException.php +++ /dev/null @@ -1,14 +0,0 @@ -_deprecatedException('delete()', 'Remove this method call.'); + $this->_deprecatedMethod('delete()', 'Remove this method call.'); + + return parent::delete($table); } /** @@ -58,7 +60,9 @@ public function delete(?string $table = null) */ public function cache($key, $config = 'default') { - $this->_deprecatedException('cache()', 'Use execute() instead.'); + $this->_deprecatedMethod('cache()', 'Use execute() instead.'); + + return parent::cache($key, $config); } /** @@ -66,7 +70,9 @@ public function cache($key, $config = 'default') */ public function all(): ResultSetInterface { - $this->_deprecatedException('all()', 'Use execute() instead.'); + $this->_deprecatedMethod('all()', 'Use execute() instead.'); + + return parent::all(); } /** @@ -74,7 +80,9 @@ public function all(): ResultSetInterface */ public function select($fields = [], bool $overwrite = false) { - $this->_deprecatedException('select()'); + $this->_deprecatedMethod('select()'); + + return parent::select($fields, $overwrite); } /** @@ -82,7 +90,9 @@ public function select($fields = [], bool $overwrite = false) */ public function distinct($on = [], $overwrite = false) { - $this->_deprecatedException('distinct()'); + $this->_deprecatedMethod('distinct()'); + + return parent::distinct($on, $overwrite); } /** @@ -90,7 +100,9 @@ public function distinct($on = [], $overwrite = false) */ public function modifier($modifiers, $overwrite = false) { - $this->_deprecatedException('modifier()'); + $this->_deprecatedMethod('modifier()'); + + return parent::modifier($modifiers, $overwrite); } /** @@ -98,7 +110,9 @@ public function modifier($modifiers, $overwrite = false) */ public function join($tables, $types = [], $overwrite = false) { - $this->_deprecatedException('join()'); + $this->_deprecatedMethod('join()'); + + return parent::join($tables, $types, $overwrite); } /** @@ -106,7 +120,9 @@ public function join($tables, $types = [], $overwrite = false) */ public function removeJoin(string $name) { - $this->_deprecatedException('removeJoin()'); + $this->_deprecatedMethod('removeJoin()'); + + return parent::removeJoin($name); } /** @@ -114,7 +130,9 @@ public function removeJoin(string $name) */ public function leftJoin($table, $conditions = [], $types = []) { - $this->_deprecatedException('leftJoin()'); + $this->_deprecatedMethod('leftJoin()'); + + return parent::leftJoin($table, $conditions, $types); } /** @@ -122,7 +140,9 @@ public function leftJoin($table, $conditions = [], $types = []) */ public function rightJoin($table, $conditions = [], $types = []) { - $this->_deprecatedException('rightJoin()'); + $this->_deprecatedMethod('rightJoin()'); + + return parent::rightJoin($table, $conditions, $types); } /** @@ -130,7 +150,9 @@ public function rightJoin($table, $conditions = [], $types = []) */ public function innerJoin($table, $conditions = [], $types = []) { - $this->_deprecatedException('innerJoin()'); + $this->_deprecatedMethod('innerJoin()'); + + return parent::innerJoin($table, $conditions, $types); } /** @@ -138,7 +160,9 @@ public function innerJoin($table, $conditions = [], $types = []) */ public function group($fields, $overwrite = false) { - $this->_deprecatedException('group()'); + $this->_deprecatedMethod('group()'); + + return parent::group($fields, $overwrite); } /** @@ -146,7 +170,9 @@ public function group($fields, $overwrite = false) */ public function having($conditions = null, $types = [], $overwrite = false) { - $this->_deprecatedException('having()'); + $this->_deprecatedMethod('having()'); + + return parent::having($conditions, $types, $overwrite); } /** @@ -154,7 +180,9 @@ public function having($conditions = null, $types = [], $overwrite = false) */ public function andHaving($conditions, $types = []) { - $this->_deprecatedException('andHaving()'); + $this->_deprecatedMethod('andHaving()'); + + return parent::andHaving($conditions, $types); } /** @@ -162,7 +190,9 @@ public function andHaving($conditions, $types = []) */ public function page(int $num, ?int $limit = null) { - $this->_deprecatedException('page()'); + $this->_deprecatedMethod('page()'); + + return parent::page($num, $limit); } /** @@ -170,7 +200,9 @@ public function page(int $num, ?int $limit = null) */ public function union($query, $overwrite = false) { - $this->_deprecatedException('union()'); + $this->_deprecatedMethod('union()'); + + return parent::union($query, $overwrite); } /** @@ -178,7 +210,9 @@ public function union($query, $overwrite = false) */ public function unionAll($query, $overwrite = false) { - $this->_deprecatedException('union()'); + $this->_deprecatedMethod('union()'); + + return parent::unionAll($query, $overwrite); } /** @@ -186,7 +220,9 @@ public function unionAll($query, $overwrite = false) */ public function insert(array $columns, array $types = []) { - $this->_deprecatedException('insert()'); + $this->_deprecatedMethod('insert()'); + + return parent::insert($columns, $types); } /** @@ -194,7 +230,9 @@ public function insert(array $columns, array $types = []) */ public function into(string $table) { - $this->_deprecatedException('into()', 'Use from() instead.'); + $this->_deprecatedMethod('into()', 'Use from() instead.'); + + return parent::into($table); } /** @@ -202,7 +240,9 @@ public function into(string $table) */ public function values($data) { - $this->_deprecatedException('values()'); + $this->_deprecatedMethod('values()'); + + return parent::values($data); } /** @@ -210,7 +250,9 @@ public function values($data) */ public function update($table = null) { - $this->_deprecatedException('update()'); + $this->_deprecatedMethod('update()'); + + return parent::update($table); } /** @@ -218,6 +260,8 @@ public function update($table = null) */ public function set($key, $value = null, $types = []) { - $this->_deprecatedException('set()'); + $this->_deprecatedMethod('set()'); + + return parent::set($key, $value, $types); } } diff --git a/Table.php b/Table.php index c53afb95..ccfdffe3 100644 --- a/Table.php +++ b/Table.php @@ -39,8 +39,8 @@ use Cake\ORM\Exception\MissingEntityException; use Cake\ORM\Exception\PersistenceFailedException; use Cake\ORM\Exception\RolledbackTransactionException; -use Cake\ORM\Rule\IsUnique; use Cake\ORM\Query\DeleteQuery; +use Cake\ORM\Rule\IsUnique; use Cake\Utility\Inflector; use Cake\Validation\ValidatorAwareInterface; use Cake\Validation\ValidatorAwareTrait; @@ -1720,9 +1720,9 @@ public function query(): Query /** * Creates a new DeleteQuery instance for a table. * - * @return \Cake\ORM\DeleteQuery + * @return \Cake\ORM\Query\DeleteQuery */ - public function deleteQuery(): Query + public function deleteQuery(): DeleteQuery { return new DeleteQuery($this->getConnection(), $this); } From 81ec716c43caf6b4a40a0b4170d392fbdd40aadc Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 11 Oct 2022 00:45:19 +0530 Subject: [PATCH 1831/2059] Improve type annotations. --- BehaviorRegistry.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 0fe2dcf7..6ed51647 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -134,7 +134,7 @@ protected function _throwMissingClassError(string $class, ?string $plugin): void * Part of the template method for Cake\Core\ObjectRegistry::load() * Enabled behaviors will be registered with the event manager. * - * @param \Cake\ORM\Behavior|string $class The classname that is missing. + * @param \Cake\ORM\Behavior|class-string<\Cake\ORM\Behavior> $class The classname that is missing. * @param string $alias The alias of the object. * @param array $config An array of config to use for the behavior. * @return \Cake\ORM\Behavior The constructed behavior class. @@ -145,7 +145,6 @@ protected function _create(object|string $class, string $alias, array $config): return $class; } - /** @var \Cake\ORM\Behavior $instance */ $instance = new $class($this->_table, $config); $enable = $config['enabled'] ?? true; if ($enable) { From 181d84b4b0591fe5562d355e8f1cd289e7b9f3d6 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 10 Oct 2022 11:41:27 -0400 Subject: [PATCH 1832/2059] Reduce assert() usage We don't need to assert() types when we'll also check types on return values. Coercing filesystem iterators helps reduce overhead of type checks as well. --- BehaviorRegistry.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 19769c5a..a819c353 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -145,8 +145,8 @@ protected function _create(object|string $class, string $alias, array $config): return $class; } + /** @var \Cake\ORM\Behavior $instance */ $instance = new $class($this->_table, $config); - assert($instance instanceof Behavior); $enable = $config['enabled'] ?? true; if ($enable) { From 19a5d7747a05f54d27f93f2257cd7ae2717a4d14 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 10 Oct 2022 22:41:29 -0400 Subject: [PATCH 1833/2059] Add methods I missed earlier. --- Query/DeleteQuery.php | 96 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 93 insertions(+), 3 deletions(-) diff --git a/Query/DeleteQuery.php b/Query/DeleteQuery.php index 7af1440a..9f9a7a6b 100644 --- a/Query/DeleteQuery.php +++ b/Query/DeleteQuery.php @@ -80,7 +80,7 @@ public function all(): ResultSetInterface */ public function select($fields = [], bool $overwrite = false) { - $this->_deprecatedMethod('select()'); + $this->_deprecatedMethod('select()', 'Create your query with selectQuery() instead.'); return parent::select($fields, $overwrite); } @@ -145,6 +145,26 @@ public function rightJoin($table, $conditions = [], $types = []) return parent::rightJoin($table, $conditions, $types); } + /** + * @inheritDoc + */ + public function leftJoinWith(string $assoc, ?callable $builder = null) + { + $this->_deprecatedMethod('leftJoinWith()'); + + return parent::leftJoinWith($assoc, $builder); + } + + /** + * @inheritDoc + */ + public function rightJoinWith(string $assoc, ?callable $builder = null) + { + $this->_deprecatedMethod('rightJoinWith()'); + + return parent::rightJoinWith($assoc, $builder); + } + /** * @inheritDoc */ @@ -155,6 +175,16 @@ public function innerJoin($table, $conditions = [], $types = []) return parent::innerJoin($table, $conditions, $types); } + /** + * @inheritDoc + */ + public function innerJoinWith(string $assoc, ?callable $builder = null) + { + $this->_deprecatedMethod('innerJoinWith()'); + + return parent::innerJoinWith($assoc, $builder); + } + /** * @inheritDoc */ @@ -220,7 +250,7 @@ public function unionAll($query, $overwrite = false) */ public function insert(array $columns, array $types = []) { - $this->_deprecatedMethod('insert()'); + $this->_deprecatedMethod('insert()', 'Create your query with insertQuery() instead.'); return parent::insert($columns, $types); } @@ -250,7 +280,7 @@ public function values($data) */ public function update($table = null) { - $this->_deprecatedMethod('update()'); + $this->_deprecatedMethod('update()', 'Create your query with updateQuery() instead.'); return parent::update($table); } @@ -264,4 +294,64 @@ public function set($key, $value = null, $types = []) return parent::set($key, $value, $types); } + + /** + * @inheritDoc + */ + public function matching(string $assoc, ?callable $builder = null) + { + $this->_deprecatedMethod('matching()'); + + return parent::matching($assoc, $builder); + } + + /** + * @inheritDoc + */ + public function notMatching(string $assoc, ?callable $builder = null) + { + $this->_deprecatedMethod('notMatching()'); + + return parent::notMatching($assoc, $builder); + } + + /** + * @inheritDoc + */ + public function contain($associations, $override = false) + { + $this->_deprecatedMethod('contain()'); + + return parent::contain($associations, $override); + } + + /** + * @inheritDoc + */ + public function getContain(): array + { + $this->_deprecatedMethod('getContain()'); + + return parent::getContain(); + } + + /** + * @inheritDoc + */ + public function cleanContain() + { + $this->_deprecatedMethod('cleanContain()'); + + return parent::cleanContain(); + } + + /** + * @inheritDoc + */ + public function find(string $finder, array $options = []) + { + $this->_deprecatedMethod('find()'); + + return parent::find($finder, $options); + } } From 24b14af942bd18b55aa950b69eab0055e779699e Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 10 Oct 2022 23:11:03 -0400 Subject: [PATCH 1834/2059] 4.5 - Add InsertQuery Add InsertQuery with deprecations for all the methods that will not be on ORM\InsertQuery in 5.x --- Query/InsertQuery.php | 433 ++++++++++++++++++++++++++++++++++++++++++ Table.php | 14 +- 2 files changed, 446 insertions(+), 1 deletion(-) create mode 100644 Query/InsertQuery.php diff --git a/Query/InsertQuery.php b/Query/InsertQuery.php new file mode 100644 index 00000000..90fa1425 --- /dev/null +++ b/Query/InsertQuery.php @@ -0,0 +1,433 @@ +_deprecatedMethod('delete()', 'Create your query with deleteQuery() instead.'); + + return parent::delete($table); + } + + /** + * @inheritDoc + */ + public function cache($key, $config = 'default') + { + $this->_deprecatedMethod('cache()', 'Use execute() instead.'); + + return parent::cache($key, $config); + } + + /** + * @inheritDoc + */ + public function all(): ResultSetInterface + { + $this->_deprecatedMethod('all()', 'Use execute() instead.'); + + return parent::all(); + } + + /** + * @inheritDoc + */ + public function select($fields = [], bool $overwrite = false) + { + $this->_deprecatedMethod('select()', 'Create your query with selectQuery() instead.'); + + return parent::select($fields, $overwrite); + } + + /** + * @inheritDoc + */ + public function distinct($on = [], $overwrite = false) + { + $this->_deprecatedMethod('distinct()'); + + return parent::distinct($on, $overwrite); + } + + /** + * @inheritDoc + */ + public function modifier($modifiers, $overwrite = false) + { + $this->_deprecatedMethod('modifier()'); + + return parent::modifier($modifiers, $overwrite); + } + + /** + * @inheritDoc + */ + public function join($tables, $types = [], $overwrite = false) + { + $this->_deprecatedMethod('join()'); + + return parent::join($tables, $types, $overwrite); + } + + /** + * @inheritDoc + */ + public function removeJoin(string $name) + { + $this->_deprecatedMethod('removeJoin()'); + + return parent::removeJoin($name); + } + + /** + * @inheritDoc + */ + public function leftJoin($table, $conditions = [], $types = []) + { + $this->_deprecatedMethod('leftJoin()'); + + return parent::leftJoin($table, $conditions, $types); + } + + /** + * @inheritDoc + */ + public function rightJoin($table, $conditions = [], $types = []) + { + $this->_deprecatedMethod('rightJoin()'); + + return parent::rightJoin($table, $conditions, $types); + } + + /** + * @inheritDoc + */ + public function leftJoinWith(string $assoc, ?callable $builder = null) + { + $this->_deprecatedMethod('leftJoinWith()'); + + return parent::leftJoinWith($assoc, $builder); + } + + /** + * @inheritDoc + */ + public function rightJoinWith(string $assoc, ?callable $builder = null) + { + $this->_deprecatedMethod('rightJoinWith()'); + + return parent::rightJoinWith($assoc, $builder); + } + + /** + * @inheritDoc + */ + public function innerJoin($table, $conditions = [], $types = []) + { + $this->_deprecatedMethod('innerJoin()'); + + return parent::innerJoin($table, $conditions, $types); + } + + /** + * @inheritDoc + */ + public function innerJoinWith(string $assoc, ?callable $builder = null) + { + $this->_deprecatedMethod('innerJoinWith()'); + + return parent::innerJoinWith($assoc, $builder); + } + + /** + * @inheritDoc + */ + public function group($fields, $overwrite = false) + { + $this->_deprecatedMethod('group()'); + + return parent::group($fields, $overwrite); + } + + /** + * @inheritDoc + */ + public function having($conditions = null, $types = [], $overwrite = false) + { + $this->_deprecatedMethod('having()'); + + return parent::having($conditions, $types, $overwrite); + } + + /** + * @inheritDoc + */ + public function andHaving($conditions, $types = []) + { + $this->_deprecatedMethod('andHaving()'); + + return parent::andHaving($conditions, $types); + } + + /** + * @inheritDoc + */ + public function page(int $num, ?int $limit = null) + { + $this->_deprecatedMethod('page()'); + + return parent::page($num, $limit); + } + + /** + * @inheritDoc + */ + public function offset($offset) + { + $this->_deprecatedMethod('offset()'); + + return parent::offset($offset); + } + + /** + * @inheritDoc + */ + public function union($query, $overwrite = false) + { + $this->_deprecatedMethod('union()'); + + return parent::union($query, $overwrite); + } + + /** + * @inheritDoc + */ + public function unionAll($query, $overwrite = false) + { + $this->_deprecatedMethod('union()'); + + return parent::unionAll($query, $overwrite); + } + + /** + * @inheritDoc + */ + public function from($tables = [], $overwrite = false) + { + $this->_deprecatedMethod('from()', 'Use into() instead.'); + + return parent::from($tables, $overwrite); + } + + /** + * @inheritDoc + */ + public function update($table = null) + { + $this->_deprecatedMethod('update()', 'Create your query with updateQuery() instead.'); + + return parent::update($table); + } + + /** + * @inheritDoc + */ + public function set($key, $value = null, $types = []) + { + $this->_deprecatedMethod('set()'); + + return parent::set($key, $value, $types); + } + + /** + * @inheritDoc + */ + public function matching(string $assoc, ?callable $builder = null) + { + $this->_deprecatedMethod('matching()'); + + return parent::matching($assoc, $builder); + } + + /** + * @inheritDoc + */ + public function notMatching(string $assoc, ?callable $builder = null) + { + $this->_deprecatedMethod('notMatching()'); + + return parent::notMatching($assoc, $builder); + } + + /** + * @inheritDoc + */ + public function contain($associations, $override = false) + { + $this->_deprecatedMethod('contain()'); + + return parent::contain($associations, $override); + } + + /** + * @inheritDoc + */ + public function getContain(): array + { + $this->_deprecatedMethod('getContain()'); + + return parent::getContain(); + } + + /** + * @inheritDoc + */ + public function cleanContain() + { + $this->_deprecatedMethod('cleanContain()'); + + return parent::cleanContain(); + } + + /** + * @inheritDoc + */ + public function find(string $finder, array $options = []) + { + $this->_deprecatedMethod('find()'); + + return parent::find($finder, $options); + } + + /** + * @inheritDoc + */ + public function where($conditions = null, array $types = [], bool $overwrite = false) + { + $this->_deprecatedMethod('where()'); + + return parent::where($conditions, $types, $overwrite); + } + + /** + * @inheritDoc + */ + public function whereNotNull($fields) + { + $this->_deprecatedMethod('whereNotNull()'); + + return parent::whereNotNull($fields); + } + + /** + * @inheritDoc + */ + public function whereNull($fields) + { + $this->_deprecatedMethod('whereNull()'); + + return parent::whereNull($fields); + } + + /** + * @inheritDoc + */ + public function whereInList(string $field, array $values, array $options = []) + { + $this->_deprecatedMethod('whereInList()'); + + return parent::whereInList($field, $values, $options); + } + + /** + * @inheritDoc + */ + public function whereNotInList(string $field, array $values, array $options = []) + { + $this->_deprecatedMethod('whereNotInList()'); + + return parent::whereNotInList($field, $values, $options); + } + + /** + * @inheritDoc + */ + public function whereNotInListOrNull(string $field, array $values, array $options = []) + { + $this->_deprecatedMethod('whereNotInListOrNull()'); + + return parent::whereNotInListOrNull($field, $values, $options); + } + + /** + * @inheritDoc + */ + public function andWhere($conditions, array $types = []) + { + $this->_deprecatedMethod('andWhere()'); + + return parent::andWhere($conditions, $types); + } + + /** + * @inheritDoc + */ + public function order($fields, $overwrite = false) + { + $this->_deprecatedMethod('order()'); + + return parent::order($fields, $overwrite); + } + + /** + * @inheritDoc + */ + public function orderAsc($field, $overwrite = false) + { + $this->_deprecatedMethod('orderAsc()'); + + return parent::orderAsc($field, $overwrite); + } + + /** + * @inheritDoc + */ + public function orderDesc($field, $overwrite = false) + { + $this->_deprecatedMethod('orderDesc()'); + + return parent::orderDesc($field, $overwrite); + } +} diff --git a/Table.php b/Table.php index ccfdffe3..cbdf3529 100644 --- a/Table.php +++ b/Table.php @@ -40,6 +40,7 @@ use Cake\ORM\Exception\PersistenceFailedException; use Cake\ORM\Exception\RolledbackTransactionException; use Cake\ORM\Query\DeleteQuery; +use Cake\ORM\Query\InsertQuery; use Cake\ORM\Rule\IsUnique; use Cake\Utility\Inflector; use Cake\Validation\ValidatorAwareInterface; @@ -1717,6 +1718,16 @@ public function query(): Query return new Query($this->getConnection(), $this); } + /** + * Creates a new InsertQuery instance for a table. + * + * @return \Cake\ORM\Query\InsertQuery + */ + public function insertQuery(): InsertQuery + { + return new InsertQuery($this->getConnection(), $this); + } + /** * Creates a new DeleteQuery instance for a table. * @@ -2098,7 +2109,8 @@ protected function _insert(EntityInterface $entity, array $data) return false; } - $statement = $this->query()->insert(array_keys($data)) + $statement = $this->insertQuery() + ->insert(array_keys($data)) ->values($data) ->execute(); From f9972ebfce226e71a171f9eb510a491ec18aab86 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 11 Oct 2022 10:02:28 -0400 Subject: [PATCH 1835/2059] Remove methods causing phpstan/psalm errors --- Query/DeleteQuery.php | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/Query/DeleteQuery.php b/Query/DeleteQuery.php index 9f9a7a6b..74a853ba 100644 --- a/Query/DeleteQuery.php +++ b/Query/DeleteQuery.php @@ -155,16 +155,6 @@ public function leftJoinWith(string $assoc, ?callable $builder = null) return parent::leftJoinWith($assoc, $builder); } - /** - * @inheritDoc - */ - public function rightJoinWith(string $assoc, ?callable $builder = null) - { - $this->_deprecatedMethod('rightJoinWith()'); - - return parent::rightJoinWith($assoc, $builder); - } - /** * @inheritDoc */ @@ -335,16 +325,6 @@ public function getContain(): array return parent::getContain(); } - /** - * @inheritDoc - */ - public function cleanContain() - { - $this->_deprecatedMethod('cleanContain()'); - - return parent::cleanContain(); - } - /** * @inheritDoc */ From 2e87703e8b01b15bca99b8b37da190f6c800d740 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 11 Oct 2022 02:09:41 +0530 Subject: [PATCH 1836/2059] Update phpstan config --- Behavior/CounterCacheBehavior.php | 6 +++--- Behavior/Translate/EavStrategy.php | 4 ++-- Behavior/Translate/ShadowTableStrategy.php | 6 +++--- Behavior/Translate/TranslateStrategyInterface.php | 4 ++-- ResultSet.php | 2 +- Table.php | 6 +++--- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 825d84f2..e78b6162 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -117,7 +117,7 @@ class CounterCacheBehavior extends Behavior * * @param \Cake\Event\EventInterface $event The beforeSave event that was fired * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved - * @param \ArrayObject $options The options for the query + * @param \ArrayObject $options The options for the query * @return void */ public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void @@ -155,7 +155,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array * * @param \Cake\Event\EventInterface $event The afterSave event that was fired. * @param \Cake\Datasource\EntityInterface $entity The entity that was saved. - * @param \ArrayObject $options The options for the query + * @param \ArrayObject $options The options for the query * @return void */ public function afterSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void @@ -175,7 +175,7 @@ public function afterSave(EventInterface $event, EntityInterface $entity, ArrayO * * @param \Cake\Event\EventInterface $event The afterDelete event that was fired. * @param \Cake\Datasource\EntityInterface $entity The entity that was deleted. - * @param \ArrayObject $options The options for the query + * @param \ArrayObject $options The options for the query * @return void */ public function afterDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options): void diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index 9d228e48..cc572f70 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -162,7 +162,7 @@ protected function setupAssociations(): void * * @param \Cake\Event\EventInterface $event The beforeFind event that was fired. * @param \Cake\ORM\Query\SelectQuery $query Query - * @param \ArrayObject $options The options for the query + * @param \ArrayObject $options The options for the query * @return void */ public function beforeFind(EventInterface $event, SelectQuery $query, ArrayObject $options): void @@ -226,7 +226,7 @@ public function beforeFind(EventInterface $event, SelectQuery $query, ArrayObjec * * @param \Cake\Event\EventInterface $event The beforeSave event that was fired * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved - * @param \ArrayObject $options the options passed to the save method + * @param \ArrayObject $options the options passed to the save method * @return void */ public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 539a821a..67a0d4da 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -120,7 +120,7 @@ protected function setupAssociations(): void * * @param \Cake\Event\EventInterface $event The beforeFind event that was fired. * @param \Cake\ORM\Query\SelectQuery $query Query. - * @param \ArrayObject $options The options for the query. + * @param \ArrayObject $options The options for the query. * @return void */ public function beforeFind(EventInterface $event, SelectQuery $query, ArrayObject $options): void @@ -154,7 +154,7 @@ public function beforeFind(EventInterface $event, SelectQuery $query, ArrayObjec * Create a hasOne association for record with required locale. * * @param string $locale Locale - * @param \ArrayObject $options Find options + * @param \ArrayObject $options Find options * @return void */ protected function setupHasOneAssociation(string $locale, ArrayObject $options): void @@ -338,7 +338,7 @@ function ($expression) use ($fields, $alias, $mainTableAlias, $mainTableFields, * * @param \Cake\Event\EventInterface $event The beforeSave event that was fired. * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved. - * @param \ArrayObject $options the options passed to the save method. + * @param \ArrayObject $options the options passed to the save method. * @return void */ public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void diff --git a/Behavior/Translate/TranslateStrategyInterface.php b/Behavior/Translate/TranslateStrategyInterface.php index 90bc4ad3..550706bd 100644 --- a/Behavior/Translate/TranslateStrategyInterface.php +++ b/Behavior/Translate/TranslateStrategyInterface.php @@ -92,7 +92,7 @@ public function groupTranslations(ResultSetInterface $results): CollectionInterf * * @param \Cake\Event\EventInterface $event The beforeFind event that was fired. * @param \Cake\ORM\Query\SelectQuery $query Query - * @param \ArrayObject $options The options for the query + * @param \ArrayObject $options The options for the query * @return void */ public function beforeFind(EventInterface $event, SelectQuery $query, ArrayObject $options): void; @@ -103,7 +103,7 @@ public function beforeFind(EventInterface $event, SelectQuery $query, ArrayObjec * * @param \Cake\Event\EventInterface $event The beforeSave event that was fired * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved - * @param \ArrayObject $options the options passed to the save method + * @param \ArrayObject $options the options passed to the save method * @return void */ public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void; diff --git a/ResultSet.php b/ResultSet.php index c084bcea..dc047a94 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -52,7 +52,7 @@ class ResultSet implements ResultSetInterface /** * Results that have been fetched or hydrated into the results. * - * @var \SplFixedArray + * @var \SplFixedArray<\Cake\Datasource\EntityInterface|array> */ protected SplFixedArray $_results; diff --git a/Table.php b/Table.php index 124031e4..7b80a280 100644 --- a/Table.php +++ b/Table.php @@ -1941,7 +1941,7 @@ public function saveOrFail(EntityInterface $entity, array $options = []): Entity * Performs the actual saving of an entity based on the passed options. * * @param \Cake\Datasource\EntityInterface $entity the entity to be saved - * @param \ArrayObject $options the options to use for the save operation + * @param \ArrayObject $options the options to use for the save operation * @return \Cake\Datasource\EntityInterface|false * @throws \Cake\Database\Exception\DatabaseException When an entity is missing some of the primary keys. * @throws \Cake\ORM\Exception\RolledbackTransactionException If the transaction @@ -2024,7 +2024,7 @@ protected function _processSave(EntityInterface $entity, ArrayObject $options): * once the entity for this table has been saved successfully. * * @param \Cake\Datasource\EntityInterface $entity the entity to be saved - * @param \ArrayObject $options the options to use for the save operation + * @param \ArrayObject $options the options to use for the save operation * @return bool True on success * @throws \Cake\ORM\Exception\RolledbackTransactionException If the transaction * is aborted in the afterSave event. @@ -2486,7 +2486,7 @@ public function deleteOrFail(EntityInterface $entity, array $options = []): bool * dependent associations, and clear out join tables for BelongsToMany associations. * * @param \Cake\Datasource\EntityInterface $entity The entity to delete. - * @param \ArrayObject $options The options for the delete. + * @param \ArrayObject $options The options for the delete. * @throws \InvalidArgumentException if there are no primary key values of the * passed entity * @return bool success From 1180884792eb6a0ac7fc893b67ba21ce7946e0f6 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 13 Oct 2022 12:16:51 +0530 Subject: [PATCH 1837/2059] Improve type declaration/detection. Removed assert() calls just before return a value. It provides no practical benifit other than throwing an AssertionError instead of TypeError when the method already has a typed return. --- Behavior/Translate/EavStrategy.php | 3 +-- Behavior/Translate/ShadowTableStrategy.php | 3 +-- Behavior/TreeBehavior.php | 10 ++++++---- LazyEagerLoader.php | 2 +- Marshaller.php | 17 ++++++++++------- 5 files changed, 19 insertions(+), 16 deletions(-) diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index cc572f70..4d0f3493 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -285,6 +285,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array $preexistent = []; if ($key) { + /** @var \Traversable $preexistent */ $preexistent = $this->translationTable->find() ->select(['id', 'field']) ->where([ @@ -299,8 +300,6 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array $modified = []; foreach ($preexistent as $field => $translation) { - assert($translation instanceof EntityInterface); - $translation->set('content', $values[$field]); $modified[$field] = $translation; } diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 67a0d4da..233df093 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -249,10 +249,10 @@ protected function addFieldsToQuery(SelectQuery $query, array $config): bool protected function iterateClause(SelectQuery $query, string $name = '', array $config = []): bool { $clause = $query->clause($name); + assert($clause === null || $clause instanceof QueryExpression); if (!$clause || !$clause->count()) { return false; } - assert($clause instanceof QueryExpression); $alias = $config['hasOneAlias']; $fields = $this->translatedFields(); @@ -415,7 +415,6 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array ] ); } - assert($translation instanceof EntityInterface); $entity->set('_i18n', array_merge($bundled, [$translation])); $entity->set('_locale', $locale, ['setter' => false]); diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 0606f912..9720dd47 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -196,14 +196,15 @@ protected function _setChildrenLevel(EntityInterface $entity): void $primaryKeyValue = $entity->get($primaryKey); $depths = [$primaryKeyValue => $entity->get($config['level'])]; + /** @var \Traversable<\Cake\Datasource\EntityInterface> $children */ $children = $this->_table->find('children', [ 'for' => $primaryKeyValue, 'fields' => [$this->_getPrimaryKey(), $config['parent'], $config['level']], 'order' => $config['left'], - ]); + ]) + ->all(); foreach ($children as $node) { - assert($node instanceof EntityInterface); $parentIdValue = $node->get($config['parent']); $depth = $depths[$parentIdValue] + 1; $depths[$node->get($primaryKey)] = $depth; @@ -733,10 +734,12 @@ protected function _moveDown(EntityInterface $node, int|bool $number): EntityInt { $config = $this->getConfig(); [$parent, $left, $right] = [$config['parent'], $config['left'], $config['right']]; + assert(is_string($parent) && is_string($left) && is_string($right)); [$nodeParent, $nodeLeft, $nodeRight] = array_values($node->extract([$parent, $left, $right])); $targetNode = null; if ($number !== true) { + /** @var \Cake\Datasource\EntityInterface|null $targetNode */ $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) ->where(["$parent IS" => $nodeParent]) @@ -747,6 +750,7 @@ protected function _moveDown(EntityInterface $node, int|bool $number): EntityInt ->first(); } if (!$targetNode) { + /** @var \Cake\Datasource\EntityInterface|null $targetNode */ $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) ->where(["$parent IS" => $nodeParent]) @@ -759,7 +763,6 @@ protected function _moveDown(EntityInterface $node, int|bool $number): EntityInt return $node; } } - assert($targetNode instanceof EntityInterface); [, $targetRight] = array_values($targetNode->extract([$left, $right])); $edge = $this->_getMax(); @@ -773,7 +776,6 @@ protected function _moveDown(EntityInterface $node, int|bool $number): EntityInt $this->_sync($shift, '-', "BETWEEN {$leftBoundary} AND {$rightBoundary}"); $this->_sync($nodeToHole, '-', "> {$edge}"); - assert(is_string($left) && is_string($right)); $node->set($left, $targetRight - ($nodeRight - $nodeLeft)); $node->set($right, $targetRight); diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 39d7fd2e..2680e91e 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -146,6 +146,7 @@ protected function _injectResults( $injected = []; $properties = $this->_getPropertyMap($source, $associations); $primaryKey = (array)$source->getPrimaryKey(); + /** @var array $results */ $results = $results ->all() ->indexBy(fn(EntityInterface $e) => implode(';', $e->extract($primaryKey))) @@ -159,7 +160,6 @@ protected function _injectResults( } $loaded = $results[$key]; - assert($loaded instanceof EntityInterface); foreach ($associations as $assoc) { $property = $properties[$assoc]; $object->set($property, $loaded->get($property), ['useSetters' => false]); diff --git a/Marshaller.php b/Marshaller.php index 73873f4e..6dd27ca3 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -311,7 +311,8 @@ protected function _marshalAssociation(Association $assoc, mixed $value, array $ } } if ($type === Association::MANY_TO_MANY) { - /** @psalm-suppress ArgumentTypeCoercion */ + assert($assoc instanceof BelongsToMany); + return $marshaller->_belongsToMany($assoc, $value, $options); } @@ -402,14 +403,15 @@ protected function _belongsToMany(BelongsToMany $assoc, array $data, array $opti } if (!empty($conditions)) { - $query = $target->find(); - $query->andWhere(fn(QueryExpression $exp) => $exp->or($conditions)); + /** @var \Traversable<\Cake\Datasource\EntityInterface> $results */ + $results = $target->find() + ->andWhere(fn(QueryExpression $exp) => $exp->or($conditions)) + ->all(); $keyFields = array_keys($primaryKey); $existing = []; - foreach ($query as $row) { - assert($row instanceof EntityInterface); + foreach ($results as $row) { $k = implode(';', $row->extract($keyFields)); $existing[$k] = $row; } @@ -689,8 +691,9 @@ public function mergeMany(iterable $entities, array $data, array $options = []): $maybeExistentQuery = $this->_table->find()->where($conditions); if (!empty($indexed) && count($maybeExistentQuery->clause('where'))) { - foreach ($maybeExistentQuery as $entity) { - assert($entity instanceof EntityInterface); + /** @var \Traversable<\Cake\Datasource\EntityInterface> $existent */ + $existent = $maybeExistentQuery->all(); + foreach ($existent as $entity) { $key = implode(';', $entity->extract($primary)); if (isset($indexed[$key])) { $output[] = $this->merge($entity, $indexed[$key], $options); From 5eb6113f4aa36cc5dc60a6cf0dcb08b0db453c36 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 14 Oct 2022 09:56:36 -0400 Subject: [PATCH 1838/2059] Add test coverage for InsertQuery --- Query/InsertQuery.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Query/InsertQuery.php b/Query/InsertQuery.php index 90fa1425..e7024fa8 100644 --- a/Query/InsertQuery.php +++ b/Query/InsertQuery.php @@ -41,6 +41,16 @@ public function delete(?string $table = null) return parent::delete($table); } + /** + * @inheritDoc + */ + public function into(string $table) + { + $this->_deprecatedMethod('into()', 'Remove this method call'); + + return parent::into($table); + } + /** * @inheritDoc */ From 9601095627a6e4326ed7efd66cb268ba27400952 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 14 Oct 2022 13:43:51 -0400 Subject: [PATCH 1839/2059] Correct test. --- Query/InsertQuery.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Query/InsertQuery.php b/Query/InsertQuery.php index e7024fa8..90fa1425 100644 --- a/Query/InsertQuery.php +++ b/Query/InsertQuery.php @@ -41,16 +41,6 @@ public function delete(?string $table = null) return parent::delete($table); } - /** - * @inheritDoc - */ - public function into(string $table) - { - $this->_deprecatedMethod('into()', 'Remove this method call'); - - return parent::into($table); - } - /** * @inheritDoc */ From f4db142d117cbe80214ab31209c58c04d1fc9ed0 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 14 Oct 2022 20:39:08 -0400 Subject: [PATCH 1840/2059] Remove methods that aren't defined in 4.next These methods are 5.x only. --- Query/InsertQuery.php | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/Query/InsertQuery.php b/Query/InsertQuery.php index 90fa1425..6c3462f9 100644 --- a/Query/InsertQuery.php +++ b/Query/InsertQuery.php @@ -141,16 +141,6 @@ public function leftJoinWith(string $assoc, ?callable $builder = null) return parent::leftJoinWith($assoc, $builder); } - /** - * @inheritDoc - */ - public function rightJoinWith(string $assoc, ?callable $builder = null) - { - $this->_deprecatedMethod('rightJoinWith()'); - - return parent::rightJoinWith($assoc, $builder); - } - /** * @inheritDoc */ @@ -311,16 +301,6 @@ public function getContain(): array return parent::getContain(); } - /** - * @inheritDoc - */ - public function cleanContain() - { - $this->_deprecatedMethod('cleanContain()'); - - return parent::cleanContain(); - } - /** * @inheritDoc */ From ee7ff15664058aaa599ad006b220a1e93ba16004 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 15 Oct 2022 12:38:15 -0400 Subject: [PATCH 1841/2059] 4.5 - Add UpdateQuery * Add UpdateQuery forwards compatibility shim. * Improve DeleteQuery to better match the behavior in 5.x * Add test coverage for matching() --- Query/DeleteQuery.php | 18 +-- Query/UpdateQuery.php | 267 ++++++++++++++++++++++++++++++++++++++++++ Table.php | 25 ++-- 3 files changed, 293 insertions(+), 17 deletions(-) create mode 100644 Query/UpdateQuery.php diff --git a/Query/DeleteQuery.php b/Query/DeleteQuery.php index 74a853ba..40470210 100644 --- a/Query/DeleteQuery.php +++ b/Query/DeleteQuery.php @@ -16,10 +16,9 @@ */ namespace Cake\ORM\Query; -use Cake\Database\Connection; +use Cake\Database\ValueBinder; use Cake\Datasource\ResultSetInterface; use Cake\ORM\Query; -use Cake\ORM\Table; /** * Delete Query forward compatibility shim. @@ -34,15 +33,16 @@ class DeleteQuery extends Query protected $_type = 'delete'; /** - * Constructor - * - * @param \Cake\Database\Connection $connection The connection object - * @param \Cake\ORM\Table $table The table this query is starting on + * @inheritDoc */ - public function __construct(Connection $connection, Table $table) + public function sql(?ValueBinder $binder = null): string { - parent::__construct($connection, $table); - $this->from([$table->getAlias() => $table->getTable()]); + if ($this->_type === 'delete' && empty($this->_parts['from'])) { + $repository = $this->getRepository(); + $this->from([$repository->getAlias() => $repository->getTable()]); + } + + return parent::sql($binder); } /** diff --git a/Query/UpdateQuery.php b/Query/UpdateQuery.php new file mode 100644 index 00000000..ff25a89d --- /dev/null +++ b/Query/UpdateQuery.php @@ -0,0 +1,267 @@ +_type === 'update' && empty($this->_parts['update'])) { + $repository = $this->getRepository(); + $this->update($repository->getTable()); + } + + return parent::sql($binder); + } + + /** + * @inheritDoc + */ + public function delete(?string $table = null) + { + $this->_deprecatedMethod('delete()', 'Create your query with deleteQuery() instead.'); + + return parent::delete($table); + } + + /** + * @inheritDoc + */ + public function cache($key, $config = 'default') + { + $this->_deprecatedMethod('cache()', 'Use execute() instead.'); + + return parent::cache($key, $config); + } + + /** + * @inheritDoc + */ + public function all(): ResultSetInterface + { + $this->_deprecatedMethod('all()', 'Use execute() instead.'); + + return parent::all(); + } + + /** + * @inheritDoc + */ + public function select($fields = [], bool $overwrite = false) + { + $this->_deprecatedMethod('select()', 'Create your query with selectQuery() instead.'); + + return parent::select($fields, $overwrite); + } + + /** + * @inheritDoc + */ + public function distinct($on = [], $overwrite = false) + { + $this->_deprecatedMethod('distinct()'); + + return parent::distinct($on, $overwrite); + } + + /** + * @inheritDoc + */ + public function modifier($modifiers, $overwrite = false) + { + $this->_deprecatedMethod('modifier()'); + + return parent::modifier($modifiers, $overwrite); + } + + /** + * @inheritDoc + */ + public function group($fields, $overwrite = false) + { + $this->_deprecatedMethod('group()'); + + return parent::group($fields, $overwrite); + } + + /** + * @inheritDoc + */ + public function having($conditions = null, $types = [], $overwrite = false) + { + $this->_deprecatedMethod('having()'); + + return parent::having($conditions, $types, $overwrite); + } + + /** + * @inheritDoc + */ + public function andHaving($conditions, $types = []) + { + $this->_deprecatedMethod('andHaving()'); + + return parent::andHaving($conditions, $types); + } + + /** + * @inheritDoc + */ + public function page(int $num, ?int $limit = null) + { + $this->_deprecatedMethod('page()'); + + return parent::page($num, $limit); + } + + /** + * @inheritDoc + */ + public function offset($offset) + { + $this->_deprecatedMethod('offset()'); + + return parent::offset($offset); + } + + /** + * @inheritDoc + */ + public function union($query, $overwrite = false) + { + $this->_deprecatedMethod('union()'); + + return parent::union($query, $overwrite); + } + + /** + * @inheritDoc + */ + public function unionAll($query, $overwrite = false) + { + $this->_deprecatedMethod('union()'); + + return parent::unionAll($query, $overwrite); + } + + /** + * @inheritDoc + */ + public function insert(array $columns, array $types = []) + { + $this->_deprecatedMethod('insert()', 'Create your query with insertQuery() instead.'); + + return parent::insert($columns, $types); + } + + /** + * @inheritDoc + */ + public function into(string $table) + { + $this->_deprecatedMethod('into()', 'Use update() instead.'); + + return parent::into($table); + } + + /** + * @inheritDoc + */ + public function values($data) + { + $this->_deprecatedMethod('values()'); + + return parent::values($data); + } + + /** + * @inheritDoc + */ + public function from($tables = [], $overwrite = false) + { + $this->_deprecatedMethod('from()', 'Use update() instead.'); + + return parent::from($tables, $overwrite); + } + + /** + * @inheritDoc + */ + public function matching(string $assoc, ?callable $builder = null) + { + $this->_deprecatedMethod('matching()'); + + return parent::matching($assoc, $builder); + } + + /** + * @inheritDoc + */ + public function notMatching(string $assoc, ?callable $builder = null) + { + $this->_deprecatedMethod('notMatching()'); + + return parent::notMatching($assoc, $builder); + } + + /** + * @inheritDoc + */ + public function contain($associations, $override = false) + { + $this->_deprecatedMethod('contain()'); + + return parent::contain($associations, $override); + } + + /** + * @inheritDoc + */ + public function getContain(): array + { + $this->_deprecatedMethod('getContain()'); + + return parent::getContain(); + } + + /** + * @inheritDoc + */ + public function find(string $finder, array $options = []) + { + $this->_deprecatedMethod('find()'); + + return parent::find($finder, $options); + } +} diff --git a/Table.php b/Table.php index cbdf3529..1875d467 100644 --- a/Table.php +++ b/Table.php @@ -41,6 +41,7 @@ use Cake\ORM\Exception\RolledbackTransactionException; use Cake\ORM\Query\DeleteQuery; use Cake\ORM\Query\InsertQuery; +use Cake\ORM\Query\UpdateQuery; use Cake\ORM\Rule\IsUnique; use Cake\Utility\Inflector; use Cake\Validation\ValidatorAwareInterface; @@ -1718,6 +1719,16 @@ public function query(): Query return new Query($this->getConnection(), $this); } + /** + * Creates a new DeleteQuery instance for a table. + * + * @return \Cake\ORM\Query\DeleteQuery + */ + public function deleteQuery(): DeleteQuery + { + return new DeleteQuery($this->getConnection(), $this); + } + /** * Creates a new InsertQuery instance for a table. * @@ -1729,13 +1740,13 @@ public function insertQuery(): InsertQuery } /** - * Creates a new DeleteQuery instance for a table. + * Creates a new UpdateQuery instance for a table. * - * @return \Cake\ORM\Query\DeleteQuery + * @return \Cake\ORM\Query\UpdateQuery */ - public function deleteQuery(): DeleteQuery + public function updateQuery(): UpdateQuery { - return new DeleteQuery($this->getConnection(), $this); + return new UpdateQuery($this->getConnection(), $this); } /** @@ -1754,8 +1765,7 @@ public function subquery(): Query */ public function updateAll($fields, $conditions): int { - $statement = $this->query() - ->update() + $statement = $this->updateQuery() ->set($fields) ->where($conditions) ->execute(); @@ -2191,8 +2201,7 @@ protected function _update(EntityInterface $entity, array $data) throw new InvalidArgumentException($message); } - $statement = $this->query() - ->update() + $statement = $this->updateQuery() ->set($data) ->where($primaryKey) ->execute(); From 9f03160a0823a1d9c87e53ce308e5ee957a0126c Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 16 Oct 2022 22:54:51 -0400 Subject: [PATCH 1842/2059] 4.5 - Add SelectQuery Add SelectQuery and deprecate Table::query(). I needed to update several mocks for queries. --- Association/BelongsToMany.php | 2 +- Behavior/TreeBehavior.php | 30 ++++++------ Query/SelectQuery.php | 92 +++++++++++++++++++++++++++++++++++ Table.php | 23 +++++++-- 4 files changed, 128 insertions(+), 19 deletions(-) create mode 100644 Query/SelectQuery.php diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index f4340225..8c95cbc7 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1206,7 +1206,7 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { // Create a subquery join to ensure we get // the correct entity passed to callbacks. - $existing = $junction->query() + $existing = $junction->selectQuery() ->from([$junctionQueryAlias => $matches]) ->innerJoin( [$junction->getAlias() => $junction->getTable()], diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index b009d0d5..493bea37 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -18,6 +18,7 @@ use Cake\Collection\CollectionInterface; use Cake\Database\Expression\IdentifierExpression; +use Cake\Database\Expression\QueryExpression; use Cake\Datasource\EntityInterface; use Cake\Datasource\Exception\RecordNotFoundException; use Cake\Event\EventInterface; @@ -227,20 +228,24 @@ public function beforeDelete(EventInterface $event, EntityInterface $entity) $diff = $right - $left + 1; if ($diff > 2) { - $query = $this->_scope($this->_table->query()) - ->where(function ($exp) use ($config, $left, $right) { - /** @var \Cake\Database\Expression\QueryExpression $exp */ - return $exp - ->gte($config['leftField'], $left + 1) - ->lte($config['leftField'], $right - 1); - }); if ($this->getConfig('cascadeCallbacks')) { + $query = $this->_scope($this->_table->selectQuery()) + ->where(function (QueryExpression $exp) use ($config, $left, $right) { + return $exp + ->gte($config['leftField'], $left + 1) + ->lte($config['leftField'], $right - 1); + }); $entities = $query->toArray(); foreach ($entities as $entityToDelete) { $this->_table->delete($entityToDelete, ['atomic' => false]); } } else { - $query->delete(); + $query = $this->_scope($this->_table->deleteQuery()) + ->where(function (QueryExpression $exp) use ($config, $left, $right) { + return $exp + ->gte($config['leftField'], $left + 1) + ->lte($config['leftField'], $right - 1); + }); $statement = $query->execute(); $statement->closeCursor(); } @@ -848,7 +853,7 @@ protected function _recoverTree(int $lftRght = 1, $parentId = null, $level = 0): $primaryKey = $this->_getPrimaryKey(); $order = $config['recoverOrder'] ?: $primaryKey; - $nodes = $this->_scope($this->_table->query()) + $nodes = $this->_scope($this->_table->selectQuery()) ->select($primaryKey) ->where([$parent . ' IS' => $parentId]) ->order($order) @@ -911,7 +916,7 @@ protected function _sync(int $shift, string $dir, string $conditions, bool $mark $config = $this->_config; foreach ([$config['leftField'], $config['rightField']] as $field) { - $query = $this->_scope($this->_table->query()); + $query = $this->_scope($this->_table->updateQuery()); $exp = $query->newExpr(); $movement = clone $exp; @@ -925,10 +930,7 @@ protected function _sync(int $shift, string $dir, string $conditions, bool $mark $where = clone $exp; $where->add($field)->add($conditions)->setConjunction(''); - $query->update() - ->set($exp->eq($field, $movement)) - ->where($where); - + $query->set($exp->eq($field, $movement))->where($where); $query->execute()->closeCursor(); } } diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php new file mode 100644 index 00000000..06942fab --- /dev/null +++ b/Query/SelectQuery.php @@ -0,0 +1,92 @@ +_deprecatedMethod('delete()', 'Create your query with deleteQuery() instead.'); + + return parent::delete($table); + } + + /** + * @inheritDoc + */ + public function insert(array $columns, array $types = []) + { + $this->_deprecatedMethod('insert()', 'Create your query with insertQuery() instead.'); + + return parent::insert($columns, $types); + } + + /** + * @inheritDoc + */ + public function into(string $table) + { + $this->_deprecatedMethod('into()', 'Use from() instead.'); + + return parent::into($table); + } + + /** + * @inheritDoc + */ + public function values($data) + { + $this->_deprecatedMethod('values()'); + + return parent::values($data); + } + + /** + * @inheritDoc + */ + public function update($table = null) + { + $this->_deprecatedMethod('update()', 'Create your query with updateQuery() instead.'); + + return parent::update($table); + } + + /** + * @inheritDoc + */ + public function set($key, $value = null, $types = []) + { + $this->_deprecatedMethod('set()'); + + return parent::set($key, $value, $types); + } +} diff --git a/Table.php b/Table.php index 1875d467..cbdb3d48 100644 --- a/Table.php +++ b/Table.php @@ -41,6 +41,7 @@ use Cake\ORM\Exception\RolledbackTransactionException; use Cake\ORM\Query\DeleteQuery; use Cake\ORM\Query\InsertQuery; +use Cake\ORM\Query\SelectQuery; use Cake\ORM\Query\UpdateQuery; use Cake\ORM\Rule\IsUnique; use Cake\Utility\Inflector; @@ -1268,7 +1269,7 @@ public function belongsToMany(string $associated, array $options = []): BelongsT */ public function find(string $type = 'all', array $options = []): Query { - return $this->callFinder($type, $this->query()->select(), $options); + return $this->callFinder($type, $this->selectQuery()->select(), $options); } /** @@ -1713,9 +1714,13 @@ protected function _getFindOrCreateQuery($search): Query */ public function query(): Query { - // TODO(markstory) Add deprecation here to encourage new methods. - // Remember to remind people implementing this method to - // implement the new ones instead. + deprecationWarning( + 'As of 4.5.0 using query() is deprecated. Instead use `insertQuery()`, ' . + '`deleteQuery()`, `selectQuery()` or `updateQuery()`. The query objects ' . + 'returned by these methods will emit deprecations that will become fatal errors in 5.0.' . + 'See https://book.cakephp.org/4/en/appendices/4-5-migration-guide.html for more information.' + ); + return new Query($this->getConnection(), $this); } @@ -1739,6 +1744,16 @@ public function insertQuery(): InsertQuery return new InsertQuery($this->getConnection(), $this); } + /** + * Creates a new SelectQuery instance for a table. + * + * @return \Cake\ORM\Query\SelectQuery + */ + public function selectQuery(): SelectQuery + { + return new SelectQuery($this->getConnection(), $this); + } + /** * Creates a new UpdateQuery instance for a table. * From 1fb6cd09e7906231d2a3dd5a1d40a02ed4fc4ca0 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Tue, 18 Oct 2022 03:57:54 -0500 Subject: [PATCH 1843/2059] Add orderBy() and groupBy() to match sql --- Association/Loader/SelectLoader.php | 6 +++--- Behavior/TreeBehavior.php | 16 ++++++++-------- Query/SelectQuery.php | 8 ++++---- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index f8ec7adf..9a8d0fac 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -190,7 +190,7 @@ protected function _buildQuery(array $options): SelectQuery } if (!empty($options['sort'])) { - $fetchQuery->order($options['sort']); + $fetchQuery->orderBy($options['sort']); } if (!empty($options['contain'])) { @@ -418,12 +418,12 @@ protected function _buildSubquery(SelectQuery $query): SelectQuery // Ignore limit if there is no order since we need all rows to find matches if (!$filterQuery->clause('limit') || !$filterQuery->clause('order')) { $filterQuery->limit(null); - $filterQuery->order([], true); + $filterQuery->orderBy([], true); $filterQuery->offset(null); } $fields = $this->_subqueryFields($query); - $filterQuery->select($fields['select'], true)->group($fields['group']); + $filterQuery->select($fields['select'], true)->groupBy($fields['group']); return $filterQuery; } diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 9720dd47..295f63fa 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -409,7 +409,7 @@ function ($field) { "$left <=" => $node->get($config['left']), "$right >=" => $node->get($config['right']), ]) - ->order([$left => 'ASC']); + ->orderBy([$left => 'ASC']); } /** @@ -470,7 +470,7 @@ function ($field) { } if ($query->clause('order') === null) { - $query->order([$left => 'ASC']); + $query->orderBy([$left => 'ASC']); } if ($direct) { @@ -655,7 +655,7 @@ protected function _moveUp(EntityInterface $node, int|bool $number): EntityInter ->select([$left, $right]) ->where(["$parent IS" => $nodeParent]) ->where(fn (QueryExpression $exp) => $exp->lt($config['rightField'], $nodeLeft)) - ->orderDesc($config['leftField']) + ->orderByDesc($config['leftField']) ->offset($number - 1) ->limit(1) ->first(); @@ -666,7 +666,7 @@ protected function _moveUp(EntityInterface $node, int|bool $number): EntityInter ->select([$left, $right]) ->where(["$parent IS" => $nodeParent]) ->where(fn (QueryExpression $exp) => $exp->lt($config['rightField'], $nodeLeft)) - ->orderAsc($config['leftField']) + ->orderByAsc($config['leftField']) ->limit(1) ->first(); @@ -744,7 +744,7 @@ protected function _moveDown(EntityInterface $node, int|bool $number): EntityInt ->select([$left, $right]) ->where(["$parent IS" => $nodeParent]) ->where(fn (QueryExpression $exp) => $exp->gt($config['leftField'], $nodeRight)) - ->orderAsc($config['leftField']) + ->orderByAsc($config['leftField']) ->offset($number - 1) ->limit(1) ->first(); @@ -755,7 +755,7 @@ protected function _moveDown(EntityInterface $node, int|bool $number): EntityInt ->select([$left, $right]) ->where(["$parent IS" => $nodeParent]) ->where(fn (QueryExpression $exp) => $exp->gt($config['leftField'], $nodeRight)) - ->orderDesc($config['leftField']) + ->orderByDesc($config['leftField']) ->limit(1) ->first(); @@ -845,7 +845,7 @@ protected function _recoverTree(int $lftRght = 1, mixed $parentId = null, int $l $nodes = $this->_scope($this->_table->query()) ->select($primaryKey) ->where([$parent . ' IS' => $parentId]) - ->order($order) + ->orderBy($order) ->disableHydration() ->all(); @@ -878,7 +878,7 @@ protected function _getMax(): int $rightField = $this->_config['rightField']; $edge = $this->_scope($this->_table->find()) ->select([$field]) - ->orderDesc($rightField) + ->orderByDesc($rightField) ->first(); if ($edge === null || empty($edge[$field])) { diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index 2ec710e8..864fb5b9 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -1135,7 +1135,7 @@ public function matching(string $assoc, ?Closure $builder = null) * $usersQuery * ->select(['total_articles' => $query->func()->count('Articles.id')]) * ->leftJoinWith('Articles') - * ->group(['Users.id']) + * ->groupBy(['Users.id']) * ->enableAutoFields(); * ``` * @@ -1148,7 +1148,7 @@ public function matching(string $assoc, ?Closure $builder = null) * ->leftJoinWith('Articles', function ($q) { * return $q->where(['Articles.votes >=' => 5]); * }) - * ->group(['Users.id']) + * ->groupBy(['Users.id']) * ->enableAutoFields(); * ``` * @@ -1172,7 +1172,7 @@ public function matching(string $assoc, ?Closure $builder = null) * ->leftJoinWith('Comments.Users', function ($q) { * return $q->where(['username' => 'markstory']); * }) - * ->group(['Users.id']); + * ->groupBy(['Users.id']); * ``` * * Please note that the query passed to the closure will only accept calling @@ -1335,7 +1335,7 @@ public function cleanCopy(): static $clone->triggerBeforeFind(); $clone->disableAutoFields(); $clone->limit(null); - $clone->order([], true); + $clone->orderBy([], true); $clone->offset(null); $clone->mapReduce(null, null, true); $clone->formatResults(null, self::OVERWRITE); From 5fda61c6bb1e6847f378807548ac34b27df67678 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Tue, 18 Oct 2022 02:39:38 -0500 Subject: [PATCH 1844/2059] Add ORM\Query::selectAlso() which does not disable auto-selecting fields --- Query.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Query.php b/Query.php index dc09e165..749be59e 100644 --- a/Query.php +++ b/Query.php @@ -242,6 +242,24 @@ public function select($fields = [], bool $overwrite = false) return parent::select($fields, $overwrite); } + /** + * Behaves the exact same as `select()` except adds the field to the list of fields selected and + * does not disable auto-selecting fields for Associations. + * + * Use this instead of calling `select()` then `enableAutoFields()` to re-enable auto-fields. + * + * @param \Cake\Database\ExpressionInterface|\Cake\ORM\Table|\Cake\ORM\Association|callable|array|string $fields Fields + * to be added to the list. + * @return $this + */ + public function selectAlso($fields) + { + $this->select($fields); + $this->_autoFields = true; + + return $this; + } + /** * All the fields associated with the passed table except the excluded * fields will be added to the select clause of the query. Passed excluded fields should not be aliased. From 668067c4ae701eeda7b3051ea47680a0bf28ca42 Mon Sep 17 00:00:00 2001 From: Felix Kempf Date: Sun, 23 Oct 2022 17:00:42 +0200 Subject: [PATCH 1845/2059] Fix method signature in DocBlock (#16822) Fix method annotation. Co-authored-by: Mark Scherer --- Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Query.php b/Query.php index 75318f6f..7ba36800 100644 --- a/Query.php +++ b/Query.php @@ -43,7 +43,7 @@ * @method \Cake\ORM\Table getRepository() Returns the default table object that will be used by this query, * that is, the table that will appear in the from clause. * @method \Cake\Collection\CollectionInterface each(callable $c) Passes each of the query results to the callable - * @method \Cake\Collection\CollectionInterface sortBy($callback, int $dir) Sorts the query with the callback + * @method \Cake\Collection\CollectionInterface sortBy(callable|string $path, int $order = \SORT_DESC, int $sort = \SORT_NUMERIC) Sorts the query with the callback * @method \Cake\Collection\CollectionInterface filter(callable $c = null) Keeps the results using passing the callable test * @method \Cake\Collection\CollectionInterface reject(callable $c) Removes the results passing the callable test * @method bool every(callable $c) Returns true if all the results pass the callable test From 9e2c12bd27f0a5b742b5ffcb2ef5389f33c1e6f6 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 3 Nov 2022 00:32:17 -0400 Subject: [PATCH 1846/2059] 4.5 - Deprecate newQuery() This completes the backport of the separate query classes to 4.5 --- Query.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Query.php b/Query.php index 43905198..0f16a7d0 100644 --- a/Query.php +++ b/Query.php @@ -995,8 +995,8 @@ protected function _performCount(): int ->disableAutoFields() ->execute(); } else { - $statement = $this->getConnection()->newQuery() - ->select($count) + $statement = $this->getConnection() + ->selectQuery($count) ->from(['count_source' => $query]) ->execute(); } From 39256f4cb8f523bcc5bb51355208005000b0f5f3 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 6 Nov 2022 12:51:51 +0530 Subject: [PATCH 1847/2059] Fix stan and CS errors --- Query/SelectQuery.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index f8db7dee..de2af8a9 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -829,7 +829,7 @@ public function select( * * Use this instead of calling `select()` then `enableAutoFields()` to re-enable auto-fields. * - * @param \Cake\Database\ExpressionInterface|\Cake\ORM\Table|\Cake\ORM\Association|Closure|array|string|float|int $fields Fields + * @param \Cake\Database\ExpressionInterface|\Cake\ORM\Table|\Cake\ORM\Association|\Closure|array|string|float|int $fields Fields * to be added to the list. * @return $this */ From 287965bf75a14a276c1af4a3d9b20811da6d5dab Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 12 Nov 2022 18:41:42 +0530 Subject: [PATCH 1848/2059] Fix CS errors --- Association/HasMany.php | 2 +- Behavior/Translate/EavStrategy.php | 5 ++++- Behavior/Translate/ShadowTableStrategy.php | 5 ++++- EagerLoader.php | 2 +- LazyEagerLoader.php | 4 ++-- Marshaller.php | 4 ++-- Rule/ExistsIn.php | 2 +- Table.php | 10 +++++----- 8 files changed, 20 insertions(+), 14 deletions(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index 432fa011..5dcebadf 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -287,7 +287,7 @@ public function link(EntityInterface $sourceEntity, array $targetEntities, array $sourceEntity->set($property, $currentEntities); - $savedEntity = $this->getConnection()->transactional(fn() => $this->saveAssociated($sourceEntity, $options)); + $savedEntity = $this->getConnection()->transactional(fn () => $this->saveAssociated($sourceEntity, $options)); $ok = ($savedEntity instanceof EntityInterface); $this->setSaveStrategy($saveStrategy); diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index 4d0f3493..78a7e419 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -217,7 +217,10 @@ public function beforeFind(EventInterface $event, SelectQuery $query, ArrayObjec } $query->contain($contain); - $query->formatResults(fn(CollectionInterface $results) => $this->rowMapper($results, $locale), $query::PREPEND); + $query->formatResults( + fn (CollectionInterface $results) => $this->rowMapper($results, $locale), + $query::PREPEND + ); } /** diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 233df093..a2e846c9 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -147,7 +147,10 @@ public function beforeFind(EventInterface $event, SelectQuery $query, ArrayObjec $query->contain([$config['hasOneAlias']]); - $query->formatResults(fn(CollectionInterface $results) => $this->rowMapper($results, $locale), $query::PREPEND); + $query->formatResults( + fn (CollectionInterface $results) => $this->rowMapper($results, $locale), + $query::PREPEND + ); } /** diff --git a/EagerLoader.php b/EagerLoader.php index cad157b5..afac2d75 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -371,7 +371,7 @@ protected function _reformatContain(array $associations, array $original): array if (isset($options['queryBuilder'], $pointer[$table]['queryBuilder'])) { $first = $pointer[$table]['queryBuilder']; $second = $options['queryBuilder']; - $options['queryBuilder'] = fn($query) => $second($first($query)); + $options['queryBuilder'] = fn ($query) => $second($first($query)); } if (!is_array($options)) { diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 2680e91e..e720d382 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -76,7 +76,7 @@ protected function _getQuery(CollectionInterface $objects, array $contain, Table $primaryKey = $source->getPrimaryKey(); $method = is_string($primaryKey) ? 'get' : 'extract'; - $keys = $objects->map(fn(EntityInterface $entity) => $entity->{$method}($primaryKey)); + $keys = $objects->map(fn (EntityInterface $entity) => $entity->{$method}($primaryKey)); $query = $source ->find() @@ -149,7 +149,7 @@ protected function _injectResults( /** @var array $results */ $results = $results ->all() - ->indexBy(fn(EntityInterface $e) => implode(';', $e->extract($primaryKey))) + ->indexBy(fn (EntityInterface $e) => implode(';', $e->extract($primaryKey))) ->toArray(); foreach ($objects as $k => $object) { diff --git a/Marshaller.php b/Marshaller.php index 6dd27ca3..ba9a747b 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -76,7 +76,7 @@ protected function _buildPropertyMap(array $data, array $options): array $prop = (string)$prop; $columnType = $schema->getColumnType($prop); if ($columnType) { - $map[$prop] = fn($value) => TypeFactory::build($columnType)->marshal($value); + $map[$prop] = fn ($value) => TypeFactory::build($columnType)->marshal($value); } } @@ -405,7 +405,7 @@ protected function _belongsToMany(BelongsToMany $assoc, array $data, array $opti if (!empty($conditions)) { /** @var \Traversable<\Cake\Datasource\EntityInterface> $results */ $results = $target->find() - ->andWhere(fn(QueryExpression $exp) => $exp->or($conditions)) + ->andWhere(fn (QueryExpression $exp) => $exp->or($conditions)) ->all(); $keyFields = array_keys($primaryKey); diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 83c4232c..1ff8fe34 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -133,7 +133,7 @@ public function __invoke(EntityInterface $entity, array $options): bool } $primary = array_map( - fn($key) => $target->aliasField($key) . ' IS', + fn ($key) => $target->aliasField($key) . ' IS', $bindingKey ); $conditions = array_combine( diff --git a/Table.php b/Table.php index 7b80a280..44983fae 100644 --- a/Table.php +++ b/Table.php @@ -1359,7 +1359,7 @@ public function findList(SelectQuery $query, array $options): SelectQuery ['keyField', 'valueField', 'groupField'] ); - return $query->formatResults(fn(CollectionInterface $results) => + return $query->formatResults(fn (CollectionInterface $results) => $results->combine( $options['keyField'], $options['valueField'], @@ -1401,7 +1401,7 @@ public function findThreaded(SelectQuery $query, array $options): SelectQuery $options = $this->_setFieldMatchers($options, ['keyField', 'parentField']); - return $query->formatResults(fn(CollectionInterface $results) => + return $query->formatResults(fn (CollectionInterface $results) => $results->nest($options['keyField'], $options['parentField'], $options['nestingKey'])); } @@ -1591,7 +1591,7 @@ public function findOrCreate( ]); $entity = $this->_executeTransaction( - fn() => $this->_processFindOrCreate($search, $callback, $options->getArrayCopy()), + fn () => $this->_processFindOrCreate($search, $callback, $options->getArrayCopy()), $options['atomic'] ); @@ -1897,7 +1897,7 @@ public function save( } $success = $this->_executeTransaction( - fn() => $this->_processSave($entity, $options), + fn () => $this->_processSave($entity, $options), $options['atomic'] ); @@ -2362,7 +2362,7 @@ public function delete(EntityInterface $entity, array $options = []): bool ]); $success = $this->_executeTransaction( - fn() => $this->_processDelete($entity, $options), + fn () => $this->_processDelete($entity, $options), $options['atomic'] ); From 6f8a4a3cb7d18e766791d8c2e808af0107749314 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 16 Nov 2022 17:20:59 +0530 Subject: [PATCH 1849/2059] Avoid unnecessarily decorating a resultset. --- Association.php | 55 ++++++++++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/Association.php b/Association.php index 7d2cf61c..9621dd2c 100644 --- a/Association.php +++ b/Association.php @@ -17,11 +17,13 @@ namespace Cake\ORM; use Cake\Collection\Collection; +use Cake\Collection\CollectionInterface; use Cake\Core\App; use Cake\Core\ConventionsTrait; use Cake\Database\Expression\IdentifierExpression; use Cake\Datasource\EntityInterface; use Cake\Datasource\ResultSetDecorator; +use Cake\Datasource\ResultSetInterface; use Cake\ORM\Locator\LocatorAwareTrait; use Cake\Utility\Inflector; use Closure; @@ -1003,35 +1005,40 @@ protected function _formatAssociationResults(Query $query, Query $surrogate, arr $property = $options['propertyPath']; $propertyPath = explode('.', $property); - $query->formatResults(function ($results, $query) use ($formatters, $property, $propertyPath) { - $extracted = []; - foreach ($results as $result) { - foreach ($propertyPath as $propertyPathItem) { - if (!isset($result[$propertyPathItem])) { - $result = null; - break; + $query->formatResults( + function (CollectionInterface $results, $query) use ($formatters, $property, $propertyPath) { + $extracted = []; + foreach ($results as $result) { + foreach ($propertyPath as $propertyPathItem) { + if (!isset($result[$propertyPathItem])) { + $result = null; + break; + } + $result = $result[$propertyPathItem]; + } + $extracted[] = $result; + } + $extracted = new Collection($extracted); + foreach ($formatters as $callable) { + $extracted = $callable($extracted, $query); + if (!$extracted instanceof ResultSetInterface) { + $extracted = new ResultSetDecorator($extracted); } - $result = $result[$propertyPathItem]; } - $extracted[] = $result; - } - $extracted = new Collection($extracted); - foreach ($formatters as $callable) { - $extracted = new ResultSetDecorator($callable($extracted, $query)); - } - /** @var \Cake\Collection\CollectionInterface $results */ - $results = $results->insert($property, $extracted); - if ($query->isHydrationEnabled()) { - $results = $results->map(function ($result) { - $result->clean(); + $results = $results->insert($property, $extracted); + if ($query->isHydrationEnabled()) { + $results = $results->map(function ($result) { + $result->clean(); - return $result; - }); - } + return $result; + }); + } - return $results; - }, Query::PREPEND); + return $results; + }, + Query::PREPEND + ); } /** From 76a6b4b85ed88b1f70e4676b32f8ce9afedcfaaa Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 16 Nov 2022 19:40:55 +0530 Subject: [PATCH 1850/2059] Use null coalescing assignment. --- Query/SelectQuery.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index de2af8a9..7e2baeeb 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -1594,11 +1594,7 @@ protected function _execute(): iterable */ protected function resultSetFactory(): ResultSetFactory { - if (isset($this->resultSetFactory)) { - return $this->resultSetFactory; - } - - return $this->resultSetFactory = new ResultSetFactory(); + return $this->resultSetFactory ??= new ResultSetFactory(); } /** From 19c119c2e8c3c3ccf723cde9c6fc2836a6d13c95 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 24 Nov 2022 10:25:43 +0530 Subject: [PATCH 1851/2059] Fix infinite loop. Throw an exception if both `alias` and `table` are not specified for a `Table` and they can't be inferred from the classname. Closes #16874. --- Table.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Table.php b/Table.php index f3e58492..0c825069 100644 --- a/Table.php +++ b/Table.php @@ -20,6 +20,7 @@ use BadMethodCallException; use Cake\Core\App; use Cake\Core\Configure; +use Cake\Core\Exception\CakeException; use Cake\Database\Connection; use Cake\Database\Schema\TableSchemaInterface; use Cake\Database\TypeFactory; @@ -387,9 +388,11 @@ public function getTable(): string { if ($this->_table === null) { $table = namespaceSplit(static::class); - $table = substr(end($table), 0, -5); + $table = substr(end($table), 0, -5) ?: $this->_alias; if (!$table) { - $table = $this->getAlias(); + throw new CakeException( + 'You must specify either the `alias` or the `table` option for the constructor.' + ); } $this->_table = Inflector::underscore($table); } @@ -419,7 +422,12 @@ public function getAlias(): string { if ($this->_alias === null) { $alias = namespaceSplit(static::class); - $alias = substr(end($alias), 0, -5) ?: $this->getTable(); + $alias = substr(end($alias), 0, -5) ?: $this->_table; + if (!$alias) { + throw new CakeException( + 'You must specify either the `alias` or the `table` option for the constructor.' + ); + } $this->_alias = $alias; } From 35357e35e062a272572324ab6c3165d80ab00fd6 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Sun, 20 Nov 2022 22:32:10 -0600 Subject: [PATCH 1852/2059] Decoupled connection roles from ConnectionManager --- EagerLoader.php | 2 +- ResultSet.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index 1778c32d..d4fa10a2 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -630,7 +630,7 @@ public function loadExternal(Query $query, StatementInterface $statement): State return $statement; } - $driver = $query->getConnection()->getDriver(); + $driver = $query->getConnection()->getDriver($query->getConnectionRole()); [$collected, $statement] = $this->_collectKeys($external, $query, $statement); // No records found, skip trying to attach associations. diff --git a/ResultSet.php b/ResultSet.php index 1fe3cf69..75115e7f 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -161,7 +161,7 @@ public function __construct(Query $query, StatementInterface $statement) { $repository = $query->getRepository(); $this->_statement = $statement; - $this->_driver = $query->getConnection()->getDriver(); + $this->_driver = $query->getConnection()->getDriver($query->getConnectionRole()); $this->_defaultTable = $repository; $this->_calculateAssociationMap($query); $this->_hydrate = $query->isHydrationEnabled(); From 83cda0eda6ecd4da3819a6b17e0637fa8517a949 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Sun, 13 Nov 2022 03:12:32 -0600 Subject: [PATCH 1853/2059] Support Chronos 3 change to not extend DateTimeImmutable --- Behavior/TimestampBehavior.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index a9232f24..b5208180 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -16,6 +16,7 @@ */ namespace Cake\ORM\Behavior; +use Cake\Chronos\Chronos; use Cake\Database\Type\DateTimeType; use Cake\Database\TypeFactory; use Cake\Datasource\EntityInterface; @@ -145,11 +146,11 @@ public function implementedEvents(): array * If an explicit date time is passed, the config option `refreshTimestamp` is * automatically set to false. * - * @param \DateTimeInterface|null $ts Timestamp + * @param \Cake\Chronos\Chronos|\DateTimeInterface|null $ts Timestamp * @param bool $refreshTimestamp If true timestamp is refreshed. * @return \Cake\I18n\DateTime */ - public function timestamp(?DateTimeInterface $ts = null, bool $refreshTimestamp = false): DateTimeInterface + public function timestamp(Chronos|DateTimeInterface|null $ts = null, bool $refreshTimestamp = false): DateTime { if ($ts) { if ($this->_config['refreshTimestamp']) { From 95c75e51a96eb643e87eddaff05b6c8843017e04 Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Mon, 28 Nov 2022 09:21:11 +0100 Subject: [PATCH 1854/2059] 4.next: add psalm template annotation (#16880) Add psalm template annotation to ResultSetInterface and ResultSet Co-authored-by: ADmad --- ResultSet.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/ResultSet.php b/ResultSet.php index 75115e7f..012c4002 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -29,6 +29,9 @@ * This object is responsible for correctly nesting result keys reported from * the query, casting each field to the correct type and executing the extra * queries required for eager loading external associations. + * + * @template T of \Cake\Datasource\EntityInterface|array + * @implements \Cake\Datasource\ResultSetInterface */ class ResultSet implements ResultSetInterface { @@ -51,7 +54,8 @@ class ResultSet implements ResultSetInterface /** * Last record fetched from the statement * - * @var object|array + * @var \Cake\Datasource\EntityInterface|array + * @psalm-var T */ protected $_current; @@ -182,7 +186,8 @@ public function __construct(Query $query, StatementInterface $statement) * * Part of Iterator interface. * - * @return object|array + * @return \Cake\Datasource\EntityInterface|array + * @psalm-return T */ #[\ReturnTypeWillChange] public function current() @@ -276,7 +281,8 @@ public function valid(): bool * * This method will also close the underlying statement cursor. * - * @return object|array|null + * @return \Cake\Datasource\EntityInterface|array|null + * @psalm-return T|null */ public function first() { From 8b6414515f7306932c3647b35af268c141f7de5e Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 30 Nov 2022 10:49:14 +0530 Subject: [PATCH 1855/2059] Fix errors reported by static analyzers. --- ResultSet.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ResultSet.php b/ResultSet.php index 76e7e971..93426ffc 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -44,10 +44,10 @@ class ResultSet implements ResultSetInterface /** * Last record fetched from the statement * - * @var \Cake\Datasource\EntityInterface|array - * @psalm-var T + * @var \Cake\Datasource\EntityInterface|array|null + * @psalm-var T|null */ - protected EntityInterface|array $_current = []; + protected EntityInterface|array|null $_current; /** * Holds the count of records in this result set @@ -59,7 +59,7 @@ class ResultSet implements ResultSetInterface /** * Results that have been fetched or hydrated into the results. * - * @var \SplFixedArray<\Cake\Datasource\EntityInterface|array> + * @var \SplFixedArray */ protected SplFixedArray $_results; @@ -78,10 +78,10 @@ public function __construct(array $results) * * Part of Iterator interface. * - * @return \Cake\Datasource\EntityInterface|array - * @psalm-return T + * @return \Cake\Datasource\EntityInterface|array|null + * @psalm-return T|null */ - public function current(): EntityInterface|array + public function current(): EntityInterface|array|null { return $this->_current; } From be8d2a7efd4cb91c09ecde3f93d99cbfc304f0c5 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 2 Dec 2022 19:26:21 +0530 Subject: [PATCH 1856/2059] Fix errors reported by psalm. --- Association/BelongsTo.php | 2 +- Association/BelongsToMany.php | 6 +++--- Association/HasMany.php | 2 +- Association/HasOne.php | 2 +- Query/SelectQuery.php | 1 - Table.php | 1 + 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 6a1b6005..26f56781 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -143,7 +143,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []): En return false; } - /** @psalm-suppress InvalidArgument */ + /** @psalm-suppress InvalidScalarArgument */ $properties = array_combine( (array)$this->getForeignKey(), $targetEntity->extract((array)$this->getBindingKey()) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 5e62f895..d503aded 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -583,7 +583,7 @@ public function cascadeDelete(EntityInterface $entity, array $options = []): boo $conditions = []; if (!empty($bindingKey)) { - /** @psalm-suppress InvalidArgument */ + /** @psalm-suppress InvalidScalarArgument */ $conditions = array_combine($foreignKey, $entity->extract($bindingKey)); } @@ -798,9 +798,9 @@ protected function _saveLinks(EntityInterface $sourceEntity, array $targetEntiti if (!$joint || !($joint instanceof EntityInterface)) { $joint = new $entityClass([], ['markNew' => true, 'source' => $junctionRegistryAlias]); } - /** @psalm-suppress InvalidArgument */ + /** @psalm-suppress InvalidScalarArgument */ $sourceKeys = array_combine($foreignKey, $sourceEntity->extract($bindingKey)); - /** @psalm-suppress InvalidArgument */ + /** @psalm-suppress InvalidScalarArgument */ $targetKeys = array_combine($assocForeignKey, $e->extract($targetBindingKey)); /** @psalm-suppress InvalidArgument */ diff --git a/Association/HasMany.php b/Association/HasMany.php index 5dcebadf..de57eddd 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -166,7 +166,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []): En throw new InvalidArgumentException($message); } - /** @psalm-suppress InvalidArgument */ + /** @psalm-suppress InvalidScalarArgument */ $foreignKeyReference = array_combine( (array)$this->getForeignKey(), $entity->extract((array)$this->getBindingKey()) diff --git a/Association/HasOne.php b/Association/HasOne.php index dd418f02..7777588a 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -121,7 +121,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []): En return $entity; } - /** @psalm-suppress InvalidArgument */ + /** @psalm-suppress InvalidScalarArgument */ $properties = array_combine( (array)$this->getForeignKey(), $entity->extract((array)$this->getBindingKey()) diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index 7e2baeeb..53d7c33d 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -539,7 +539,6 @@ public function formatResults(?Closure $formatter = null, int|bool $mode = self: $this->_formatters = []; } if ($formatter === null) { - /** @psalm-suppress RedundantCondition */ if ($mode !== self::OVERWRITE) { throw new InvalidArgumentException('$formatter can be null only when $mode is overwrite.'); } diff --git a/Table.php b/Table.php index 215f575f..7358ad34 100644 --- a/Table.php +++ b/Table.php @@ -332,6 +332,7 @@ public function __construct(array $config = []) $this->_behaviors = $behaviors ?: new BehaviorRegistry(); $this->_behaviors->setTable($this); $this->_associations = $associations ?: new AssociationCollection(); + /** @psalm-suppress TypeDoesNotContainType */ $this->queryFactory ??= new QueryFactory(); $this->initialize($config); From 18802d5b969ca5bf8636a0207aef88a51f65cd71 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 25 Dec 2022 23:14:21 -0500 Subject: [PATCH 1857/2059] Fix ShadowStrategy failing on missing data When loading optional associations with contain() and also loading translations for those associations we should not raise an error when association data is missing. Instead return the null value like EavStrategy does. Fixes #16914 --- Behavior/Translate/ShadowTableStrategy.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index a4ac9764..3284c3dc 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -537,7 +537,10 @@ protected function rowMapper($results, $locale) public function groupTranslations($results): CollectionInterface { return $results->map(function ($row) { - $translations = (array)$row['_i18n']; + if (!($row instanceof EntityInterface)) { + return $row; + } + $translations = (array)$row->get('_i18n'); if (empty($translations) && $row->get('_translations')) { return $row; } From 859efab9aef63e3ee869e23a75fcd03054ed5f25 Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Wed, 30 Nov 2022 22:37:20 +0100 Subject: [PATCH 1858/2059] rebase --- Behavior/CounterCacheBehavior.php | 15 ++++++++++----- Behavior/TimestampBehavior.php | 3 ++- Behavior/Translate/EavStrategy.php | 6 ++++-- Behavior/Translate/ShadowTableStrategy.php | 6 ++++-- Behavior/Translate/TranslateStrategyInterface.php | 9 ++++++--- Behavior/Translate/TranslateStrategyTrait.php | 3 ++- Behavior/TreeBehavior.php | 9 ++++++--- Query/SelectQuery.php | 2 +- 8 files changed, 35 insertions(+), 18 deletions(-) diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index e78b6162..ada50e57 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -115,7 +115,8 @@ class CounterCacheBehavior extends Behavior * * Check if a field, which should be ignored, is dirty * - * @param \Cake\Event\EventInterface $event The beforeSave event that was fired + * @template TSubject of object + * @param \Cake\Event\EventInterface $event The beforeSave event that was fired * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved * @param \ArrayObject $options The options for the query * @return void @@ -153,7 +154,8 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array * * Makes sure to update counter cache when a new record is created or updated. * - * @param \Cake\Event\EventInterface $event The afterSave event that was fired. + * @template TSubject of object + * @param \Cake\Event\EventInterface $event The afterSave event that was fired. * @param \Cake\Datasource\EntityInterface $entity The entity that was saved. * @param \ArrayObject $options The options for the query * @return void @@ -173,7 +175,8 @@ public function afterSave(EventInterface $event, EntityInterface $entity, ArrayO * * Makes sure to update counter cache when a record is deleted. * - * @param \Cake\Event\EventInterface $event The afterDelete event that was fired. + * @template TSubject of object + * @param \Cake\Event\EventInterface $event The afterDelete event that was fired. * @param \Cake\Datasource\EntityInterface $entity The entity that was deleted. * @param \ArrayObject $options The options for the query * @return void @@ -190,7 +193,8 @@ public function afterDelete(EventInterface $event, EntityInterface $entity, Arra /** * Iterate all associations and update counter caches. * - * @param \Cake\Event\EventInterface $event Event instance. + * @template TSubject of object + * @param \Cake\Event\EventInterface $event Event instance. * @param \Cake\Datasource\EntityInterface $entity Entity. * @return void */ @@ -205,7 +209,8 @@ protected function _processAssociations(EventInterface $event, EntityInterface $ /** * Updates counter cache for a single association * - * @param \Cake\Event\EventInterface $event Event instance. + * @template TSubject of object + * @param \Cake\Event\EventInterface $event Event instance. * @param \Cake\Datasource\EntityInterface $entity Entity * @param \Cake\ORM\Association $assoc The association object * @param array $settings The settings for counter cache for this association diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index b5208180..e7c9a87f 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -87,7 +87,8 @@ public function initialize(array $config): void /** * There is only one event handler, it can be configured to be called for any event * - * @param \Cake\Event\EventInterface $event Event instance. + * @template TSubject of object + * @param \Cake\Event\EventInterface $event Event instance. * @param \Cake\Datasource\EntityInterface $entity Entity instance. * @throws \UnexpectedValueException if a field's when value is misdefined * @return true Returns true irrespective of the behavior logic, the save will not be prevented. diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index 78a7e419..b3253495 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -160,7 +160,8 @@ protected function setupAssociations(): void * table. It modifies the passed query by eager loading the translated fields * and adding a formatter to copy the values into the main table records. * - * @param \Cake\Event\EventInterface $event The beforeFind event that was fired. + * @template TSubject of object + * @param \Cake\Event\EventInterface $event The beforeFind event that was fired. * @param \Cake\ORM\Query\SelectQuery $query Query * @param \ArrayObject $options The options for the query * @return void @@ -227,7 +228,8 @@ public function beforeFind(EventInterface $event, SelectQuery $query, ArrayObjec * Modifies the entity before it is saved so that translated fields are persisted * in the database too. * - * @param \Cake\Event\EventInterface $event The beforeSave event that was fired + * @template TSubject of object + * @param \Cake\Event\EventInterface $event The beforeSave event that was fired * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved * @param \ArrayObject $options the options passed to the save method * @return void diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index a2e846c9..260e30bd 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -118,7 +118,8 @@ protected function setupAssociations(): void * table. It modifies the passed query by eager loading the translated fields * and adding a formatter to copy the values into the main table records. * - * @param \Cake\Event\EventInterface $event The beforeFind event that was fired. + * @template TSubject of object + * @param \Cake\Event\EventInterface $event The beforeFind event that was fired. * @param \Cake\ORM\Query\SelectQuery $query Query. * @param \ArrayObject $options The options for the query. * @return void @@ -339,7 +340,8 @@ function ($expression) use ($fields, $alias, $mainTableAlias, $mainTableFields, * Modifies the entity before it is saved so that translated fields are persisted * in the database too. * - * @param \Cake\Event\EventInterface $event The beforeSave event that was fired. + * @template TSubject of object + * @param \Cake\Event\EventInterface $event The beforeSave event that was fired. * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved. * @param \ArrayObject $options the options passed to the save method. * @return void diff --git a/Behavior/Translate/TranslateStrategyInterface.php b/Behavior/Translate/TranslateStrategyInterface.php index 550706bd..9cf71094 100644 --- a/Behavior/Translate/TranslateStrategyInterface.php +++ b/Behavior/Translate/TranslateStrategyInterface.php @@ -90,7 +90,8 @@ public function groupTranslations(ResultSetInterface $results): CollectionInterf * table. It modifies the passed query by eager loading the translated fields * and adding a formatter to copy the values into the main table records. * - * @param \Cake\Event\EventInterface $event The beforeFind event that was fired. + * @template TSubject of object + * @param \Cake\Event\EventInterface $event The beforeFind event that was fired. * @param \Cake\ORM\Query\SelectQuery $query Query * @param \ArrayObject $options The options for the query * @return void @@ -101,7 +102,8 @@ public function beforeFind(EventInterface $event, SelectQuery $query, ArrayObjec * Modifies the entity before it is saved so that translated fields are persisted * in the database too. * - * @param \Cake\Event\EventInterface $event The beforeSave event that was fired + * @template TSubject of object + * @param \Cake\Event\EventInterface $event The beforeSave event that was fired * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved * @param \ArrayObject $options the options passed to the save method * @return void @@ -111,7 +113,8 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array /** * Unsets the temporary `_i18n` property after the entity has been saved * - * @param \Cake\Event\EventInterface $event The beforeSave event that was fired + * @template TSubject of object + * @param \Cake\Event\EventInterface $event The beforeSave event that was fired * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved * @return void */ diff --git a/Behavior/Translate/TranslateStrategyTrait.php b/Behavior/Translate/TranslateStrategyTrait.php index fe4a269f..f0d81e7a 100644 --- a/Behavior/Translate/TranslateStrategyTrait.php +++ b/Behavior/Translate/TranslateStrategyTrait.php @@ -186,7 +186,8 @@ public function buildMarshalMap(Marshaller $marshaller, array $map, array $optio /** * Unsets the temporary `_i18n` property after the entity has been saved * - * @param \Cake\Event\EventInterface $event The beforeSave event that was fired + * @template TSubject of object + * @param \Cake\Event\EventInterface $event The beforeSave event that was fired * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved * @return void */ diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 9d43c8a6..82970468 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -96,7 +96,8 @@ public function initialize(array $config): void * Transparently manages setting the lft and rght fields if the parent field is * included in the parameters to be saved. * - * @param \Cake\Event\EventInterface $event The beforeSave event that was fired + * @template TSubject of object + * @param \Cake\Event\EventInterface $event The beforeSave event that was fired * @param \Cake\Datasource\EntityInterface $entity the entity that is going to be saved * @return void * @throws \Cake\Database\Exception\DatabaseException if the parent to set for the node is invalid @@ -165,7 +166,8 @@ public function beforeSave(EventInterface $event, EntityInterface $entity): void * * Manages updating level of descendants of currently saved entity. * - * @param \Cake\Event\EventInterface $event The afterSave event that was fired + * @template TSubject of object + * @param \Cake\Event\EventInterface $event The afterSave event that was fired * @param \Cake\Datasource\EntityInterface $entity the entity that is going to be saved * @return void */ @@ -219,7 +221,8 @@ protected function _setChildrenLevel(EntityInterface $entity): void /** * Also deletes the nodes in the subtree of the entity to be delete * - * @param \Cake\Event\EventInterface $event The beforeDelete event that was fired + * @template TSubject of object + * @param \Cake\Event\EventInterface $event The beforeDelete event that was fired * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved * @return void */ diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index 53d7c33d..2510e515 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -224,7 +224,7 @@ public function setResult(iterable $results) * iterated without having to call execute() manually, thus making it look like * a result set instead of the query itself. * - * @return \Cake\Datasource\ResultSetInterface + * @return \Cake\Datasource\ResultSetInterface */ public function getIterator(): ResultSetInterface { From 6b9e9491841c78208cfe513bd5d01196375f62ca Mon Sep 17 00:00:00 2001 From: Mark Story Date: Wed, 30 Nov 2022 00:36:21 -0500 Subject: [PATCH 1859/2059] Fix more phpstan errors. --- Behavior/Translate/TranslateStrategyInterface.php | 2 +- BehaviorRegistry.php | 2 ++ Query/QueryFactory.php | 2 +- Query/SelectQuery.php | 12 +++++++----- ResultSetFactory.php | 4 +++- Table.php | 5 +++-- 6 files changed, 17 insertions(+), 10 deletions(-) diff --git a/Behavior/Translate/TranslateStrategyInterface.php b/Behavior/Translate/TranslateStrategyInterface.php index 9cf71094..083ee569 100644 --- a/Behavior/Translate/TranslateStrategyInterface.php +++ b/Behavior/Translate/TranslateStrategyInterface.php @@ -80,7 +80,7 @@ public function translationField(string $field): string; * Modifies the results from a table find in order to merge full translation records * into each entity under the `_translations` key * - * @param \Cake\Datasource\ResultSetInterface $results Results to modify. + * @param \Cake\Datasource\ResultSetInterface<\Cake\Datasource\EntityInterface|array> $results Results to modify. * @return \Cake\Collection\CollectionInterface */ public function groupTranslations(ResultSetInterface $results): CollectionInterface; diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index d7eef4e1..64fb8301 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -32,6 +32,8 @@ * This class also provides method for checking and dispatching behavior methods. * * @extends \Cake\Core\ObjectRegistry<\Cake\ORM\Behavior> + * @implements \Cake\Event\EventDispatcherInterface<\Cake\ORM\Table> + * @template-uses \Cake\Event\EventDispatcherTrait<\Cake\ORM\Table> */ class BehaviorRegistry extends ObjectRegistry implements EventDispatcherInterface { diff --git a/Query/QueryFactory.php b/Query/QueryFactory.php index b9d04993..d92c800e 100644 --- a/Query/QueryFactory.php +++ b/Query/QueryFactory.php @@ -27,7 +27,7 @@ class QueryFactory * Create a new Query instance. * * @param \Cake\ORM\Table $table The table this query is starting on. - * @return \Cake\ORM\Query\SelectQuery + * @return \Cake\ORM\Query\SelectQuery */ public function select(Table $table): SelectQuery { diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index 2510e515..3a2f0aec 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -43,6 +43,8 @@ * loading, automatic fields selection, automatic type casting and to wrap results * into a specific iterator that will be responsible for hydrating results if * required. + * + * @extends \Cake\Database\Query\SelectQuery<\Cake\Datasource\EntityInterface|mixed> */ class SelectQuery extends DbSelectQuery implements JsonSerializable, QueryInterface { @@ -141,7 +143,7 @@ class SelectQuery extends DbSelectQuery implements JsonSerializable, QueryInterf /** * Resultset factory * - * @var \Cake\ORM\ResultSetFactory + * @var \Cake\ORM\ResultSetFactory<\Cake\Datasource\EntityInterface|array> */ protected ResultSetFactory $resultSetFactory; @@ -224,7 +226,7 @@ public function setResult(iterable $results) * iterated without having to call execute() manually, thus making it look like * a result set instead of the query itself. * - * @return \Cake\Datasource\ResultSetInterface + * @return \Cake\Datasource\ResultSetInterface */ public function getIterator(): ResultSetInterface { @@ -362,7 +364,7 @@ public function aliasFields(array $fields, ?string $defaultAlias = null): array * ResultSetDecorator is a traversable object that implements the methods found * on Cake\Collection\Collection. * - * @return \Cake\Datasource\ResultSetInterface + * @return \Cake\Datasource\ResultSetInterface<\Cake\Datasource\EntityInterface|mixed> */ public function all(): ResultSetInterface { @@ -723,7 +725,7 @@ public function applyOptions(array $options) * Decorates the results iterator with MapReduce routines and formatters * * @param iterable $result Original results - * @return \Cake\Datasource\ResultSetInterface + * @return \Cake\Datasource\ResultSetInterface<\Cake\Datasource\EntityInterface|mixed> */ protected function _decorateResults(iterable $result): ResultSetInterface { @@ -1740,7 +1742,7 @@ public function __debugInfo(): array * * Part of JsonSerializable interface. * - * @return \Cake\Datasource\ResultSetInterface The data to convert to JSON. + * @return \Cake\Datasource\ResultSetInterface The data to convert to JSON. */ public function jsonSerialize(): ResultSetInterface { diff --git a/ResultSetFactory.php b/ResultSetFactory.php index 709f630b..4492d899 100644 --- a/ResultSetFactory.php +++ b/ResultSetFactory.php @@ -25,6 +25,8 @@ * * It is responsible for correctly nesting result keys reported from the query * and hydrating entities. + * + * @template T of array|\Cake\Datasource\EntityInterface */ class ResultSetFactory { @@ -33,7 +35,7 @@ class ResultSetFactory * * @param \Cake\ORM\Query\SelectQuery $query Query from where results came. * @param array $results Results array. - * @return \Cake\ORM\ResultSet + * @return \Cake\ORM\ResultSet */ public function createResultSet(SelectQuery $query, array $results): ResultSet { diff --git a/Table.php b/Table.php index 7358ad34..7c38cb55 100644 --- a/Table.php +++ b/Table.php @@ -149,6 +149,7 @@ * * @see \Cake\Event\EventManager for reference on the events system. * @link https://book.cakephp.org/4/en/orm/table-objects.html#event-list + * @implements \Cake\Event\EventDispatcherInterface<\Cake\View\View> */ class Table implements RepositoryInterface, EventListenerInterface, EventDispatcherInterface, ValidatorAwareInterface { @@ -1614,7 +1615,7 @@ public function findOrCreate( /** * Performs the actual find and/or create of an entity based on the passed options. * - * @param \Cake\ORM\Query\SelectQuery |callable|array $search The criteria to find an existing record by, or a callable tha will + * @param \Cake\ORM\Query\SelectQuery|callable|array $search The criteria to find an existing record by, or a callable tha will * customize the find query. * @param callable|null $callback A callback that will be invoked for newly * created entities. This callback will be called *before* the entity @@ -1658,7 +1659,7 @@ protected function _processFindOrCreate( /** * Gets the query object for findOrCreate(). * - * @param \Cake\ORM\Query\SelectQuery |callable|array $search The criteria to find existing records by. + * @param \Cake\ORM\Query\SelectQuery|callable|array $search The criteria to find existing records by. * @return \Cake\ORM\Query\SelectQuery */ protected function _getFindOrCreateQuery(SelectQuery|callable|array $search): SelectQuery From 9fccb45bdd5fa9b2fe6fe411c46dbce754a5f9fe Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Wed, 30 Nov 2022 22:42:56 +0100 Subject: [PATCH 1860/2059] fix duplicate InvalidArgumentException --- ResultSetFactory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ResultSetFactory.php b/ResultSetFactory.php index 4492d899..7d1f5486 100644 --- a/ResultSetFactory.php +++ b/ResultSetFactory.php @@ -33,7 +33,7 @@ class ResultSetFactory /** * Constructor * - * @param \Cake\ORM\Query\SelectQuery $query Query from where results came. + * @param \Cake\ORM\Query\SelectQuery $query Query from where results came. * @param array $results Results array. * @return \Cake\ORM\ResultSet */ From 62e61a8978d6525270eb8c341a858dec19266d70 Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Thu, 1 Dec 2022 09:01:36 +0100 Subject: [PATCH 1861/2059] specify controller and table generics in components and behaviors --- Behavior/CounterCacheBehavior.php | 15 +++++---------- Behavior/TimestampBehavior.php | 3 +-- Behavior/Translate/EavStrategy.php | 6 ++---- Behavior/Translate/ShadowTableStrategy.php | 6 ++---- Behavior/Translate/TranslateStrategyInterface.php | 9 +++------ Behavior/Translate/TranslateStrategyTrait.php | 3 +-- Behavior/TreeBehavior.php | 9 +++------ 7 files changed, 17 insertions(+), 34 deletions(-) diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index ada50e57..84e3bff5 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -115,8 +115,7 @@ class CounterCacheBehavior extends Behavior * * Check if a field, which should be ignored, is dirty * - * @template TSubject of object - * @param \Cake\Event\EventInterface $event The beforeSave event that was fired + * @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event The beforeSave event that was fired * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved * @param \ArrayObject $options The options for the query * @return void @@ -154,8 +153,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array * * Makes sure to update counter cache when a new record is created or updated. * - * @template TSubject of object - * @param \Cake\Event\EventInterface $event The afterSave event that was fired. + * @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event The afterSave event that was fired. * @param \Cake\Datasource\EntityInterface $entity The entity that was saved. * @param \ArrayObject $options The options for the query * @return void @@ -175,8 +173,7 @@ public function afterSave(EventInterface $event, EntityInterface $entity, ArrayO * * Makes sure to update counter cache when a record is deleted. * - * @template TSubject of object - * @param \Cake\Event\EventInterface $event The afterDelete event that was fired. + * @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event The afterDelete event that was fired. * @param \Cake\Datasource\EntityInterface $entity The entity that was deleted. * @param \ArrayObject $options The options for the query * @return void @@ -193,8 +190,7 @@ public function afterDelete(EventInterface $event, EntityInterface $entity, Arra /** * Iterate all associations and update counter caches. * - * @template TSubject of object - * @param \Cake\Event\EventInterface $event Event instance. + * @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event Event instance. * @param \Cake\Datasource\EntityInterface $entity Entity. * @return void */ @@ -209,8 +205,7 @@ protected function _processAssociations(EventInterface $event, EntityInterface $ /** * Updates counter cache for a single association * - * @template TSubject of object - * @param \Cake\Event\EventInterface $event Event instance. + * @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event Event instance. * @param \Cake\Datasource\EntityInterface $entity Entity * @param \Cake\ORM\Association $assoc The association object * @param array $settings The settings for counter cache for this association diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index e7c9a87f..80013677 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -87,8 +87,7 @@ public function initialize(array $config): void /** * There is only one event handler, it can be configured to be called for any event * - * @template TSubject of object - * @param \Cake\Event\EventInterface $event Event instance. + * @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event Event instance. * @param \Cake\Datasource\EntityInterface $entity Entity instance. * @throws \UnexpectedValueException if a field's when value is misdefined * @return true Returns true irrespective of the behavior logic, the save will not be prevented. diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index b3253495..9e03470d 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -160,8 +160,7 @@ protected function setupAssociations(): void * table. It modifies the passed query by eager loading the translated fields * and adding a formatter to copy the values into the main table records. * - * @template TSubject of object - * @param \Cake\Event\EventInterface $event The beforeFind event that was fired. + * @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event The beforeFind event that was fired. * @param \Cake\ORM\Query\SelectQuery $query Query * @param \ArrayObject $options The options for the query * @return void @@ -228,8 +227,7 @@ public function beforeFind(EventInterface $event, SelectQuery $query, ArrayObjec * Modifies the entity before it is saved so that translated fields are persisted * in the database too. * - * @template TSubject of object - * @param \Cake\Event\EventInterface $event The beforeSave event that was fired + * @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event The beforeSave event that was fired * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved * @param \ArrayObject $options the options passed to the save method * @return void diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 260e30bd..60d84616 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -118,8 +118,7 @@ protected function setupAssociations(): void * table. It modifies the passed query by eager loading the translated fields * and adding a formatter to copy the values into the main table records. * - * @template TSubject of object - * @param \Cake\Event\EventInterface $event The beforeFind event that was fired. + * @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event The beforeFind event that was fired. * @param \Cake\ORM\Query\SelectQuery $query Query. * @param \ArrayObject $options The options for the query. * @return void @@ -340,8 +339,7 @@ function ($expression) use ($fields, $alias, $mainTableAlias, $mainTableFields, * Modifies the entity before it is saved so that translated fields are persisted * in the database too. * - * @template TSubject of object - * @param \Cake\Event\EventInterface $event The beforeSave event that was fired. + * @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event The beforeSave event that was fired. * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved. * @param \ArrayObject $options the options passed to the save method. * @return void diff --git a/Behavior/Translate/TranslateStrategyInterface.php b/Behavior/Translate/TranslateStrategyInterface.php index 083ee569..bd39605d 100644 --- a/Behavior/Translate/TranslateStrategyInterface.php +++ b/Behavior/Translate/TranslateStrategyInterface.php @@ -90,8 +90,7 @@ public function groupTranslations(ResultSetInterface $results): CollectionInterf * table. It modifies the passed query by eager loading the translated fields * and adding a formatter to copy the values into the main table records. * - * @template TSubject of object - * @param \Cake\Event\EventInterface $event The beforeFind event that was fired. + * @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event The beforeFind event that was fired. * @param \Cake\ORM\Query\SelectQuery $query Query * @param \ArrayObject $options The options for the query * @return void @@ -102,8 +101,7 @@ public function beforeFind(EventInterface $event, SelectQuery $query, ArrayObjec * Modifies the entity before it is saved so that translated fields are persisted * in the database too. * - * @template TSubject of object - * @param \Cake\Event\EventInterface $event The beforeSave event that was fired + * @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event The beforeSave event that was fired * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved * @param \ArrayObject $options the options passed to the save method * @return void @@ -113,8 +111,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array /** * Unsets the temporary `_i18n` property after the entity has been saved * - * @template TSubject of object - * @param \Cake\Event\EventInterface $event The beforeSave event that was fired + * @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event The beforeSave event that was fired * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved * @return void */ diff --git a/Behavior/Translate/TranslateStrategyTrait.php b/Behavior/Translate/TranslateStrategyTrait.php index f0d81e7a..be9b1f95 100644 --- a/Behavior/Translate/TranslateStrategyTrait.php +++ b/Behavior/Translate/TranslateStrategyTrait.php @@ -186,8 +186,7 @@ public function buildMarshalMap(Marshaller $marshaller, array $map, array $optio /** * Unsets the temporary `_i18n` property after the entity has been saved * - * @template TSubject of object - * @param \Cake\Event\EventInterface $event The beforeSave event that was fired + * @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event The beforeSave event that was fired * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved * @return void */ diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 82970468..95924803 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -96,8 +96,7 @@ public function initialize(array $config): void * Transparently manages setting the lft and rght fields if the parent field is * included in the parameters to be saved. * - * @template TSubject of object - * @param \Cake\Event\EventInterface $event The beforeSave event that was fired + * @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event The beforeSave event that was fired * @param \Cake\Datasource\EntityInterface $entity the entity that is going to be saved * @return void * @throws \Cake\Database\Exception\DatabaseException if the parent to set for the node is invalid @@ -166,8 +165,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity): void * * Manages updating level of descendants of currently saved entity. * - * @template TSubject of object - * @param \Cake\Event\EventInterface $event The afterSave event that was fired + * @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event The afterSave event that was fired * @param \Cake\Datasource\EntityInterface $entity the entity that is going to be saved * @return void */ @@ -221,8 +219,7 @@ protected function _setChildrenLevel(EntityInterface $entity): void /** * Also deletes the nodes in the subtree of the entity to be delete * - * @template TSubject of object - * @param \Cake\Event\EventInterface $event The beforeDelete event that was fired + * @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event The beforeDelete event that was fired * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved * @return void */ From c401abdd9a8cca03a1d56efac23cc1aa5b77c334 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 1 Dec 2022 10:26:04 -0500 Subject: [PATCH 1862/2059] Fix more annotation types --- BehaviorRegistry.php | 1 - Table.php | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 64fb8301..c74d16a9 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -33,7 +33,6 @@ * * @extends \Cake\Core\ObjectRegistry<\Cake\ORM\Behavior> * @implements \Cake\Event\EventDispatcherInterface<\Cake\ORM\Table> - * @template-uses \Cake\Event\EventDispatcherTrait<\Cake\ORM\Table> */ class BehaviorRegistry extends ObjectRegistry implements EventDispatcherInterface { diff --git a/Table.php b/Table.php index 7c38cb55..591ca7ae 100644 --- a/Table.php +++ b/Table.php @@ -149,7 +149,7 @@ * * @see \Cake\Event\EventManager for reference on the events system. * @link https://book.cakephp.org/4/en/orm/table-objects.html#event-list - * @implements \Cake\Event\EventDispatcherInterface<\Cake\View\View> + * @implements \Cake\Event\EventDispatcherInterface<\Cake\ORM\Table> */ class Table implements RepositoryInterface, EventListenerInterface, EventDispatcherInterface, ValidatorAwareInterface { From 30e3a125875ab3e472a527e0484701afcff12bde Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Tue, 27 Dec 2022 16:02:15 +0100 Subject: [PATCH 1863/2059] add use phpdoc to EventDispatcherTrait usages --- BehaviorRegistry.php | 3 +++ Table.php | 3 +++ 2 files changed, 6 insertions(+) diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index c74d16a9..1eecba6a 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -36,6 +36,9 @@ */ class BehaviorRegistry extends ObjectRegistry implements EventDispatcherInterface { + /** + * @use \Cake\Event\EventDispatcherTrait<\Cake\ORM\Table> + */ use EventDispatcherTrait; /** diff --git a/Table.php b/Table.php index 591ca7ae..a8e2e870 100644 --- a/Table.php +++ b/Table.php @@ -153,6 +153,9 @@ */ class Table implements RepositoryInterface, EventListenerInterface, EventDispatcherInterface, ValidatorAwareInterface { + /** + * @use \Cake\Event\EventDispatcherTrait<\Cake\ORM\Table> + */ use EventDispatcherTrait; use RulesAwareTrait; use ValidatorAwareTrait; From 7ebdfc530ad660a368de5c49533014673e92d914 Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Tue, 27 Dec 2022 16:04:00 +0100 Subject: [PATCH 1864/2059] fix cs --- Query/SelectQuery.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index 3a2f0aec..153c1930 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -226,7 +226,7 @@ public function setResult(iterable $results) * iterated without having to call execute() manually, thus making it look like * a result set instead of the query itself. * - * @return \Cake\Datasource\ResultSetInterface + * @return \Cake\Datasource\ResultSetInterface<(\Cake\ORM\Query\Cake\Datasource\EntityInterface|mixed)> */ public function getIterator(): ResultSetInterface { @@ -1742,7 +1742,7 @@ public function __debugInfo(): array * * Part of JsonSerializable interface. * - * @return \Cake\Datasource\ResultSetInterface The data to convert to JSON. + * @return \Cake\Datasource\ResultSetInterface<(\Cake\ORM\Query\Cake\Datasource\EntityInterface|mixed)> The data to convert to JSON. */ public function jsonSerialize(): ResultSetInterface { From 6ca224f5c638de919fe06b46822eed2a1fb9c322 Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Tue, 27 Dec 2022 17:26:32 +0100 Subject: [PATCH 1865/2059] add more generics --- Query/QueryFactory.php | 2 +- Query/SelectQuery.php | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Query/QueryFactory.php b/Query/QueryFactory.php index d92c800e..b9d04993 100644 --- a/Query/QueryFactory.php +++ b/Query/QueryFactory.php @@ -27,7 +27,7 @@ class QueryFactory * Create a new Query instance. * * @param \Cake\ORM\Table $table The table this query is starting on. - * @return \Cake\ORM\Query\SelectQuery + * @return \Cake\ORM\Query\SelectQuery */ public function select(Table $table): SelectQuery { diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index 153c1930..462eaf19 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -44,7 +44,8 @@ * into a specific iterator that will be responsible for hydrating results if * required. * - * @extends \Cake\Database\Query\SelectQuery<\Cake\Datasource\EntityInterface|mixed> + * @template TSubject of \Cake\Datasource\EntityInterface|array + * @extends \Cake\Database\Query\SelectQuery */ class SelectQuery extends DbSelectQuery implements JsonSerializable, QueryInterface { @@ -226,7 +227,7 @@ public function setResult(iterable $results) * iterated without having to call execute() manually, thus making it look like * a result set instead of the query itself. * - * @return \Cake\Datasource\ResultSetInterface<(\Cake\ORM\Query\Cake\Datasource\EntityInterface|mixed)> + * @return \Cake\Datasource\ResultSetInterface<\Cake\Datasource\EntityInterface|array> */ public function getIterator(): ResultSetInterface { @@ -1742,7 +1743,7 @@ public function __debugInfo(): array * * Part of JsonSerializable interface. * - * @return \Cake\Datasource\ResultSetInterface<(\Cake\ORM\Query\Cake\Datasource\EntityInterface|mixed)> The data to convert to JSON. + * @return \Cake\Datasource\ResultSetInterface<(\Cake\Datasource\EntityInterface|mixed)> The data to convert to JSON. */ public function jsonSerialize(): ResultSetInterface { From 21b0cab1ea39f92656894c9b2062da1bbf87d41a Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Tue, 3 Jan 2023 12:51:52 +0100 Subject: [PATCH 1866/2059] adjust phpdoc --- Query/SelectQuery.php | 5 ++--- ResultSetFactory.php | 2 +- Table.php | 5 +++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index 462eaf19..ac3077f7 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -365,7 +365,7 @@ public function aliasFields(array $fields, ?string $defaultAlias = null): array * ResultSetDecorator is a traversable object that implements the methods found * on Cake\Collection\Collection. * - * @return \Cake\Datasource\ResultSetInterface<\Cake\Datasource\EntityInterface|mixed> + * @return \Cake\Datasource\ResultSetInterface */ public function all(): ResultSetInterface { @@ -1684,14 +1684,13 @@ protected function _addDefaultSelectTypes(): void * * @param string $finder The finder method to use. * @param array $options The options for the finder. - * @return static Returns a modified query. + * @return static Returns a modified query. * @psalm-suppress MoreSpecificReturnType */ public function find(string $finder, array $options = []): static { $table = $this->getRepository(); - /** @psalm-suppress LessSpecificReturnStatement */ return $table->callFinder($finder, $this, $options); } diff --git a/ResultSetFactory.php b/ResultSetFactory.php index 7d1f5486..83e67e85 100644 --- a/ResultSetFactory.php +++ b/ResultSetFactory.php @@ -35,7 +35,7 @@ class ResultSetFactory * * @param \Cake\ORM\Query\SelectQuery $query Query from where results came. * @param array $results Results array. - * @return \Cake\ORM\ResultSet + * @return \Cake\ORM\ResultSet */ public function createResultSet(SelectQuery $query, array $results): ResultSet { diff --git a/Table.php b/Table.php index a8e2e870..af7a8da0 100644 --- a/Table.php +++ b/Table.php @@ -2569,10 +2569,11 @@ public function hasFinder(string $type): bool /** * Calls a finder method and applies it to the passed query. * + * @template TSubject of \Cake\Datasource\EntityInterface|array * @param string $type Name of the finder to be called. - * @param \Cake\ORM\Query\SelectQuery $query The query object to apply the finder options to. + * @param \Cake\ORM\Query\SelectQuery $query The query object to apply the finder options to. * @param array $options List of options to pass to the finder. - * @return \Cake\ORM\Query\SelectQuery + * @return \Cake\ORM\Query\SelectQuery * @throws \BadMethodCallException * @uses findAll() * @uses findList() From 0a62253a379ef392064262fed8a3be8cda14fa07 Mon Sep 17 00:00:00 2001 From: mscherer Date: Sat, 7 Jan 2023 04:32:36 +0100 Subject: [PATCH 1867/2059] Fix up TypeError cases found by PHPStan level 7/8 --- Association/BelongsToMany.php | 10 ++++------ Behavior/CounterCacheBehavior.php | 3 +-- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index d503aded..2b870613 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -786,7 +786,9 @@ protected function _saveLinks(EntityInterface $sourceEntity, array $targetEntiti $junction = $this->junction(); $entityClass = $junction->getEntityClass(); $belongsTo = $junction->getAssociation($target->getAlias()); + /** @var array $foreignKey */ $foreignKey = (array)$this->getForeignKey(); + /** @var array $assocForeignKey */ $assocForeignKey = (array)$belongsTo->getForeignKey(); $targetBindingKey = (array)$belongsTo->getBindingKey(); $bindingKey = (array)$this->getBindingKey(); @@ -798,12 +800,9 @@ protected function _saveLinks(EntityInterface $sourceEntity, array $targetEntiti if (!$joint || !($joint instanceof EntityInterface)) { $joint = new $entityClass([], ['markNew' => true, 'source' => $junctionRegistryAlias]); } - /** @psalm-suppress InvalidScalarArgument */ $sourceKeys = array_combine($foreignKey, $sourceEntity->extract($bindingKey)); - /** @psalm-suppress InvalidScalarArgument */ $targetKeys = array_combine($assocForeignKey, $e->extract($targetBindingKey)); - /** @psalm-suppress InvalidArgument */ $changedKeys = $sourceKeys !== $joint->extract($foreignKey) || $targetKeys !== $joint->extract($assocForeignKey); @@ -1265,11 +1264,11 @@ protected function _diffLinks( $junction = $this->junction(); $target = $this->getTarget(); $belongsTo = $junction->getAssociation($target->getAlias()); + /** @var array $foreignKey */ $foreignKey = (array)$this->getForeignKey(); /** @var array $assocForeignKey */ $assocForeignKey = (array)$belongsTo->getForeignKey(); - /** @var array $keys */ $keys = array_merge($foreignKey, $assocForeignKey); $deletes = $unmatchedEntityKeys = $present = []; @@ -1404,14 +1403,13 @@ protected function _collectJointEntities(EntityInterface $sourceEntity, array $t $belongsTo = $junction->getAssociation($target->getAlias()); $hasMany = $source->getAssociation($junction->getAlias()); + /** @var array $foreignKey */ $foreignKey = (array)$this->getForeignKey(); $foreignKey = array_map(function ($key) { - /** @psalm-suppress PossiblyFalseOperand getForeignKey() returns false */ return $key . ' IS'; }, $foreignKey); $assocForeignKey = (array)$belongsTo->getForeignKey(); $assocForeignKey = array_map(function ($key) { - /** @psalm-suppress PossiblyFalseOperand getForeignKey() returns false */ return $key . ' IS'; }, $assocForeignKey); $sourceKey = $sourceEntity->extract((array)$source->getPrimaryKey()); diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 84e3bff5..fc7019d0 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -218,8 +218,8 @@ protected function _processAssociation( Association $assoc, array $settings ): void { + /** @var array $foreignKeys */ $foreignKeys = (array)$assoc->getForeignKey(); - /** @psalm-suppress InvalidArgument */ $countConditions = $entity->extract($foreignKeys); foreach ($countConditions as $field => $value) { @@ -232,7 +232,6 @@ protected function _processAssociation( $primaryKeys = (array)$assoc->getBindingKey(); $updateConditions = array_combine($primaryKeys, $countConditions); - /** @psalm-suppress InvalidArgument */ $countOriginalConditions = $entity->extractOriginalChanged($foreignKeys); if ($countOriginalConditions !== []) { $updateOriginalConditions = array_combine($primaryKeys, $countOriginalConditions); From 6b03a0395084bddeee1b095e4b8bd05b814b6a41 Mon Sep 17 00:00:00 2001 From: mscherer Date: Tue, 10 Jan 2023 15:03:42 +0100 Subject: [PATCH 1868/2059] Fix up TypeError cases found by PHPStan level 7/8 --- Behavior/CounterCacheBehavior.php | 1 + Behavior/Translate/EavStrategy.php | 6 ++--- Behavior/Translate/ShadowTableStrategy.php | 23 ++++++++++--------- Behavior/TranslateBehavior.php | 2 +- Behavior/TreeBehavior.php | 2 +- BehaviorRegistry.php | 1 + EagerLoader.php | 2 +- LazyEagerLoader.php | 6 +++-- Marshaller.php | 26 ++++++++++++++++------ Query/CommonQueryTrait.php | 2 +- ResultSetFactory.php | 11 ++++----- Table.php | 5 +++-- 12 files changed, 54 insertions(+), 33 deletions(-) diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index fc7019d0..e687f71d 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -128,6 +128,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array foreach ($this->_config as $assoc => $settings) { $assoc = $this->_table->getAssociation($assoc); + /** @var string|int $field */ foreach ($settings as $field => $config) { if (is_int($field)) { continue; diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index 9e03470d..c3c989a7 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -267,7 +267,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array } $primaryKey = (array)$this->table->getPrimaryKey(); - $key = $entity->get(current($primaryKey)); + $key = $entity->get((string)current($primaryKey)); // When we have no key and bundled translations, we // need to mark the entity dirty so the root @@ -385,7 +385,7 @@ protected function rowMapper(CollectionInterface $results, string $locale): Coll $row['_locale'] = $locale; if ($hydrated) { - /** @psalm-suppress PossiblyInvalidMethodCall */ + /** @var \Cake\Datasource\EntityInterface $row */ $row->clean(); } @@ -451,7 +451,7 @@ protected function bundleTranslatedFields(EntityInterface $entity): void $fields = $this->_config['fields']; $primaryKey = (array)$this->table->getPrimaryKey(); - $key = $entity->get(current($primaryKey)); + $key = $entity->get((string)current($primaryKey)); $find = []; $contents = []; diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 51071841..dd4c2d88 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -379,7 +379,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array } $primaryKey = (array)$this->table->getPrimaryKey(); - $id = $entity->get(current($primaryKey)); + $id = $entity->get((string)current($primaryKey)); // When we have no key and bundled translations, we // need to mark the entity dirty so the root @@ -487,21 +487,23 @@ protected function rowMapper(CollectionInterface $results, string $locale): Coll unset($row['translation']); if ($hydrated) { - /** @psalm-suppress PossiblyInvalidMethodCall */ + /** @var \Cake\Datasource\EntityInterface $row */ $row->clean(); } return $row; } - /** @var \Cake\ORM\Entity|array $translation */ + /** @var \Cake\Datasource\EntityInterface|array $translation */ $translation = $row['translation']; - /** - * @psalm-suppress PossiblyInvalidMethodCall - * @psalm-suppress PossiblyInvalidArgument - */ - $keys = $hydrated ? $translation->getVisible() : array_keys($translation); + if ($hydrated) { + /** @var \Cake\Datasource\EntityInterface $translation */ + $keys = $translation->getVisible(); + } else { + /** @var array $translation */ + $keys = array_keys($translation); + } foreach ($keys as $field) { if ($field === 'locale') { @@ -516,10 +518,11 @@ protected function rowMapper(CollectionInterface $results, string $locale): Coll } } + /** @var array $row */ unset($row['translation']); if ($hydrated) { - /** @psalm-suppress PossiblyInvalidMethodCall */ + /** @var \Cake\Datasource\EntityInterface $row */ $row->clean(); } @@ -579,7 +582,7 @@ protected function bundleTranslatedFields(EntityInterface $entity): void } $primaryKey = (array)$this->table->getPrimaryKey(); - $key = $entity->get(current($primaryKey)); + $key = $entity->get((string)current($primaryKey)); foreach ($translations as $lang => $translation) { if (!$translation->id) { diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index d1f9446a..ef150d11 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -353,7 +353,7 @@ public function __call(string $method, array $args): mixed protected function referenceName(Table $table): string { $name = namespaceSplit($table::class); - $name = substr(end($name), 0, -5); + $name = substr((string)end($name), 0, -5); if (empty($name)) { $name = $table->getTable() ?: $table->getAlias(); $name = Inflector::camelize($name); diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 95924803..1e44ad08 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -229,7 +229,7 @@ public function beforeDelete(EventInterface $event, EntityInterface $entity): vo $this->_ensureFields($entity); $left = $entity->get($config['left']); $right = $entity->get($config['right']); - $diff = $right - $left + 1; + $diff = (int)($right - $left + 1); if ($diff > 2) { if ($this->getConfig('cascadeCallbacks')) { diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 1eecba6a..aa8e8c61 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -279,6 +279,7 @@ public function callFinder(string $type, array $args = []): SelectQuery if ($this->hasFinder($type) && $this->has($this->_finderMap[$type][0])) { [$behavior, $callMethod] = $this->_finderMap[$type]; + /** @var callable $callable */ $callable = [$this->_loaded[$behavior], $callMethod]; return $callable(...$args); diff --git a/EagerLoader.php b/EagerLoader.php index afac2d75..71e96cfa 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -785,8 +785,8 @@ protected function _collectKeys(array $external, SelectQuery $query, array $resu $alias = $source->getAlias(); $pkFields = []; + /** @var string $key */ foreach ($keys as $key) { - /** @psalm-suppress PossiblyFalseArgument getForeignKey() returns false */ $pkFields[] = key($query->aliasField($key, $alias)); } $collectKeys[$meta->aliasPath()] = [$alias, $pkFields, count($pkFields) === 1]; diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index e720d382..f336520a 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -59,6 +59,7 @@ public function loadInto(EntityInterface|array $entities, array $contain, Table $entities = $this->_injectResults($entities, $query, $associations, $source); + /** @var \Cake\Datasource\EntityInterface|array<\Cake\Datasource\EntityInterface> */ return $returnSingle ? array_shift($entities) : $entities; } @@ -120,8 +121,9 @@ protected function _getPropertyMap(Table $source, array $associations): array $map = []; $container = $source->associations(); foreach ($associations as $assoc) { - /** @psalm-suppress PossiblyNullReference */ - $map[$assoc] = $container->get($assoc)->getProperty(); + /** @var \Cake\ORM\Association $association */ + $association = $container->get($assoc); + $map[$assoc] = $association->getProperty(); } return $map; diff --git a/Marshaller.php b/Marshaller.php index ba9a747b..f792a908 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -90,8 +90,8 @@ protected function _buildPropertyMap(array $data, array $options): array } // If the key is not a special field like _ids or _joinData // it is a missing association that we should error on. - if (!$this->_table->hasAssociation($key)) { - if (!str_starts_with($key, '_')) { + if (!$this->_table->hasAssociation((string)$key)) { + if (!str_starts_with((string)$key, '_')) { throw new InvalidArgumentException(sprintf( 'Cannot marshal data for `%s` association. It is not associated with `%s`.', (string)$key, @@ -100,7 +100,7 @@ protected function _buildPropertyMap(array $data, array $options): array } continue; } - $assoc = $this->_table->getAssociation($key); + $assoc = $this->_table->getAssociation((string)$key); if (isset($options['forceNew'])) { $nested['forceNew'] = $options['forceNew']; @@ -165,7 +165,7 @@ protected function _buildPropertyMap(array $data, array $options): array * ]); * ``` * - * @param array $data The data to hydrate. + * @param array $data The data to hydrate. * @param array $options List of options * @return \Cake\Datasource\EntityInterface * @see \Cake\ORM\Table::newEntity() @@ -190,6 +190,9 @@ public function one(array $data, array $options = []): EntityInterface $options['isMerge'] = false; $propertyMap = $this->_buildPropertyMap($data, $options); $properties = []; + /** + * @var string $key + */ foreach ($data as $key => $value) { if (!empty($errors[$key])) { if ($entity instanceof InvalidPropertyInterface) { @@ -258,7 +261,7 @@ protected function _validate(array $data, string|bool $validator, bool $isNew): /** * Returns data and options prepared to validate and marshall. * - * @param array $data The data to prepare. + * @param array $data The data to prepare. * @param array $options The options passed to this marshaller. * @return array An array containing prepared data and options. */ @@ -544,6 +547,9 @@ public function merge(EntityInterface $entity, array $data, array $options = []) $options['isMerge'] = true; $propertyMap = $this->_buildPropertyMap($data, $options); $properties = []; + /** + * @var string $key + */ foreach ($data as $key => $value) { if (!empty($errors[$key])) { if ($entity instanceof InvalidPropertyInterface) { @@ -739,10 +745,14 @@ protected function _mergeAssociation( $types = [Association::ONE_TO_ONE, Association::MANY_TO_ONE]; $type = $assoc->type(); if (in_array($type, $types, true)) { + /** @var \Cake\Datasource\EntityInterface $original */ return $marshaller->merge($original, $value, $options); } if ($type === Association::MANY_TO_MANY) { - /** @phpstan-ignore-next-line */ + /** + * @var array<\Cake\Datasource\EntityInterface> $original + * @var \Cake\ORM\Association\BelongsToMany $assoc + */ return $marshaller->_mergeBelongsToMany($original, $assoc, $value, $options); } @@ -757,7 +767,9 @@ protected function _mergeAssociation( } } - /** @psalm-suppress PossiblyInvalidArgument */ + /** + * @var array<\Cake\Datasource\EntityInterface> $original + */ return $marshaller->mergeMany($original, $value, $options); } diff --git a/Query/CommonQueryTrait.php b/Query/CommonQueryTrait.php index 0873f77a..3b824930 100644 --- a/Query/CommonQueryTrait.php +++ b/Query/CommonQueryTrait.php @@ -66,7 +66,7 @@ public function setRepository(RepositoryInterface $repository) { assert( $repository instanceof Table, - '`$repository` must be an instance of Cake\ORM\Table.' + '`$repository` must be an instance of `' . Table::class . '`.' ); $this->_repository = $repository; diff --git a/ResultSetFactory.php b/ResultSetFactory.php index 83e67e85..5fe27b0a 100644 --- a/ResultSetFactory.php +++ b/ResultSetFactory.php @@ -155,20 +155,21 @@ protected function groupResult(array $row, array $data): EntityInterface|array foreach ($data['containAssoc'] as $assoc) { $alias = $assoc['nestKey']; - - if ($assoc['canBeJoined'] && empty($data['fields'][$alias])) { + /** @var bool $canBeJoined */ + $canBeJoined = $assoc['canBeJoined']; + if ($canBeJoined && empty($data['fields'][$alias])) { continue; } $instance = $assoc['instance']; assert($instance instanceof Association); - if (!$assoc['canBeJoined'] && !isset($row[$alias])) { - $results = $instance->defaultRowValue($results, $assoc['canBeJoined']); + if (!$canBeJoined && !isset($row[$alias])) { + $results = $instance->defaultRowValue($results, $canBeJoined); continue; } - if (!$assoc['canBeJoined']) { + if (!$canBeJoined) { $results[$alias] = $row[$alias]; } diff --git a/Table.php b/Table.php index af7a8da0..15f76b95 100644 --- a/Table.php +++ b/Table.php @@ -407,7 +407,7 @@ public function getTable(): string { if ($this->_table === null) { $table = namespaceSplit(static::class); - $table = substr(end($table), 0, -5) ?: $this->_alias; + $table = substr((string)end($table), 0, -5) ?: $this->_alias; if (!$table) { throw new CakeException( 'You must specify either the `alias` or the `table` option for the constructor.' @@ -441,7 +441,7 @@ public function getAlias(): string { if ($this->_alias === null) { $alias = namespaceSplit(static::class); - $alias = substr(end($alias), 0, -5) ?: $this->_table; + $alias = substr((string)end($alias), 0, -5) ?: $this->_table; if (!$alias) { throw new CakeException( 'You must specify either the `alias` or the `table` option for the constructor.' @@ -538,6 +538,7 @@ public function getSchema(): TableSchemaInterface } } + /** @var \Cake\Database\Schema\TableSchemaInterface */ return $this->_schema; } From e339df316832c65a6b7d7f6cf8d26a049a2bdf30 Mon Sep 17 00:00:00 2001 From: mscherer Date: Tue, 10 Jan 2023 19:41:31 +0100 Subject: [PATCH 1869/2059] Fix up TypeError cases found by PHPStan level 7/8 --- Association.php | 4 +++- Association/BelongsTo.php | 5 +++-- Association/BelongsToMany.php | 11 ++++++----- Association/DependentDeleteHelper.php | 5 +++-- Association/HasMany.php | 5 +++-- Association/HasOne.php | 5 +++-- Association/Loader/SelectLoader.php | 2 +- EagerLoader.php | 3 ++- Table.php | 3 +++ 9 files changed, 27 insertions(+), 16 deletions(-) diff --git a/Association.php b/Association.php index 5e0da955..d80f4b48 100644 --- a/Association.php +++ b/Association.php @@ -773,7 +773,9 @@ protected function _appendNotMatching(SelectQuery $query, array $options): void if (!empty($options['negateMatch'])) { $primaryKey = $query->aliasFields((array)$target->getPrimaryKey(), $this->_name); $query->andWhere(function ($exp) use ($primaryKey) { - array_map([$exp, 'isNull'], $primaryKey); + /** @var callable $callable */ + $callable = [$exp, 'isNull']; + array_map($callable, $primaryKey); return $exp; }); diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 26f56781..58c34ba1 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -143,9 +143,10 @@ public function saveAssociated(EntityInterface $entity, array $options = []): En return false; } - /** @psalm-suppress InvalidScalarArgument */ + /** @var array $foreignKeys */ + $foreignKeys = (array)$this->getForeignKey(); $properties = array_combine( - (array)$this->getForeignKey(), + $foreignKeys, $targetEntity->extract((array)$this->getBindingKey()) ); $entity->set($properties, ['guard' => false]); diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 2b870613..28feb637 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -578,13 +578,14 @@ public function cascadeDelete(EntityInterface $entity, array $options = []): boo if (!$this->getDependent()) { return true; } - $foreignKey = (array)$this->getForeignKey(); - $bindingKey = (array)$this->getBindingKey(); + + /** @var array $foreignKeys */ + $foreignKeys = (array)$this->getForeignKey(); + $bindingKeys = (array)$this->getBindingKey(); $conditions = []; - if (!empty($bindingKey)) { - /** @psalm-suppress InvalidScalarArgument */ - $conditions = array_combine($foreignKey, $entity->extract($bindingKey)); + if ($bindingKeys) { + $conditions = array_combine($foreignKeys, $entity->extract($bindingKeys)); } $table = $this->junction(); diff --git a/Association/DependentDeleteHelper.php b/Association/DependentDeleteHelper.php index e965d5de..c8f82075 100644 --- a/Association/DependentDeleteHelper.php +++ b/Association/DependentDeleteHelper.php @@ -42,8 +42,9 @@ public function cascadeDelete(Association $association, EntityInterface $entity, return true; } $table = $association->getTarget(); - /** @psalm-suppress InvalidArgument */ - $foreignKey = array_map([$association, 'aliasField'], (array)$association->getForeignKey()); + /** @var callable $callable */ + $callable = [$association, 'aliasField']; + $foreignKey = array_map($callable, (array)$association->getForeignKey()); $bindingKey = (array)$association->getBindingKey(); $bindingValue = $entity->extract($bindingKey); if (in_array(null, $bindingValue, true)) { diff --git a/Association/HasMany.php b/Association/HasMany.php index de57eddd..eee41ea1 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -166,9 +166,10 @@ public function saveAssociated(EntityInterface $entity, array $options = []): En throw new InvalidArgumentException($message); } - /** @psalm-suppress InvalidScalarArgument */ + /** @var array $foreignKeys */ + $foreignKeys = (array)$this->getForeignKey(); $foreignKeyReference = array_combine( - (array)$this->getForeignKey(), + $foreignKeys, $entity->extract((array)$this->getBindingKey()) ); diff --git a/Association/HasOne.php b/Association/HasOne.php index 7777588a..961445fb 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -121,9 +121,10 @@ public function saveAssociated(EntityInterface $entity, array $options = []): En return $entity; } - /** @psalm-suppress InvalidScalarArgument */ + /** @var array $foreignKeys */ + $foreignKeys = (array)$this->getForeignKey(); $properties = array_combine( - (array)$this->getForeignKey(), + $foreignKeys, $entity->extract((array)$this->getBindingKey()) ); $targetEntity->set($properties, ['guard' => false]); diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 9a8d0fac..c8f37d5b 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -513,7 +513,7 @@ protected function _resultInjector(SelectQuery $fetchQuery, array $resultMap, ar $sourceKeys = []; foreach ((array)$keys as $key) { $f = $fetchQuery->aliasField($key, $this->sourceAlias); - $sourceKeys[] = key($f); + $sourceKeys[] = (string)key($f); } $nestKey = $options['nestKey']; diff --git a/EagerLoader.php b/EagerLoader.php index 71e96cfa..13c66153 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -691,7 +691,8 @@ public function associationsMap(Table $table): array return $map; } - /** @psalm-suppress PossiblyNullReference */ + assert($this->_matching !== null, 'EagerLoader not available'); + $map = $this->_buildAssociationsMap($map, $this->_matching->normalized($table), true); $map = $this->_buildAssociationsMap($map, $this->normalized($table)); diff --git a/Table.php b/Table.php index 15f76b95..7c22ca2c 100644 --- a/Table.php +++ b/Table.php @@ -340,6 +340,9 @@ public function __construct(array $config = []) $this->queryFactory ??= new QueryFactory(); $this->initialize($config); + + assert($this->_eventManager !== null, 'EventManager not available'); + $this->_eventManager->on($this); $this->dispatchEvent('Model.initialize'); } From e32b9146b44f133ea857a624bae305a2cefb4dff Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 13 Jan 2023 18:46:32 +0530 Subject: [PATCH 1870/2059] Don't call deprecated methods. Refs #16818. --- Query/SelectQuery.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index ac3077f7..2976c73d 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -701,10 +701,10 @@ public function applyOptions(array $options) 'fields' => 'select', 'conditions' => 'where', 'join' => 'join', - 'order' => 'order', + 'order' => 'orderBy', 'limit' => 'limit', 'offset' => 'offset', - 'group' => 'group', + 'group' => 'groupBy', 'having' => 'having', 'contain' => 'contain', 'page' => 'page', From 777085ea2e3368a1b118744f9fc26675913e7877 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Sat, 14 Jan 2023 03:53:24 +0100 Subject: [PATCH 1871/2059] Consolidate code fencing in exception messages. (#16956) Consolidate code fencing in exception messages. --- Association.php | 4 ++-- Association/BelongsToMany.php | 2 +- Association/HasMany.php | 2 +- Behavior/TimestampBehavior.php | 2 +- BehaviorRegistry.php | 4 ++-- Exception/MissingBehaviorException.php | 2 +- Exception/MissingEntityException.php | 2 +- Exception/RolledbackTransactionException.php | 2 +- Table.php | 4 ++-- 9 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Association.php b/Association.php index d80f4b48..086be22f 100644 --- a/Association.php +++ b/Association.php @@ -571,8 +571,8 @@ public function getProperty(): string if (!isset($this->_propertyName)) { $this->_propertyName = $this->_propertyName(); if (in_array($this->_propertyName, $this->_sourceTable->getSchema()->columns(), true)) { - $msg = 'Association property name "%s" clashes with field of same name of table "%s".' . - ' You should explicitly specify the "propertyName" option.'; + $msg = 'Association property name `%s` clashes with field of same name of table `%s`.' . + ' You should explicitly specify the `propertyName` option.'; trigger_error( sprintf($msg, $this->_propertyName, $this->_sourceTable->getTable()), E_USER_WARNING diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 28feb637..99c479ed 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -635,7 +635,7 @@ public function isOwningSide(Table $side): bool public function setSaveStrategy(string $strategy) { if (!in_array($strategy, [self::SAVE_APPEND, self::SAVE_REPLACE], true)) { - $msg = sprintf('Invalid save strategy "%s"', $strategy); + $msg = sprintf('Invalid save strategy `%s`', $strategy); throw new InvalidArgumentException($msg); } diff --git a/Association/HasMany.php b/Association/HasMany.php index eee41ea1..8a8da1dc 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -112,7 +112,7 @@ public function isOwningSide(Table $side): bool public function setSaveStrategy(string $strategy) { if (!in_array($strategy, [self::SAVE_APPEND, self::SAVE_REPLACE], true)) { - $msg = sprintf('Invalid save strategy "%s"', $strategy); + $msg = sprintf('Invalid save strategy `%s`', $strategy); throw new InvalidArgumentException($msg); } diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 80013677..e647eea3 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -220,7 +220,7 @@ protected function _updateField(EntityInterface $entity, string $field, bool $re $type = TypeFactory::build($columnType); assert( $type instanceof DateTimeType, - 'TimestampBehavior only supports columns of type DateTimeType.' + sprintf('TimestampBehavior only supports columns of type `%s`.', DateTimeType::class) ); $class = $type->getDateTimeClassName(); diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index aa8e8c61..f83463f4 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -261,7 +261,7 @@ public function call(string $method, array $args = []): mixed } throw new BadMethodCallException( - sprintf('Cannot call `%s` it does not belong to any attached behavior.', $method) + sprintf('Cannot call `%s`, it does not belong to any attached behavior.', $method) ); } @@ -286,7 +286,7 @@ public function callFinder(string $type, array $args = []): SelectQuery } throw new BadMethodCallException( - sprintf('Cannot call finder "%s" it does not belong to any attached behavior.', $type) + sprintf('Cannot call finder `%s`, it does not belong to any attached behavior.', $type) ); } } diff --git a/Exception/MissingBehaviorException.php b/Exception/MissingBehaviorException.php index 2a524300..a9fd0ade 100644 --- a/Exception/MissingBehaviorException.php +++ b/Exception/MissingBehaviorException.php @@ -24,5 +24,5 @@ class MissingBehaviorException extends CakeException /** * @var string */ - protected string $_messageTemplate = 'Behavior class %s could not be found.'; + protected string $_messageTemplate = 'Behavior class `%s` could not be found.'; } diff --git a/Exception/MissingEntityException.php b/Exception/MissingEntityException.php index 2f0c57f3..902cf6b1 100644 --- a/Exception/MissingEntityException.php +++ b/Exception/MissingEntityException.php @@ -28,5 +28,5 @@ class MissingEntityException extends CakeException /** * @var string */ - protected string $_messageTemplate = 'Entity class %s could not be found.'; + protected string $_messageTemplate = 'Entity class `%s` could not be found.'; } diff --git a/Exception/RolledbackTransactionException.php b/Exception/RolledbackTransactionException.php index b577c327..c542b1fa 100644 --- a/Exception/RolledbackTransactionException.php +++ b/Exception/RolledbackTransactionException.php @@ -24,6 +24,6 @@ class RolledbackTransactionException extends CakeException /** * @var string */ - protected string $_messageTemplate = 'The afterSave event in "%s" is aborting the transaction' + protected string $_messageTemplate = 'The afterSave event in `%s` is aborting the transaction' . ' before the save process is done.'; } diff --git a/Table.php b/Table.php index 7c22ca2c..34221128 100644 --- a/Table.php +++ b/Table.php @@ -2088,7 +2088,7 @@ protected function _insert(EntityInterface $entity, array $data): EntityInterfac $primary = (array)$this->getPrimaryKey(); if (empty($primary)) { $msg = sprintf( - 'Cannot insert row in "%s" table, it has no primary key.', + 'Cannot insert row in `%s` table, it has no primary key.', $this->getTable() ); throw new DatabaseException($msg); @@ -2689,7 +2689,7 @@ public function __call(string $method, array $args): mixed } throw new BadMethodCallException( - sprintf('Unknown method "%s" called on %s', $method, static::class) + sprintf('Unknown method `%s` called on `%s`', $method, static::class) ); } From e72001628ab062bf88b7dc9b1d9bb216c4db1e48 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Wed, 25 Jan 2023 22:09:40 +0100 Subject: [PATCH 1872/2059] Fix up issues detected by IDE (#16977) Fix up issues detected by IDE --- Marshaller.php | 2 +- Table.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index f792a908..4f4e80ff 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -430,7 +430,7 @@ protected function _belongsToMany(BelongsToMany $assoc, array $data, array $opti // Update existing record and child associations if (isset($existing[$key])) { - $records[$i] = $this->merge($existing[$key], $data[$i], $options); + $records[$i] = $this->merge($existing[$key], $row, $options); } } } diff --git a/Table.php b/Table.php index 34221128..b94d03f8 100644 --- a/Table.php +++ b/Table.php @@ -738,7 +738,7 @@ public function getEntityClass(): string */ public function setEntityClass(string $name) { - /** @psalm-var class-string<\Cake\Datasource\EntityInterface>|null */ + /** @var class-string<\Cake\Datasource\EntityInterface>|null $class */ $class = App::className($name, 'Model/Entity'); if ($class === null) { throw new MissingEntityException([$name]); From 40199f4ee1036271f0bcb6a89143a1358698e56f Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Sun, 19 Feb 2023 20:33:23 -0600 Subject: [PATCH 1873/2059] Upgrade to psalm 5.7 --- Table.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Table.php b/Table.php index b94d03f8..3eed0260 100644 --- a/Table.php +++ b/Table.php @@ -1587,7 +1587,7 @@ protected function _transactionCommitted(bool $atomic, bool $primary): bool * transaction (default: true) * - defaults: Whether to use the search criteria as default values for the new entity (default: true) * - * @param \Cake\ORM\Query\SelectQuery |callable|array $search The criteria to find existing + * @param \Cake\ORM\Query\SelectQuery|callable|array $search The criteria to find existing * records by. Note that when you pass a query object you'll have to use * the 2nd arg of the method to modify the entity data before saving. * @param callable|null $callback A callback that will be invoked for newly @@ -2587,6 +2587,7 @@ public function callFinder(string $type, SelectQuery $query, array $options = [] { assert(empty($options) || !array_is_list($options), 'Finder options should be an associative array not a list'); + /** @var array $options */ $query->applyOptions($options); $options = $query->getOptions(); $finder = 'find' . $type; From c3a141542db484c428d122c53ed63b868a468200 Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Tue, 21 Feb 2023 22:09:41 +0100 Subject: [PATCH 1874/2059] 4.next: add more fallbacks to table displayField logic (#17018) * add more fallbacks to table displayfield logic * add regex for secure fields --------- Co-authored-by: Mark Scherer --- Table.php | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 102ed184..dc6f9606 100644 --- a/Table.php +++ b/Table.php @@ -700,14 +700,31 @@ public function setDisplayField($field) public function getDisplayField() { if ($this->_displayField === null) { + $displayFieldFound = false; $schema = $this->getSchema(); $this->_displayField = $this->getPrimaryKey(); - foreach (['title', 'name', 'label'] as $field) { + $expectedFields = ['title', 'name', 'label']; + foreach ($expectedFields as $field) { if ($schema->hasColumn($field)) { $this->_displayField = $field; + $displayFieldFound = true; break; } } + if (!$displayFieldFound) { + foreach ($schema->columns() as $column) { + $columnSchema = $schema->getColumn($column); + if ( + $columnSchema && + $columnSchema['null'] !== true && + $columnSchema['type'] === 'string' && + !preg_match('/pass|token|secret/', $column) + ) { + $this->_displayField = $column; + break; + } + } + } } return $this->_displayField; From 7c263cac4dbc1e1a29e1820024771fcd6da42227 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 22 Feb 2023 04:24:04 +0530 Subject: [PATCH 1875/2059] Reduce cognitive complexity. Update return type. --- Table.php | 47 +++++++++++++++++++++-------------------------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/Table.php b/Table.php index dc6f9606..68dce7af 100644 --- a/Table.php +++ b/Table.php @@ -695,39 +695,34 @@ public function setDisplayField($field) /** * Returns the display field. * - * @return array|string|null + * @return array|string */ public function getDisplayField() { - if ($this->_displayField === null) { - $displayFieldFound = false; - $schema = $this->getSchema(); - $this->_displayField = $this->getPrimaryKey(); - $expectedFields = ['title', 'name', 'label']; - foreach ($expectedFields as $field) { - if ($schema->hasColumn($field)) { - $this->_displayField = $field; - $displayFieldFound = true; - break; - } + if ($this->_displayField !== null) { + return $this->_displayField; + } + + $schema = $this->getSchema(); + foreach (['title', 'name', 'label'] as $field) { + if ($schema->hasColumn($field)) { + return $this->_displayField = $field; } - if (!$displayFieldFound) { - foreach ($schema->columns() as $column) { - $columnSchema = $schema->getColumn($column); - if ( - $columnSchema && - $columnSchema['null'] !== true && - $columnSchema['type'] === 'string' && - !preg_match('/pass|token|secret/', $column) - ) { - $this->_displayField = $column; - break; - } - } + } + + foreach ($schema->columns() as $column) { + $columnSchema = $schema->getColumn($column); + if ( + $columnSchema && + $columnSchema['null'] !== true && + $columnSchema['type'] === 'string' && + !preg_match('/pass|token|secret/', $column) + ) { + return $this->_displayField = $column; } } - return $this->_displayField; + return $this->_displayField = $this->getPrimaryKey(); } /** From 04b6a1bee31de5d29774458c8af1decc04ef62a5 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 22 Feb 2023 09:50:02 +0530 Subject: [PATCH 1876/2059] Use case insensitive check Co-authored-by: othercorey --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 68dce7af..7045ae75 100644 --- a/Table.php +++ b/Table.php @@ -716,7 +716,7 @@ public function getDisplayField() $columnSchema && $columnSchema['null'] !== true && $columnSchema['type'] === 'string' && - !preg_match('/pass|token|secret/', $column) + !preg_match('/pass|token|secret/i', $column) ) { return $this->_displayField = $column; } From 3840b31e2b7fe6dc4f7527ec61ce6743008c6fe4 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 26 Feb 2023 20:57:38 +0530 Subject: [PATCH 1877/2059] Avoid unnecessary (re)assignments. --- Association/BelongsToMany.php | 2 +- Association/Loader/SelectLoader.php | 2 +- Locator/LocatorAwareTrait.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 99c479ed..394d18fc 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -486,7 +486,7 @@ protected function _appendNotMatching(SelectQuery $query, array $options): void if (empty($options['negateMatch'])) { return; } - $options['conditions'] = $options['conditions'] ?? []; + $options['conditions'] ??= []; $junction = $this->junction(); $belongsTo = $junction->getAssociation($this->getSource()->getAlias()); $conds = $belongsTo->_joinCondition(['foreignKey' => $belongsTo->getForeignKey()]); diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index c8f37d5b..b1c36021 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -162,7 +162,7 @@ protected function _buildQuery(array $options): SelectQuery $filter = $options['keys']; $useSubquery = $options['strategy'] === Association::STRATEGY_SUBQUERY; $finder = $this->finder; - $options['fields'] = $options['fields'] ?? []; + $options['fields'] ??= []; $query = $finder(); assert($query instanceof SelectQuery); diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index 1363882b..2a4aed93 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -80,7 +80,7 @@ public function getTableLocator(): LocatorInterface */ public function fetchTable(?string $alias = null, array $options = []): Table { - $alias = $alias ?? $this->defaultTable; + $alias ??= $this->defaultTable; if (empty($alias)) { throw new UnexpectedValueException( 'You must provide an `$alias` or set the `$defaultTable` property to a non empty string.' From a54e5fcebe9bb59cd0015c430112f28283b9063f Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 6 Mar 2023 00:07:19 +0530 Subject: [PATCH 1878/2059] Remove unused variables. --- Association/HasMany.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Association/HasMany.php b/Association/HasMany.php index 8a8da1dc..76fdf101 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -441,6 +441,7 @@ public function replace(EntityInterface $sourceEntity, array $targetEntities, ar $ok = ($result instanceof EntityInterface); if ($ok) { + // phpcs:ignore SlevomatCodingStandard.Variables.UnusedVariable.UnusedVariable $sourceEntity = $result; } $this->setSaveStrategy($saveStrategy); From 353c8b5fb9ef384f3ce8bae7c504891a03707f46 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Wed, 8 Mar 2023 23:26:05 -0500 Subject: [PATCH 1879/2059] Fix up typehints and finish adding remaining methods. --- Query.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Query.php b/Query.php index 0f16a7d0..c4a186e4 100644 --- a/Query.php +++ b/Query.php @@ -51,28 +51,28 @@ * @method \Cake\Collection\CollectionInterface map(callable $c) Modifies each of the results using the callable * @method mixed reduce(callable $c, $zero = null) Folds all the results into a single value using the callable. * @method \Cake\Collection\CollectionInterface extract($field) Extracts a single column from each row - * @method mixed max($field) Returns the maximum value for a single column in all the results. - * @method mixed min($field) Returns the minimum value for a single column in all the results. + * @method mixed max($field, $sort = \SORT_NUMERIC) Returns the maximum value for a single column in all the results. + * @method mixed min($field, $sort = \SORT_NUMERIC) Returns the minimum value for a single column in all the results. * @method \Cake\Collection\CollectionInterface groupBy(callable|string $field) In-memory group all results by the value of a column. * @method \Cake\Collection\CollectionInterface indexBy(callable|string $callback) Returns the results indexed by the value of a column. * @method \Cake\Collection\CollectionInterface countBy(callable|string $field) Returns the number of unique values for a column - * @method float sumOf(callable|string $field) Returns the sum of all values for a single column + * @method int|float sumOf($field = null) Returns the sum of all values for a single column * @method \Cake\Collection\CollectionInterface shuffle() In-memory randomize the order the results are returned * @method \Cake\Collection\CollectionInterface sample(int $size = 10) In-memory shuffle the results and return a subset of them. * @method \Cake\Collection\CollectionInterface take(int $size = 1, int $from = 0) In-memory limit and offset for the query results. * @method \Cake\Collection\CollectionInterface skip(int $howMany) Skips some rows from the start of the query result. * @method mixed last() Return the last row of the query result - * @method \Cake\Collection\CollectionInterface append(array|\Traversable $items) Appends more rows to the result of the query. + * @method \Cake\Collection\CollectionInterface append(mixed $items) Appends more rows to the result of the query. * @method \Cake\Collection\CollectionInterface combine($k, $v, $g = null) Returns the values of the column $v index by column $k, * and grouped by $g. * @method \Cake\Collection\CollectionInterface nest($k, $p, $n = 'children') Creates a tree structure by nesting the values of column $p into that * with the same value for $k using $n as the nesting key. * @method array toArray() Returns a key-value array with the results of this query. * @method array toList() Returns a numerically indexed array with the results of this query. - * @method \Cake\Collection\CollectionInterface stopWhen(callable $c) Returns each row until the callable returns true. - * @method \Cake\Collection\CollectionInterface zip(array|\Traversable $c) Returns the first result of both the query and $c in an array, + * @method \Cake\Collection\CollectionInterface stopWhen(callable|array $c) Returns each row until the callable returns true. + * @method \Cake\Collection\CollectionInterface zip(iterable $c) Returns the first result of both the query and $c in an array, * then the second results and so on. - * @method \Cake\Collection\CollectionInterface zipWith($collections, callable $callable) Returns each of the results out of calling $c + * @method \Cake\Collection\CollectionInterface zipWith(iterable $collections, callable $callable) Returns each of the results out of calling $c * with the first rows of the query and each of the items, then the second rows and so on. * @method \Cake\Collection\CollectionInterface chunk(int $size) Groups the results in arrays of $size rows each. * @method bool isEmpty() Returns true if this query found no results. From e259e04c7d42945883d4ea22ec2327d3d7ac8c6b Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 10 Mar 2023 12:15:58 +0530 Subject: [PATCH 1880/2059] Namespace all functions. Add global versions if not already defined. --- Association.php | 2 ++ Association/BelongsTo.php | 1 + Association/HasOne.php | 1 + AssociationCollection.php | 2 ++ Behavior.php | 1 + Behavior/Translate/ShadowTableStrategy.php | 1 + Behavior/TranslateBehavior.php | 1 + Locator/TableLocator.php | 1 + Marshaller.php | 2 ++ Query.php | 1 + Rule/LinkConstraint.php | 1 + RulesChecker.php | 2 ++ Table.php | 3 +++ 13 files changed, 19 insertions(+) diff --git a/Association.php b/Association.php index 9621dd2c..8601283b 100644 --- a/Association.php +++ b/Association.php @@ -29,6 +29,8 @@ use Closure; use InvalidArgumentException; use RuntimeException; +use function Cake\Core\deprecationWarning; +use function Cake\Core\pluginSplit; /** * An Association is a relationship established between two tables and is used diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 1950b256..ad229448 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -24,6 +24,7 @@ use Cake\Utility\Inflector; use Closure; use RuntimeException; +use function Cake\Core\pluginSplit; /** * Represents an 1 - N relationship where the source side of the relation is diff --git a/Association/HasOne.php b/Association/HasOne.php index b48ea721..746caddf 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -22,6 +22,7 @@ use Cake\ORM\Table; use Cake\Utility\Inflector; use Closure; +use function Cake\Core\pluginSplit; /** * Represents an 1 - 1 relationship where the source side of the relation is diff --git a/AssociationCollection.php b/AssociationCollection.php index 5e7d6ff2..659ad362 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -23,6 +23,8 @@ use InvalidArgumentException; use IteratorAggregate; use Traversable; +use function Cake\Core\namespaceSplit; +use function Cake\Core\pluginSplit; /** * A container/collection for association classes. diff --git a/Behavior.php b/Behavior.php index 31ab1f29..48577088 100644 --- a/Behavior.php +++ b/Behavior.php @@ -21,6 +21,7 @@ use Cake\Event\EventListenerInterface; use ReflectionClass; use ReflectionMethod; +use function Cake\Core\deprecationWarning; /** * Base class for behaviors. diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 3284c3dc..f2384162 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -27,6 +27,7 @@ use Cake\ORM\Query; use Cake\ORM\Table; use Cake\Utility\Hash; +use function Cake\Core\pluginSplit; /** * This class provides a way to translate dynamic data by keeping translations diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index dc0df667..576ea890 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -25,6 +25,7 @@ use Cake\ORM\Query; use Cake\ORM\Table; use Cake\Utility\Inflector; +use function Cake\Core\namespaceSplit; /** * This behavior provides a way to translate dynamic data by keeping translations diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 67a87520..3fefe3f4 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -25,6 +25,7 @@ use Cake\ORM\Table; use Cake\Utility\Inflector; use RuntimeException; +use function Cake\Core\pluginSplit; /** * Provides a default registry/factory for Table objects. diff --git a/Marshaller.php b/Marshaller.php index 7511aafa..9bc04fd5 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -26,6 +26,8 @@ use Cake\Utility\Hash; use InvalidArgumentException; use RuntimeException; +use function Cake\Core\deprecationWarning; +use function Cake\Core\getTypeName; /** * Contains logic to convert array data into entities. diff --git a/Query.php b/Query.php index c4a186e4..3f40d5b6 100644 --- a/Query.php +++ b/Query.php @@ -31,6 +31,7 @@ use JsonSerializable; use RuntimeException; use Traversable; +use function Cake\Core\deprecationWarning; /** * Extends the base Query class to provide new methods related to association diff --git a/Rule/LinkConstraint.php b/Rule/LinkConstraint.php index 37fbdf7d..fe2b1cc7 100644 --- a/Rule/LinkConstraint.php +++ b/Rule/LinkConstraint.php @@ -19,6 +19,7 @@ use Cake\Datasource\EntityInterface; use Cake\ORM\Association; use Cake\ORM\Table; +use function Cake\Core\getTypeName; /** * Checks whether links to a given association exist / do not exist. diff --git a/RulesChecker.php b/RulesChecker.php index 8136b6d8..dd6a8ee4 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -23,6 +23,8 @@ use Cake\ORM\Rule\LinkConstraint; use Cake\ORM\Rule\ValidCount; use Cake\Utility\Inflector; +use function Cake\Core\getTypeName; +use function Cake\I18n\__d; /** * ORM flavoured rules checker. diff --git a/Table.php b/Table.php index 7045ae75..ba058dd8 100644 --- a/Table.php +++ b/Table.php @@ -52,6 +52,9 @@ use InvalidArgumentException; use ReflectionMethod; use RuntimeException; +use function Cake\Core\deprecationWarning; +use function Cake\Core\getTypeName; +use function Cake\Core\namespaceSplit; /** * Represents a single database table. From 8aa1d503e316b49965f5e4bd747a727d72db9ccc Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 11 Apr 2023 22:24:05 +0530 Subject: [PATCH 1881/2059] Remove unnecessary use of collection. --- LazyEagerLoader.php | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index f336520a..c5dab69b 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -16,12 +16,11 @@ */ namespace Cake\ORM; -use Cake\Collection\Collection; -use Cake\Collection\CollectionInterface; use Cake\Database\Expression\QueryExpression; use Cake\Database\Expression\TupleComparison; use Cake\Datasource\EntityInterface; use Cake\ORM\Query\SelectQuery; +use Cake\Utility\Hash; /** * Contains methods that are capable of injecting eagerly loaded associations into @@ -53,7 +52,6 @@ public function loadInto(EntityInterface|array $entities, array $contain, Table $returnSingle = true; } - $entities = new Collection($entities); $query = $this->_getQuery($entities, $contain, $source); $associations = array_keys($query->getContain()); @@ -67,17 +65,17 @@ public function loadInto(EntityInterface|array $entities, array $contain, Table * Builds a query for loading the passed list of entity objects along with the * associations specified in $contain. * - * @param \Cake\Collection\CollectionInterface $objects The original entities + * @param array $entities The original entities * @param array $contain The associations to be loaded * @param \Cake\ORM\Table $source The table to use for fetching the top level entities * @return \Cake\ORM\Query\SelectQuery */ - protected function _getQuery(CollectionInterface $objects, array $contain, Table $source): SelectQuery + protected function _getQuery(array $entities, array $contain, Table $source): SelectQuery { $primaryKey = $source->getPrimaryKey(); $method = is_string($primaryKey) ? 'get' : 'extract'; - $keys = $objects->map(fn (EntityInterface $entity) => $entity->{$method}($primaryKey)); + $keys = Hash::map($entities, '{*}', fn (EntityInterface $entity) => $entity->{$method}($primaryKey)); $query = $source ->find() @@ -88,13 +86,13 @@ protected function _getQuery(CollectionInterface $objects, array $contain, Table } if (is_string($primaryKey)) { - return $exp->in($source->aliasField($primaryKey), $keys->toList()); + return $exp->in($source->aliasField($primaryKey), $keys); } $types = array_intersect_key($q->getDefaultTypes(), array_flip($primaryKey)); $primaryKey = array_map([$source, 'aliasField'], $primaryKey); - return new TupleComparison($primaryKey, $keys->toList(), $types, 'IN'); + return new TupleComparison($primaryKey, $keys, $types, 'IN'); }) ->enableAutoFields() ->contain($contain); @@ -133,28 +131,28 @@ protected function _getPropertyMap(Table $source, array $associations): array * Injects the results of the eager loader query into the original list of * entities. * - * @param iterable<\Cake\Datasource\EntityInterface> $objects The original list of entities - * @param \Cake\ORM\Query\SelectQuery $results The loaded results + * @param array<\Cake\Datasource\EntityInterface> $entities The original list of entities + * @param \Cake\ORM\Query\SelectQuery $query The query to load results * @param array $associations The top level associations that were loaded * @param \Cake\ORM\Table $source The table where the entities came from * @return array<\Cake\Datasource\EntityInterface> */ protected function _injectResults( - iterable $objects, - SelectQuery $results, + array $entities, + SelectQuery $query, array $associations, Table $source ): array { $injected = []; $properties = $this->_getPropertyMap($source, $associations); $primaryKey = (array)$source->getPrimaryKey(); - /** @var array $results */ - $results = $results + /** @var array<\Cake\Datasource\EntityInterface> $results */ + $results = $query ->all() ->indexBy(fn (EntityInterface $e) => implode(';', $e->extract($primaryKey))) ->toArray(); - foreach ($objects as $k => $object) { + foreach ($entities as $k => $object) { $key = implode(';', $object->extract($primaryKey)); if (!isset($results[$key])) { $injected[$k] = $object; From bdcec35eb489d6704ae0217516359429f0543f5a Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Tue, 11 Apr 2023 23:06:49 +0200 Subject: [PATCH 1882/2059] update psalm to v5 --- AssociationCollection.php | 2 ++ Behavior/Translate/ShadowTableStrategy.php | 2 -- Marshaller.php | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/AssociationCollection.php b/AssociationCollection.php index 659ad362..b4fa71cc 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -31,6 +31,8 @@ * * Contains methods for managing associations, and * ordering operations around saving and deleting. + * + * @template-implements \IteratorAggregate */ class AssociationCollection implements IteratorAggregate { diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index f2384162..2408090b 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -267,7 +267,6 @@ function ($c, &$field) use ($fields, $alias, $mainTableAlias, $mainTableFields, return $c; } - /** @psalm-suppress ParadoxicalCondition */ if (in_array($field, $fields, true)) { $joinRequired = true; $field = "$alias.$field"; @@ -324,7 +323,6 @@ function ($expression) use ($fields, $alias, $mainTableAlias, $mainTableFields, return; } - /** @psalm-suppress ParadoxicalCondition */ if (in_array($field, $mainTableFields, true)) { $expression->setField("$mainTableAlias.$field"); } diff --git a/Marshaller.php b/Marshaller.php index 9bc04fd5..2caa7e57 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -661,7 +661,6 @@ public function merge(EntityInterface $entity, array $data, array $options = []) * @param array $options List of options. * @return array<\Cake\Datasource\EntityInterface> * @see \Cake\ORM\Entity::$_accessible - * @psalm-suppress NullArrayOffset */ public function mergeMany(iterable $entities, array $data, array $options = []): array { @@ -757,7 +756,6 @@ protected function _mergeAssociation($original, Association $assoc, $value, arra $types = [Association::ONE_TO_ONE, Association::MANY_TO_ONE]; $type = $assoc->type(); if (in_array($type, $types, true)) { - /** @psalm-suppress PossiblyInvalidArgument, ArgumentTypeCoercion */ return $marshaller->merge($original, $value, $options); } if ($type === Association::MANY_TO_MANY) { From 1f1d6e47ece5a23db5878658807800287f6f1504 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 12 Apr 2023 18:16:49 +0530 Subject: [PATCH 1883/2059] Improve docblock type Co-authored-by: othercorey --- LazyEagerLoader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index c5dab69b..e7ca91f7 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -65,7 +65,7 @@ public function loadInto(EntityInterface|array $entities, array $contain, Table * Builds a query for loading the passed list of entity objects along with the * associations specified in $contain. * - * @param array $entities The original entities + * @param array<\Cake\Datasource\EntityInterface> $entities The original entities * @param array $contain The associations to be loaded * @param \Cake\ORM\Table $source The table to use for fetching the top level entities * @return \Cake\ORM\Query\SelectQuery From 958c3a9d4bc6890177a12ac62916585018c9c932 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Fri, 30 Sep 2022 01:05:37 -0500 Subject: [PATCH 1884/2059] Allow typed finder arguments with or without an $options parameter --- Association.php | 25 ++++++++-- Association/BelongsToMany.php | 26 +++++++++-- BehaviorRegistry.php | 20 ++++++-- Query/SelectQuery.php | 7 +-- Table.php | 86 +++++++++++++++++++++++++++++------ 5 files changed, 136 insertions(+), 28 deletions(-) diff --git a/Association.php b/Association.php index 63bde34f..9300c274 100644 --- a/Association.php +++ b/Association.php @@ -836,17 +836,36 @@ public function defaultRowValue(array $row, bool $joined): array * * @param array|string|null $type the type of query to perform, if an array is passed, * it will be interpreted as the `$options` parameter - * @param array $options The options to for the find + * @param mixed ...$args Arguments that match up to finder-specific parameters * @see \Cake\ORM\Table::find() * @return \Cake\ORM\Query\SelectQuery */ - public function find(array|string|null $type = null, array $options = []): SelectQuery + public function find(array|string|null $type = null, mixed ...$args): SelectQuery { $type = $type ?: $this->getFinder(); [$type, $opts] = $this->_extractFinder($type); + // Find $options in arguments to merge with finder options + $options = null; + if ($args) { + if (array_key_exists(0, $args)) { + $options = &$args[0]; + } elseif (array_key_exists('options', $args)) { + $options = &$args['options']; + } + } + + if ($opts) { + if (is_array($options)) { + /** @psalm-suppress ReferenceReusedFromConfusingScope */ + $options = $options + $opts; + } else { + array_unshift($args, $opts); + } + } + return $this->getTarget() - ->find($type, $options + $opts) + ->find($type, ...$args) ->where($this->getConditions()); } diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 394d18fc..713b6009 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1063,16 +1063,36 @@ protected function junctionConditions(): array * * @param array|string|null $type the type of query to perform, if an array is passed, * it will be interpreted as the `$options` parameter - * @param array $options The options to for the find + * @param mixed ...$args Arguments that match up to finder-specific parameters * @see \Cake\ORM\Table::find() * @return \Cake\ORM\Query\SelectQuery */ - public function find(array|string|null $type = null, array $options = []): SelectQuery + public function find(array|string|null $type = null, mixed ...$args): SelectQuery { $type = $type ?: $this->getFinder(); [$type, $opts] = $this->_extractFinder($type); + + // Find $options in arguments to merge with finder options + $options = null; + if ($args) { + if (array_key_exists(0, $args)) { + $options = &$args[0]; + } elseif (array_key_exists('options', $args)) { + $options = &$args['options']; + } + } + + if ($opts) { + if (is_array($options)) { + /** @psalm-suppress ReferenceReusedFromConfusingScope */ + $options = $options + $opts; + } else { + array_unshift($args, $opts); + } + } + $query = $this->getTarget() - ->find($type, $options + $opts) + ->find($type, ...$args) ->where($this->targetConditions()) ->addDefaultTypes($this->getTarget()); diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index f83463f4..733042a0 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -24,6 +24,7 @@ use Cake\ORM\Exception\MissingBehaviorException; use Cake\ORM\Query\SelectQuery; use LogicException; +use ReflectionFunction; /** * BehaviorRegistry is used as a registry for loaded behaviors and handles loading @@ -268,21 +269,30 @@ public function call(string $method, array $args = []): mixed /** * Invoke a finder on a behavior. * + * @internal * @param string $type The finder type to invoke. - * @param array $args The arguments you want to invoke the method with. + * @param \Cake\ORM\Query\SelectQuery $query The query object to apply the finder options to. + * @param mixed ...$args Arguments that match up to finder-specific parameters * @return \Cake\ORM\Query\SelectQuery The return value depends on the underlying behavior method. * @throws \BadMethodCallException When the method is unknown. */ - public function callFinder(string $type, array $args = []): SelectQuery + public function callFinder(string $type, SelectQuery $query, mixed ...$args): SelectQuery { $type = strtolower($type); if ($this->hasFinder($type) && $this->has($this->_finderMap[$type][0])) { [$behavior, $callMethod] = $this->_finderMap[$type]; - /** @var callable $callable */ - $callable = [$this->_loaded[$behavior], $callMethod]; + $callable = $this->_loaded[$behavior]->$callMethod(...); + + if (!$args) { + $reflected = new ReflectionFunction($callable); + $param = $reflected->getParameters()[1] ?? null; + if ($param?->name === 'options' && !$param->isDefaultValueAvailable()) { + $args = [[]]; + } + } - return $callable(...$args); + return $callable($query, ...$args); } throw new BadMethodCallException( diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index 2976c73d..a98907f0 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -1683,15 +1683,16 @@ protected function _addDefaultSelectTypes(): void * {@inheritDoc} * * @param string $finder The finder method to use. - * @param array $options The options for the finder. + * @param mixed ...$args Arguments that match up to finder-specific parameters * @return static Returns a modified query. * @psalm-suppress MoreSpecificReturnType */ - public function find(string $finder, array $options = []): static + public function find(string $finder, mixed ...$args): static { $table = $this->getRepository(); - return $table->callFinder($finder, $this, $options); + /** @psalm-suppress LessSpecificReturnStatement */ + return $table->callFinder($finder, $this, ...$args); } /** diff --git a/Table.php b/Table.php index bdef2750..69d58175 100644 --- a/Table.php +++ b/Table.php @@ -55,6 +55,7 @@ use Closure; use Exception; use InvalidArgumentException; +use ReflectionMethod; use function Cake\Core\namespaceSplit; /** @@ -1250,21 +1251,54 @@ public function belongsToMany(string $associated, array $options = []): BelongsT * ### Calling finders * * The find() method is the entry point for custom finder methods. - * You can invoke a finder by specifying the type: + * You can invoke a finder by specifying the type. + * + * This will invoke the `findPublished` method: * * ``` * $query = $articles->find('published'); * ``` * - * Would invoke the `findPublished` method. + * ## Typed finder arguments + * + * Finders can have typed parameters separate from the `$options` array and the + * `$options` parameter can be optional if not needed: + * + * ``` + * $query = $articles->find('byCategory', $category); + * ``` + * + * Here, the finder "findByCategory" does not have an `$options` parameter: + * + * ``` + * function findByCategory(SelectQuery $query, int $category): SelectQuery + * { + * return $query; + * } + * ``` + * + * If you need to pass query options, just add the typed arguments after `$options`: + * + * ``` + * $query = $articles->find('byCategory', [...], $category); + * ``` + * + * Here, the finder "findByCategory" does have an `$options` parameter: + * + * ``` + * function findByCategory(SelectQuery $query, array $options, int $category): SelectQuery + * { + * return $query; + * } + * ``` * * @param string $type the type of query to perform - * @param array $options An array that will be passed to Query::applyOptions() + * @param mixed ...$args Arguments that match up to finder-specific parameters * @return \Cake\ORM\Query\SelectQuery The query builder */ - public function find(string $type = 'all', array $options = []): SelectQuery + public function find(string $type = 'all', mixed ...$args): SelectQuery { - return $this->callFinder($type, $this->selectQuery(), $options); + return $this->callFinder($type, $this->selectQuery(), ...$args); } /** @@ -2586,30 +2620,54 @@ public function hasFinder(string $type): bool /** * Calls a finder method and applies it to the passed query. * + * @internal * @template TSubject of \Cake\Datasource\EntityInterface|array * @param string $type Name of the finder to be called. * @param \Cake\ORM\Query\SelectQuery $query The query object to apply the finder options to. - * @param array $options List of options to pass to the finder. + * @param mixed ...$args Arguments that match up to finder-specific parameters * @return \Cake\ORM\Query\SelectQuery * @throws \BadMethodCallException * @uses findAll() * @uses findList() * @uses findThreaded() */ - public function callFinder(string $type, SelectQuery $query, array $options = []): SelectQuery - { - assert(empty($options) || !array_is_list($options), 'Finder options should be an associative array not a list'); + public function callFinder(string $type, SelectQuery $query, mixed ...$args): SelectQuery + { + // Extract and apply $options from arguments + if ($args) { + $options = []; + if (array_key_exists(0, $args)) { + // $options is not a named argument + $options = &$args[0]; + } elseif (array_key_exists('options', $args)) { + // options is a named argument + $options = &$args['options']; + } + + if (is_array($options) && $options) { + assert(!array_is_list($options), '`$options` argument should be an associative array.'); + + $query->applyOptions($options); + /** @psalm-suppress ReferenceReusedFromConfusingScope */ + $options = $query->getOptions(); + } + } - /** @var array $options */ - $query->applyOptions($options); - $options = $query->getOptions(); $finder = 'find' . $type; if (method_exists($this, $finder)) { - return $this->{$finder}($query, $options); + if (!$args) { + $reflected = new ReflectionMethod($this, $finder); + $param = $reflected->getParameters()[1] ?? null; + if ($param?->name === 'options' && !$param->isDefaultValueAvailable()) { + $args = [[]]; + } + } + + return $this->{$finder}($query, ...$args); } if ($this->_behaviors->hasFinder($type)) { - return $this->_behaviors->callFinder($type, [$query, $options]); + return $this->_behaviors->callFinder($type, $query, ...$args); } throw new BadMethodCallException(sprintf( From 154f06d414e840a8d92f224e3c0f6f58d230d5a1 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 15 Apr 2023 22:53:53 +0530 Subject: [PATCH 1885/2059] Drop $options argument from finders. Instead add specific args as required. --- Association.php | 21 +---- Association/BelongsToMany.php | 19 +--- Association/Loader/SelectLoader.php | 2 +- Behavior/CounterCacheBehavior.php | 2 +- Behavior/TranslateBehavior.php | 3 +- Behavior/TreeBehavior.php | 77 ++++++++-------- BehaviorRegistry.php | 11 +-- Table.php | 135 +++++++++++++++------------- 8 files changed, 117 insertions(+), 153 deletions(-) diff --git a/Association.php b/Association.php index 9300c274..90d771a1 100644 --- a/Association.php +++ b/Association.php @@ -721,7 +721,7 @@ public function attachTo(SelectQuery $query, array $options = []): void [$finder, $opts] = $this->_extractFinder($options['finder']); $dummy = $this - ->find($finder, $opts) + ->find($finder, ...$opts) ->eagerLoaded(true); if (!empty($options['queryBuilder'])) { @@ -845,24 +845,7 @@ public function find(array|string|null $type = null, mixed ...$args): SelectQuer $type = $type ?: $this->getFinder(); [$type, $opts] = $this->_extractFinder($type); - // Find $options in arguments to merge with finder options - $options = null; - if ($args) { - if (array_key_exists(0, $args)) { - $options = &$args[0]; - } elseif (array_key_exists('options', $args)) { - $options = &$args['options']; - } - } - - if ($opts) { - if (is_array($options)) { - /** @psalm-suppress ReferenceReusedFromConfusingScope */ - $options = $options + $opts; - } else { - array_unshift($args, $opts); - } - } + $args += $opts; return $this->getTarget() ->find($type, ...$args) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 713b6009..6e651c96 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1072,24 +1072,7 @@ public function find(array|string|null $type = null, mixed ...$args): SelectQuer $type = $type ?: $this->getFinder(); [$type, $opts] = $this->_extractFinder($type); - // Find $options in arguments to merge with finder options - $options = null; - if ($args) { - if (array_key_exists(0, $args)) { - $options = &$args[0]; - } elseif (array_key_exists('options', $args)) { - $options = &$args['options']; - } - } - - if ($opts) { - if (is_array($options)) { - /** @psalm-suppress ReferenceReusedFromConfusingScope */ - $options = $options + $opts; - } else { - array_unshift($args, $opts); - } - } + $args += $opts; $query = $this->getTarget() ->find($type, ...$args) diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index b1c36021..07490fbb 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -168,7 +168,7 @@ protected function _buildQuery(array $options): SelectQuery assert($query instanceof SelectQuery); if (isset($options['finder'])) { [$finderName, $opts] = $this->_extractFinder($options['finder']); - $query = $query->find($finderName, $opts); + $query = $query->find($finderName, ...$opts); } $fetchQuery = $query diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index e687f71d..1fb68114 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -304,7 +304,7 @@ protected function _getCount(array $config, array $conditions): int } $config['conditions'] = array_merge($conditions, $config['conditions'] ?? []); - $query = $this->_table->find($finder, $config); + $query = $this->_table->find($finder, ...$config); return $query->count(); } diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index f84b49b1..52c39252 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -312,9 +312,8 @@ public function translationField(string $field): string * @param array $options Options * @return \Cake\ORM\Query\SelectQuery */ - public function findTranslations(SelectQuery $query, array $options): SelectQuery + public function findTranslations(SelectQuery $query, array $locales = []): SelectQuery { - $locales = $options['locales'] ?? []; $targetAlias = $this->getStrategy()->getTranslationTable()->getAlias(); return $query diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 1e44ad08..074f09db 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -28,7 +28,7 @@ use Cake\ORM\Query\DeleteQuery; use Cake\ORM\Query\SelectQuery; use Cake\ORM\Query\UpdateQuery; -use InvalidArgumentException; +use Closure; /** * Makes the table to which this is attached to behave like a nested set and @@ -197,11 +197,12 @@ protected function _setChildrenLevel(EntityInterface $entity): void $depths = [$primaryKeyValue => $entity->get($config['level'])]; /** @var \Traversable<\Cake\Datasource\EntityInterface> $children */ - $children = $this->_table->find('children', [ - 'for' => $primaryKeyValue, - 'fields' => [$this->_getPrimaryKey(), $config['parent'], $config['level']], - 'order' => $config['left'], - ]) + $children = $this->_table->find( + 'children', + for: $primaryKeyValue, + fields: [$this->_getPrimaryKey(), $config['parent'], $config['level']], + order: $config['left'], + ) ->all(); foreach ($children as $node) { @@ -388,12 +389,8 @@ function (QueryExpression $exp) use ($config) { * @return \Cake\ORM\Query\SelectQuery * @throws \InvalidArgumentException If the 'for' key is missing in options */ - public function findPath(SelectQuery $query, array $options): SelectQuery + public function findPath(SelectQuery $query, string|int $for): SelectQuery { - if (empty($options['for'])) { - throw new InvalidArgumentException('The "for" key is required for find(\'path\').'); - } - $config = $this->getConfig(); [$left, $right] = array_map( function ($field) { @@ -402,7 +399,7 @@ function ($field) { [$config['left'], $config['right']] ); - $node = $this->_table->get($options['for'], ['fields' => [$left, $right]]); + $node = $this->_table->get($for, ['fields' => [$left, $right]]); return $this->_scope($query) ->where([ @@ -452,10 +449,9 @@ public function childCount(EntityInterface $node, bool $direct = false): int * @return \Cake\ORM\Query\SelectQuery * @throws \InvalidArgumentException When the 'for' key is not passed in $options */ - public function findChildren(SelectQuery $query, array $options): SelectQuery + public function findChildren(SelectQuery $query, int|string $for, bool $direct = false): SelectQuery { $config = $this->getConfig(); - $options += ['for' => null, 'direct' => false]; [$parent, $left, $right] = array_map( function ($field) { return $this->_table->aliasField($field); @@ -463,12 +459,6 @@ function ($field) { [$config['parent'], $config['left'], $config['right']] ); - [$for, $direct] = [$options['for'], $options['direct']]; - - if (empty($for)) { - throw new InvalidArgumentException('The "for" key is required for find(\'children\').'); - } - if ($query->clause('order') === null) { $query->orderBy([$left => 'ASC']); } @@ -503,17 +493,18 @@ function ($field) { * @param array $options Array of options as described above. * @return \Cake\ORM\Query\SelectQuery */ - public function findTreeList(SelectQuery $query, array $options): SelectQuery - { + public function findTreeList( + SelectQuery $query, + Closure|string|null $keyPath = null, + Closure|string|null $valuePath = null, + ?string $spacer = null + ): SelectQuery { $left = $this->_table->aliasField($this->getConfig('left')); $results = $this->_scope($query) - ->find('threaded', [ - 'parentField' => $this->getConfig('parent'), - 'order' => [$left => 'ASC'], - ]); + ->find('threaded', parentField: $this->getConfig('parent'), order: [$left => 'ASC']); - return $this->formatTreeList($results, $options); + return $this->formatTreeList($results, $keyPath, $valuePath, $spacer); } /** @@ -533,20 +524,24 @@ public function findTreeList(SelectQuery $query, array $options): SelectQuery * @param array $options Array of options as described above. * @return \Cake\ORM\Query\SelectQuery Augmented query. */ - public function formatTreeList(SelectQuery $query, array $options = []): SelectQuery - { - return $query->formatResults(function (CollectionInterface $results) use ($options) { - $options += [ - 'keyPath' => $this->_getPrimaryKey(), - 'valuePath' => $this->_table->getDisplayField(), - 'spacer' => '_', - ]; - - $nested = $results->listNested(); - assert($nested instanceof TreeIterator); - - return $nested->printer($options['valuePath'], $options['keyPath'], $options['spacer']); - }); + public function formatTreeList( + SelectQuery $query, + Closure|string|null $keyPath = null, + Closure|string|null $valuePath = null, + ?string $spacer = null + ): SelectQuery { + return $query->formatResults( + function (CollectionInterface $results) use ($keyPath, $valuePath, $spacer) { + $keyPath ??= $this->_getPrimaryKey(); + $valuePath ??= $this->_table->getDisplayField(); + $spacer ??= '_'; + + $nested = $results->listNested(); + assert($nested instanceof TreeIterator); + + return $nested->printer($valuePath, $keyPath, $spacer); + } + ); } /** diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 733042a0..cfa940a8 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -24,7 +24,6 @@ use Cake\ORM\Exception\MissingBehaviorException; use Cake\ORM\Query\SelectQuery; use LogicException; -use ReflectionFunction; /** * BehaviorRegistry is used as a registry for loaded behaviors and handles loading @@ -284,15 +283,7 @@ public function callFinder(string $type, SelectQuery $query, mixed ...$args): Se [$behavior, $callMethod] = $this->_finderMap[$type]; $callable = $this->_loaded[$behavior]->$callMethod(...); - if (!$args) { - $reflected = new ReflectionFunction($callable); - $param = $reflected->getParameters()[1] ?? null; - if ($param?->name === 'options' && !$param->isDefaultValueAvailable()) { - $args = [[]]; - } - } - - return $callable($query, ...$args); + return $this->_table->invokeFinder($callable, $query, $args); } throw new BadMethodCallException( diff --git a/Table.php b/Table.php index 69d58175..e6a8dd37 100644 --- a/Table.php +++ b/Table.php @@ -55,7 +55,7 @@ use Closure; use Exception; use InvalidArgumentException; -use ReflectionMethod; +use ReflectionFunction; use function Cake\Core\namespaceSplit; /** @@ -1308,10 +1308,9 @@ public function find(string $type = 'all', mixed ...$args): SelectQuery * can override this method in subclasses to modify how `find('all')` works. * * @param \Cake\ORM\Query\SelectQuery $query The query to find with - * @param array $options The options to use for the find * @return \Cake\ORM\Query\SelectQuery The query builder */ - public function findAll(SelectQuery $query, array $options): SelectQuery + public function findAll(SelectQuery $query): SelectQuery { return $query; } @@ -1389,28 +1388,28 @@ public function findAll(SelectQuery $query, array $options): SelectQuery * ``` * * @param \Cake\ORM\Query\SelectQuery $query The query to find with - * @param array $options The options for the find * @return \Cake\ORM\Query\SelectQuery The query builder */ - public function findList(SelectQuery $query, array $options): SelectQuery - { - $options += [ - 'keyField' => $this->getPrimaryKey(), - 'valueField' => $this->getDisplayField(), - 'groupField' => null, - 'valueSeparator' => ';', - ]; + public function findList( + SelectQuery $query, + Closure|array|string|null $keyField = null, + Closure|array|string|null $valueField = null, + Closure|array|string|null $groupField = null, + string $valueSeparator = ';' + ): SelectQuery { + $keyField ??= $this->getPrimaryKey(); + $valueField ??= $this->getDisplayField(); if ( !$query->clause('select') && - !is_object($options['keyField']) && - !is_object($options['valueField']) && - !is_object($options['groupField']) + !is_object($keyField) && + !is_object($valueField) && + !is_object($groupField) ) { $fields = array_merge( - (array)$options['keyField'], - (array)$options['valueField'], - (array)$options['groupField'] + (array)$keyField, + (array)$valueField, + (array)$groupField ); $columns = $this->getSchema()->columns(); if (count($fields) === count(array_intersect($fields, $columns))) { @@ -1419,7 +1418,7 @@ public function findList(SelectQuery $query, array $options): SelectQuery } $options = $this->_setFieldMatchers( - $options, + compact('keyField', 'valueField', 'groupField', 'valueSeparator'), ['keyField', 'valueField', 'groupField'] ); @@ -1455,18 +1454,18 @@ public function findList(SelectQuery $query, array $options): SelectQuery * @param array $options The options to find with * @return \Cake\ORM\Query\SelectQuery The query builder */ - public function findThreaded(SelectQuery $query, array $options): SelectQuery - { - $options += [ - 'keyField' => $this->getPrimaryKey(), - 'parentField' => 'parent_id', - 'nestingKey' => 'children', - ]; + public function findThreaded( + SelectQuery $query, + Closure|array|string|null $keyField = null, + Closure|array|string $parentField = 'parent_id', + string $nestingKey = 'children' + ): SelectQuery { + $keyField ??= $this->getPrimaryKey(); - $options = $this->_setFieldMatchers($options, ['keyField', 'parentField']); + $options = $this->_setFieldMatchers(compact('keyField', 'parentField'), ['keyField', 'parentField']); return $query->formatResults(fn (CollectionInterface $results) => - $results->nest($options['keyField'], $options['parentField'], $options['nestingKey'])); + $results->nest($options['keyField'], $options['parentField'], $nestingKey)); } /** @@ -1565,7 +1564,7 @@ public function get(mixed $primaryKey, array $options = []): EntityInterface $finder = $options['finder'] ?? 'all'; unset($options['key'], $options['cache'], $options['finder']); - $query = $this->find($finder, $options)->where($conditions); + $query = $this->find($finder, ...$options)->where($conditions); if ($cacheConfig) { if (!$cacheKey) { @@ -2633,37 +2632,9 @@ public function hasFinder(string $type): bool */ public function callFinder(string $type, SelectQuery $query, mixed ...$args): SelectQuery { - // Extract and apply $options from arguments - if ($args) { - $options = []; - if (array_key_exists(0, $args)) { - // $options is not a named argument - $options = &$args[0]; - } elseif (array_key_exists('options', $args)) { - // options is a named argument - $options = &$args['options']; - } - - if (is_array($options) && $options) { - assert(!array_is_list($options), '`$options` argument should be an associative array.'); - - $query->applyOptions($options); - /** @psalm-suppress ReferenceReusedFromConfusingScope */ - $options = $query->getOptions(); - } - } - $finder = 'find' . $type; if (method_exists($this, $finder)) { - if (!$args) { - $reflected = new ReflectionMethod($this, $finder); - $param = $reflected->getParameters()[1] ?? null; - if ($param?->name === 'options' && !$param->isDefaultValueAvailable()) { - $args = [[]]; - } - } - - return $this->{$finder}($query, ...$args); + return $this->invokeFinder($this->{$finder}(...), $query, $args); } if ($this->_behaviors->hasFinder($type)) { @@ -2677,6 +2648,50 @@ public function callFinder(string $type, SelectQuery $query, mixed ...$args): Se )); } + /** + * @internal + */ + public function invokeFinder(Closure $callable, SelectQuery $query, array $args): SelectQuery + { + $options = $args; + $reflected = new ReflectionFunction($callable); + $params = $reflected->getParameters(); + + $maybeOptions = $params[1] ?? null; + $isOldOptions = false; + if ($maybeOptions?->name === 'options' && !$maybeOptions->isDefaultValueAvailable()) { + $isOldOptions = true; + if (isset($args[0])) { + $options = $args[0]; + } + } + + // Extract and apply $options from arguments + if ($options) { + $query->applyOptions($options); + $args = $query->getOptions(); + + if (!$isOldOptions) { + unset($params[0]); + $paramNames = []; + foreach ($params as $param) { + $paramNames[] = $param->getName(); + } + + foreach ($args as $key => $value) { + if (is_string($key) && !in_array($key, $paramNames, true)) { + unset($args[$key]); + } + } + } + } + if ($isOldOptions) { + $args = [$args]; + } + + return $callable($query, ...$args); + } + /** * Provides the dynamic findBy and findAllBy methods. * @@ -2735,9 +2750,7 @@ protected function _dynamicFinder(string $method, array $args): SelectQuery $conditions = $makeConditions($fields, $args); } - return $this->find($findType, [ - 'conditions' => $conditions, - ]); + return $this->find($findType, conditions: $conditions); } /** From 62895b51c4270d1521a934265fce5bf9c4cf006f Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 20 Apr 2023 19:51:45 +0530 Subject: [PATCH 1886/2059] Improve check for old $options arg for finder. --- Table.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Table.php b/Table.php index e6a8dd37..7ab6e0b1 100644 --- a/Table.php +++ b/Table.php @@ -2659,7 +2659,13 @@ public function invokeFinder(Closure $callable, SelectQuery $query, array $args) $maybeOptions = $params[1] ?? null; $isOldOptions = false; - if ($maybeOptions?->name === 'options' && !$maybeOptions->isDefaultValueAvailable()) { + if ( + $maybeOptions?->name === 'options' && + ( + $maybeOptions->getType() === null || + $maybeOptions->getType()->getName() === 'array' + ) + ) { $isOldOptions = true; if (isset($args[0])) { $options = $args[0]; From 42df4e8e386a4f6bd595b26c675064e03f0fe8ae Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 25 Apr 2023 23:36:21 -0400 Subject: [PATCH 1887/2059] Improve API docs. --- Behavior/TranslateBehavior.php | 2 +- Behavior/TreeBehavior.php | 35 +++++++++++----------------------- Table.php | 4 +++- 3 files changed, 15 insertions(+), 26 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 52c39252..84b9c74a 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -309,7 +309,7 @@ public function translationField(string $field): string * for each record. * * @param \Cake\ORM\Query\SelectQuery $query The original query to modify - * @param array $options Options + * @param array $locales A list of locales or options with the `locales` key defined * @return \Cake\ORM\Query\SelectQuery */ public function findTranslations(SelectQuery $query, array $locales = []): SelectQuery diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 074f09db..3258c7c9 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -385,7 +385,7 @@ function (QueryExpression $exp) use ($config) { * is passed in the options containing the id of the node to get its path for. * * @param \Cake\ORM\Query\SelectQuery $query The constructed query to modify - * @param array $options the list of options for the query + * @param string|int $for The path to find or an array of options with `for`. * @return \Cake\ORM\Query\SelectQuery * @throws \InvalidArgumentException If the 'for' key is missing in options */ @@ -436,16 +436,11 @@ public function childCount(EntityInterface $node, bool $direct = false): int /** * Get the children nodes of the current model * - * Available options are: - * - * - for: The id of the record to read. - * - direct: Boolean, whether to return only the direct (true), or all (false) children, - * defaults to false (all children). - * * If the direct option is set to true, only the direct children are returned (based upon the parent_id field) * * @param \Cake\ORM\Query\SelectQuery $query Query. - * @param array $options Array of options as described above + * @param string|int $for The id of the record to read. Can also be an array of options. + * @param bool $direct Whether to return only the direct (true) or all children (false). * @return \Cake\ORM\Query\SelectQuery * @throws \InvalidArgumentException When the 'for' key is not passed in $options */ @@ -481,16 +476,12 @@ function ($field) { * the primary key for the table and the values are the display field for the table. * Values are prefixed to visually indicate relative depth in the tree. * - * ### Options - * - * - keyPath: A dot separated path to fetch the field to use for the array key, or a closure to + * @param \Cake\ORM\Query\SelectQuery $query Query. + * @param \Closure|string|null $keyPath A dot separated path to fetch the field to use for the array key, or a closure to * return the key out of the provided row. - * - valuePath: A dot separated path to fetch the field to use for the array value, or a closure to + * @param \Closure|string|null $valuePath A dot separated path to fetch the field to use for the array value, or a closure to * return the value out of the provided row. - * - spacer: A string to be used as prefix for denoting the depth in the tree for each item - * - * @param \Cake\ORM\Query\SelectQuery $query Query. - * @param array $options Array of options as described above. + * @param ?string $spacer A string to be used as prefix for denoting the depth in the tree for each item. * @return \Cake\ORM\Query\SelectQuery */ public function findTreeList( @@ -512,16 +503,12 @@ public function findTreeList( * and the values are the display field for the table. Values are prefixed to visually * indicate relative depth in the tree. * - * ### Options - * - * - keyPath: A dot separated path to the field that will be the result array key, or a closure to + * @param \Cake\ORM\Query\SelectQuery $query The query object to format. + * @param \Closure|string|null $keyPath A dot separated path to the field that will be the result array key, or a closure to * return the key from the provided row. - * - valuePath: A dot separated path to the field that is the array's value, or a closure to + * @param \Closure|string|null $valuePath: A dot separated path to the field that is the array's value, or a closure to * return the value from the provided row. - * - spacer: A string to be used as prefix for denoting the depth in the tree for each item. - * - * @param \Cake\ORM\Query\SelectQuery $query The query object to format. - * @param array $options Array of options as described above. + * @param ?string $spacer A string to be used as prefix for denoting the depth in the tree for each item. * @return \Cake\ORM\Query\SelectQuery Augmented query. */ public function formatTreeList( diff --git a/Table.php b/Table.php index 7ab6e0b1..91e61daa 100644 --- a/Table.php +++ b/Table.php @@ -1451,7 +1451,9 @@ public function findList( * ``` * * @param \Cake\ORM\Query\SelectQuery $query The query to find with - * @param array $options The options to find with + * @param \Closure|array|string|null $keyField The path to the key field. + * @param \Closure|array|string|null $parentField The path to the parent field. + * @param string $nestingKey The key to nest children under. * @return \Cake\ORM\Query\SelectQuery The query builder */ public function findThreaded( From f6550d5cf0cc0164d96d66c92fe40daafd6a4c54 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 26 Apr 2023 11:38:56 +0530 Subject: [PATCH 1888/2059] Fix stan failures --- Behavior/TreeBehavior.php | 1 + BehaviorRegistry.php | 5 +++-- Table.php | 12 +++++++++--- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 3258c7c9..058978eb 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -525,6 +525,7 @@ function (CollectionInterface $results) use ($keyPath, $valuePath, $spacer) { $nested = $results->listNested(); assert($nested instanceof TreeIterator); + assert(!is_array($valuePath)); return $nested->printer($valuePath, $keyPath, $spacer); } diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index cfa940a8..11a8b2b8 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -269,10 +269,11 @@ public function call(string $method, array $args = []): mixed * Invoke a finder on a behavior. * * @internal + * @template TSubject of \Cake\Datasource\EntityInterface|array * @param string $type The finder type to invoke. - * @param \Cake\ORM\Query\SelectQuery $query The query object to apply the finder options to. + * @param \Cake\ORM\Query\SelectQuery $query The query object to apply the finder options to. * @param mixed ...$args Arguments that match up to finder-specific parameters - * @return \Cake\ORM\Query\SelectQuery The return value depends on the underlying behavior method. + * @return \Cake\ORM\Query\SelectQuery The return value depends on the underlying behavior method. * @throws \BadMethodCallException When the method is unknown. */ public function callFinder(string $type, SelectQuery $query, mixed ...$args): SelectQuery diff --git a/Table.php b/Table.php index 91e61daa..258e5737 100644 --- a/Table.php +++ b/Table.php @@ -56,6 +56,7 @@ use Exception; use InvalidArgumentException; use ReflectionFunction; +use ReflectionNamedType; use function Cake\Core\namespaceSplit; /** @@ -1452,7 +1453,7 @@ public function findList( * * @param \Cake\ORM\Query\SelectQuery $query The query to find with * @param \Closure|array|string|null $keyField The path to the key field. - * @param \Closure|array|string|null $parentField The path to the parent field. + * @param \Closure|array|string $parentField The path to the parent field. * @param string $nestingKey The key to nest children under. * @return \Cake\ORM\Query\SelectQuery The query builder */ @@ -2652,6 +2653,11 @@ public function callFinder(string $type, SelectQuery $query, mixed ...$args): Se /** * @internal + * @template TSubject of \Cake\Datasource\EntityInterface|array + * @param \Closure $callable Callable. + * @param \Cake\ORM\Query\SelectQuery $query The query object. + * @param array $args Arguments for the callable. + * @return \Cake\ORM\Query\SelectQuery */ public function invokeFinder(Closure $callable, SelectQuery $query, array $args): SelectQuery { @@ -2661,11 +2667,11 @@ public function invokeFinder(Closure $callable, SelectQuery $query, array $args) $maybeOptions = $params[1] ?? null; $isOldOptions = false; + $paramType = $maybeOptions?->getType(); if ( $maybeOptions?->name === 'options' && ( - $maybeOptions->getType() === null || - $maybeOptions->getType()->getName() === 'array' + $paramType === null || ($paramType instanceof ReflectionNamedType && $paramType->getName() === 'array') ) ) { $isOldOptions = true; From 1ed32fdd29f109bb4b99e1a2e40efa945791056a Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 26 Apr 2023 12:52:56 +0530 Subject: [PATCH 1889/2059] Avoid reflection when possible. --- Table.php | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/Table.php b/Table.php index 258e5737..d27ad624 100644 --- a/Table.php +++ b/Table.php @@ -2665,18 +2665,23 @@ public function invokeFinder(Closure $callable, SelectQuery $query, array $args) $reflected = new ReflectionFunction($callable); $params = $reflected->getParameters(); - $maybeOptions = $params[1] ?? null; $isOldOptions = false; - $paramType = $maybeOptions?->getType(); - if ( - $maybeOptions?->name === 'options' && - ( - $paramType === null || ($paramType instanceof ReflectionNamedType && $paramType->getName() === 'array') - ) - ) { - $isOldOptions = true; - if (isset($args[0])) { - $options = $args[0]; + if ($args === [] || isset($args[0])) { + $maybeOptions = $params[1] ?? null; + $paramType = $maybeOptions?->getType(); + if ( + $maybeOptions?->name === 'options' && + ( + $paramType === null || + ($paramType instanceof ReflectionNamedType && + $paramType->getName() === 'array' + ) + ) + ) { + $isOldOptions = true; + if (isset($args[0])) { + $options = $args[0]; + } } } From 98e5f47acee7641fdaec909e598e2d153c2a5f4e Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 27 Apr 2023 18:57:10 +0530 Subject: [PATCH 1890/2059] Improve Table::invokeFinder() --- Table.php | 55 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/Table.php b/Table.php index d27ad624..2009e771 100644 --- a/Table.php +++ b/Table.php @@ -2664,24 +2664,32 @@ public function invokeFinder(Closure $callable, SelectQuery $query, array $args) $options = $args; $reflected = new ReflectionFunction($callable); $params = $reflected->getParameters(); + $secondParam = $params[1] ?? null; + $secondParamType = null; - $isOldOptions = false; if ($args === [] || isset($args[0])) { - $maybeOptions = $params[1] ?? null; - $paramType = $maybeOptions?->getType(); + $secondParamType = $secondParam?->getType(); + $secondParamTypeName = $secondParamType instanceof ReflectionNamedType ? $secondParamType->getName() : null; + // Backwards compatibility of 4.x style finders with signature `findFoo(SelectQuery $query, array $options)` + // called as `find('foo')` or `find('foo', [..])` if ( - $maybeOptions?->name === 'options' && - ( - $paramType === null || - ($paramType instanceof ReflectionNamedType && - $paramType->getName() === 'array' - ) - ) + count($params) === 2 && + $secondParam?->name === 'options' && + ($secondParamType === null || $secondParamTypeName === 'array') ) { - $isOldOptions = true; - if (isset($args[0])) { - $options = $args[0]; + if (isset($options[0])) { + $options = $options[0]; } + + $query->applyOptions($options); + + return $callable($query, $query->getOptions()); + } + + // Backwards compatibility for core finders like `findList()` called in 4.x style + // with an array `find('list', ['valueField' => 'foo'])` instead of `find('list', valueField: 'foo')` + if (isset($args[0]) && is_array($args[0]) && $secondParamTypeName !== 'array') { + $options = $args = $args[0]; } } @@ -2690,23 +2698,18 @@ public function invokeFinder(Closure $callable, SelectQuery $query, array $args) $query->applyOptions($options); $args = $query->getOptions(); - if (!$isOldOptions) { - unset($params[0]); - $paramNames = []; - foreach ($params as $param) { - $paramNames[] = $param->getName(); - } + unset($params[0]); + $paramNames = []; + foreach ($params as $param) { + $paramNames[] = $param->getName(); + } - foreach ($args as $key => $value) { - if (is_string($key) && !in_array($key, $paramNames, true)) { - unset($args[$key]); - } + foreach ($args as $key => $value) { + if (is_string($key) && !in_array($key, $paramNames, true)) { + unset($args[$key]); } } } - if ($isOldOptions) { - $args = [$args]; - } return $callable($query, ...$args); } From 34ac6079ca82acfdf8a3b3a3a00fcee04f92cf2a Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 28 Apr 2023 00:34:25 +0530 Subject: [PATCH 1891/2059] Show deprecation warning for old style finder usage with options array. --- Table.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Table.php b/Table.php index 2009e771..5106da84 100644 --- a/Table.php +++ b/Table.php @@ -57,6 +57,7 @@ use InvalidArgumentException; use ReflectionFunction; use ReflectionNamedType; +use function Cake\Core\deprecationWarning; use function Cake\Core\namespaceSplit; /** @@ -2677,6 +2678,13 @@ public function invokeFinder(Closure $callable, SelectQuery $query, array $args) $secondParam?->name === 'options' && ($secondParamType === null || $secondParamTypeName === 'array') ) { + if (!$secondParam->isDefaultValueAvailable()) { + deprecationWarning( + '5.0.0', + '`$options` argument must have a default value' + ); + } + if (isset($options[0])) { $options = $options[0]; } @@ -2689,6 +2697,12 @@ public function invokeFinder(Closure $callable, SelectQuery $query, array $args) // Backwards compatibility for core finders like `findList()` called in 4.x style // with an array `find('list', ['valueField' => 'foo'])` instead of `find('list', valueField: 'foo')` if (isset($args[0]) && is_array($args[0]) && $secondParamTypeName !== 'array') { + deprecationWarning( + '5.0.0', + 'Calling this finder with options array is deprecated.' + . ' Use named arguments instead.' + ); + $options = $args = $args[0]; } } From e2d994dd1d81785e5a4ecbd44ca306d27b92cba2 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 28 Apr 2023 00:38:18 +0530 Subject: [PATCH 1892/2059] Drop unneeded variable. --- Table.php | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Table.php b/Table.php index 5106da84..8ea9a356 100644 --- a/Table.php +++ b/Table.php @@ -2662,7 +2662,6 @@ public function callFinder(string $type, SelectQuery $query, mixed ...$args): Se */ public function invokeFinder(Closure $callable, SelectQuery $query, array $args): SelectQuery { - $options = $args; $reflected = new ReflectionFunction($callable); $params = $reflected->getParameters(); $secondParam = $params[1] ?? null; @@ -2685,11 +2684,11 @@ public function invokeFinder(Closure $callable, SelectQuery $query, array $args) ); } - if (isset($options[0])) { - $options = $options[0]; + if (isset($args[0])) { + $args = $args[0]; } - $query->applyOptions($options); + $query->applyOptions($args); return $callable($query, $query->getOptions()); } @@ -2703,13 +2702,13 @@ public function invokeFinder(Closure $callable, SelectQuery $query, array $args) . ' Use named arguments instead.' ); - $options = $args = $args[0]; + $args = $args[0]; } } - // Extract and apply $options from arguments - if ($options) { - $query->applyOptions($options); + if ($args) { + $query->applyOptions($args); + // Fetch custom args without the query options. $args = $query->getOptions(); unset($params[0]); From 0cc3c4456a3502bfbc88cc62cca93cb2f7ea7230 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 28 Apr 2023 01:05:35 +0530 Subject: [PATCH 1893/2059] Code cleanup --- ResultSetFactory.php | 2 +- Table.php | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ResultSetFactory.php b/ResultSetFactory.php index 5fe27b0a..78e6fee0 100644 --- a/ResultSetFactory.php +++ b/ResultSetFactory.php @@ -149,7 +149,7 @@ protected function groupResult(array $row, array $data): EntityInterface|array // If the default table is not in the results, set // it to an empty array so that any contained // associations hydrate correctly. - $results[$data['primaryAlias']] = $results[$data['primaryAlias']] ?? []; + $results[$data['primaryAlias']] ??= []; unset($presentAliases[$data['primaryAlias']]); diff --git a/Table.php b/Table.php index bdef2750..aed49dcf 100644 --- a/Table.php +++ b/Table.php @@ -2830,7 +2830,7 @@ public function newEmptyEntity(): EntityInterface */ public function newEntity(array $data, array $options = []): EntityInterface { - $options['associated'] = $options['associated'] ?? $this->_associations->keys(); + $options['associated'] ??= $this->_associations->keys(); $marshaller = $this->marshaller(); return $marshaller->one($data, $options); @@ -2870,7 +2870,7 @@ public function newEntity(array $data, array $options = []): EntityInterface */ public function newEntities(array $data, array $options = []): array { - $options['associated'] = $options['associated'] ?? $this->_associations->keys(); + $options['associated'] ??= $this->_associations->keys(); $marshaller = $this->marshaller(); return $marshaller->many($data, $options); @@ -2929,7 +2929,7 @@ public function newEntities(array $data, array $options = []): array */ public function patchEntity(EntityInterface $entity, array $data, array $options = []): EntityInterface { - $options['associated'] = $options['associated'] ?? $this->_associations->keys(); + $options['associated'] ??= $this->_associations->keys(); $marshaller = $this->marshaller(); return $marshaller->merge($entity, $data, $options); @@ -2968,7 +2968,7 @@ public function patchEntity(EntityInterface $entity, array $data, array $options */ public function patchEntities(iterable $entities, array $data, array $options = []): array { - $options['associated'] = $options['associated'] ?? $this->_associations->keys(); + $options['associated'] ??= $this->_associations->keys(); $marshaller = $this->marshaller(); return $marshaller->mergeMany($entities, $data, $options); From ad28f3b45be0c82b9e8d3a2204a3b0c49f76b088 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 28 Apr 2023 01:23:38 +0530 Subject: [PATCH 1894/2059] Update docblocks --- Association.php | 2 +- Behavior/TranslateBehavior.php | 2 +- Behavior/TreeBehavior.php | 5 ++- Table.php | 71 ++++++++++++---------------------- 4 files changed, 30 insertions(+), 50 deletions(-) diff --git a/Association.php b/Association.php index 90d771a1..66570f52 100644 --- a/Association.php +++ b/Association.php @@ -835,7 +835,7 @@ public function defaultRowValue(array $row, bool $joined): array * configuration * * @param array|string|null $type the type of query to perform, if an array is passed, - * it will be interpreted as the `$options` parameter + * it will be interpreted as the `$args` parameter * @param mixed ...$args Arguments that match up to finder-specific parameters * @see \Cake\ORM\Table::find() * @return \Cake\ORM\Query\SelectQuery diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 84b9c74a..3f05fae5 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -301,7 +301,7 @@ public function translationField(string $field): string * ### Example: * * ``` - * $article = $articles->find('translations', ['locales' => ['eng', 'deu'])->first(); + * $article = $articles->find('translations', locales: ['eng', 'deu'])->first(); * $englishTranslatedFields = $article->get('_translations')['eng']; * ``` * diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 058978eb..d2466b37 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -434,9 +434,10 @@ public function childCount(EntityInterface $node, bool $direct = false): int } /** - * Get the children nodes of the current model + * Get the children nodes of the current model. * - * If the direct option is set to true, only the direct children are returned (based upon the parent_id field) + * If the direct option is set to true, only the direct children are returned + * (based upon the parent_id field). * * @param \Cake\ORM\Query\SelectQuery $query Query. * @param string|int $for The id of the record to read. Can also be an array of options. diff --git a/Table.php b/Table.php index 8ea9a356..cd68ee6d 100644 --- a/Table.php +++ b/Table.php @@ -1216,7 +1216,8 @@ public function belongsToMany(string $associated, array $options = []): BelongsT * Each find() will trigger a `Model.beforeFind` event for all attached * listeners. Any listener can set a valid result set using $query * - * By default, `$options` will recognize the following keys: + * By default, following special named arguments are recognized which are + * used as select query options: * * - fields * - conditions @@ -1231,14 +1232,12 @@ public function belongsToMany(string $associated, array $options = []): BelongsT * * ### Usage * - * Using the options array: - * * ``` - * $query = $articles->find('all', [ - * 'conditions' => ['published' => 1], - * 'limit' => 10, - * 'contain' => ['Users', 'Comments'] - * ]); + * $query = $articles->find('all', + * conditions: ['published' => 1], + * limit: 10, + * contain: ['Users', 'Comments'] + * ); * ``` * * Using the builder interface: @@ -1263,14 +1262,10 @@ public function belongsToMany(string $associated, array $options = []): BelongsT * * ## Typed finder arguments * - * Finders can have typed parameters separate from the `$options` array and the - * `$options` parameter can be optional if not needed: - * - * ``` - * $query = $articles->find('byCategory', $category); - * ``` + * Finders must have a `SelectQuery` instance as their 1st argument and any + * additional parameters as needed. * - * Here, the finder "findByCategory" does not have an `$options` parameter: + * Here, the finder "findByCategory" has an integer `$category` parameter: * * ``` * function findByCategory(SelectQuery $query, int $category): SelectQuery @@ -1279,19 +1274,15 @@ public function belongsToMany(string $associated, array $options = []): BelongsT * } * ``` * - * If you need to pass query options, just add the typed arguments after `$options`: + * This finder can be called as: * * ``` - * $query = $articles->find('byCategory', [...], $category); + * $query = $articles->find('byCategory', $category); * ``` * - * Here, the finder "findByCategory" does have an `$options` parameter: - * + * or using named arguments as: * ``` - * function findByCategory(SelectQuery $query, array $options, int $category): SelectQuery - * { - * return $query; - * } + * $query = $articles->find(type: 'byCategory', category: $category); * ``` * * @param string $type the type of query to perform @@ -1306,8 +1297,8 @@ public function find(string $type = 'all', mixed ...$args): SelectQuery /** * Returns the query as passed. * - * By default findAll() applies no conditions, you - * can override this method in subclasses to modify how `find('all')` works. + * By default findAll() applies no query clauses, you can override this + * method in subclasses to modify how `find('all')` works. * * @param \Cake\ORM\Query\SelectQuery $query The query to find with * @return \Cake\ORM\Query\SelectQuery The query builder @@ -1336,25 +1327,19 @@ public function findAll(SelectQuery $query): SelectQuery * ] * ``` * - * You can specify which property will be used as the key and which as value - * by using the `$options` array, when not specified, it will use the results - * of calling `primaryKey` and `displayField` respectively in this table: + * You can specify which property will be used as the key and which as value, + * when not specified, it will use the results of calling `primaryKey` and + * `displayField` respectively in this table: * * ``` - * $table->find('list', [ - * 'keyField' => 'name', - * 'valueField' => 'age' - * ]); + * $table->find('list', keyField: 'name', valueField: 'age'); * ``` * * The `valueField` can also be an array, in which case you can also specify * the `valueSeparator` option to control how the values will be concatenated: * * ``` - * $table->find('list', [ - * 'valueField' => ['first_name', 'last_name'], - * 'valueSeparator' => ' | ', - * ]); + * $table->find('list', valueField: ['first_name', 'last_name'], valueSeparator: ' | '); * ``` * * The results of this finder will be in the following form: @@ -1370,9 +1355,7 @@ public function findAll(SelectQuery $query): SelectQuery * can customize the property to use for grouping by setting `groupField`: * * ``` - * $table->find('list', [ - * 'groupField' => 'category_id', - * ]); + * $table->find('list', groupField: 'category_id'); * ``` * * When using a `groupField` results will be returned in this format: @@ -1441,15 +1424,11 @@ public function findList( * * You can customize what fields are used for nesting results, by default the * primary key and the `parent_id` fields are used. If you wish to change - * these defaults you need to provide the keys `keyField`, `parentField` or `nestingKey` in - * `$options`: + * these defaults you need to provide the `keyField`, `parentField` or `nestingKey` + * arguments: * * ``` - * $table->find('threaded', [ - * 'keyField' => 'id', - * 'parentField' => 'ancestor_id', - * 'nestingKey' => 'children' - * ]); + * $table->find('threaded', keyField: 'id', parentField: 'ancestor_id', nestingKey: 'children'); * ``` * * @param \Cake\ORM\Query\SelectQuery $query The query to find with From 686ebd9f5adff3dbc110683684cc66ef0f37af91 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 28 Apr 2023 02:14:32 +0530 Subject: [PATCH 1895/2059] Update deprecation warning --- Table.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Table.php b/Table.php index cd68ee6d..672a223b 100644 --- a/Table.php +++ b/Table.php @@ -2656,14 +2656,13 @@ public function invokeFinder(Closure $callable, SelectQuery $query, array $args) $secondParam?->name === 'options' && ($secondParamType === null || $secondParamTypeName === 'array') ) { - if (!$secondParam->isDefaultValueAvailable()) { + if (isset($args[0])) { deprecationWarning( '5.0.0', - '`$options` argument must have a default value' + 'Using options array for the `find()` call is deprecated.' + . ' Use named arguments instead.' ); - } - if (isset($args[0])) { $args = $args[0]; } From 4a7c4008592536f8614772b9cb6a3da8ecc782f9 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 28 Apr 2023 22:27:34 -0400 Subject: [PATCH 1896/2059] Add test specifically for backwards compatibility, and improve error message --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 672a223b..2e383d5e 100644 --- a/Table.php +++ b/Table.php @@ -2676,7 +2676,7 @@ public function invokeFinder(Closure $callable, SelectQuery $query, array $args) if (isset($args[0]) && is_array($args[0]) && $secondParamTypeName !== 'array') { deprecationWarning( '5.0.0', - 'Calling this finder with options array is deprecated.' + "Calling `{$reflected->getName()}` finder with options array is deprecated." . ' Use named arguments instead.' ); From ab4895e819076c66b3a9c42754c4f2803bdb293e Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 13 Jan 2023 19:59:15 +0530 Subject: [PATCH 1897/2059] Add new find() options keys. "select", "where", "orderBy" and "groupBy" were added to match the SQL clauses / SelectQuery methods. --- Query/SelectQuery.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index a98907f0..3405d03d 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -698,13 +698,17 @@ public function getOptions(): array public function applyOptions(array $options) { $valid = [ + 'select' => 'select', 'fields' => 'select', 'conditions' => 'where', + 'where' => 'where', 'join' => 'join', 'order' => 'orderBy', + 'orderBy' => 'orderBy', 'limit' => 'limit', 'offset' => 'offset', 'group' => 'groupBy', + 'groupBy' => 'groupBy', 'having' => 'having', 'contain' => 'contain', 'page' => 'page', From 15fe9eaa5511695a0018cbff2ac2cac5dd87eec0 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 1 May 2023 01:08:34 +0530 Subject: [PATCH 1898/2059] Remove unneeded variable assignments. --- AssociationCollection.php | 7 ++++++- Table.php | 36 +++++++++--------------------------- 2 files changed, 15 insertions(+), 28 deletions(-) diff --git a/AssociationCollection.php b/AssociationCollection.php index 5e7d6ff2..9e9e9c0d 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -66,6 +66,9 @@ public function __construct(?LocatorInterface $tableLocator = null) * @param string $alias The association alias * @param \Cake\ORM\Association $association The association to add. * @return \Cake\ORM\Association The association object being added. + * @template T of \Cake\ORM\Association + * @psalm-param T $association + * @psalm-return T */ public function add(string $alias, Association $association): Association { @@ -82,7 +85,9 @@ public function add(string $alias, Association $association): Association * @param array $options List of options to configure the association definition. * @return \Cake\ORM\Association * @throws \InvalidArgumentException - * @psalm-param class-string<\Cake\ORM\Association> $className + * @template T of \Cake\ORM\Association + * @psalm-param class-string $className + * @psalm-return T */ public function load(string $className, string $associated, array $options = []): Association { diff --git a/Table.php b/Table.php index 0c825069..1a37af22 100644 --- a/Table.php +++ b/Table.php @@ -632,9 +632,7 @@ protected function _initializeSchema(TableSchemaInterface $schema): TableSchemaI */ public function hasField(string $field): bool { - $schema = $this->getSchema(); - - return $schema->getColumn($field) !== null; + return $this->getSchema()->getColumn($field) !== null; } /** @@ -1048,10 +1046,7 @@ public function belongsTo(string $associated, array $options = []): BelongsTo { $options += ['sourceTable' => $this]; - /** @var \Cake\ORM\Association\BelongsTo $association */ - $association = $this->_associations->load(BelongsTo::class, $associated, $options); - - return $association; + return $this->_associations->load(BelongsTo::class, $associated, $options); } /** @@ -1094,10 +1089,7 @@ public function hasOne(string $associated, array $options = []): HasOne { $options += ['sourceTable' => $this]; - /** @var \Cake\ORM\Association\HasOne $association */ - $association = $this->_associations->load(HasOne::class, $associated, $options); - - return $association; + return $this->_associations->load(HasOne::class, $associated, $options); } /** @@ -1146,10 +1138,7 @@ public function hasMany(string $associated, array $options = []): HasMany { $options += ['sourceTable' => $this]; - /** @var \Cake\ORM\Association\HasMany $association */ - $association = $this->_associations->load(HasMany::class, $associated, $options); - - return $association; + return $this->_associations->load(HasMany::class, $associated, $options); } /** @@ -1200,10 +1189,7 @@ public function belongsToMany(string $associated, array $options = []): BelongsT { $options += ['sourceTable' => $this]; - /** @var \Cake\ORM\Association\BelongsToMany $association */ - $association = $this->_associations->load(BelongsToMany::class, $associated, $options); - - return $association; + return $this->_associations->load(BelongsToMany::class, $associated, $options); } /** @@ -2782,9 +2768,8 @@ public function newEmptyEntity(): EntityInterface public function newEntity(array $data, array $options = []): EntityInterface { $options['associated'] = $options['associated'] ?? $this->_associations->keys(); - $marshaller = $this->marshaller(); - return $marshaller->one($data, $options); + return $this->marshaller()->one($data, $options); } /** @@ -2822,9 +2807,8 @@ public function newEntity(array $data, array $options = []): EntityInterface public function newEntities(array $data, array $options = []): array { $options['associated'] = $options['associated'] ?? $this->_associations->keys(); - $marshaller = $this->marshaller(); - return $marshaller->many($data, $options); + return $this->marshaller()->many($data, $options); } /** @@ -2881,9 +2865,8 @@ public function newEntities(array $data, array $options = []): array public function patchEntity(EntityInterface $entity, array $data, array $options = []): EntityInterface { $options['associated'] = $options['associated'] ?? $this->_associations->keys(); - $marshaller = $this->marshaller(); - return $marshaller->merge($entity, $data, $options); + return $this->marshaller()->merge($entity, $data, $options); } /** @@ -2920,9 +2903,8 @@ public function patchEntity(EntityInterface $entity, array $data, array $options public function patchEntities(iterable $entities, array $data, array $options = []): array { $options['associated'] = $options['associated'] ?? $this->_associations->keys(); - $marshaller = $this->marshaller(); - return $marshaller->mergeMany($entities, $data, $options); + return $this->marshaller()->mergeMany($entities, $data, $options); } /** From 4e7d134ee49ac5de854e8aecdda564982ea9187d Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 30 Apr 2023 23:03:31 +0530 Subject: [PATCH 1899/2059] Remove $options argument from Table::get(). Instead separate arguments are added for finder, cache and cacheKey. Variadic args added for using as query options. --- Behavior/TreeBehavior.php | 2 +- Table.php | 44 ++++++++++++++++++++++++++++++--------- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index d2466b37..b7fb3d30 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -399,7 +399,7 @@ function ($field) { [$config['left'], $config['right']] ); - $node = $this->_table->get($for, ['fields' => [$left, $right]]); + $node = $this->_table->get($for, select: [$left, $right]); return $this->_scope($query) ->where([ diff --git a/Table.php b/Table.php index 09db8ec6..daf0e039 100644 --- a/Table.php +++ b/Table.php @@ -55,6 +55,7 @@ use Closure; use Exception; use InvalidArgumentException; +use Psr\SimpleCache\CacheInterface; use ReflectionFunction; use ReflectionNamedType; use function Cake\Core\deprecationWarning; @@ -1503,7 +1504,12 @@ protected function _setFieldMatchers(array $options, array $keys): array * ``` * * @param mixed $primaryKey primary key value to find - * @param array $options options accepted by `Table::find()` + * @param array|string $finder The finder to use. Passing an options array is deprecated. + * @param \Psr\SimpleCache\CacheInterface|string|null $cache The cache config to use. + * Defaults to `null`, i.e. no caching. + * @param \Closure|string|null $cacheKey The cache key to use. If not provided + * one will be autogenerated if `$cache` is not null. + * @param mixed ...$args Arguments that query options or finder specific parameters. * @return \Cake\Datasource\EntityInterface * @throws \Cake\Datasource\Exception\RecordNotFoundException if the record with such id * could not be found @@ -1511,8 +1517,13 @@ protected function _setFieldMatchers(array $options, array $keys): array * incorrect number of elements. * @see \Cake\Datasource\RepositoryInterface::find() */ - public function get(mixed $primaryKey, array $options = []): EntityInterface - { + public function get( + mixed $primaryKey, + array|string $finder = 'all', + CacheInterface|string|null $cache = null, + Closure|string|null $cacheKey = null, + mixed ...$args + ): EntityInterface { if ($primaryKey === null) { throw new InvalidPrimaryKeyException(sprintf( 'Record not found in table `%s` with primary key `[NULL]`.', @@ -1542,14 +1553,27 @@ public function get(mixed $primaryKey, array $options = []): EntityInterface } $conditions = array_combine($key, $primaryKey); - $cacheConfig = $options['cache'] ?? false; - $cacheKey = $options['key'] ?? false; - $finder = $options['finder'] ?? 'all'; - unset($options['key'], $options['cache'], $options['finder']); + if (is_array($finder)) { + deprecationWarning( + '5.0.0', + 'Calling Table::get() with options array is deprecated.' + . ' Use named arguments instead.' + ); + + $args += $finder; + $finder = $args['finder'] ?? 'all'; + if (isset($args['cache'])) { + $cache = $args['cache']; + } + if (isset($args['key'])) { + $cacheKey = $args['key']; + } + unset($args['key'], $args['cache'], $args['finder']); + } - $query = $this->find($finder, ...$options)->where($conditions); + $query = $this->find($finder, ...$args)->where($conditions); - if ($cacheConfig) { + if ($cache) { if (!$cacheKey) { $cacheKey = sprintf( 'get-%s-%s-%s', @@ -1558,7 +1582,7 @@ public function get(mixed $primaryKey, array $options = []): EntityInterface json_encode($primaryKey, JSON_THROW_ON_ERROR) ); } - $query->cache($cacheKey, $cacheConfig); + $query->cache($cacheKey, $cache); } return $query->firstOrFail(); From e4a178fee150cf3f501ce02760ce77eb747dd5bb Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 11 Jun 2023 20:03:15 +0530 Subject: [PATCH 1900/2059] Fix calls to finders with variadic argument. Refs #17108. --- Table.php | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/Table.php b/Table.php index babcc0d5..2824b0f6 100644 --- a/Table.php +++ b/Table.php @@ -2676,6 +2676,7 @@ public function invokeFinder(Closure $callable, SelectQuery $query, array $args) if ( count($params) === 2 && $secondParam?->name === 'options' && + !$secondParam?->isVariadic() && ($secondParamType === null || $secondParamTypeName === 'array') ) { if (isset($args[0])) { @@ -2712,14 +2713,19 @@ public function invokeFinder(Closure $callable, SelectQuery $query, array $args) $args = $query->getOptions(); unset($params[0]); - $paramNames = []; - foreach ($params as $param) { - $paramNames[] = $param->getName(); - } + $lastParam = end($params); + reset($params); + + if ($lastParam === false || !$lastParam?->isVariadic()) { + $paramNames = []; + foreach ($params as $param) { + $paramNames[] = $param->getName(); + } - foreach ($args as $key => $value) { - if (is_string($key) && !in_array($key, $paramNames, true)) { - unset($args[$key]); + foreach ($args as $key => $value) { + if (is_string($key) && !in_array($key, $paramNames, true)) { + unset($args[$key]); + } } } } From abd2199199a38195d20fb73c9364b29e4cb3cf9b Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 11 Jun 2023 21:22:03 +0530 Subject: [PATCH 1901/2059] Fix stan errors --- Table.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index 2824b0f6..91b31daa 100644 --- a/Table.php +++ b/Table.php @@ -2676,7 +2676,7 @@ public function invokeFinder(Closure $callable, SelectQuery $query, array $args) if ( count($params) === 2 && $secondParam?->name === 'options' && - !$secondParam?->isVariadic() && + !$secondParam->isVariadic() && ($secondParamType === null || $secondParamTypeName === 'array') ) { if (isset($args[0])) { @@ -2716,7 +2716,7 @@ public function invokeFinder(Closure $callable, SelectQuery $query, array $args) $lastParam = end($params); reset($params); - if ($lastParam === false || !$lastParam?->isVariadic()) { + if ($lastParam === false || !$lastParam->isVariadic()) { $paramNames = []; foreach ($params as $param) { $paramNames[] = $param->getName(); From 76b9078e92e397e16f3ef9b86533ae8650953130 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 16 Jun 2023 19:52:39 +0530 Subject: [PATCH 1902/2059] Remove redundant return value. --- Behavior/TimestampBehavior.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index e647eea3..3e27c1b8 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -90,10 +90,10 @@ public function initialize(array $config): void * @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event Event instance. * @param \Cake\Datasource\EntityInterface $entity Entity instance. * @throws \UnexpectedValueException if a field's when value is misdefined - * @return true Returns true irrespective of the behavior logic, the save will not be prevented. + * @return void * @throws \UnexpectedValueException When the value for an event is not 'always', 'new' or 'existing' */ - public function handleEvent(EventInterface $event, EntityInterface $entity): bool + public function handleEvent(EventInterface $event, EntityInterface $entity): void { $eventName = $event->getName(); $events = $this->_config['events']; @@ -122,8 +122,6 @@ public function handleEvent(EventInterface $event, EntityInterface $entity): boo $this->_updateField($entity, $field, $refresh); } } - - return true; } /** From 8082d2ac654952be5df4df16b7e9167a3191fe96 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 26 Jun 2023 22:00:42 +0530 Subject: [PATCH 1903/2059] Fix phpstan errors. --- Locator/LocatorAwareTrait.php | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index 2a4aed93..9bd682fa 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -59,11 +59,17 @@ public function setTableLocator(LocatorInterface $tableLocator) */ public function getTableLocator(): LocatorInterface { - /** - * @var \Cake\ORM\Locator\LocatorInterface - * @psalm-suppress PropertyTypeCoercion - */ - return $this->_tableLocator ??= FactoryLocator::get('Table'); + if (isset($this->_tableLocator)) { + return $this->_tableLocator; + } + + $locator = FactoryLocator::get('Table'); + assert( + $locator instanceof LocatorInterface, + '`FactoryLocator` must return an instance of Cake\ORM\LocatorInterface for type `Table`.' + ); + + return $this->_tableLocator = $locator; } /** From 3e75e8bc5e3e865c301c695e033531136cd0db25 Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Mon, 26 Jun 2023 23:34:44 +0200 Subject: [PATCH 1904/2059] fix stan --- Behavior/TreeBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index b7fb3d30..7eeaa61d 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -526,7 +526,7 @@ function (CollectionInterface $results) use ($keyPath, $valuePath, $spacer) { $nested = $results->listNested(); assert($nested instanceof TreeIterator); - assert(!is_array($valuePath)); + assert(is_callable($valuePath) || is_string($valuePath)); return $nested->printer($valuePath, $keyPath, $spacer); } From d8c033ee3da931a7fb933e629521215ed8f43120 Mon Sep 17 00:00:00 2001 From: fabian-mcfly <13197057+fabian-mcfly@users.noreply.github.com> Date: Thu, 31 Aug 2023 00:04:05 +0200 Subject: [PATCH 1905/2059] [5.x] modified entity getOriginal behavior (#17208) * Changed behavior of how entities remember original values Introduced setOriginalField & changes to the EntityInterface * Changed behavior of how entities remember original values Introduced setOriginalField & changes to the EntityInterface * Changed behavior of how entities remember original values Introduced setOriginalField & changes to the EntityInterface * indention * phpcs * phpcs * Changed behavior of how entities remember original values Introduced setOriginalField & changes to the EntityInterface * Removed setOriginalField() from EntityInterface Adjusted tests * Removed setOriginalField() from EntityInterface Adjusted tests --------- Co-authored-by: fabian-mcfly --- Entity.php | 13 ++++++++----- Marshaller.php | 4 ++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Entity.php b/Entity.php index 14e5d57e..f10e1d0a 100644 --- a/Entity.php +++ b/Entity.php @@ -65,13 +65,16 @@ public function __construct(array $properties = [], array $options = []) $this->setNew($options['markNew']); } - if (!empty($properties) && $options['markClean'] && !$options['useSetters']) { - $this->_fields = $properties; + if (!empty($properties)) { + //Remember the original field names here. + $this->setOriginalField(array_keys($properties)); - return; - } + if ($options['markClean'] && !$options['useSetters']) { + $this->_fields = $properties; + + return; + } - if (!empty($properties)) { $this->set($properties, [ 'setter' => $options['useSetters'], 'guard' => $options['guard'], diff --git a/Marshaller.php b/Marshaller.php index 4f4e80ff..75a6e695 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -215,11 +215,11 @@ public function one(array $data, array $options = []): EntityInterface if (isset($options['fields'])) { foreach ((array)$options['fields'] as $field) { if (array_key_exists($field, $properties)) { - $entity->set($field, $properties[$field]); + $entity->set($field, $properties[$field], ['asOriginal' => true]); } } } else { - $entity->set($properties); + $entity->set($properties, ['asOriginal' => true]); } // Don't flag clean association entities as From 3f33bc00e70820cc7535ae508de4204a937958ce Mon Sep 17 00:00:00 2001 From: andrii-pukhalevych Date: Mon, 18 Sep 2023 11:24:10 +0300 Subject: [PATCH 1906/2059] Use Table::newEmptyEntity() in Marshaller::one() --- Marshaller.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 75a6e695..af53399f 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -176,9 +176,7 @@ public function one(array $data, array $options = []): EntityInterface [$data, $options] = $this->_prepareDataAndOptions($data, $options); $primaryKey = (array)$this->_table->getPrimaryKey(); - $entityClass = $this->_table->getEntityClass(); - $entity = new $entityClass(); - $entity->setSource($this->_table->getRegistryAlias()); + $entity = $this->_table->newEmptyEntity(); if (isset($options['accessibleFields'])) { foreach ((array)$options['accessibleFields'] as $key => $value) { From 27a7b9fa406dd96711fe369a9de911915fa2168f Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Sat, 16 Sep 2023 01:40:35 -0500 Subject: [PATCH 1907/2059] Support Chronos extending DateTimeImmutable --- Behavior/TimestampBehavior.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 3e27c1b8..3d0dc33b 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -16,7 +16,6 @@ */ namespace Cake\ORM\Behavior; -use Cake\Chronos\Chronos; use Cake\Database\Type\DateTimeType; use Cake\Database\TypeFactory; use Cake\Datasource\EntityInterface; @@ -144,11 +143,11 @@ public function implementedEvents(): array * If an explicit date time is passed, the config option `refreshTimestamp` is * automatically set to false. * - * @param \Cake\Chronos\Chronos|\DateTimeInterface|null $ts Timestamp + * @param \DateTimeInterface|null $ts Timestamp * @param bool $refreshTimestamp If true timestamp is refreshed. * @return \Cake\I18n\DateTime */ - public function timestamp(Chronos|DateTimeInterface|null $ts = null, bool $refreshTimestamp = false): DateTime + public function timestamp(?DateTimeInterface $ts = null, bool $refreshTimestamp = false): DateTime { if ($ts) { if ($this->_config['refreshTimestamp']) { From 96cd43fc4e1318a512de97394af039b23efce724 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 1 Oct 2023 02:14:44 +0530 Subject: [PATCH 1908/2059] Update docs URLs --- Behavior/TranslateBehavior.php | 4 ++-- README.md | 10 +++++----- Table.php | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 3f05fae5..d962ebdb 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -250,8 +250,8 @@ public function buildMarshalMap(Marshaller $marshaller, array $map, array $optio * globally configured locale. * @return $this * @see \Cake\ORM\Behavior\TranslateBehavior::getLocale() - * @link https://book.cakephp.org/4/en/orm/behaviors/translate.html#retrieving-one-language-without-using-i18n-locale - * @link https://book.cakephp.org/4/en/orm/behaviors/translate.html#saving-in-another-language + * @link https://book.cakephp.org/5/en/orm/behaviors/translate.html#retrieving-one-language-without-using-i18n-locale + * @link https://book.cakephp.org/5/en/orm/behaviors/translate.html#saving-in-another-language */ public function setLocale(?string $locale) { diff --git a/README.md b/README.md index 5af443ac..a6b3c513 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ supports 4 association types out of the box: * belongsToMany - E.g. An article belongsToMany tags. You define associations in your table's `initialize()` method. See the -[documentation](https://book.cakephp.org/4/en/orm/associations.html) for +[documentation](https://book.cakephp.org/5/en/orm/associations.html) for complete examples. ## Reading Data @@ -99,8 +99,8 @@ foreach ($articles->find() as $article) { } ``` -You can use the [query builder](https://book.cakephp.org/4/en/orm/query-builder.html) to create -complex queries, and a [variety of methods](https://book.cakephp.org/4/en/orm/retrieving-data-and-resultsets.html) +You can use the [query builder](https://book.cakephp.org/5/en/orm/query-builder.html) to create +complex queries, and a [variety of methods](https://book.cakephp.org/5/en/orm/retrieving-data-and-resultsets.html) to access your data. ## Saving Data @@ -134,7 +134,7 @@ $articles->save($article, [ ``` The above shows how you can easily marshal and save an entity and its -associations in a simple & powerful way. Consult the [ORM documentation](https://book.cakephp.org/4/en/orm/saving-data.html) +associations in a simple & powerful way. Consult the [ORM documentation](https://book.cakephp.org/5/en/orm/saving-data.html) for more in-depth examples. ## Deleting Data @@ -237,5 +237,5 @@ Configure::write('App.namespace', 'My\Log\SubNamespace'); ## Additional Documentation -Consult [the CakePHP ORM documentation](https://book.cakephp.org/4/en/orm.html) +Consult [the CakePHP ORM documentation](https://book.cakephp.org/5/en/orm.html) for more in-depth documentation. diff --git a/Table.php b/Table.php index 91b31daa..cb097f97 100644 --- a/Table.php +++ b/Table.php @@ -153,7 +153,7 @@ * - `afterDeleteCommit(EventInterface $event, EntityInterface $entity, ArrayObject $options)` * * @see \Cake\Event\EventManager for reference on the events system. - * @link https://book.cakephp.org/4/en/orm/table-objects.html#event-list + * @link https://book.cakephp.org/5/en/orm/table-objects.html#event-list * @implements \Cake\Event\EventDispatcherInterface<\Cake\ORM\Table> */ class Table implements RepositoryInterface, EventListenerInterface, EventDispatcherInterface, ValidatorAwareInterface From 2b6b3d19ad43e60dda9b5e843d3af9720525b7dc Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 1 Oct 2023 22:43:39 +0530 Subject: [PATCH 1909/2059] Allow using _translations.{locale}.field_name style naming for default locale. This allows consistently naming the translated fields for all locales in forms. Refs https://discourse.cakephp.org/t/translate-behavior-in-cakephp-5-x/11530. --- Behavior/TranslateBehavior.php | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index d962ebdb..c7c3e8ed 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -16,7 +16,9 @@ */ namespace Cake\ORM\Behavior; +use ArrayObject; use Cake\Datasource\QueryInterface; +use Cake\Event\EventInterface; use Cake\I18n\I18n; use Cake\ORM\Behavior; use Cake\ORM\Behavior\Translate\ShadowTableStrategy; @@ -211,11 +213,42 @@ public function implementedEvents(): array { return [ 'Model.beforeFind' => 'beforeFind', + 'Model.beforeMarshal' => 'beforeMarshal', 'Model.beforeSave' => 'beforeSave', 'Model.afterSave' => 'afterSave', ]; } + /** + * Hoist fields for the default locale under `_translations` key to the root + * in the data. + * + * This allows `_translations.{locale}.field_name` type naming even for the + * default locale in forms. + * + * @param \Cake\Event\EventInterface $event + * @param \ArrayObject $data + * @param \ArrayObject $options + * @return void + */ + public function beforeMarshal(EventInterface $event, ArrayObject $data, ArrayObject $options): void + { + if (isset($options['translations']) && !$options['translations']) { + return; + } + + $defaultLocale = $this->getConfig('defaultLocale'); + if (!isset($data['_translations'][$defaultLocale])) { + return; + } + + foreach ($data['_translations'][$defaultLocale] as $field => $value) { + $data[$field] = $value; + } + + unset($data['_translations'][$defaultLocale]); + } + /** * {@inheritDoc} * From b99389dc7bdd7e35854b103418df20dd4eaf928a Mon Sep 17 00:00:00 2001 From: mscherer Date: Wed, 18 Oct 2023 01:41:08 +0200 Subject: [PATCH 1910/2059] Fix up missing class alias. Fix up missing class_exists(). --- Query/SelectQuery.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index 3405d03d..b6062d97 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -1795,3 +1795,7 @@ public function isAutoFieldsEnabled(): ?bool return $this->_autoFields; } } + +// phpcs:disable +class_exists('Cake\ORM\Query'); +// phpcs:enable From a71613d1fb251ae0e43f7bd1a8c46c806072df03 Mon Sep 17 00:00:00 2001 From: mscherer Date: Sun, 5 Nov 2023 17:39:33 +0100 Subject: [PATCH 1911/2059] Clean up old messages/docs. --- TableRegistry.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/TableRegistry.php b/TableRegistry.php index 0445606f..1c6671e1 100644 --- a/TableRegistry.php +++ b/TableRegistry.php @@ -34,9 +34,6 @@ * * ``` * TableRegistry::getTableLocator()->setConfig('Users', ['table' => 'my_users']); - * - * // Prior to 3.6.0 - * TableRegistry::config('Users', ['table' => 'my_users']); * ``` * * Configuration data is stored *per alias* if you use the same table with @@ -51,9 +48,6 @@ * * ``` * $table = TableRegistry::getTableLocator()->get('Users', $config); - * - * // Prior to 3.6.0 - * $table = TableRegistry::get('Users', $config); * ``` */ class TableRegistry From a58c76cd0b8b19023afe4e0814b2743ef3ce1d83 Mon Sep 17 00:00:00 2001 From: mscherer Date: Mon, 6 Nov 2023 09:38:05 +0100 Subject: [PATCH 1912/2059] CS Cleanups. --- Behavior/TreeBehavior.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 7eeaa61d..d7584b48 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -482,7 +482,7 @@ function ($field) { * return the key out of the provided row. * @param \Closure|string|null $valuePath A dot separated path to fetch the field to use for the array value, or a closure to * return the value out of the provided row. - * @param ?string $spacer A string to be used as prefix for denoting the depth in the tree for each item. + * @param string|null $spacer A string to be used as prefix for denoting the depth in the tree for each item. * @return \Cake\ORM\Query\SelectQuery */ public function findTreeList( @@ -505,11 +505,11 @@ public function findTreeList( * indicate relative depth in the tree. * * @param \Cake\ORM\Query\SelectQuery $query The query object to format. - * @param \Closure|string|null $keyPath A dot separated path to the field that will be the result array key, or a closure to + * @param \Closure|string|null $keyPath A dot separated path to the field that will be the result array key, or a closure to * return the key from the provided row. - * @param \Closure|string|null $valuePath: A dot separated path to the field that is the array's value, or a closure to + * @param \Closure|string|null $valuePath A dot separated path to the field that is the array's value, or a closure to * return the value from the provided row. - * @param ?string $spacer A string to be used as prefix for denoting the depth in the tree for each item. + * @param string|null $spacer A string to be used as prefix for denoting the depth in the tree for each item. * @return \Cake\ORM\Query\SelectQuery Augmented query. */ public function formatTreeList( From ae86536cdb74168d263be851faf33eda769f4550 Mon Sep 17 00:00:00 2001 From: mscherer Date: Fri, 24 Nov 2023 13:09:01 +0100 Subject: [PATCH 1913/2059] Make PHPStan see the code better. --- Rule/IsUnique.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index 17109ec7..d19959f5 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -75,17 +75,20 @@ public function __invoke(EntityInterface $entity, array $options): bool return true; } - $alias = $options['repository']->getAlias(); + /** @var \Cake\ORM\Table $repository */ + $repository = $options['repository']; + + $alias = $repository->getAlias(); $conditions = $this->_alias($alias, $fields); if ($entity->isNew() === false) { - $keys = (array)$options['repository']->getPrimaryKey(); + $keys = (array)$repository->getPrimaryKey(); $keys = $this->_alias($alias, $entity->extract($keys)); if (Hash::filter($keys)) { $conditions['NOT'] = $keys; } } - return !$options['repository']->exists($conditions); + return !$repository->exists($conditions); } /** From b94079dded91df7ce55b90be0e48338e7ad5e6f5 Mon Sep 17 00:00:00 2001 From: mscherer Date: Fri, 24 Nov 2023 13:32:15 +0100 Subject: [PATCH 1914/2059] Make PHPStan see the code better. --- Association/BelongsToMany.php | 8 ++++++-- Association/HasMany.php | 6 ++++-- Association/Loader/SelectLoader.php | 10 +++++++--- Association/Loader/SelectWithPivotLoader.php | 1 + Behavior/CounterCacheBehavior.php | 4 +++- Behavior/Translate/ShadowTableStrategy.php | 1 + Rule/ExistsIn.php | 8 ++++++-- Table.php | 2 ++ 8 files changed, 30 insertions(+), 10 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 6e651c96..f92f77e8 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -475,7 +475,9 @@ public function attachTo(SelectQuery $query, array $options = []): void $foreignKey = $this->getTargetForeignKey(); $thisJoin = $query->clause('join')[$this->getName()]; - $thisJoin['conditions']->add($assoc->_joinCondition(['foreignKey' => $foreignKey])); + /** @var \Cake\Database\Expression\QueryExpression $conditions */ + $conditions = $thisJoin['conditions']; + $conditions->add($assoc->_joinCondition(['foreignKey' => $foreignKey])); } /** @@ -752,7 +754,9 @@ protected function _saveTarget( // Saving the new linked entity failed, copy errors back into the // original entity if applicable and abort. if (!empty($options['atomic'])) { - $original[$k]->setErrors($entity->getErrors()); + /** @var \Cake\Datasource\EntityInterface $originalEntity */ + $originalEntity = $original[$k]; + $originalEntity->setErrors($entity->getErrors()); } return false; diff --git a/Association/HasMany.php b/Association/HasMany.php index 76fdf101..8066dd3b 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -234,9 +234,11 @@ protected function _saveTarget( } if (!empty($options['atomic'])) { - $original[$k]->setErrors($entity->getErrors()); + /** @var \Cake\ORM\Entity $originEntity */ + $originEntity = $original[$k]; + $originEntity->setErrors($entity->getErrors()); if ($entity instanceof InvalidPropertyInterface) { - $original[$k]->setInvalid($entity->getInvalid()); + $originEntity->setInvalid($entity->getInvalid()); } return false; diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 07490fbb..6dfb545d 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -171,19 +171,22 @@ protected function _buildQuery(array $options): SelectQuery $query = $query->find($finderName, ...$opts); } + /** @var \Cake\ORM\Query\SelectQuery $selectQuery */ + $selectQuery = $options['query']; + $fetchQuery = $query ->select($options['fields']) ->where($options['conditions']) ->eagerLoaded(true) - ->enableHydration($options['query']->isHydrationEnabled()); - if ($options['query']->isResultsCastingEnabled()) { + ->enableHydration($selectQuery->isHydrationEnabled()); + if ($selectQuery->isResultsCastingEnabled()) { $fetchQuery->enableResultsCasting(); } else { $fetchQuery->disableResultsCasting(); } if ($useSubquery) { - $filter = $this->_buildSubquery($options['query']); + $filter = $this->_buildSubquery($selectQuery); $fetchQuery = $this->_addFilteringJoin($fetchQuery, $key, $filter); } else { $fetchQuery = $this->_addFilteringCondition($fetchQuery, $key, $filter); @@ -449,6 +452,7 @@ protected function _subqueryFields(SelectQuery $query): array $fields = $query->aliasFields($keys, $this->sourceAlias); $group = $fields = array_values($fields); + /** @var \Cake\Database\Expression\QueryExpression $order */ $order = $query->clause('order'); if ($order) { $columns = $query->clause('select'); diff --git a/Association/Loader/SelectWithPivotLoader.php b/Association/Loader/SelectWithPivotLoader.php index 5b9b8911..3ed2197a 100644 --- a/Association/Loader/SelectWithPivotLoader.php +++ b/Association/Loader/SelectWithPivotLoader.php @@ -94,6 +94,7 @@ protected function _buildQuery(array $options): SelectQuery $query = parent::_buildQuery($options); if ($queryBuilder) { + /** @var \Cake\ORM\Query\SelectQuery $query */ $query = $queryBuilder($query); } diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 1fb68114..e64200d9 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -136,12 +136,14 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array $registryAlias = $assoc->getTarget()->getRegistryAlias(); $entityAlias = $assoc->getProperty(); + /** @var \Cake\Datasource\EntityInterface $assocEntity */ + $assocEntity = $entity->$entityAlias; if ( !is_callable($config) && isset($config['ignoreDirty']) && $config['ignoreDirty'] === true && - $entity->$entityAlias->isDirty($field) + $assocEntity->isDirty($field) ) { $this->_ignoreDirty[$registryAlias][$field] = true; } diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 80a86eb3..e0bd1c80 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -402,6 +402,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array if ($id) { $where['id'] = $id; + /** @var \Cake\Datasource\EntityInterface|null $translation */ $translation = $this->translationTable->find() ->select(array_merge(['id', 'locale'], $fields)) ->where($where) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 1ff8fe34..ea9a5f4c 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -82,7 +82,10 @@ public function __construct(array|string $fields, Table|Association|string $repo public function __invoke(EntityInterface $entity, array $options): bool { if (is_string($this->_repository)) { - if (!$options['repository']->hasAssociation($this->_repository)) { + /** @var \Cake\ORM\Table $table */ + $table = $options['repository']; + + if (!$table->hasAssociation($this->_repository)) { throw new DatabaseException(sprintf( 'ExistsIn rule for `%s` is invalid. `%s` is not associated with `%s`.', implode(', ', $this->_fields), @@ -90,7 +93,7 @@ public function __invoke(EntityInterface $entity, array $options): bool get_class($options['repository']) )); } - $repository = $options['repository']->getAssociation($this->_repository); + $repository = $table->getAssociation($this->_repository); $this->_repository = $repository; } @@ -124,6 +127,7 @@ public function __invoke(EntityInterface $entity, array $options): bool } if ($this->_options['allowNullableNulls']) { + /** @var \Cake\ORM\Table $source */ $schema = $source->getSchema(); foreach ($fields as $i => $field) { if ($schema->getColumn($field) && $schema->isNullable($field) && $entity->get($field) === null) { diff --git a/Table.php b/Table.php index cb097f97..43a6d336 100644 --- a/Table.php +++ b/Table.php @@ -338,6 +338,7 @@ public function __construct(array $config = []) } } $this->_eventManager = $eventManager ?: new EventManager(); + /** @var \Cake\ORM\BehaviorRegistry $behaviors */ $this->_behaviors = $behaviors ?: new BehaviorRegistry(); $this->_behaviors->setTable($this); $this->_associations = $associations ?: new AssociationCollection(); @@ -2327,6 +2328,7 @@ protected function _saveMany( /** @var array $isNew */ $isNew = []; $cleanupOnFailure = function ($entities) use (&$isNew): void { + /** @var iterable<\Cake\Datasource\EntityInterface> $entities */ foreach ($entities as $key => $entity) { if (isset($isNew[$key]) && $isNew[$key]) { $entity->unset($this->getPrimaryKey()); From e688f5c98c3b87d181e180e8eb6fcc100fc9ecfa Mon Sep 17 00:00:00 2001 From: mscherer Date: Fri, 24 Nov 2023 14:47:15 +0100 Subject: [PATCH 1915/2059] Make PHPStan see the code better. --- Association/BelongsToMany.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 6e651c96..5fe4b1e8 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1282,6 +1282,7 @@ protected function _diffLinks( } foreach ($existing as $existingLink) { + /** @var \Cake\ORM\Entity $existingLink */ $existingKeys = $existingLink->extract($keys); $found = false; foreach ($unmatchedEntityKeys as $i => $unmatchedKeys) { From 9e5cf698f57a4904990dc96adc801fe3ebb4ddd7 Mon Sep 17 00:00:00 2001 From: Marcelo Rocha Date: Mon, 27 Nov 2023 11:26:30 -0300 Subject: [PATCH 1916/2059] Update Association phpdoc to use 'Generics' for Table --- Association/BelongsTo.php | 3 +++ Association/BelongsToMany.php | 3 +++ 2 files changed, 6 insertions(+) diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index e0df4711..9acac06b 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -31,6 +31,9 @@ * related to only one record in the target table. * * An example of a BelongsTo association would be Article belongs to Author. + * + * @template T of \Cake\ORM\Table + * @mixin T */ class BelongsTo extends Association { diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index a0a219ab..d7309ac9 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -37,6 +37,9 @@ * * An example of a BelongsToMany association would be Article belongs to many Tags. * In this example 'Article' is the source table and 'Tags' is the target table. + * + * @template T of \Cake\ORM\Table + * @mixin T */ class BelongsToMany extends Association { From ea170b81e865d596d9e7f16c9f5f1cb4df0f0a90 Mon Sep 17 00:00:00 2001 From: Marcelo Rocha Date: Mon, 27 Nov 2023 11:29:33 -0300 Subject: [PATCH 1917/2059] Update Association phpdoc to use 'Generics' for Table --- Association/HasMany.php | 3 +++ Association/HasOne.php | 3 +++ 2 files changed, 6 insertions(+) diff --git a/Association/HasMany.php b/Association/HasMany.php index 8066dd3b..1a28aea1 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -34,6 +34,9 @@ * will have one or multiple records per each one in the source side. * * An example of a HasMany association would be Author has many Articles. + * + * @template T of \Cake\ORM\Table + * @mixin T */ class HasMany extends Association { diff --git a/Association/HasOne.php b/Association/HasOne.php index 99196fb2..b73673b8 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -29,6 +29,9 @@ * related to only one record in the target table and vice versa. * * An example of a HasOne association would be User has one Profile. + * + * @template T of \Cake\ORM\Table + * @mixin T */ class HasOne extends Association { From 3e9940a4117cd82b97300295c69ec0f8d66e39d9 Mon Sep 17 00:00:00 2001 From: mscherer Date: Sat, 9 Dec 2023 07:19:14 +0100 Subject: [PATCH 1918/2059] Less cloaking and more strict comparison where possible. --- EagerLoader.php | 2 +- Query/SelectQuery.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index 13c66153..25d784a6 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -400,7 +400,7 @@ protected function _reformatContain(array $associations, array $original): array */ public function attachAssociations(SelectQuery $query, Table $repository, bool $includeFields): void { - if (empty($this->_containments) && $this->_matching === null) { + if (!$this->_containments && $this->_matching === null) { return; } diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index b6062d97..58712c66 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -736,7 +736,7 @@ protected function _decorateResults(iterable $result): ResultSetInterface { $decorator = $this->_decoratorClass(); - if (!empty($this->_mapReduce)) { + if ($this->_mapReduce) { foreach ($this->_mapReduce as $functions) { $result = new MapReduce($result, $functions['mapper'], $functions['reducer']); } @@ -747,7 +747,7 @@ protected function _decorateResults(iterable $result): ResultSetInterface $result = new $decorator($result); } - if (!empty($this->_formatters)) { + if ($this->_formatters) { foreach ($this->_formatters as $formatter) { $result = $formatter($result, $this); } From 3143cb76f2114fd7e0c001b8ad523c08961a08f7 Mon Sep 17 00:00:00 2001 From: fabian-mcfly <13197057+fabian-mcfly@users.noreply.github.com> Date: Sun, 10 Dec 2023 21:53:52 +0100 Subject: [PATCH 1919/2059] Mark modified fields as clean instead of the entire entity --- Behavior/Translate/EavStrategy.php | 5 +++-- Behavior/Translate/ShadowTableStrategy.php | 11 ++++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index c3c989a7..81be77e7 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -386,7 +386,8 @@ protected function rowMapper(CollectionInterface $results, string $locale): Coll $row['_locale'] = $locale; if ($hydrated) { /** @var \Cake\Datasource\EntityInterface $row */ - $row->clean(); + $row->setDirty($field, false); + $row->setDirty('_locale', false); } return $row; @@ -425,8 +426,8 @@ public function groupTranslations(CollectionInterface $results): CollectionInter $options = ['setter' => false, 'guard' => false]; $row->set('_translations', $result, $options); + $row->setDirty('_translations', false); unset($row['_i18n']); - $row->clean(); return $row; }); diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index e0bd1c80..1520cd64 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -490,7 +490,7 @@ protected function rowMapper(CollectionInterface $results, string $locale): Coll if ($hydrated) { /** @var \Cake\Datasource\EntityInterface $row */ - $row->clean(); + $row->setDirty('_locale', false); } return $row; @@ -516,6 +516,11 @@ protected function rowMapper(CollectionInterface $results, string $locale): Coll if ($translation[$field] !== null) { if ($allowEmpty || $translation[$field] !== '') { $row[$field] = $translation[$field]; + + if ($hydrated) { + /** @var \Cake\Datasource\EntityInterface $row */ + $row->setDirty($field, false); + } } } } @@ -525,7 +530,7 @@ protected function rowMapper(CollectionInterface $results, string $locale): Coll if ($hydrated) { /** @var \Cake\Datasource\EntityInterface $row */ - $row->clean(); + $row->setDirty('_locale', false); } return $row; @@ -559,7 +564,7 @@ public function groupTranslations(CollectionInterface $results): CollectionInter $row['_translations'] = $result; unset($row['_i18n']); if ($row instanceof EntityInterface) { - $row->clean(); + $row->setDirty('_translations', false); } return $row; From faefb7b81e1009eb14d606655aab74a0fab1d937 Mon Sep 17 00:00:00 2001 From: fabian-mcfly <13197057+fabian-mcfly@users.noreply.github.com> Date: Mon, 11 Dec 2023 09:24:50 +0100 Subject: [PATCH 1920/2059] Fixed EAV Strategy mistake & added tests --- Behavior/Translate/EavStrategy.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index 81be77e7..ff2c91d7 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -378,6 +378,11 @@ protected function rowMapper(CollectionInterface $results, string $locale): Coll $content = $translation['content'] ?? null; if ($content !== null) { $row[$field] = $content; + + if ($hydrated) { + /** @var \Cake\Datasource\EntityInterface $row */ + $row->setDirty($field, false); + } } unset($row[$name]); @@ -386,7 +391,6 @@ protected function rowMapper(CollectionInterface $results, string $locale): Coll $row['_locale'] = $locale; if ($hydrated) { /** @var \Cake\Datasource\EntityInterface $row */ - $row->setDirty($field, false); $row->setDirty('_locale', false); } From 6eaa7d6304160fdca3126e4d6f42c1d1a89f14c1 Mon Sep 17 00:00:00 2001 From: fabian-mcfly <13197057+fabian-mcfly@users.noreply.github.com> Date: Mon, 11 Dec 2023 17:21:59 +0100 Subject: [PATCH 1921/2059] CS & Annotations --- Behavior/Translate/EavStrategy.php | 4 +--- Behavior/Translate/ShadowTableStrategy.php | 6 +----- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index ff2c91d7..360fdf2d 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -364,7 +364,7 @@ protected function rowMapper(CollectionInterface $results, string $locale): Coll if ($row === null) { return $row; } - $hydrated = !is_array($row); + $hydrated = $row instanceof EntityInterface; foreach ($this->_config['fields'] as $field) { $name = $field . '_translation'; @@ -380,7 +380,6 @@ protected function rowMapper(CollectionInterface $results, string $locale): Coll $row[$field] = $content; if ($hydrated) { - /** @var \Cake\Datasource\EntityInterface $row */ $row->setDirty($field, false); } } @@ -390,7 +389,6 @@ protected function rowMapper(CollectionInterface $results, string $locale): Coll $row['_locale'] = $locale; if ($hydrated) { - /** @var \Cake\Datasource\EntityInterface $row */ $row->setDirty('_locale', false); } diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 1520cd64..bdcdd3f3 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -482,14 +482,13 @@ protected function rowMapper(CollectionInterface $results, string $locale): Coll return $row; } - $hydrated = !is_array($row); + $hydrated = $row instanceof EntityInterface; if (empty($row['translation'])) { $row['_locale'] = $locale; unset($row['translation']); if ($hydrated) { - /** @var \Cake\Datasource\EntityInterface $row */ $row->setDirty('_locale', false); } @@ -518,18 +517,15 @@ protected function rowMapper(CollectionInterface $results, string $locale): Coll $row[$field] = $translation[$field]; if ($hydrated) { - /** @var \Cake\Datasource\EntityInterface $row */ $row->setDirty($field, false); } } } } - /** @var array $row */ unset($row['translation']); if ($hydrated) { - /** @var \Cake\Datasource\EntityInterface $row */ $row->setDirty('_locale', false); } From eb6292203fd18df9b4988f49d25dd65f272370e3 Mon Sep 17 00:00:00 2001 From: mscherer Date: Tue, 2 Jan 2024 17:29:14 +0100 Subject: [PATCH 1922/2059] Avoid Typed property not init exception. --- Association/BelongsToMany.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index d7309ac9..faeec689 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -118,9 +118,9 @@ class BelongsToMany extends Association /** * The table instance for the junction relation. * - * @var \Cake\ORM\Table|string + * @var \Cake\ORM\Table|string|null */ - protected Table|string $_through; + protected Table|string|null $_through = null; /** * Valid strategies for this type of association @@ -261,7 +261,7 @@ public function junction(Table|string|null $table = null): Table } $tableLocator = $this->getTableLocator(); - if ($table === null && isset($this->_through)) { + if ($table === null && $this->_through !== null) { $table = $this->_through; } elseif ($table === null) { $tableName = $this->_junctionTableName(); @@ -988,10 +988,11 @@ public function setThrough(Table|string $through) /** * Gets the current join table, either the name of the Table instance or the instance itself. + * Returns null if not defined. * - * @return \Cake\ORM\Table|string + * @return \Cake\ORM\Table|string|null */ - public function getThrough(): Table|string + public function getThrough(): Table|string|null { return $this->_through; } From 92462402b338dec83100f394ea0a6f4ce784e462 Mon Sep 17 00:00:00 2001 From: mscherer Date: Fri, 5 Jan 2024 17:56:20 +0100 Subject: [PATCH 1923/2059] Refactored into non cloaking where clearly defined. --- Association.php | 2 +- Association/BelongsTo.php | 4 ++-- Association/BelongsToMany.php | 2 +- Association/HasOne.php | 2 +- Association/Loader/SelectLoader.php | 2 +- AssociationCollection.php | 8 ++++---- Behavior/Translate/EavStrategy.php | 6 +++--- Behavior/Translate/ShadowTableStrategy.php | 4 ++-- Behavior/Translate/TranslateTrait.php | 2 +- Behavior/TranslateBehavior.php | 2 +- EagerLoader.php | 6 +++--- Entity.php | 2 +- Locator/LocatorAwareTrait.php | 2 +- Marshaller.php | 8 ++++---- Query/SelectQuery.php | 4 ++-- Table.php | 8 ++++---- 16 files changed, 32 insertions(+), 32 deletions(-) diff --git a/Association.php b/Association.php index 66570f52..343a8c04 100644 --- a/Association.php +++ b/Association.php @@ -1079,7 +1079,7 @@ protected function _joinCondition(array $options): array $bindingKey = (array)$this->getBindingKey(); if (count($foreignKey) !== count($bindingKey)) { - if (empty($bindingKey)) { + if (!$bindingKey) { $table = $this->getTarget()->getTable(); if ($this->isOwningSide($this->getSource())) { $table = $this->getSource()->getTable(); diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 9acac06b..c147df9b 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -137,7 +137,7 @@ public function type(): string public function saveAssociated(EntityInterface $entity, array $options = []): EntityInterface|false { $targetEntity = $entity->get($this->getProperty()); - if (empty($targetEntity) || !($targetEntity instanceof EntityInterface)) { + if (!$targetEntity || !($targetEntity instanceof EntityInterface)) { return $entity; } @@ -176,7 +176,7 @@ protected function _joinCondition(array $options): array $bindingKey = (array)$this->getBindingKey(); if (count($foreignKey) !== count($bindingKey)) { - if (empty($bindingKey)) { + if (!$bindingKey) { $msg = 'The `%s` table does not define a primary key. Please set one.'; throw new DatabaseException(sprintf($msg, $this->getTarget()->getTable())); } diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index faeec689..72ac9f9c 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1410,7 +1410,7 @@ protected function _collectJointEntities(EntityInterface $sourceEntity, array $t $result[] = $joint; } - if (empty($missing)) { + if (!$missing) { return $result; } diff --git a/Association/HasOne.php b/Association/HasOne.php index b73673b8..ecf2374b 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -121,7 +121,7 @@ public function type(): string public function saveAssociated(EntityInterface $entity, array $options = []): EntityInterface|false { $targetEntity = $entity->get($this->getProperty()); - if (empty($targetEntity) || !($targetEntity instanceof EntityInterface)) { + if (!$targetEntity || !($targetEntity instanceof EntityInterface)) { return $entity; } diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 6dfb545d..b87ea734 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -253,7 +253,7 @@ protected function _assertFieldsPresent(SelectQuery $fetchQuery, array $key): vo } $select = $fetchQuery->aliasFields($fetchQuery->clause('select')); - if (empty($select)) { + if (!$select) { return; } $missingKey = function ($fieldList, $key) { diff --git a/AssociationCollection.php b/AssociationCollection.php index 0bece029..e66b5a4e 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -216,7 +216,7 @@ public function removeAll(): void */ public function saveParents(Table $table, EntityInterface $entity, array $associations, array $options = []): bool { - if (empty($associations)) { + if (!$associations) { return true; } @@ -238,7 +238,7 @@ public function saveParents(Table $table, EntityInterface $entity, array $associ */ public function saveChildren(Table $table, EntityInterface $entity, array $associations, array $options): bool { - if (empty($associations)) { + if (!$associations) { return true; } @@ -308,7 +308,7 @@ protected function _save( if (!$entity->isDirty($association->getProperty())) { return true; } - if (!empty($nested)) { + if ($nested) { $options = $nested + $options; } @@ -361,7 +361,7 @@ public function normalizeKeys(array|string|bool $keys): array $keys = $this->keys(); } - if (empty($keys)) { + if (!$keys) { return []; } diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index c3c989a7..f283c40f 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -407,7 +407,7 @@ public function groupTranslations(CollectionInterface $results): CollectionInter return $row; } $translations = (array)$row->get('_i18n'); - if (empty($translations) && $row->get('_translations')) { + if (!$translations && $row->get('_translations')) { return $row; } $grouped = new Collection($translations); @@ -445,7 +445,7 @@ protected function bundleTranslatedFields(EntityInterface $entity): void /** @var array $translations */ $translations = (array)$entity->get('_translations'); - if (empty($translations) && !$entity->isDirty('_translations')) { + if (!$translations && !$entity->isDirty('_translations')) { return; } @@ -467,7 +467,7 @@ protected function bundleTranslatedFields(EntityInterface $entity): void } } - if (empty($find)) { + if (!$find) { return; } diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index e0bd1c80..0e655484 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -546,7 +546,7 @@ public function groupTranslations(CollectionInterface $results): CollectionInter return $row; } $translations = (array)$row->get('_i18n'); - if (empty($translations) && $row->get('_translations')) { + if (!$translations && $row->get('_translations')) { return $row; } @@ -579,7 +579,7 @@ protected function bundleTranslatedFields(EntityInterface $entity): void /** @var array $translations */ $translations = (array)$entity->get('_translations'); - if (empty($translations) && !$entity->isDirty('_translations')) { + if (!$translations && !$entity->isDirty('_translations')) { return; } diff --git a/Behavior/Translate/TranslateTrait.php b/Behavior/Translate/TranslateTrait.php index 95741243..6c4c5e18 100644 --- a/Behavior/Translate/TranslateTrait.php +++ b/Behavior/Translate/TranslateTrait.php @@ -42,7 +42,7 @@ public function translation(string $language) $i18n = $this->get('_translations'); $created = false; - if (empty($i18n)) { + if (!$i18n) { $i18n = []; $created = true; } diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index c7c3e8ed..631e6765 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -387,7 +387,7 @@ protected function referenceName(Table $table): string { $name = namespaceSplit($table::class); $name = substr((string)end($name), 0, -5); - if (empty($name)) { + if (!$name) { $name = $table->getTable() ?: $table->getAlias(); $name = Inflector::camelize($name); } diff --git a/EagerLoader.php b/EagerLoader.php index 25d784a6..a62cae96 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -614,13 +614,13 @@ protected function _resolveJoins(array $associations, array $matching = []): arr */ public function loadExternal(SelectQuery $query, array $results): array { - if (empty($results)) { + if (!$results) { return $results; } $table = $query->getRepository(); $external = $this->externalAssociations($table); - if (empty($external)) { + if (!$external) { return $results; } @@ -792,7 +792,7 @@ protected function _collectKeys(array $external, SelectQuery $query, array $resu } $collectKeys[$meta->aliasPath()] = [$alias, $pkFields, count($pkFields) === 1]; } - if (empty($collectKeys)) { + if (!$collectKeys) { return []; } diff --git a/Entity.php b/Entity.php index f10e1d0a..b5ca0f92 100644 --- a/Entity.php +++ b/Entity.php @@ -65,7 +65,7 @@ public function __construct(array $properties = [], array $options = []) $this->setNew($options['markNew']); } - if (!empty($properties)) { + if ($properties) { //Remember the original field names here. $this->setOriginalField(array_keys($properties)); diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index 9bd682fa..fee43868 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -87,7 +87,7 @@ public function getTableLocator(): LocatorInterface public function fetchTable(?string $alias = null, array $options = []): Table { $alias ??= $this->defaultTable; - if (empty($alias)) { + if (!$alias) { throw new UnexpectedValueException( 'You must provide an `$alias` or set the `$defaultTable` property to a non empty string.' ); diff --git a/Marshaller.php b/Marshaller.php index af53399f..66ad81fa 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -403,7 +403,7 @@ protected function _belongsToMany(BelongsToMany $assoc, array $data, array $opti } } - if (!empty($conditions)) { + if ($conditions) { /** @var \Traversable<\Cake\Datasource\EntityInterface> $results */ $results = $target->find() ->andWhere(fn (QueryExpression $exp) => $exp->or($conditions)) @@ -460,7 +460,7 @@ protected function _belongsToMany(BelongsToMany $assoc, array $data, array $opti */ protected function _loadAssociatedByIds(Association $assoc, array $ids): array { - if (empty($ids)) { + if (!$ids) { return []; } @@ -694,7 +694,7 @@ public function mergeMany(iterable $entities, array $data, array $options = []): }, ['OR' => []]); $maybeExistentQuery = $this->_table->find()->where($conditions); - if (!empty($indexed) && count($maybeExistentQuery->clause('where'))) { + if ($indexed && count($maybeExistentQuery->clause('where'))) { /** @var \Traversable<\Cake\Datasource\EntityInterface> $existent */ $existent = $maybeExistentQuery->all(); foreach ($existent as $entity) { @@ -795,7 +795,7 @@ protected function _mergeBelongsToMany(array $original, BelongsToMany $assoc, ar return []; } - if (!empty($associated) && !in_array('_joinData', $associated, true) && !isset($associated['_joinData'])) { + if ($associated && !in_array('_joinData', $associated, true) && !isset($associated['_joinData'])) { return $this->mergeMany($original, $value, $options); } diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index 58712c66..2e5876d7 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -1078,10 +1078,10 @@ protected function _addAssociationsToTypeMap(Table $table, TypeMap $typeMap, arr $association = $table->getAssociation($name); $target = $association->getTarget(); $primary = (array)$target->getPrimaryKey(); - if (empty($primary) || $typeMap->type($target->aliasField($primary[0])) === null) { + if (!$primary || $typeMap->type($target->aliasField($primary[0])) === null) { $this->addDefaultTypes($target); } - if (!empty($nested)) { + if ($nested) { $this->_addAssociationsToTypeMap($target, $typeMap, $nested); } } diff --git a/Table.php b/Table.php index 43a6d336..f1e54e8a 100644 --- a/Table.php +++ b/Table.php @@ -2138,7 +2138,7 @@ protected function _onSaveSuccess(EntityInterface $entity, ArrayObject $options) protected function _insert(EntityInterface $entity, array $data): EntityInterface|false { $primary = (array)$this->getPrimaryKey(); - if (empty($primary)) { + if (!$primary) { $msg = sprintf( 'Cannot insert row in `%s` table, it has no primary key.', $this->getTable() @@ -2172,7 +2172,7 @@ protected function _insert(EntityInterface $entity, array $data): EntityInterfac } } - if (empty($data)) { + if (!$data) { return false; } @@ -2239,7 +2239,7 @@ protected function _update(EntityInterface $entity, array $data): EntityInterfac $primaryKey = $entity->extract($primaryColumns); $data = array_diff_key($data, $primaryKey); - if (empty($data)) { + if (!$data) { return $entity; } @@ -2748,7 +2748,7 @@ protected function _dynamicFinder(string $method, array $args): SelectQuery { $method = Inflector::underscore($method); preg_match('/^find_([\w]+)_by_/', $method, $matches); - if (empty($matches)) { + if (!$matches) { // find_by_ is 8 characters. $fields = substr($method, 8); $findType = 'all'; From e8a5fb1d7e5447529c278812cd04d48455c8bb4d Mon Sep 17 00:00:00 2001 From: mscherer Date: Fri, 5 Jan 2024 17:58:43 +0100 Subject: [PATCH 1924/2059] Refactored into non cloaking where clearly defined. --- Behavior.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Behavior.php b/Behavior.php index 99878599..b0b7c977 100644 --- a/Behavior.php +++ b/Behavior.php @@ -325,7 +325,7 @@ public function implementedEvents(): array public function implementedFinders(): array { $methods = $this->getConfig('implementedFinders'); - if (isset($methods)) { + if ($methods !== null) { return $methods; } @@ -357,7 +357,7 @@ public function implementedFinders(): array public function implementedMethods(): array { $methods = $this->getConfig('implementedMethods'); - if (isset($methods)) { + if ($methods !== null) { return $methods; } From 91c509a94fd92a2eb73ec3bc8348e52d359106e4 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Fri, 5 Jan 2024 18:31:40 +0100 Subject: [PATCH 1925/2059] Fix up docblocks for assoc options and list arrays. (#17526) * Fix up docblocks for assoc options arrays. * Fix up docblocks for assoc options arrays. * Fix up list docblocks. * Fix up list docblocks. --- Association.php | 2 +- Table.php | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Association.php b/Association.php index 66570f52..57ee8e6f 100644 --- a/Association.php +++ b/Association.php @@ -787,7 +787,7 @@ protected function _appendNotMatching(SelectQuery $query, array $options): void * Correctly nests a result row associated values into the correct array keys inside the * source results. * - * @param array $row The row to transform + * @param array $row The row to transform * @param string $nestKey The array key under which the results for this association * should be found * @param bool $joined Whether the row is a result of a direct join diff --git a/Table.php b/Table.php index 43a6d336..29ba8d50 100644 --- a/Table.php +++ b/Table.php @@ -1940,7 +1940,7 @@ public function exists(QueryExpression|Closure|array|string|null $conditions): b * ``` * * @param \Cake\Datasource\EntityInterface $entity the entity to be saved - * @param array $options The options to use when saving. + * @param array $options The options to use when saving. * @return \Cake\Datasource\EntityInterface|false * @throws \Cake\ORM\Exception\RolledbackTransactionException If the transaction is aborted in the afterSave event. */ @@ -1991,7 +1991,7 @@ public function save( * the entity contains errors or the save was aborted by a callback. * * @param \Cake\Datasource\EntityInterface $entity the entity to be saved - * @param array $options The options to use when saving. + * @param array $options The options to use when saving. * @return \Cake\Datasource\EntityInterface * @throws \Cake\ORM\Exception\PersistenceFailedException When the entity couldn't be saved * @see \Cake\ORM\Table::save() @@ -2272,7 +2272,7 @@ protected function _update(EntityInterface $entity, array $data): EntityInterfac * error. * * @param iterable<\Cake\Datasource\EntityInterface> $entities Entities to save. - * @param array $options Options used when calling Table::save() for each entity. + * @param array $options Options used when calling Table::save() for each entity. * @return iterable<\Cake\Datasource\EntityInterface>|false False on failure, entities list on success. * @throws \Exception */ @@ -2295,7 +2295,7 @@ public function saveMany( * error. * * @param iterable<\Cake\Datasource\EntityInterface> $entities Entities to save. - * @param array $options Options used when calling Table::save() for each entity. + * @param array $options Options used when calling Table::save() for each entity. * @return iterable<\Cake\Datasource\EntityInterface> Entities list. * @throws \Exception * @throws \Cake\ORM\Exception\PersistenceFailedException If an entity couldn't be saved. @@ -2307,7 +2307,7 @@ public function saveManyOrFail(iterable $entities, array $options = []): iterabl /** * @param iterable<\Cake\Datasource\EntityInterface> $entities Entities to save. - * @param array $options Options used when calling Table::save() for each entity. + * @param array $options Options used when calling Table::save() for each entity. * @throws \Cake\ORM\Exception\PersistenceFailedException If an entity couldn't be saved. * @throws \Exception If an entity couldn't be saved. * @return iterable<\Cake\Datasource\EntityInterface> Entities list. @@ -2420,7 +2420,7 @@ protected function _saveMany( * the options used in the delete operation. * * @param \Cake\Datasource\EntityInterface $entity The entity to remove. - * @param array $options The options for the delete. + * @param array $options The options for the delete. * @return bool success */ public function delete(EntityInterface $entity, array $options = []): bool @@ -2454,7 +2454,7 @@ public function delete(EntityInterface $entity, array $options = []): bool * error. * * @param iterable<\Cake\Datasource\EntityInterface> $entities Entities to delete. - * @param array $options Options used when calling Table::save() for each entity. + * @param array $options Options used when calling Table::save() for each entity. * @return iterable<\Cake\Datasource\EntityInterface>|false Entities list * on success, false on failure. * @see \Cake\ORM\Table::delete() for options and events related to this method. @@ -2478,7 +2478,7 @@ public function deleteMany(iterable $entities, array $options = []): iterable|fa * error. * * @param iterable<\Cake\Datasource\EntityInterface> $entities Entities to delete. - * @param array $options Options used when calling Table::save() for each entity. + * @param array $options Options used when calling Table::save() for each entity. * @return iterable<\Cake\Datasource\EntityInterface> Entities list. * @throws \Cake\ORM\Exception\PersistenceFailedException * @see \Cake\ORM\Table::delete() for options and events related to this method. @@ -2496,7 +2496,7 @@ public function deleteManyOrFail(iterable $entities, array $options = []): itera /** * @param iterable<\Cake\Datasource\EntityInterface> $entities Entities to delete. - * @param array $options Options used. + * @param array $options Options used. * @return \Cake\Datasource\EntityInterface|null */ protected function _deleteMany(iterable $entities, array $options = []): ?EntityInterface @@ -2534,7 +2534,7 @@ protected function _deleteMany(iterable $entities, array $options = []): ?Entity * has no primary key value, application rules checks failed or the delete was aborted by a callback. * * @param \Cake\Datasource\EntityInterface $entity The entity to remove. - * @param array $options The options for the delete. + * @param array $options The options for the delete. * @return true * @throws \Cake\ORM\Exception\PersistenceFailedException * @see \Cake\ORM\Table::delete() From 903d607c37dc6d82191d0e24d9204db97e4eb0f1 Mon Sep 17 00:00:00 2001 From: mscherer Date: Fri, 5 Jan 2024 18:34:41 +0100 Subject: [PATCH 1926/2059] Fix up list docblocks. --- Association/Loader/SelectLoader.php | 6 +++--- Behavior/TranslateBehavior.php | 2 +- LazyEagerLoader.php | 6 +++--- Query/SelectQuery.php | 2 +- Rule/IsUnique.php | 4 ++-- Rule/LinkConstraint.php | 2 +- RulesChecker.php | 2 +- Table.php | 2 +- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 6dfb545d..631a06d1 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -242,7 +242,7 @@ protected function _extractFinder(array|string $finderData): array * If the required fields are missing, throws an exception. * * @param \Cake\ORM\Query\SelectQuery $fetchQuery The association fetching query - * @param array $key The foreign key fields to check + * @param list $key The foreign key fields to check * @return void * @throws \InvalidArgumentException */ @@ -345,7 +345,7 @@ protected function _addFilteringCondition(SelectQuery $query, array|string $key, * from $keys with the tuple values in $filter using the provided operator. * * @param \Cake\ORM\Query\SelectQuery $query Target table's query - * @param array $keys the fields that should be used for filtering + * @param list $keys the fields that should be used for filtering * @param mixed $filter the value that should be used to match for $key * @param string $operator The operator for comparing the tuples * @return \Cake\Database\Expression\TupleComparison @@ -542,7 +542,7 @@ protected function _resultInjector(SelectQuery $fetchQuery, array $resultMap, ar * be done with multiple foreign keys * * @param array $resultMap A keyed arrays containing the target table - * @param array $sourceKeys An array with aliased keys to match + * @param list $sourceKeys An array with aliased keys to match * @param string $nestKey The key under which results should be nested * @return \Closure */ diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index c7c3e8ed..bdce565c 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -342,7 +342,7 @@ public function translationField(string $field): string * for each record. * * @param \Cake\ORM\Query\SelectQuery $query The original query to modify - * @param array $locales A list of locales or options with the `locales` key defined + * @param list $locales A list of locales or options with the `locales` key defined * @return \Cake\ORM\Query\SelectQuery */ public function findTranslations(SelectQuery $query, array $locales = []): SelectQuery diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index e7ca91f7..61e958c6 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -111,8 +111,8 @@ protected function _getQuery(array $entities, array $contain, Table $source): Se * in the top level entities. * * @param \Cake\ORM\Table $source The table having the top level associations - * @param array $associations The name of the top level associations - * @return array + * @param list $associations The name of the top level associations + * @return array */ protected function _getPropertyMap(Table $source, array $associations): array { @@ -133,7 +133,7 @@ protected function _getPropertyMap(Table $source, array $associations): array * * @param array<\Cake\Datasource\EntityInterface> $entities The original list of entities * @param \Cake\ORM\Query\SelectQuery $query The query to load results - * @param array $associations The top level associations that were loaded + * @param list $associations The top level associations that were loaded * @param \Cake\ORM\Table $source The table where the entities came from * @return array<\Cake\Datasource\EntityInterface> */ diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index 58712c66..4d514211 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -856,7 +856,7 @@ public function selectAlso( * pass overwrite boolean true which will reset the select clause removing all previous additions. * * @param \Cake\ORM\Table|\Cake\ORM\Association $table The table to use to get an array of columns - * @param array $excludedFields The un-aliased column names you do not want selected from $table + * @param list $excludedFields The un-aliased column names you do not want selected from $table * @param bool $overwrite Whether to reset/remove previous selected fields * @return $this */ diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index d19959f5..7546ef03 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -27,7 +27,7 @@ class IsUnique /** * The list of fields to check * - * @var array + * @var list */ protected array $_fields; @@ -47,7 +47,7 @@ class IsUnique * * - `allowMultipleNulls` Allows any field to have multiple null values. Defaults to true. * - * @param array $fields The list of fields to check uniqueness for + * @param list $fields The list of fields to check uniqueness for * @param array $options The options for unique checks. */ public function __construct(array $fields, array $options = []) diff --git a/Rule/LinkConstraint.php b/Rule/LinkConstraint.php index f0b39954..6c8161bf 100644 --- a/Rule/LinkConstraint.php +++ b/Rule/LinkConstraint.php @@ -118,7 +118,7 @@ public function __invoke(EntityInterface $entity, array $options): bool /** * Alias fields. * - * @param array $fields The fields that should be aliased. + * @param list $fields The fields that should be aliased. * @param \Cake\ORM\Table $source The object to use for aliasing. * @return array The aliased fields */ diff --git a/RulesChecker.php b/RulesChecker.php index 3dcaf15f..c28b66e4 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -48,7 +48,7 @@ class RulesChecker extends BaseRulesChecker * * - `allowMultipleNulls` Allows any field to have multiple null values. Defaults to false. * - * @param array $fields The list of fields to check for uniqueness. + * @param list $fields The list of fields to check for uniqueness. * @param array|string|null $message The error message to show in case the rule does not pass. Can * also be an array of options. When an array, the 'message' key can be used to provide a message. * @return \Cake\Datasource\RuleInvoker diff --git a/Table.php b/Table.php index 29ba8d50..4e8cb99f 100644 --- a/Table.php +++ b/Table.php @@ -1460,7 +1460,7 @@ public function findThreaded( * composite keys when comparing values. * * @param array $options the original options passed to a finder - * @param array $keys the keys to check in $options to build matchers from + * @param list $keys the keys to check in $options to build matchers from * the associated value * @return array */ From 446debed9ce958d989453416040643934cc6a9f9 Mon Sep 17 00:00:00 2001 From: mscherer Date: Fri, 5 Jan 2024 19:03:28 +0100 Subject: [PATCH 1927/2059] Refactored into non cloaking where clearly defined and nullable. --- Locator/LocatorAwareTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index fee43868..1788b915 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -59,7 +59,7 @@ public function setTableLocator(LocatorInterface $tableLocator) */ public function getTableLocator(): LocatorInterface { - if (isset($this->_tableLocator)) { + if ($this->_tableLocator !== null) { return $this->_tableLocator; } From 993852d8cf7b9ee2cf3096af9209227a3fb86db5 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Fri, 5 Jan 2024 23:57:48 +0100 Subject: [PATCH 1928/2059] Update src/ORM/Association/BelongsTo.php Co-authored-by: ADmad --- Association/BelongsTo.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index c147df9b..0a584716 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -137,7 +137,7 @@ public function type(): string public function saveAssociated(EntityInterface $entity, array $options = []): EntityInterface|false { $targetEntity = $entity->get($this->getProperty()); - if (!$targetEntity || !($targetEntity instanceof EntityInterface)) { + if (!$targetEntity instanceof EntityInterface) { return $entity; } From 052e8bfd04f287fca93e46a41cd9f97fda74d6a3 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Sat, 6 Jan 2024 00:00:19 +0100 Subject: [PATCH 1929/2059] Update src/ORM/Association/HasOne.php Co-authored-by: ADmad --- Association/HasOne.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/HasOne.php b/Association/HasOne.php index ecf2374b..fff3b284 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -121,7 +121,7 @@ public function type(): string public function saveAssociated(EntityInterface $entity, array $options = []): EntityInterface|false { $targetEntity = $entity->get($this->getProperty()); - if (!$targetEntity || !($targetEntity instanceof EntityInterface)) { + if (!$targetEntity instanceof EntityInterface) { return $entity; } From 4cf4bad2ce1941256ba4a4c9599978b16be038eb Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Sat, 6 Jan 2024 00:16:44 +0100 Subject: [PATCH 1930/2059] Update src/ORM/Behavior.php Co-authored-by: ADmad --- Behavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior.php b/Behavior.php index b0b7c977..7bee00bc 100644 --- a/Behavior.php +++ b/Behavior.php @@ -325,7 +325,7 @@ public function implementedEvents(): array public function implementedFinders(): array { $methods = $this->getConfig('implementedFinders'); - if ($methods !== null) { + if (!$methods) { return $methods; } From 58186906123f587d0db03c527dc5fc72db3f0701 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Sat, 6 Jan 2024 00:17:08 +0100 Subject: [PATCH 1931/2059] Update src/ORM/Behavior.php Co-authored-by: ADmad --- Behavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Behavior.php b/Behavior.php index 7bee00bc..b52034e7 100644 --- a/Behavior.php +++ b/Behavior.php @@ -357,7 +357,7 @@ public function implementedFinders(): array public function implementedMethods(): array { $methods = $this->getConfig('implementedMethods'); - if ($methods !== null) { + if (!$methods) { return $methods; } From 8912711dbee147f32fa6421ec6a2933453670f26 Mon Sep 17 00:00:00 2001 From: mscherer Date: Sat, 6 Jan 2024 00:20:54 +0100 Subject: [PATCH 1932/2059] Fix up PHPStan --- Behavior.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Behavior.php b/Behavior.php index b52034e7..6b495969 100644 --- a/Behavior.php +++ b/Behavior.php @@ -326,7 +326,7 @@ public function implementedFinders(): array { $methods = $this->getConfig('implementedFinders'); if (!$methods) { - return $methods; + return []; } return $this->_reflectionCache()['finders']; @@ -358,7 +358,7 @@ public function implementedMethods(): array { $methods = $this->getConfig('implementedMethods'); if (!$methods) { - return $methods; + return []; } return $this->_reflectionCache()['methods']; From 4fda6c1a1b942aeb72c236af2d6f46d2fc65c2fb Mon Sep 17 00:00:00 2001 From: mscherer Date: Sat, 6 Jan 2024 00:23:06 +0100 Subject: [PATCH 1933/2059] Fix up check. --- Behavior.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Behavior.php b/Behavior.php index 6b495969..3f947a36 100644 --- a/Behavior.php +++ b/Behavior.php @@ -325,8 +325,8 @@ public function implementedEvents(): array public function implementedFinders(): array { $methods = $this->getConfig('implementedFinders'); - if (!$methods) { - return []; + if ($methods) { + return $methods; } return $this->_reflectionCache()['finders']; @@ -357,8 +357,8 @@ public function implementedFinders(): array public function implementedMethods(): array { $methods = $this->getConfig('implementedMethods'); - if (!$methods) { - return []; + if ($methods) { + return $methods; } return $this->_reflectionCache()['methods']; From dd87abc2e034fab35118d775f47e1be51cbbd1e3 Mon Sep 17 00:00:00 2001 From: mscherer Date: Sat, 6 Jan 2024 00:25:45 +0100 Subject: [PATCH 1934/2059] Revert --- Behavior.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Behavior.php b/Behavior.php index 3f947a36..b0b7c977 100644 --- a/Behavior.php +++ b/Behavior.php @@ -325,7 +325,7 @@ public function implementedEvents(): array public function implementedFinders(): array { $methods = $this->getConfig('implementedFinders'); - if ($methods) { + if ($methods !== null) { return $methods; } @@ -357,7 +357,7 @@ public function implementedFinders(): array public function implementedMethods(): array { $methods = $this->getConfig('implementedMethods'); - if ($methods) { + if ($methods !== null) { return $methods; } From 09f41d60cc60966845bc2def91ae34c26a88df20 Mon Sep 17 00:00:00 2001 From: fabian-mcfly <13197057+fabian-mcfly@users.noreply.github.com> Date: Mon, 8 Jan 2024 17:49:05 +0100 Subject: [PATCH 1935/2059] CS & Annotations --- Behavior/Translate/EavStrategy.php | 2 ++ Behavior/Translate/ShadowTableStrategy.php | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index 360fdf2d..41a8def3 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -380,6 +380,7 @@ protected function rowMapper(CollectionInterface $results, string $locale): Coll $row[$field] = $content; if ($hydrated) { + /** @var \Cake\Datasource\EntityInterface $row */ $row->setDirty($field, false); } } @@ -389,6 +390,7 @@ protected function rowMapper(CollectionInterface $results, string $locale): Coll $row['_locale'] = $locale; if ($hydrated) { + /** @var \Cake\Datasource\EntityInterface $row */ $row->setDirty('_locale', false); } diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index bdcdd3f3..8e15cd89 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -489,6 +489,7 @@ protected function rowMapper(CollectionInterface $results, string $locale): Coll unset($row['translation']); if ($hydrated) { + /** @var \Cake\Datasource\EntityInterface $row */ $row->setDirty('_locale', false); } @@ -517,15 +518,18 @@ protected function rowMapper(CollectionInterface $results, string $locale): Coll $row[$field] = $translation[$field]; if ($hydrated) { + /** @var \Cake\Datasource\EntityInterface $row */ $row->setDirty($field, false); } } } } + /** @var array $row */ unset($row['translation']); if ($hydrated) { + /** @var \Cake\Datasource\EntityInterface $row */ $row->setDirty('_locale', false); } From 760d0d06875564b8870c8ce10e06b4ef5fa0c47a Mon Sep 17 00:00:00 2001 From: mscherer Date: Mon, 8 Jan 2024 18:38:53 +0100 Subject: [PATCH 1936/2059] Prefer defined vars over undefined checks. --- Behavior/CounterCacheBehavior.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index e64200d9..7efd9f0d 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -236,6 +236,7 @@ protected function _processAssociation( $updateConditions = array_combine($primaryKeys, $countConditions); $countOriginalConditions = $entity->extractOriginalChanged($foreignKeys); + $updateOriginalConditions = null; if ($countOriginalConditions !== []) { $updateOriginalConditions = array_combine($primaryKeys, $countOriginalConditions); } @@ -264,7 +265,7 @@ protected function _processAssociation( } } - if (isset($updateOriginalConditions) && $this->_shouldUpdateCount($updateOriginalConditions)) { + if ($updateOriginalConditions && $this->_shouldUpdateCount($updateOriginalConditions)) { if ($config instanceof Closure) { $count = $config($event, $entity, $this->_table, true); } else { From 901029e2d35245bf63b457e907a196e88cd2c4ef Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Wed, 17 Jan 2024 19:59:53 +0100 Subject: [PATCH 1937/2059] clarify deep marshalling --- Marshaller.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Marshaller.php b/Marshaller.php index 66ad81fa..2236a774 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -165,6 +165,16 @@ protected function _buildPropertyMap(array $data, array $options): array * ]); * ``` * + * ``` + * $result = $marshaller->one($data, [ + * 'associated' => [ + * 'Tags' => [ + * 'associated' => ['DeeperAssoc1', 'DeeperAssoc2'] + * ] + * ] + * ]); + * ``` + * * @param array $data The data to hydrate. * @param array $options List of options * @return \Cake\Datasource\EntityInterface @@ -517,6 +527,16 @@ protected function _loadAssociatedByIds(Association $assoc, array $ids): array * ]); * ``` * + * ``` + * $result = $marshaller->merge($entity, $data, [ + * 'associated' => [ + * 'Tags' => [ + * 'associated' => ['DeeperAssoc1', 'DeeperAssoc2'] + * ] + * ] + * ]); + * ``` + * * @param \Cake\Datasource\EntityInterface $entity the entity that will get the * data merged in * @param array $data key value list of fields to be merged into the entity From f0d4fb8e6c375c48e3163b793aa5cd4a98d6861f Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Sun, 21 Jan 2024 17:27:12 +0100 Subject: [PATCH 1938/2059] stan update --- Table.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Table.php b/Table.php index d3a3639d..ffe06dcd 100644 --- a/Table.php +++ b/Table.php @@ -2337,6 +2337,7 @@ protected function _saveMany( } }; + /** @var \Cake\Datasource\EntityInterface|null $failed */ $failed = null; try { $this->getConnection() From 233171b547d9587c01ef27eba71a02da36eb3cda Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Tue, 23 Jan 2024 21:48:40 +0100 Subject: [PATCH 1939/2059] Psalm suggested fixes. (#17555) * Psalm suggested fixes. * Fix CS --------- Co-authored-by: mscherer --- Association/Loader/SelectLoader.php | 2 +- Marshaller.php | 10 ++++++++-- Table.php | 4 ++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index d9541039..b1ff3470 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -256,7 +256,7 @@ protected function _assertFieldsPresent(SelectQuery $fetchQuery, array $key): vo if (!$select) { return; } - $missingKey = function ($fieldList, $key) { + $missingKey = function ($fieldList, $key): bool { foreach ($key as $keyField) { if (!in_array($keyField, $fieldList, true)) { return true; diff --git a/Marshaller.php b/Marshaller.php index 66ad81fa..e234584d 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -106,13 +106,19 @@ protected function _buildPropertyMap(array $data, array $options): array $nested['forceNew'] = $options['forceNew']; } if (isset($options['isMerge'])) { - $callback = function ($value, EntityInterface $entity) use ($assoc, $nested) { + $callback = function ( + $value, + EntityInterface $entity + ) use ( + $assoc, + $nested + ): array|EntityInterface|null { $options = $nested + ['associated' => [], 'association' => $assoc]; return $this->_mergeAssociation($entity->get($assoc->getProperty()), $assoc, $value, $options); }; } else { - $callback = function ($value, $entity) use ($assoc, $nested) { + $callback = function ($value, $entity) use ($assoc, $nested): array|EntityInterface|null { $options = $nested + ['associated' => []]; return $this->_marshalAssociation($assoc, $value, $options); diff --git a/Table.php b/Table.php index d3a3639d..c05ff52a 100644 --- a/Table.php +++ b/Table.php @@ -1478,7 +1478,7 @@ protected function _setFieldMatchers(array $options, array $keys): array $fields = $options[$field]; $glue = in_array($field, ['keyField', 'parentField'], true) ? ';' : $options['valueSeparator']; - $options[$field] = function ($row) use ($fields, $glue) { + $options[$field] = function ($row) use ($fields, $glue): string { $matches = []; foreach ($fields as $field) { $matches[] = $row[$field]; @@ -2759,7 +2759,7 @@ protected function _dynamicFinder(string $method, array $args): SelectQuery $hasOr = str_contains($fields, '_or_'); $hasAnd = str_contains($fields, '_and_'); - $makeConditions = function ($fields, $args) { + $makeConditions = function ($fields, $args): array { $conditions = []; if (count($args) < count($fields)) { throw new BadMethodCallException(sprintf( From b63d68819f4074fb8717503f270ff3fac6cf8f4b Mon Sep 17 00:00:00 2001 From: mscherer Date: Thu, 23 Nov 2023 02:35:08 +0100 Subject: [PATCH 1940/2059] More subpackages validation. --- .gitattributes | 10 +++++++ composer.json | 5 ++++ phpstan.neon.dist | 16 ++++++++++ tests/phpstan-bootstrap.php | 60 +++++++++++++++++++++++++++++++++++++ 4 files changed, 91 insertions(+) create mode 100644 .gitattributes create mode 100644 phpstan.neon.dist create mode 100644 tests/phpstan-bootstrap.php diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..0086560d --- /dev/null +++ b/.gitattributes @@ -0,0 +1,10 @@ +# Define the line ending behavior of the different file extensions +# Set default behavior, in case users don't have core.autocrlf set. +* text text=auto eol=lf + +.php diff=php + +# Remove files for archives generated using `git archive` +.gitattributes export-ignore +phpstan.neon.dist export-ignore +tests/ export-ignore diff --git a/composer.json b/composer.json index 8f2f0cd2..c29f4853 100644 --- a/composer.json +++ b/composer.json @@ -43,5 +43,10 @@ "files": [ "bootstrap.php" ] + }, + "require-dev": { + "cakephp/cache": "^5.0", + "cakephp/i18n": "^5.0", + "phpstan/phpstan": "^1.10" } } diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 00000000..1a74c1b9 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,16 @@ +parameters: + level: 8 + checkMissingIterableValueType: false + checkGenericClassInNonGenericObjectType: false + treatPhpDocTypesAsCertain: false + bootstrapFiles: + - tests/phpstan-bootstrap.php + paths: + - ./ + excludePaths: + - vendor/ + ignoreErrors: + - '#Unsafe usage of new static\(\).#' + - "#^Method Cake\\\\ORM\\\\Behavior\\\\TreeBehavior\\:\\:_scope\\(\\) should return T of Cake\\\\ORM\\\\Query\\\\DeleteQuery\\|Cake\\\\ORM\\\\Query\\\\SelectQuery\\|Cake\\\\ORM\\\\Query\\\\UpdateQuery but returns Cake\\\\ORM\\\\Query\\\\DeleteQuery\\|Cake\\\\ORM\\\\Query\\\\SelectQuery\\|Cake\\\\ORM\\\\Query\\\\UpdateQuery\\.$#" + - "#^PHPDoc tag @return with type Cake\\\\ORM\\\\Query\\\\SelectQuery\\ is not subtype of native type static\\(Cake\\\\ORM\\\\Query\\\\SelectQuery\\\\)\\.$#" + - "#^Method Cake\\\\ORM\\\\Query\\\\SelectQuery\\:\\:find\\(\\) should return static\\(Cake\\\\ORM\\\\Query\\\\SelectQuery\\\\) but returns Cake\\\\ORM\\\\Query\\\\SelectQuery\\\\.$#" diff --git a/tests/phpstan-bootstrap.php b/tests/phpstan-bootstrap.php new file mode 100644 index 00000000..0e60e7fb --- /dev/null +++ b/tests/phpstan-bootstrap.php @@ -0,0 +1,60 @@ + 'App', + 'encoding' => 'UTF-8', +]); + +ini_set('intl.default_locale', 'en_US'); +ini_set('session.gc_divisor', '1'); +ini_set('assert.exception', '1'); From 67c4d949086f1614c10cbe3ce3dbab8328fefa2e Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Tue, 27 Feb 2024 17:49:04 +0100 Subject: [PATCH 1941/2059] Fix up array list<> docblocks. (#17597) * Fix up array list<> docblocks. --------- Co-authored-by: ADmad --- Association.php | 14 +++++------ Association/BelongsTo.php | 6 ++--- Association/BelongsToMany.php | 26 ++++++++++---------- Association/HasMany.php | 6 ++--- Association/HasOne.php | 6 ++--- Association/Loader/SelectLoader.php | 6 ++--- Association/Loader/SelectWithPivotLoader.php | 2 +- AssociationCollection.php | 4 +-- Behavior/CounterCacheBehavior.php | 2 +- Behavior/Translate/ShadowTableStrategy.php | 5 ++-- Exception/PersistenceFailedException.php | 2 +- Locator/TableLocator.php | 4 +-- Rule/ExistsIn.php | 4 +-- Rule/LinkConstraint.php | 7 +++--- RulesChecker.php | 2 +- Table.php | 14 +++++------ 16 files changed, 56 insertions(+), 54 deletions(-) diff --git a/Association.php b/Association.php index 8d09ae29..ce44251b 100644 --- a/Association.php +++ b/Association.php @@ -112,14 +112,14 @@ abstract class Association /** * The field name in the owning side table that is used to match with the foreignKey * - * @var array|string + * @var list|string */ protected array|string $_bindingKey; /** * The name of the field representing the foreign key to the table to load * - * @var array|string|false + * @var list|string|false */ protected array|string|false $_foreignKey; @@ -195,7 +195,7 @@ abstract class Association /** * Valid strategies for this association. Subclasses can narrow this down. * - * @var array + * @var list */ protected array $_validStrategies = [ self::STRATEGY_JOIN, @@ -430,7 +430,7 @@ public function getConditions(): Closure|array * Sets the name of the field representing the binding field with the target table. * When not manually specified the primary key of the owning side table is used. * - * @param array|string $key the table field or fields to be used to link both tables together + * @param list|string $key the table field or fields to be used to link both tables together * @return $this */ public function setBindingKey(array|string $key) @@ -444,7 +444,7 @@ public function setBindingKey(array|string $key) * Gets the name of the field representing the binding field with the target table. * When not manually specified the primary key of the owning side table is used. * - * @return array|string + * @return list|string */ public function getBindingKey(): array|string { @@ -460,7 +460,7 @@ public function getBindingKey(): array|string /** * Gets the name of the field representing the foreign key to the target table. * - * @return array|string|false + * @return list|string|false */ public function getForeignKey(): array|string|false { @@ -470,7 +470,7 @@ public function getForeignKey(): array|string|false /** * Sets the name of the field representing the foreign key to the target table. * - * @param array|string $key the key or keys to be used to link both tables together + * @param list|string $key the key or keys to be used to link both tables together * @return $this */ public function setForeignKey(array|string $key) diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 0a584716..01555394 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -40,7 +40,7 @@ class BelongsTo extends Association /** * Valid strategies for this type of association * - * @var array + * @var list */ protected array $_validStrategies = [ self::STRATEGY_JOIN, @@ -62,7 +62,7 @@ public function getForeignKey(): array|string|false /** * Sets the name of the field representing the foreign key to the target table. * - * @param array|string|false $key the key or keys to be used to link both tables together, if set to `false` + * @param list|string|false $key the key or keys to be used to link both tables together, if set to `false` * no join conditions will be generated automatically. * @return $this */ @@ -147,7 +147,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []): En return false; } - /** @var array $foreignKeys */ + /** @var list $foreignKeys */ $foreignKeys = (array)$this->getForeignKey(); $properties = array_combine( $foreignKeys, diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 72ac9f9c..950af585 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -111,7 +111,7 @@ class BelongsToMany extends Association /** * The name of the field representing the foreign key to the target table * - * @var array|string|null + * @var list|string|null */ protected array|string|null $_targetForeignKey = null; @@ -125,7 +125,7 @@ class BelongsToMany extends Association /** * Valid strategies for this type of association * - * @var array + * @var list */ protected array $_validStrategies = [ self::STRATEGY_SELECT, @@ -166,7 +166,7 @@ class BelongsToMany extends Association /** * Sets the name of the field representing the foreign key to the target table. * - * @param array|string $key the key to be used to link both tables together + * @param list|string $key the key to be used to link both tables together * @return $this */ public function setTargetForeignKey(array|string $key) @@ -179,7 +179,7 @@ public function setTargetForeignKey(array|string $key) /** * Gets the name of the field representing the foreign key to the target table. * - * @return array|string + * @return list|string */ public function getTargetForeignKey(): array|string { @@ -584,7 +584,7 @@ public function cascadeDelete(EntityInterface $entity, array $options = []): boo return true; } - /** @var array $foreignKeys */ + /** @var list $foreignKeys */ $foreignKeys = (array)$this->getForeignKey(); $bindingKeys = (array)$this->getBindingKey(); $conditions = []; @@ -794,9 +794,9 @@ protected function _saveLinks(EntityInterface $sourceEntity, array $targetEntiti $junction = $this->junction(); $entityClass = $junction->getEntityClass(); $belongsTo = $junction->getAssociation($target->getAlias()); - /** @var array $foreignKey */ + /** @var list $foreignKey */ $foreignKey = (array)$this->getForeignKey(); - /** @var array $assocForeignKey */ + /** @var list $assocForeignKey */ $assocForeignKey = (array)$belongsTo->getForeignKey(); $targetBindingKey = (array)$belongsTo->getBindingKey(); $bindingKey = (array)$this->getBindingKey(); @@ -910,9 +910,9 @@ function () use ($sourceEntity, $targetEntities, $options) { * this association. * @param array<\Cake\Datasource\EntityInterface> $targetEntities List of entities persisted in the target table for * this association. - * @param array|bool $options List of options to be passed to the internal `delete` call, + * @param array|bool $options List of options to be passed to the internal `delete` call, * or a `boolean` as `cleanProperty` key shortcut. - * @throws \InvalidArgumentException If non persisted entities are passed or if + * @throws \InvalidArgumentException If non-persisted entities are passed or if * any of them is lacking a primary key value. * @return bool Success */ @@ -1194,7 +1194,7 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { $junction = $this->junction(); $target = $this->getTarget(); - /** @var array $foreignKey */ + /** @var list $foreignKey */ $foreignKey = (array)$this->getForeignKey(); $assocForeignKey = (array)$junction->getAssociation($target->getAlias())->getForeignKey(); $prefixedForeignKey = array_map([$junction, 'aliasField'], $foreignKey); @@ -1276,9 +1276,9 @@ protected function _diffLinks( $junction = $this->junction(); $target = $this->getTarget(); $belongsTo = $junction->getAssociation($target->getAlias()); - /** @var array $foreignKey */ + /** @var list $foreignKey */ $foreignKey = (array)$this->getForeignKey(); - /** @var array $assocForeignKey */ + /** @var list $assocForeignKey */ $assocForeignKey = (array)$belongsTo->getForeignKey(); $keys = array_merge($foreignKey, $assocForeignKey); @@ -1416,7 +1416,7 @@ protected function _collectJointEntities(EntityInterface $sourceEntity, array $t $belongsTo = $junction->getAssociation($target->getAlias()); $hasMany = $source->getAssociation($junction->getAlias()); - /** @var array $foreignKey */ + /** @var list $foreignKey */ $foreignKey = (array)$this->getForeignKey(); $foreignKey = array_map(function ($key) { return $key . ' IS'; diff --git a/Association/HasMany.php b/Association/HasMany.php index 1a28aea1..f41f425f 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -64,7 +64,7 @@ class HasMany extends Association /** * Valid strategies for this type of association * - * @var array + * @var list */ protected array $_validStrategies = [ self::STRATEGY_SELECT, @@ -169,7 +169,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []): En throw new InvalidArgumentException($message); } - /** @var array $foreignKeys */ + /** @var list $foreignKeys */ $foreignKeys = (array)$this->getForeignKey(); $foreignKeyReference = array_combine( $foreignKeys, @@ -368,7 +368,7 @@ public function unlink(EntityInterface $sourceEntity, array $targetEntities, arr 'OR' => (new Collection($targetEntities)) ->map(function (EntityInterface $entity) use ($targetPrimaryKey) { /** @psalm-suppress InvalidArgument,UnusedPsalmSuppress */ - /** @var array $targetPrimaryKey */ + /** @var list $targetPrimaryKey */ return $entity->extract($targetPrimaryKey); }) ->toList(), diff --git a/Association/HasOne.php b/Association/HasOne.php index fff3b284..9da1390e 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -38,7 +38,7 @@ class HasOne extends Association /** * Valid strategies for this type of association * - * @var array + * @var list */ protected array $_validStrategies = [ self::STRATEGY_JOIN, @@ -60,7 +60,7 @@ public function getForeignKey(): array|string|false /** * Sets the name of the field representing the foreign key to the target table. * - * @param array|string|false $key the key or keys to be used to link both tables together, if set to `false` + * @param list|string|false $key the key or keys to be used to link both tables together, if set to `false` * no join conditions will be generated automatically. * @return $this */ @@ -125,7 +125,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []): En return $entity; } - /** @var array $foreignKeys */ + /** @var list $foreignKeys */ $foreignKeys = (array)$this->getForeignKey(); $properties = array_combine( $foreignKeys, diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index b1ff3470..3ee2417c 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -289,7 +289,7 @@ protected function _assertFieldsPresent(SelectQuery $fetchQuery, array $key): vo * filtering needs to be done using a subquery. * * @param \Cake\ORM\Query\SelectQuery $query Target table's query - * @param array|string $key the fields that should be used for filtering + * @param list|string $key the fields that should be used for filtering * @param \Cake\ORM\Query\SelectQuery $subquery The Subquery to use for filtering * @return \Cake\ORM\Query\SelectQuery */ @@ -325,7 +325,7 @@ protected function _addFilteringJoin(SelectQuery $query, array|string $key, Sele * target table query given a filter key and some filtering values. * * @param \Cake\ORM\Query\SelectQuery $query Target table's query - * @param array|string $key The fields that should be used for filtering + * @param list|string $key The fields that should be used for filtering * @param mixed $filter The value that should be used to match for $key * @return \Cake\ORM\Query\SelectQuery */ @@ -372,7 +372,7 @@ protected function _createTupleCondition( * which the filter should be applied * * @param array $options The options for getting the link field. - * @return array|string + * @return list|string * @throws \Cake\Database\Exception\DatabaseException */ protected function _linkField(array $options): array|string diff --git a/Association/Loader/SelectWithPivotLoader.php b/Association/Loader/SelectWithPivotLoader.php index 3ed2197a..52e1d216 100644 --- a/Association/Loader/SelectWithPivotLoader.php +++ b/Association/Loader/SelectWithPivotLoader.php @@ -146,7 +146,7 @@ protected function _assertFieldsPresent(SelectQuery $fetchQuery, array $key): vo * which the filter should be applied * * @param array $options the options to use for getting the link field. - * @return array|string + * @return list|string */ protected function _linkField(array $options): array|string { diff --git a/AssociationCollection.php b/AssociationCollection.php index e66b5a4e..86ef452b 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -146,7 +146,7 @@ public function has(string $alias): bool /** * Get the names of all the associations in the collection. * - * @return array + * @return list */ public function keys(): array { @@ -156,7 +156,7 @@ public function keys(): array /** * Get an array of associations matching a specific type. * - * @param array|string $class The type of associations you want. + * @param list|string $class The type of associations you want. * For example 'BelongsTo' or array like ['BelongsTo', 'HasOne'] * @return array<\Cake\ORM\Association> An array of Association objects. * @since 3.5.3 diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 7efd9f0d..292b2c8f 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -221,7 +221,7 @@ protected function _processAssociation( Association $assoc, array $settings ): void { - /** @var array $foreignKeys */ + /** @var list $foreignKeys */ $foreignKeys = (array)$assoc->getForeignKey(); $countConditions = $entity->extract($foreignKeys); diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index e3389ffb..8ee4fc9d 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -607,10 +607,11 @@ protected function bundleTranslatedFields(EntityInterface $entity): void /** * Lazy define and return the main table fields. * - * @return array + * @return list */ protected function mainFields(): array { + /** @var list $fields */ $fields = $this->getConfig('mainTableFields'); if ($fields) { @@ -627,7 +628,7 @@ protected function mainFields(): array /** * Lazy define and return the translation table fields. * - * @return array + * @return list */ protected function translatedFields(): array { diff --git a/Exception/PersistenceFailedException.php b/Exception/PersistenceFailedException.php index 68881429..534f88b9 100644 --- a/Exception/PersistenceFailedException.php +++ b/Exception/PersistenceFailedException.php @@ -40,7 +40,7 @@ class PersistenceFailedException extends CakeException * Constructor. * * @param \Cake\Datasource\EntityInterface $entity The entity on which the persistence operation failed - * @param array|string $message Either the string of the error message, or an array of attributes + * @param list|string $message Either the string of the error message, or an array of attributes * that are made available in the view, and sprintf()'d into Exception::$_messageTemplate * @param int|null $code The code of the error, is also the HTTP status code for the error. * @param \Throwable|null $previous the previous exception. diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 0ef5a0fb..30efe1c9 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -36,7 +36,7 @@ class TableLocator extends AbstractLocator implements LocatorInterface /** * Contains a list of locations where table classes should be looked for. * - * @var array + * @var list */ protected array $locations = []; @@ -83,7 +83,7 @@ class TableLocator extends AbstractLocator implements LocatorInterface /** * Constructor. * - * @param array|null $locations Locations where tables should be looked for. + * @param list|null $locations Locations where tables should be looked for. * If none provided, the default `Model\Table` under your app's namespace is used. */ public function __construct(?array $locations = null, ?QueryFactory $queryFactory = null) diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index ea9a5f4c..6272430e 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -30,7 +30,7 @@ class ExistsIn /** * The list of fields to check * - * @var array + * @var list */ protected array $_fields; @@ -54,7 +54,7 @@ class ExistsIn * Available option for $options is 'allowNullableNulls' flag. * Set to true to accept composite foreign keys where one or more nullable columns are null. * - * @param array|string $fields The field or fields to check existence as primary key. + * @param list|string $fields The field or fields to check existence as primary key. * @param \Cake\ORM\Table|\Cake\ORM\Association|string $repository The repository where the * field will be looked for, or the association name for the repository. * @param array $options The options that modify the rule's behavior. diff --git a/Rule/LinkConstraint.php b/Rule/LinkConstraint.php index 6c8161bf..98f62adc 100644 --- a/Rule/LinkConstraint.php +++ b/Rule/LinkConstraint.php @@ -120,7 +120,7 @@ public function __invoke(EntityInterface $entity, array $options): bool * * @param list $fields The fields that should be aliased. * @param \Cake\ORM\Table $source The object to use for aliasing. - * @return array The aliased fields + * @return list The aliased fields */ protected function _aliasFields(array $fields, Table $source): array { @@ -134,9 +134,9 @@ protected function _aliasFields(array $fields, Table $source): array /** * Build conditions. * - * @param array $fields The condition fields. + * @param list $fields The condition fields. * @param array $values The condition values. - * @return array A conditions array combined from the passed fields and values. + * @return array A conditions array combined from the passed fields and values. */ protected function _buildConditions(array $fields, array $values): array { @@ -162,6 +162,7 @@ protected function _countLinks(Association $association, EntityInterface $entity { $source = $association->getSource(); + /** @var list $primaryKey */ $primaryKey = (array)$source->getPrimaryKey(); if (!$entity->has($primaryKey)) { throw new DatabaseException(sprintf( diff --git a/RulesChecker.php b/RulesChecker.php index c28b66e4..a7c19a41 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -90,7 +90,7 @@ public function isUnique(array $fields, array|string|null $message = null): Rule * 'message' sets a custom error message. * Set 'allowNullableNulls' to true to accept composite foreign keys where one or more nullable columns are null. * - * @param array|string $field The field or list of fields to check for existence by + * @param list|string $field The field or list of fields to check for existence by * primary key lookup in the other table. * @param \Cake\ORM\Table|\Cake\ORM\Association|string $table The table name where the fields existence will be checked. * @param array|string|null $message The error message to show in case the rule does not pass. Can diff --git a/Table.php b/Table.php index a469099d..566ccc61 100644 --- a/Table.php +++ b/Table.php @@ -232,14 +232,14 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc /** * The name of the field that represents the primary key in the table * - * @var array|string|null + * @var list|string|null */ protected array|string|null $_primaryKey = null; /** * The name of the field that represents a human-readable representation of a row * - * @var array|string|null + * @var list|string|null */ protected array|string|null $_displayField = null; @@ -637,7 +637,7 @@ public function hasField(string $field): bool /** * Sets the primary key field name. * - * @param array|string $key Sets a new name to be used as primary key + * @param list|string $key Sets a new name to be used as primary key * @return $this */ public function setPrimaryKey(array|string $key) @@ -650,7 +650,7 @@ public function setPrimaryKey(array|string $key) /** * Returns the primary key field name. * - * @return array|string + * @return list|string */ public function getPrimaryKey(): array|string { @@ -668,7 +668,7 @@ public function getPrimaryKey(): array|string /** * Sets the display field. * - * @param array|string $field Name to be used as display field. + * @param list|string $field Name to be used as display field. * @return $this */ public function setDisplayField(array|string $field) @@ -681,7 +681,7 @@ public function setDisplayField(array|string $field) /** * Returns the display field. * - * @return array|string + * @return list|string */ public function getDisplayField(): array|string|null { @@ -2210,7 +2210,7 @@ protected function _insert(EntityInterface $entity, array $data): EntityInterfac * Note: The ORM will not generate primary key values for composite primary keys. * You can overwrite _newId() in your table class. * - * @param array $primary The primary key columns to get a new ID for. + * @param list $primary The primary key columns to get a new ID for. * @return string|null Either null or the primary key value or a list of primary key values. */ protected function _newId(array $primary): ?string From 7264ec385ce29f5c762674cb21fbd3961a8f7433 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 16 Jan 2024 12:21:03 +0530 Subject: [PATCH 1942/2059] Add supported for unbuffered ORM resultsets. --- EagerLoader.php | 89 ++++++++++++++++++++ Query/SelectQuery.php | 13 ++- ResultSet.php | 189 +----------------------------------------- ResultSetFactory.php | 13 ++- 4 files changed, 111 insertions(+), 193 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index a62cae96..1fb88aef 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -16,6 +16,7 @@ */ namespace Cake\ORM; +use Cake\Collection\Collection; use Cake\ORM\Query\SelectQuery; use Closure; use InvalidArgumentException; @@ -668,6 +669,94 @@ public function loadExternal(SelectQuery $query, array $results): array return $results; } + /** + * Inject data from associations that cannot be joined directly for unbuffered queries. + * + * @param \Cake\ORM\Query\SelectQuery $query The query for which to eager load external. + * associations. + * @param iterable $results Results array. + * @return iterable + * @throws \RuntimeException + */ + public function loadExternalUnbuffered(SelectQuery $query, iterable $results): iterable + { + $table = $query->getRepository(); + $external = $this->externalAssociations($table); + if (!$external) { + return $results; + } + + $assocData = []; + + foreach ($external as $meta) { + // $contain = $meta->associations(); + $instance = $meta->instance(); + $config = $meta->getConfig(); + $alias = $instance->getSource()->getAlias(); + $path = $meta->aliasPath(); + + if ($instance->requiresKeys($config)) { + $source = $instance->getSource(); + $keys = $instance->type() === Association::MANY_TO_ONE ? + (array)$instance->getForeignKey() : + (array)$instance->getBindingKey(); + + $alias = $source->getAlias(); + $pkFields = []; + /** @var string $key */ + foreach ($keys as $key) { + $pkFields[] = key($query->aliasField($key, $alias)); + } + $assocData[$path] = [ + 'alias' => $alias, + 'pkFields' => $pkFields, + ]; + } + } + + $results = (new Collection($results)) + ->map(function ($row) use ($query, $external, $assocData) { + foreach ($external as $meta) { + $path = $meta->aliasPath(); + $keys = null; + if (count($assocData[$path]['pkFields']) === 1) { + // Missed joins will have null in the results. + if (array_key_exists($assocData[$path]['pkFields'][0], $row)) { + // Assign empty array to avoid not found association when optional. + if (!isset($row[$assocData[$path]['pkFields'][0]])) { + $keys = []; + } else { + $value = $row[$assocData[$path]['pkFields'][0]]; + $keys = [$value => $value]; + } + } + } else { + // Handle composite keys. + $collected = []; + foreach ($assocData[$path]['pkFields'] as $key) { + $collected[] = $row[$key]; + } + $keys[implode(';', $collected)] = $collected; + } + + $callback = $meta->instance()->eagerLoader( + $meta->getConfig() + [ + 'query' => $query, + 'contain' => $meta->associations(), + 'keys' => $keys, + 'nestKey' => $meta->aliasPath(), + ] + ); + + $row = $callback($row); + } + + return $row; + }); + + return $results; + } + /** * Returns an array having as keys a dotted path of associations that participate * in this eager loader. The values of the array will contain the following keys: diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index cb702b0d..80fc563f 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -1584,11 +1584,16 @@ protected function _execute(): iterable return $this->_results; } - $results = parent::all(); - if (!is_array($results)) { - $results = iterator_to_array($results); + if ($this->useBufferedResults) { + $results = parent::all(); + if (!is_array($results)) { + $results = iterator_to_array($results); + } + $results = $this->getEagerLoader()->loadExternal($this, $results); + } else { + $statement = $this->execute(); + $results = $this->getEagerLoader()->loadExternalUnbuffered($this, $statement); } - $results = $this->getEagerLoader()->loadExternal($this, $results); return $this->resultSetFactory()->createResultSet($this, $results); } diff --git a/ResultSet.php b/ResultSet.php index 93426ffc..242ab7be 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -16,10 +16,8 @@ */ namespace Cake\ORM; -use Cake\Collection\CollectionTrait; -use Cake\Datasource\EntityInterface; +use Cake\Collection\Collection; use Cake\Datasource\ResultSetInterface; -use SplFixedArray; /** * Represents the results obtained after executing a query for a specific table @@ -30,184 +28,8 @@ * @template T of \Cake\Datasource\EntityInterface|array * @implements \Cake\Datasource\ResultSetInterface */ -class ResultSet implements ResultSetInterface +class ResultSet extends Collection implements ResultSetInterface { - use CollectionTrait; - - /** - * Points to the next record number that should be fetched - * - * @var int - */ - protected int $_index = 0; - - /** - * Last record fetched from the statement - * - * @var \Cake\Datasource\EntityInterface|array|null - * @psalm-var T|null - */ - protected EntityInterface|array|null $_current; - - /** - * Holds the count of records in this result set - * - * @var int - */ - protected int $_count = 0; - - /** - * Results that have been fetched or hydrated into the results. - * - * @var \SplFixedArray - */ - protected SplFixedArray $_results; - - /** - * Constructor - * - * @param array $results Results array. - */ - public function __construct(array $results) - { - $this->__unserialize($results); - } - - /** - * Returns the current record in the result iterator. - * - * Part of Iterator interface. - * - * @return \Cake\Datasource\EntityInterface|array|null - * @psalm-return T|null - */ - public function current(): EntityInterface|array|null - { - return $this->_current; - } - - /** - * Returns the key of the current record in the iterator. - * - * Part of Iterator interface. - * - * @return int - */ - public function key(): int - { - return $this->_index; - } - - /** - * Advances the iterator pointer to the next record. - * - * Part of Iterator interface. - * - * @return void - */ - public function next(): void - { - $this->_index++; - } - - /** - * Rewinds a ResultSet. - * - * Part of Iterator interface. - * - * @return void - */ - public function rewind(): void - { - $this->_index = 0; - } - - /** - * Whether there are more results to be fetched from the iterator. - * - * Part of Iterator interface. - * - * @return bool - */ - public function valid(): bool - { - if ($this->_index < $this->_count) { - $this->_current = $this->_results[$this->_index]; - - return true; - } - - return false; - } - - /** - * Get the first record from a result set. - * - * This method will also close the underlying statement cursor. - * - * @return \Cake\Datasource\EntityInterface|array|null - * @psalm-return T|null - */ - public function first(): EntityInterface|array|null - { - foreach ($this as $result) { - return $result; - } - - return null; - } - - /** - * Serializes a resultset. - * - * @return array - */ - public function __serialize(): array - { - return $this->_results->toArray(); - } - - /** - * Unserializes a resultset. - * - * @param array $data Data array. - * @return void - */ - public function __unserialize(array $data): void - { - $this->_results = SplFixedArray::fromArray($data); - $this->_count = $this->_results->count(); - } - - /** - * Gives the number of rows in the result set. - * - * Part of the Countable interface. - * - * @return int - */ - public function count(): int - { - return $this->_count; - } - - /** - * @inheritDoc - */ - public function countKeys(): int - { - // This is an optimization over the implementation provided by CollectionTrait::countKeys() - return $this->_count; - } - - /** - * @inheritDoc - */ - public function isEmpty(): bool - { - return !$this->_count; - } - /** * Returns an array that can be used to describe the internal state of this * object. @@ -216,13 +38,8 @@ public function isEmpty(): bool */ public function __debugInfo(): array { - $currentIndex = $this->_index; - // toArray() adjusts the current index, so we have to reset it - $items = $this->toArray(); - $this->_index = $currentIndex; - return [ - 'items' => $items, + 'items' => $this->toArray(), ]; } } diff --git a/ResultSetFactory.php b/ResultSetFactory.php index 78e6fee0..eff9b13e 100644 --- a/ResultSetFactory.php +++ b/ResultSetFactory.php @@ -37,12 +37,19 @@ class ResultSetFactory * @param array $results Results array. * @return \Cake\ORM\ResultSet */ - public function createResultSet(SelectQuery $query, array $results): ResultSet + public function createResultSet(SelectQuery $query, iterable $results, bool $bufferedResults = true): ResultSet { $data = $this->collectData($query); - foreach ($results as $i => $row) { - $results[$i] = $this->groupResult($row, $data); + if (is_array($results)) { + foreach ($results as $i => $row) { + $results[$i] = $this->groupResult($row, $data); + } + } else { + $results = (new Collection($results)) + ->map(function ($row) use ($data) { + return $this->groupResult($row, $data); + }); } return new ResultSet($results); From 4036cc43f189b2741bf20ec8e1a822e9252db177 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 7 Apr 2024 23:44:46 +0530 Subject: [PATCH 1943/2059] Fix loading of external associations for unbuffered queries. Since new queries can't be executed for the same connection for unbuffered MySQL queries, containing externally loaded associations will implicitly buffer results. --- EagerLoader.php | 103 ++++-------------------------------------- Query/SelectQuery.php | 8 +--- ResultSetFactory.php | 2 +- 3 files changed, 13 insertions(+), 100 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index 1fb88aef..2ec3cd85 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -16,7 +16,6 @@ */ namespace Cake\ORM; -use Cake\Collection\Collection; use Cake\ORM\Query\SelectQuery; use Closure; use InvalidArgumentException; @@ -609,22 +608,28 @@ protected function _resolveJoins(array $associations, array $matching = []): arr * * @param \Cake\ORM\Query\SelectQuery $query The query for which to eager load external. * associations. - * @param array $results Results array. + * @param iterable $results Results. * @return array * @throws \RuntimeException */ - public function loadExternal(SelectQuery $query, array $results): array + public function loadExternal(SelectQuery $query, iterable $results): array { if (!$results) { return $results; } - $table = $query->getRepository(); - $external = $this->externalAssociations($table); + $external = $this->externalAssociations($query->getRepository()); if (!$external) { return $results; } + if (!is_array($results)) { + $results = iterator_to_array($results); + } + if (!$results) { + return $results; + } + $collected = $this->_collectKeys($external, $query, $results); foreach ($external as $meta) { @@ -669,94 +674,6 @@ public function loadExternal(SelectQuery $query, array $results): array return $results; } - /** - * Inject data from associations that cannot be joined directly for unbuffered queries. - * - * @param \Cake\ORM\Query\SelectQuery $query The query for which to eager load external. - * associations. - * @param iterable $results Results array. - * @return iterable - * @throws \RuntimeException - */ - public function loadExternalUnbuffered(SelectQuery $query, iterable $results): iterable - { - $table = $query->getRepository(); - $external = $this->externalAssociations($table); - if (!$external) { - return $results; - } - - $assocData = []; - - foreach ($external as $meta) { - // $contain = $meta->associations(); - $instance = $meta->instance(); - $config = $meta->getConfig(); - $alias = $instance->getSource()->getAlias(); - $path = $meta->aliasPath(); - - if ($instance->requiresKeys($config)) { - $source = $instance->getSource(); - $keys = $instance->type() === Association::MANY_TO_ONE ? - (array)$instance->getForeignKey() : - (array)$instance->getBindingKey(); - - $alias = $source->getAlias(); - $pkFields = []; - /** @var string $key */ - foreach ($keys as $key) { - $pkFields[] = key($query->aliasField($key, $alias)); - } - $assocData[$path] = [ - 'alias' => $alias, - 'pkFields' => $pkFields, - ]; - } - } - - $results = (new Collection($results)) - ->map(function ($row) use ($query, $external, $assocData) { - foreach ($external as $meta) { - $path = $meta->aliasPath(); - $keys = null; - if (count($assocData[$path]['pkFields']) === 1) { - // Missed joins will have null in the results. - if (array_key_exists($assocData[$path]['pkFields'][0], $row)) { - // Assign empty array to avoid not found association when optional. - if (!isset($row[$assocData[$path]['pkFields'][0]])) { - $keys = []; - } else { - $value = $row[$assocData[$path]['pkFields'][0]]; - $keys = [$value => $value]; - } - } - } else { - // Handle composite keys. - $collected = []; - foreach ($assocData[$path]['pkFields'] as $key) { - $collected[] = $row[$key]; - } - $keys[implode(';', $collected)] = $collected; - } - - $callback = $meta->instance()->eagerLoader( - $meta->getConfig() + [ - 'query' => $query, - 'contain' => $meta->associations(), - 'keys' => $keys, - 'nestKey' => $meta->aliasPath(), - ] - ); - - $row = $callback($row); - } - - return $row; - }); - - return $results; - } - /** * Returns an array having as keys a dotted path of associations that participate * in this eager loader. The values of the array will contain the following keys: diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index 80fc563f..e846d66d 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -1586,14 +1586,10 @@ protected function _execute(): iterable if ($this->useBufferedResults) { $results = parent::all(); - if (!is_array($results)) { - $results = iterator_to_array($results); - } - $results = $this->getEagerLoader()->loadExternal($this, $results); } else { - $statement = $this->execute(); - $results = $this->getEagerLoader()->loadExternalUnbuffered($this, $statement); + $results = $this->execute(); } + $results = $this->getEagerLoader()->loadExternal($this, $results); return $this->resultSetFactory()->createResultSet($this, $results); } diff --git a/ResultSetFactory.php b/ResultSetFactory.php index eff9b13e..08998630 100644 --- a/ResultSetFactory.php +++ b/ResultSetFactory.php @@ -37,7 +37,7 @@ class ResultSetFactory * @param array $results Results array. * @return \Cake\ORM\ResultSet */ - public function createResultSet(SelectQuery $query, iterable $results, bool $bufferedResults = true): ResultSet + public function createResultSet(SelectQuery $query, iterable $results): ResultSet { $data = $this->collectData($query); From 01134052a9241ae0238d11f00f52a38beb20b561 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 7 Apr 2024 23:48:16 +0530 Subject: [PATCH 1944/2059] Rename property --- Query/SelectQuery.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index e846d66d..4597f645 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -1584,7 +1584,7 @@ protected function _execute(): iterable return $this->_results; } - if ($this->useBufferedResults) { + if ($this->bufferedResults) { $results = parent::all(); } else { $results = $this->execute(); From 86adc5ae228a29ca83b5ea32c41289db52a6081d Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 8 Apr 2024 00:10:13 +0530 Subject: [PATCH 1945/2059] Update StatementInterface. Fix errors reported by static analysis --- EagerLoader.php | 4 ++-- ResultSetFactory.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index 2ec3cd85..5d2d4477 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -609,10 +609,10 @@ protected function _resolveJoins(array $associations, array $matching = []): arr * @param \Cake\ORM\Query\SelectQuery $query The query for which to eager load external. * associations. * @param iterable $results Results. - * @return array + * @return iterable * @throws \RuntimeException */ - public function loadExternal(SelectQuery $query, iterable $results): array + public function loadExternal(SelectQuery $query, iterable $results): iterable { if (!$results) { return $results; diff --git a/ResultSetFactory.php b/ResultSetFactory.php index 08998630..ca0a664d 100644 --- a/ResultSetFactory.php +++ b/ResultSetFactory.php @@ -34,7 +34,7 @@ class ResultSetFactory * Constructor * * @param \Cake\ORM\Query\SelectQuery $query Query from where results came. - * @param array $results Results array. + * @param iterable $results Results. * @return \Cake\ORM\ResultSet */ public function createResultSet(SelectQuery $query, iterable $results): ResultSet From ccccafc5ebdb9f7ddb90e3479dbee0170e2a129d Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 8 Apr 2024 00:37:59 +0530 Subject: [PATCH 1946/2059] Optimize ResultSet creation --- ResultSetFactory.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ResultSetFactory.php b/ResultSetFactory.php index ca0a664d..0d6358f5 100644 --- a/ResultSetFactory.php +++ b/ResultSetFactory.php @@ -19,6 +19,7 @@ use Cake\Collection\Collection; use Cake\Datasource\EntityInterface; use Cake\ORM\Query\SelectQuery; +use SplFixedArray; /** * Factory class for generation ResulSet instances. @@ -45,6 +46,8 @@ public function createResultSet(SelectQuery $query, iterable $results): ResultSe foreach ($results as $i => $row) { $results[$i] = $this->groupResult($row, $data); } + + $results = SplFixedArray::fromArray($results); } else { $results = (new Collection($results)) ->map(function ($row) use ($data) { From 38d674f521956db45762c949bef216184b04eb49 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 8 Apr 2024 23:44:07 -0400 Subject: [PATCH 1947/2059] Fix finder compatibilty with 4.x We have rector rules to update the callsites of finders to use named parameters. What we lack is rector operations to upgrade finder declarations to use named parameters. Because this is an impossible task, we should have better compatibility for new style finder calls with old style options arrays. This helps improve finder ergonomics (less typing) for more folks. This would have saved me a bunch of work when upgraded an application recently and making the upgrade smoother is important to me. --- Table.php | 68 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/Table.php b/Table.php index ffe06dcd..dd9454c7 100644 --- a/Table.php +++ b/Table.php @@ -2669,45 +2669,49 @@ public function invokeFinder(Closure $callable, SelectQuery $query, array $args) $reflected = new ReflectionFunction($callable); $params = $reflected->getParameters(); $secondParam = $params[1] ?? null; - $secondParamType = null; - if ($args === [] || isset($args[0])) { - $secondParamType = $secondParam?->getType(); - $secondParamTypeName = $secondParamType instanceof ReflectionNamedType ? $secondParamType->getName() : null; - // Backwards compatibility of 4.x style finders with signature `findFoo(SelectQuery $query, array $options)` + $secondParamType = $secondParam?->getType(); + $secondParamTypeName = $secondParamType instanceof ReflectionNamedType ? $secondParamType->getName() : null; + + $secondParamIsOptions = ( + count($params) === 2 && + $secondParam?->name === 'options' && + !$secondParam->isVariadic() && + ($secondParamType === null || $secondParamTypeName === 'array') + ); + + if (($args === [] || isset($args[0])) && $secondParamIsOptions) { + // Backwards compatibility of 4.x style finders + // with signature `findFoo(SelectQuery $query, array $options)` // called as `find('foo')` or `find('foo', [..])` - if ( - count($params) === 2 && - $secondParam?->name === 'options' && - !$secondParam->isVariadic() && - ($secondParamType === null || $secondParamTypeName === 'array') - ) { - if (isset($args[0])) { - deprecationWarning( - '5.0.0', - 'Using options array for the `find()` call is deprecated.' - . ' Use named arguments instead.' - ); + if (isset($args[0])) { + $args = $args[0]; + } + $query->applyOptions($args); - $args = $args[0]; - } + return $callable($query, $query->getOptions()); + } - $query->applyOptions($args); + // Backwards compatibility for 4.x style finders with signatures like + // `findFoo(SelectQuery $query, array $options)` called as + // `find('foo', key: $value)`. + if (!isset($args[0]) && $secondParamIsOptions) { + $query->applyOptions($args); - return $callable($query, $query->getOptions()); - } + return $callable($query, $query->getOptions()); + } - // Backwards compatibility for core finders like `findList()` called in 4.x style - // with an array `find('list', ['valueField' => 'foo'])` instead of `find('list', valueField: 'foo')` - if (isset($args[0]) && is_array($args[0]) && $secondParamTypeName !== 'array') { - deprecationWarning( - '5.0.0', - "Calling `{$reflected->getName()}` finder with options array is deprecated." - . ' Use named arguments instead.' - ); + // Backwards compatibility for core finders like `findList()` called in 4.x + // style with an array `find('list', ['valueField' => 'foo'])` instead of + // `find('list', valueField: 'foo')` + if (isset($args[0]) && is_array($args[0]) && $secondParamTypeName !== 'array') { + deprecationWarning( + '5.0.0', + "Calling `{$reflected->getName()}` finder with options array is deprecated." + . ' Use named arguments instead.' + ); - $args = $args[0]; - } + $args = $args[0]; } if ($args) { From 1adabcc73a4ee49d832cc26a2c82c62dfa04ea1a Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Sat, 30 Mar 2024 17:17:30 -0500 Subject: [PATCH 1948/2059] Copy connection role to select eager loader --- Association/Loader/SelectLoader.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index d9541039..9f086baa 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -178,7 +178,8 @@ protected function _buildQuery(array $options): SelectQuery ->select($options['fields']) ->where($options['conditions']) ->eagerLoaded(true) - ->enableHydration($selectQuery->isHydrationEnabled()); + ->enableHydration($selectQuery->isHydrationEnabled()) + ->setConnectionRole($selectQuery->getConnectionRole()); if ($selectQuery->isResultsCastingEnabled()) { $fetchQuery->enableResultsCasting(); } else { From ba4305e262070f9c164adfaec279670c9372db8e Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 9 Apr 2024 14:11:27 -0400 Subject: [PATCH 1949/2059] Restore deprecation for options finders. --- Table.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Table.php b/Table.php index dd9454c7..7f981bef 100644 --- a/Table.php +++ b/Table.php @@ -2685,6 +2685,11 @@ public function invokeFinder(Closure $callable, SelectQuery $query, array $args) // with signature `findFoo(SelectQuery $query, array $options)` // called as `find('foo')` or `find('foo', [..])` if (isset($args[0])) { + deprecationWarning( + '5.0.0', + 'Calling finders with options arrays is deprecated.' + . ' Update your finder methods to used named arguments instead.' + ); $args = $args[0]; } $query->applyOptions($args); From 90233e3eb88983b1a6aa617774e6828ee31b067b Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 11 Apr 2024 19:40:55 +0530 Subject: [PATCH 1950/2059] Use constant instead of string. --- EagerLoader.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index a62cae96..16f0d67c 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -557,10 +557,9 @@ protected function _fixStrategies(): void protected function _correctStrategy(EagerLoadable $loadable): void { $config = $loadable->getConfig(); - $currentStrategy = $config['strategy'] ?? - 'join'; + $currentStrategy = $config['strategy'] ?? Association::STRATEGY_JOIN; - if (!$loadable->canBeJoined() || $currentStrategy !== 'join') { + if (!$loadable->canBeJoined() || $currentStrategy !== Association::STRATEGY_JOIN) { return; } From 0d7bdfed4a53f6d42017d6effa831a8489f9fa73 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 1 May 2024 18:56:23 +0530 Subject: [PATCH 1951/2059] Throw exception when setting association with duplicate alias. --- AssociationCollection.php | 6 ++++++ Behavior/Translate/EavStrategy.php | 7 +++++++ Behavior/Translate/ShadowTableStrategy.php | 9 +++++++++ 3 files changed, 22 insertions(+) diff --git a/AssociationCollection.php b/AssociationCollection.php index 86ef452b..b61c414a 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -17,6 +17,7 @@ namespace Cake\ORM; use ArrayIterator; +use Cake\Core\Exception\CakeException; use Cake\Datasource\EntityInterface; use Cake\ORM\Locator\LocatorAwareTrait; use Cake\ORM\Locator\LocatorInterface; @@ -70,6 +71,7 @@ public function __construct(?LocatorInterface $tableLocator = null) * @param string $alias The association alias * @param \Cake\ORM\Association $association The association to add. * @return \Cake\ORM\Association The association object being added. + * @throws \Cake\Core\Exception\CakeException If the alias is already added. * @template T of \Cake\ORM\Association * @psalm-param T $association * @psalm-return T @@ -78,6 +80,10 @@ public function add(string $alias, Association $association): Association { [, $alias] = pluginSplit($alias); + if (isset($this->_items[$alias])) { + throw new CakeException(sprintf('Association alias `%s` is already in use.', $alias)); + } + return $this->_items[$alias] = $association; } diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index 12c28e8e..0350a3f4 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -131,6 +131,10 @@ protected function setupAssociations(): void $conditions[$name . '.content !='] = ''; } + if ($this->table->associations()->has($name)) { + $this->table->associations()->remove($name); + } + $this->table->hasOne($name, [ 'targetTable' => $fieldTable, 'foreignKey' => 'foreign_key', @@ -145,6 +149,9 @@ protected function setupAssociations(): void $conditions["$targetAlias.content !="] = ''; } + if ($this->table->associations()->has($targetAlias)) { + $this->table->associations()->remove($targetAlias); + } $this->table->hasMany($targetAlias, [ 'className' => $table, 'foreignKey' => 'foreign_key', diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 8ee4fc9d..dedfedf8 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -105,6 +105,11 @@ protected function setupAssociations(): void $config = $this->getConfig(); $targetAlias = $this->translationTable->getAlias(); + + if ($this->table->associations()->has($targetAlias)) { + $this->table->associations()->remove($targetAlias); + } + $this->table->hasMany($targetAlias, [ 'className' => $config['translationTable'], 'foreignKey' => 'id', @@ -184,6 +189,10 @@ protected function setupHasOneAssociation(string $locale, ArrayObject $options): $joinType = $config['onlyTranslated'] ? 'INNER' : 'LEFT'; } + if ($this->table->associations()->has($config['hasOneAlias'])) { + $this->table->associations()->remove($config['hasOneAlias']); + } + $this->table->hasOne($config['hasOneAlias'], [ 'foreignKey' => ['id'], 'joinType' => $joinType, From 5bcb6f82746f7a259765ba77cbfd58758bf8d10f Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 2 May 2024 00:25:12 +0530 Subject: [PATCH 1952/2059] Update exception message. Co-authored-by: othercorey --- AssociationCollection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AssociationCollection.php b/AssociationCollection.php index b61c414a..080ddbcf 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -81,7 +81,7 @@ public function add(string $alias, Association $association): Association [, $alias] = pluginSplit($alias); if (isset($this->_items[$alias])) { - throw new CakeException(sprintf('Association alias `%s` is already in use.', $alias)); + throw new CakeException(sprintf('Association alias `%s` is already set.', $alias)); } return $this->_items[$alias] = $association; From f57837a1a14c9a5dc0cb65845f56ac897acc8292 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 17 May 2024 10:30:42 -0400 Subject: [PATCH 1953/2059] Fix failing tests and linting - newer phpunit eats our deprecation warnings. - Fix phpcs error and phpstan config warning - Fix new type issues that phpstan found - Update split repos phpstan config --- Query/SelectQuery.php | 2 +- phpstan.neon.dist | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index 4597f645..2a6e9f5e 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -1580,7 +1580,7 @@ public function sql(?ValueBinder $binder = null): string protected function _execute(): iterable { $this->triggerBeforeFind(); - if ($this->_results) { + if ($this->_results !== null) { return $this->_results; } diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 1a74c1b9..648671d5 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,7 +1,5 @@ parameters: level: 8 - checkMissingIterableValueType: false - checkGenericClassInNonGenericObjectType: false treatPhpDocTypesAsCertain: false bootstrapFiles: - tests/phpstan-bootstrap.php @@ -10,7 +8,14 @@ parameters: excludePaths: - vendor/ ignoreErrors: + - + identifier: missingType.iterableValue + - + identifier: missingType.generics + - '#Unsafe usage of new static\(\).#' - "#^Method Cake\\\\ORM\\\\Behavior\\\\TreeBehavior\\:\\:_scope\\(\\) should return T of Cake\\\\ORM\\\\Query\\\\DeleteQuery\\|Cake\\\\ORM\\\\Query\\\\SelectQuery\\|Cake\\\\ORM\\\\Query\\\\UpdateQuery but returns Cake\\\\ORM\\\\Query\\\\DeleteQuery\\|Cake\\\\ORM\\\\Query\\\\SelectQuery\\|Cake\\\\ORM\\\\Query\\\\UpdateQuery\\.$#" - "#^PHPDoc tag @return with type Cake\\\\ORM\\\\Query\\\\SelectQuery\\ is not subtype of native type static\\(Cake\\\\ORM\\\\Query\\\\SelectQuery\\\\)\\.$#" - "#^Method Cake\\\\ORM\\\\Query\\\\SelectQuery\\:\\:find\\(\\) should return static\\(Cake\\\\ORM\\\\Query\\\\SelectQuery\\\\) but returns Cake\\\\ORM\\\\Query\\\\SelectQuery\\\\.$#" + - "#^Access to an undefined property Cake\\\\ORM\\\\Query\\\\SelectQuery\\\\:\\:\\$bufferedResults\\.$#" + - "#^Parameter \\#2 \\$results of method Cake\\\\ORM\\\\EagerLoader\\:\\:loadExternal\\(\\) expects iterable, Cake\\\\Database\\\\StatementInterface\\|iterable given\\.$#" From f5e913b3f12066267614032bdf00bd9edb0f86b8 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 23 May 2024 22:41:11 -0400 Subject: [PATCH 1954/2059] Fix phpstan errors from newer phpstan Backport fixes from 5.next --- Query/SelectQuery.php | 2 +- phpstan.neon.dist | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index cb702b0d..2ff18ac7 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -1580,7 +1580,7 @@ public function sql(?ValueBinder $binder = null): string protected function _execute(): iterable { $this->triggerBeforeFind(); - if ($this->_results) { + if ($this->_results !== null) { return $this->_results; } diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 1a74c1b9..7e4dc886 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,7 +1,5 @@ parameters: level: 8 - checkMissingIterableValueType: false - checkGenericClassInNonGenericObjectType: false treatPhpDocTypesAsCertain: false bootstrapFiles: - tests/phpstan-bootstrap.php @@ -10,6 +8,10 @@ parameters: excludePaths: - vendor/ ignoreErrors: + - + identifier: missingType.iterableValue + - + identifier: missingType.generics - '#Unsafe usage of new static\(\).#' - "#^Method Cake\\\\ORM\\\\Behavior\\\\TreeBehavior\\:\\:_scope\\(\\) should return T of Cake\\\\ORM\\\\Query\\\\DeleteQuery\\|Cake\\\\ORM\\\\Query\\\\SelectQuery\\|Cake\\\\ORM\\\\Query\\\\UpdateQuery but returns Cake\\\\ORM\\\\Query\\\\DeleteQuery\\|Cake\\\\ORM\\\\Query\\\\SelectQuery\\|Cake\\\\ORM\\\\Query\\\\UpdateQuery\\.$#" - "#^PHPDoc tag @return with type Cake\\\\ORM\\\\Query\\\\SelectQuery\\ is not subtype of native type static\\(Cake\\\\ORM\\\\Query\\\\SelectQuery\\\\)\\.$#" From 8137b33814b201e587c5ebddf7baec40ea66ed20 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 10 Jun 2024 00:16:51 -0400 Subject: [PATCH 1955/2059] Fix removeBehavior not clearing state When behaviors are unloaded we should clear up the additional method mappings that BehaviorRegistry collects. Fixes #17697 --- BehaviorRegistry.php | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 11a8b2b8..5eb34e6e 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -211,6 +211,31 @@ protected function _getMethods(Behavior $instance, string $class, string $alias) return compact('methods', 'finders'); } + /** + * Remove an object from the registry. + * + * If this registry has an event manager, the object will be detached from any events as well. + * + * @param string $name The name of the object to remove from the registry. + * @return $this + */ + public function unload(string $name) + { + $instance = $this->get($name); + $result = parent::unload($name); + + $methods = $instance->implementedMethods(); + foreach ($methods as $method) { + unset($this->_methodMap[$method]); + } + $finders = $instance->implementedFinders(); + foreach ($finders as $finder) { + unset($this->_finderMap[$finder]); + } + + return $result; + } + /** * Check if any loaded behavior implements a method. * From 6cf3fe33dbc4bb143da46fe1896c0ce0d78d6733 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 25 Nov 2022 13:00:14 +0530 Subject: [PATCH 1956/2059] Improve ResultSetFactory. The result decorator class is now fetched from the ResetSetFactory instead of being hardcoded in Association. --- Association.php | 7 +++---- Query/SelectQuery.php | 17 +++-------------- ResultSetFactory.php | 43 ++++++++++++++++++++++++++++--------------- 3 files changed, 34 insertions(+), 33 deletions(-) diff --git a/Association.php b/Association.php index ce44251b..444544d3 100644 --- a/Association.php +++ b/Association.php @@ -16,7 +16,6 @@ */ namespace Cake\ORM; -use Cake\Collection\Collection; use Cake\Collection\CollectionInterface; use Cake\Core\App; use Cake\Core\ConventionsTrait; @@ -25,7 +24,6 @@ use Cake\Database\Expression\QueryExpression; use Cake\Database\ExpressionInterface; use Cake\Datasource\EntityInterface; -use Cake\Datasource\ResultSetDecorator; use Cake\Datasource\ResultSetInterface; use Cake\ORM\Locator\LocatorAwareTrait; use Cake\ORM\Query\SelectQuery; @@ -996,11 +994,12 @@ function (CollectionInterface $results, SelectQuery $query) use ($formatters, $p } $extracted[] = $result; } - $extracted = new Collection($extracted); + $extracted = $query->resultSetFactory()->createResultSet($extracted); + $decoratorClass = $query->resultSetFactory()->decoratorClass(); foreach ($formatters as $callable) { $extracted = $callable($extracted, $query); if (!$extracted instanceof ResultSetInterface) { - $extracted = new ResultSetDecorator($extracted); + $extracted = new $decoratorClass($extracted); } } diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index 2a6e9f5e..69c83dab 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -27,7 +27,6 @@ use Cake\Datasource\Exception\RecordNotFoundException; use Cake\Datasource\QueryCacher; use Cake\Datasource\QueryInterface; -use Cake\Datasource\ResultSetDecorator; use Cake\Datasource\ResultSetInterface; use Cake\ORM\Association; use Cake\ORM\EagerLoader; @@ -734,7 +733,7 @@ public function applyOptions(array $options) */ protected function _decorateResults(iterable $result): ResultSetInterface { - $decorator = $this->_decoratorClass(); + $decorator = $this->resultSetFactory()->decoratorClass(); if ($this->_mapReduce) { foreach ($this->_mapReduce as $functions) { @@ -760,16 +759,6 @@ protected function _decorateResults(iterable $result): ResultSetInterface return $result; } - /** - * Returns the name of the class to be used for decorating results - * - * @return class-string<\Cake\Datasource\ResultSetInterface> - */ - protected function _decoratorClass(): string - { - return ResultSetDecorator::class; - } - /** * Adds new fields to be returned by a `SELECT` statement when this query is * executed. Fields can be passed as an array of strings, array of expression @@ -1591,7 +1580,7 @@ protected function _execute(): iterable } $results = $this->getEagerLoader()->loadExternal($this, $results); - return $this->resultSetFactory()->createResultSet($this, $results); + return $this->resultSetFactory()->createResultSet($results, $this); } /** @@ -1599,7 +1588,7 @@ protected function _execute(): iterable * * @return \Cake\ORM\ResultSetFactory */ - protected function resultSetFactory(): ResultSetFactory + public function resultSetFactory(): ResultSetFactory { return $this->resultSetFactory ??= new ResultSetFactory(); } diff --git a/ResultSetFactory.php b/ResultSetFactory.php index 0d6358f5..55687b6b 100644 --- a/ResultSetFactory.php +++ b/ResultSetFactory.php @@ -18,11 +18,12 @@ use Cake\Collection\Collection; use Cake\Datasource\EntityInterface; +use Cake\Datasource\ResultSetDecorator; use Cake\ORM\Query\SelectQuery; use SplFixedArray; /** - * Factory class for generation ResulSet instances. + * Factory class for generating ResulSet instances. * * It is responsible for correctly nesting result keys reported from the query * and hydrating entities. @@ -32,27 +33,29 @@ class ResultSetFactory { /** - * Constructor + * Create a resultset instance. * - * @param \Cake\ORM\Query\SelectQuery $query Query from where results came. * @param iterable $results Results. + * @param \Cake\ORM\Query\SelectQuery|null $query Query from where results came. * @return \Cake\ORM\ResultSet */ - public function createResultSet(SelectQuery $query, iterable $results): ResultSet + public function createResultSet(iterable $results, ?SelectQuery $query = null): ResultSet { - $data = $this->collectData($query); + if ($query) { + $data = $this->collectData($query); - if (is_array($results)) { - foreach ($results as $i => $row) { - $results[$i] = $this->groupResult($row, $data); - } + if (is_array($results)) { + foreach ($results as $i => $row) { + $results[$i] = $this->groupResult($row, $data); + } - $results = SplFixedArray::fromArray($results); - } else { - $results = (new Collection($results)) - ->map(function ($row) use ($data) { - return $this->groupResult($row, $data); - }); + $results = SplFixedArray::fromArray($results); + } else { + $results = (new Collection($results)) + ->map(function ($row) use ($data) { + return $this->groupResult($row, $data); + }); + } } return new ResultSet($results); @@ -230,4 +233,14 @@ protected function groupResult(array $row, array $data): EntityInterface|array return $results; } + + /** + * Returns the name of the class to be used for decorating results + * + * @return class-string<\Cake\Datasource\ResultSetInterface> + */ + public function decoratorClass(): string + { + return ResultSetDecorator::class; + } } From 9eedceee7b4a2a2d8de34171abbadd6523fde1a0 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 4 Jul 2024 22:39:36 +0530 Subject: [PATCH 1957/2059] Drop use of resultset decorator class in the ORM. It serves no purpose anymore since ResultSetInterface simply extends CollectionInterface and ResultSet extends Collection. --- Association.php | 4 ++-- Query/SelectQuery.php | 8 ++++---- ResultSet.php | 2 +- ResultSetFactory.php | 41 ++++++++++++++++++++++++++++++++++------- 4 files changed, 41 insertions(+), 14 deletions(-) diff --git a/Association.php b/Association.php index 444544d3..146d8fa6 100644 --- a/Association.php +++ b/Association.php @@ -995,11 +995,11 @@ function (CollectionInterface $results, SelectQuery $query) use ($formatters, $p $extracted[] = $result; } $extracted = $query->resultSetFactory()->createResultSet($extracted); - $decoratorClass = $query->resultSetFactory()->decoratorClass(); + $resultSetClass = $query->resultSetFactory()->getResultSetClass(); foreach ($formatters as $callable) { $extracted = $callable($extracted, $query); if (!$extracted instanceof ResultSetInterface) { - $extracted = new $decoratorClass($extracted); + $extracted = new $resultSetClass($extracted); } } diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index 69c83dab..8369cf09 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -733,17 +733,17 @@ public function applyOptions(array $options) */ protected function _decorateResults(iterable $result): ResultSetInterface { - $decorator = $this->resultSetFactory()->decoratorClass(); + $resultSetClass = $this->resultSetFactory()->getResultSetClass(); if ($this->_mapReduce) { foreach ($this->_mapReduce as $functions) { $result = new MapReduce($result, $functions['mapper'], $functions['reducer']); } - $result = new $decorator($result); + $result = new $resultSetClass($result); } if (!($result instanceof ResultSetInterface)) { - $result = new $decorator($result); + $result = new $resultSetClass($result); } if ($this->_formatters) { @@ -752,7 +752,7 @@ protected function _decorateResults(iterable $result): ResultSetInterface } if (!($result instanceof ResultSetInterface)) { - $result = new $decorator($result); + $result = new $resultSetClass($result); } } diff --git a/ResultSet.php b/ResultSet.php index 242ab7be..a3ed77fd 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -25,7 +25,7 @@ * the query, casting each field to the correct type and executing the extra * queries required for eager loading external associations. * - * @template T of \Cake\Datasource\EntityInterface|array + * @template T * @implements \Cake\Datasource\ResultSetInterface */ class ResultSet extends Collection implements ResultSetInterface diff --git a/ResultSetFactory.php b/ResultSetFactory.php index 55687b6b..e735f58b 100644 --- a/ResultSetFactory.php +++ b/ResultSetFactory.php @@ -18,8 +18,9 @@ use Cake\Collection\Collection; use Cake\Datasource\EntityInterface; -use Cake\Datasource\ResultSetDecorator; +use Cake\Datasource\ResultSetInterface; use Cake\ORM\Query\SelectQuery; +use InvalidArgumentException; use SplFixedArray; /** @@ -32,14 +33,19 @@ */ class ResultSetFactory { + /** + * @var class-string<\Cake\Datasource\ResultSetInterface> + */ + protected string $resultSetClass = ResultSet::class; + /** * Create a resultset instance. * * @param iterable $results Results. * @param \Cake\ORM\Query\SelectQuery|null $query Query from where results came. - * @return \Cake\ORM\ResultSet + * @return \Cake\Datasource\ResultSetInterface */ - public function createResultSet(iterable $results, ?SelectQuery $query = null): ResultSet + public function createResultSet(iterable $results, ?SelectQuery $query = null): ResultSetInterface { if ($query) { $data = $this->collectData($query); @@ -58,7 +64,7 @@ public function createResultSet(iterable $results, ?SelectQuery $query = null): } } - return new ResultSet($results); + return new $this->resultSetClass($results); } /** @@ -235,12 +241,33 @@ protected function groupResult(array $row, array $data): EntityInterface|array } /** - * Returns the name of the class to be used for decorating results + * Set the ResultSet class to use. + * + * @param class-string<\Cake\Datasource\ResultSetInterface> $resultSetClass Class name. + * @return $this + */ + public function setResultSetClass(string $resultSetClass) + { + if (!is_a($resultSetClass, ResultSetInterface::class, true)) { + throw new InvalidArgumentException(sprintf( + 'Invalid ResultSet class `%s`. It must implement `%s`', + $resultSetClass, + ResultSetInterface::class + )); + } + + $this->resultSetClass = $resultSetClass; + + return $this; + } + + /** + * Get the ResultSet class to use. * * @return class-string<\Cake\Datasource\ResultSetInterface> */ - public function decoratorClass(): string + public function getResultSetClass(): string { - return ResultSetDecorator::class; + return $this->resultSetClass; } } From f858e59dfb75baf0d9d16dc0d1769b35d6ee72ae Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 20 Jul 2024 19:12:09 +0530 Subject: [PATCH 1958/2059] Change default value of `valueSeparator` argument. Closes #17692 --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 40dde961..a30bb76e 100644 --- a/Table.php +++ b/Table.php @@ -1380,7 +1380,7 @@ public function findList( Closure|array|string|null $keyField = null, Closure|array|string|null $valueField = null, Closure|array|string|null $groupField = null, - string $valueSeparator = ';' + string $valueSeparator = ' ' ): SelectQuery { $keyField ??= $this->getPrimaryKey(); $valueField ??= $this->getDisplayField(); From 1cfbc3792d9ea4758b15c9be40dbbfc6b228232e Mon Sep 17 00:00:00 2001 From: Adam Halfar Date: Thu, 1 Aug 2024 22:31:53 +0200 Subject: [PATCH 1959/2059] Rector fixes from rule set LevelSetList::UP_TO_PHP_81 --- Association.php | 6 +++--- Association/BelongsToMany.php | 4 ++-- Association/DependentDeleteHelper.php | 2 +- Association/Loader/SelectLoader.php | 2 +- LazyEagerLoader.php | 2 +- Marshaller.php | 6 +++--- Query/SelectQuery.php | 2 +- Rule/ExistsIn.php | 2 +- Table.php | 2 +- 9 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Association.php b/Association.php index 146d8fa6..70098b4c 100644 --- a/Association.php +++ b/Association.php @@ -295,7 +295,7 @@ public function setClassName(string $className) throw new InvalidArgumentException(sprintf( 'The class name `%s` doesn\'t match the target table class name of `%s`.', $className, - get_class($this->_targetTable) + $this->_targetTable::class )); } @@ -384,10 +384,10 @@ public function getTarget(): Table throw new DatabaseException(sprintf( $msg, - isset($this->_sourceTable) ? get_class($this->_sourceTable) : 'null', + isset($this->_sourceTable) ? $this->_sourceTable::class : 'null', $this->getName(), $this->type(), - get_class($this->_targetTable), + $this->_targetTable::class, $className )); } diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 950af585..d85ea06b 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -518,7 +518,7 @@ protected function _appendNotMatching(SelectQuery $query, array $options): void return $exp ->or([ $exp->notIn($identifiers, $subquery), - $nullExp->and(array_map([$nullExp, 'isNull'], array_keys($conds))), + $nullExp->and(array_map($nullExp->isNull(...), array_keys($conds))), ]); }); } @@ -1197,7 +1197,7 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { /** @var list $foreignKey */ $foreignKey = (array)$this->getForeignKey(); $assocForeignKey = (array)$junction->getAssociation($target->getAlias())->getForeignKey(); - $prefixedForeignKey = array_map([$junction, 'aliasField'], $foreignKey); + $prefixedForeignKey = array_map($junction->aliasField(...), $foreignKey); $junctionPrimaryKey = (array)$junction->getPrimaryKey(); $junctionQueryAlias = $junction->getAlias() . '__matches'; diff --git a/Association/DependentDeleteHelper.php b/Association/DependentDeleteHelper.php index c8f82075..d33dea0f 100644 --- a/Association/DependentDeleteHelper.php +++ b/Association/DependentDeleteHelper.php @@ -43,7 +43,7 @@ public function cascadeDelete(Association $association, EntityInterface $entity, } $table = $association->getTarget(); /** @var callable $callable */ - $callable = [$association, 'aliasField']; + $callable = $association->aliasField(...); $foreignKey = array_map($callable, (array)$association->getForeignKey()); $bindingKey = (array)$association->getBindingKey(); $bindingValue = $entity->extract($bindingKey); diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 67a8ce2b..773d819c 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -270,7 +270,7 @@ protected function _assertFieldsPresent(SelectQuery $fetchQuery, array $key): vo $missingFields = $missingKey($select, $key); if ($missingFields) { $driver = $fetchQuery->getConnection()->getDriver(); - $quoted = array_map([$driver, 'quoteIdentifier'], $key); + $quoted = array_map($driver->quoteIdentifier(...), $key); $missingFields = $missingKey($select, $quoted); } diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 61e958c6..3dbb3201 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -90,7 +90,7 @@ protected function _getQuery(array $entities, array $contain, Table $source): Se } $types = array_intersect_key($q->getDefaultTypes(), array_flip($primaryKey)); - $primaryKey = array_map([$source, 'aliasField'], $primaryKey); + $primaryKey = array_map($source->aliasField(...), $primaryKey); return new TupleComparison($primaryKey, $keys, $types, 'IN'); }) diff --git a/Marshaller.php b/Marshaller.php index fa1bc7c5..59bcc0fa 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -81,7 +81,7 @@ protected function _buildPropertyMap(array $data, array $options): array } // Map associations - $options['associated'] = $options['associated'] ?? []; + $options['associated'] ??= []; $include = $this->_normalizeAssociations($options['associated']); foreach ($include as $key => $nested) { if (is_int($key) && is_scalar($nested)) { @@ -483,7 +483,7 @@ protected function _loadAssociatedByIds(Association $assoc, array $ids): array $target = $assoc->getTarget(); $primaryKey = (array)$target->getPrimaryKey(); $multi = count($primaryKey) > 1; - $primaryKey = array_map([$target, 'aliasField'], $primaryKey); + $primaryKey = array_map($target->aliasField(...), $primaryKey); if ($multi) { $first = current($ids); @@ -713,7 +713,7 @@ public function mergeMany(iterable $entities, array $data, array $options = []): }) ->filter(fn ($keys) => count(Hash::filter($keys)) === count($primary)) ->reduce(function ($conditions, $keys) use ($primary) { - $fields = array_map([$this->_table, 'aliasField'], $primary); + $fields = array_map($this->_table->aliasField(...), $primary); $conditions['OR'][] = array_combine($fields, $keys); return $conditions; diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index 8369cf09..2a812d1c 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -1787,5 +1787,5 @@ public function isAutoFieldsEnabled(): ?bool } // phpcs:disable -class_exists('Cake\ORM\Query'); +class_exists(\Cake\ORM\Query::class); // phpcs:enable diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 6272430e..18414a90 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -90,7 +90,7 @@ public function __invoke(EntityInterface $entity, array $options): bool 'ExistsIn rule for `%s` is invalid. `%s` is not associated with `%s`.', implode(', ', $this->_fields), $this->_repository, - get_class($options['repository']) + $options['repository']::class )); } $repository = $table->getAssociation($this->_repository); diff --git a/Table.php b/Table.php index a30bb76e..9637aa45 100644 --- a/Table.php +++ b/Table.php @@ -2282,7 +2282,7 @@ public function saveMany( ): iterable|false { try { return $this->_saveMany($entities, $options); - } catch (PersistenceFailedException $exception) { + } catch (PersistenceFailedException) { return false; } } From 15660615e173d1e34dac1ee10e841b7bed5cf30d Mon Sep 17 00:00:00 2001 From: Adam Halfar Date: Sun, 4 Aug 2024 13:09:18 +0200 Subject: [PATCH 1960/2059] Add changes from rector rule UseIdenticalOverEqualWithSameTypeRector --- Association/BelongsToMany.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 950af585..0dd6bd63 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1299,7 +1299,7 @@ protected function _diffLinks( if (is_object($unmatchedKeys[$key]) && is_object($existingKeys[$key])) { // If both sides are an object then use == so that value objects // are seen as equivalent. - $matched = $existingKeys[$key] == $unmatchedKeys[$key]; + $matched = $existingKeys[$key] === $unmatchedKeys[$key]; } else { // Use strict equality for all other values. $matched = $existingKeys[$key] === $unmatchedKeys[$key]; From 78fa73de95dbd18ce15b067d9f200f63b6ac325d Mon Sep 17 00:00:00 2001 From: Adam Halfar Date: Sun, 4 Aug 2024 13:31:11 +0200 Subject: [PATCH 1961/2059] Add changes from rector rule SimplifyBoolIdenticalTrueRector --- Behavior/TimestampBehavior.php | 2 +- Table.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 3d0dc33b..5c1521ca 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -97,7 +97,7 @@ public function handleEvent(EventInterface $event, EntityInterface $entity): voi $eventName = $event->getName(); $events = $this->_config['events']; - $new = $entity->isNew() !== false; + $new = $entity->isNew(); $refresh = $this->_config['refreshTimestamp']; foreach ($events[$eventName] as $field => $when) { diff --git a/Table.php b/Table.php index a30bb76e..0cad6d36 100644 --- a/Table.php +++ b/Table.php @@ -2785,7 +2785,7 @@ protected function _dynamicFinder(string $method, array $args): SelectQuery return $conditions; }; - if ($hasOr !== false && $hasAnd !== false) { + if ($hasOr && $hasAnd) { throw new BadMethodCallException( 'Cannot mix "and" & "or" in a magic finder. Use find() instead.' ); @@ -2793,7 +2793,7 @@ protected function _dynamicFinder(string $method, array $args): SelectQuery if ($hasOr === false && $hasAnd === false) { $conditions = $makeConditions([$fields], $args); - } elseif ($hasOr !== false) { + } elseif ($hasOr) { $fields = explode('_or_', $fields); $conditions = [ 'OR' => $makeConditions($fields, $args), From 2d10def207f8e5c8fc69fec4f74ff9c88bc50077 Mon Sep 17 00:00:00 2001 From: Adam Halfar Date: Sun, 4 Aug 2024 13:34:57 +0200 Subject: [PATCH 1962/2059] Add changes from rector rule CombineIfRector --- Behavior/Translate/ShadowTableStrategy.php | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index dedfedf8..11c77e91 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -522,14 +522,11 @@ protected function rowMapper(CollectionInterface $results, string $locale): Coll continue; } - if ($translation[$field] !== null) { - if ($allowEmpty || $translation[$field] !== '') { - $row[$field] = $translation[$field]; - - if ($hydrated) { - /** @var \Cake\Datasource\EntityInterface $row */ - $row->setDirty($field, false); - } + if ($translation[$field] !== null && ($allowEmpty || $translation[$field] !== '')) { + $row[$field] = $translation[$field]; + if ($hydrated) { + /** @var \Cake\Datasource\EntityInterface $row */ + $row->setDirty($field, false); } } } From dbf600c4f23ff104972213c0924ea1209230dc5e Mon Sep 17 00:00:00 2001 From: Adam Halfar Date: Sun, 4 Aug 2024 16:07:23 +0200 Subject: [PATCH 1963/2059] Revert of wrong fix --- Association/BelongsToMany.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 0dd6bd63..950af585 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1299,7 +1299,7 @@ protected function _diffLinks( if (is_object($unmatchedKeys[$key]) && is_object($existingKeys[$key])) { // If both sides are an object then use == so that value objects // are seen as equivalent. - $matched = $existingKeys[$key] === $unmatchedKeys[$key]; + $matched = $existingKeys[$key] == $unmatchedKeys[$key]; } else { // Use strict equality for all other values. $matched = $existingKeys[$key] === $unmatchedKeys[$key]; From 8a7555023d6ba62a0234e6418b08430bde5d58ed Mon Sep 17 00:00:00 2001 From: Adam Halfar Date: Sun, 4 Aug 2024 16:38:50 +0200 Subject: [PATCH 1964/2059] Add changes from rector rule SplitDoubleAssignRector --- Association/BelongsToMany.php | 11 +++++++---- Association/Loader/SelectLoader.php | 3 ++- Association/Loader/SelectWithPivotLoader.php | 3 ++- Marshaller.php | 3 ++- ResultSetFactory.php | 3 ++- Rule/ExistsIn.php | 3 ++- Table.php | 4 +++- 7 files changed, 20 insertions(+), 10 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 950af585..92a265bf 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -968,7 +968,8 @@ function () use ($sourceEntity, $targetEntities, $options): void { public function setConditions(Closure|array $conditions) { parent::setConditions($conditions); - $this->_targetConditions = $this->_junctionConditions = null; + $this->_targetConditions = null; + $this->_junctionConditions = null; return $this; } @@ -1201,8 +1202,8 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { $junctionPrimaryKey = (array)$junction->getPrimaryKey(); $junctionQueryAlias = $junction->getAlias() . '__matches'; - - $keys = $matchesConditions = []; + $keys = []; + $matchesConditions = []; /** @var string $key */ foreach (array_merge($assocForeignKey, $junctionPrimaryKey) as $key) { $aliased = $junction->aliasField($key); @@ -1282,7 +1283,9 @@ protected function _diffLinks( $assocForeignKey = (array)$belongsTo->getForeignKey(); $keys = array_merge($foreignKey, $assocForeignKey); - $deletes = $unmatchedEntityKeys = $present = []; + $deletes = []; + $unmatchedEntityKeys = []; + $present = []; foreach ($jointEntities as $i => $entity) { $unmatchedEntityKeys[$i] = $entity->extract($keys); diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 67a8ce2b..292da7e1 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -451,7 +451,8 @@ protected function _subqueryFields(SelectQuery $query): array } $fields = $query->aliasFields($keys, $this->sourceAlias); - $group = $fields = array_values($fields); + $group = array_values($fields); + $fields = $group; /** @var \Cake\Database\Expression\QueryExpression $order */ $order = $query->clause('order'); diff --git a/Association/Loader/SelectWithPivotLoader.php b/Association/Loader/SelectWithPivotLoader.php index 52e1d216..1cc28050 100644 --- a/Association/Loader/SelectWithPivotLoader.php +++ b/Association/Loader/SelectWithPivotLoader.php @@ -107,7 +107,8 @@ protected function _buildQuery(array $options): SelectQuery $tempName = $this->alias . '_CJoin'; $schema = $assoc->getSchema(); - $joinFields = $types = []; + $joinFields = []; + $types = []; foreach ($schema->typeMap() as $f => $type) { $key = $tempName . '__' . $f; diff --git a/Marshaller.php b/Marshaller.php index fa1bc7c5..e82efc9b 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -393,7 +393,8 @@ protected function _belongsToMany(BelongsToMany $assoc, array $data, array $opti $target = $assoc->getTarget(); $primaryKey = array_flip((array)$target->getPrimaryKey()); - $records = $conditions = []; + $records = []; + $conditions = []; $primaryCount = count($primaryKey); foreach ($data as $i => $row) { diff --git a/ResultSetFactory.php b/ResultSetFactory.php index e735f58b..e8708090 100644 --- a/ResultSetFactory.php +++ b/ResultSetFactory.php @@ -134,7 +134,8 @@ protected function collectData(SelectQuery $query): array */ protected function groupResult(array $row, array $data): EntityInterface|array { - $results = $presentAliases = []; + $results = []; + $presentAliases = []; $options = [ 'useSetters' => false, 'markClean' => true, diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 6272430e..8088e56b 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -98,7 +98,8 @@ public function __invoke(EntityInterface $entity, array $options): bool } $fields = $this->_fields; - $source = $target = $this->_repository; + $source = $this->_repository; + $target = $this->_repository; if ($target instanceof Association) { $bindingKey = (array)$target->getBindingKey(); $realTarget = $target->getTarget(); diff --git a/Table.php b/Table.php index 0cad6d36..7919039a 100644 --- a/Table.php +++ b/Table.php @@ -318,7 +318,9 @@ public function __construct(array $config = []) if (!empty($config['entityClass'])) { $this->setEntityClass($config['entityClass']); } - $eventManager = $behaviors = $associations = null; + $eventManager = null; + $behaviors = null; + $associations = null; if (!empty($config['eventManager'])) { $eventManager = $config['eventManager']; } From b8be5d33a8cc00c1b0e3744f39a74df674ac9d67 Mon Sep 17 00:00:00 2001 From: Adam Halfar Date: Sun, 4 Aug 2024 16:50:54 +0200 Subject: [PATCH 1965/2059] Add changes from rector rule CountArrayToEmptyArrayComparisonRector --- Association/BelongsToMany.php | 2 +- Association/HasMany.php | 4 ++-- Table.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 950af585..80b71a04 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1237,7 +1237,7 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { $property = $this->getProperty(); - if (count($inserts)) { + if ($inserts !== []) { /** @psalm-suppress RedundantConditionGivenDocblockType */ $inserted = array_combine( array_keys($inserts), diff --git a/Association/HasMany.php b/Association/HasMany.php index f41f425f..d95bed90 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -355,7 +355,7 @@ public function unlink(EntityInterface $sourceEntity, array $targetEntities, arr } else { $options += ['cleanProperty' => true]; } - if (count($targetEntities) === 0) { + if ($targetEntities === []) { return; } @@ -489,7 +489,7 @@ function ($v) { $conditions = $foreignKeyReference; - if (count($exclusions) > 0) { + if ($exclusions !== []) { $conditions = [ 'NOT' => [ 'OR' => $exclusions, diff --git a/Table.php b/Table.php index 0cad6d36..65b0eb66 100644 --- a/Table.php +++ b/Table.php @@ -2243,7 +2243,7 @@ protected function _update(EntityInterface $entity, array $data): EntityInterfac return $entity; } - if (count($primaryColumns) === 0) { + if ($primaryColumns === []) { $entityClass = $entity::class; $table = $this->getTable(); $message = "Cannot update `$entityClass`. The `$table` has no primary key."; From a938b56a04f2ddb5f66fe75bfa34a5b04a86a3b6 Mon Sep 17 00:00:00 2001 From: Adam Halfar Date: Sun, 4 Aug 2024 16:55:23 +0200 Subject: [PATCH 1966/2059] Add changes from rector rule SymplifyQuoteEscapeRector --- Association.php | 6 +++--- Behavior/TreeBehavior.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Association.php b/Association.php index 146d8fa6..88d3cc2d 100644 --- a/Association.php +++ b/Association.php @@ -293,7 +293,7 @@ public function setClassName(string $className) get_class($this->_targetTable) !== App::className($className, 'Model/Table', 'Table') ) { throw new InvalidArgumentException(sprintf( - 'The class name `%s` doesn\'t match the target table class name of `%s`.', + "The class name `%s` doesn't match the target table class name of `%s`.", $className, get_class($this->_targetTable) )); @@ -378,8 +378,8 @@ public function getTarget(): Table $className = App::className($this->_className, 'Model/Table', 'Table') ?: Table::class; if (!$this->_targetTable instanceof $className) { - $msg = '`%s` association `%s` of type `%s` to `%s` doesn\'t match the expected class `%s`. '; - $msg .= 'You can\'t have an association of the same name with a different target '; + $msg = "`%s` association `%s` of type `%s` to `%s` doesn't match the expected class `%s`. "; + $msg .= "You can't have an association of the same name with a different target "; $msg .= '"className" option anywhere in your app.'; throw new DatabaseException(sprintf( diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index d7584b48..cd9e0909 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -111,7 +111,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity): void $level = $config['level']; if ($parent && $entity->get($primaryKey) === $parent) { - throw new DatabaseException('Cannot set a node\'s parent as itself.'); + throw new DatabaseException("Cannot set a node's parent as itself."); } if ($isNew) { From 64c4b4d834c7dadd739f2bbcc103de1908ff77cc Mon Sep 17 00:00:00 2001 From: Adam Halfar Date: Sun, 4 Aug 2024 18:46:00 +0200 Subject: [PATCH 1967/2059] Add changes from rector rule RemoveConcatAutocastRector --- Association.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Association.php b/Association.php index 146d8fa6..91b269e9 100644 --- a/Association.php +++ b/Association.php @@ -360,7 +360,7 @@ public function getTarget(): Table if (!isset($this->_targetTable)) { if (str_contains($this->_className, '.')) { [$plugin] = pluginSplit($this->_className, true); - $registryAlias = (string)$plugin . $this->_name; + $registryAlias = $plugin . $this->_name; } else { $registryAlias = $this->_name; } From b0b8b8759e7322c47291cf40e7b293494076ece3 Mon Sep 17 00:00:00 2001 From: Adam Haflar <47174548+Harfusha@users.noreply.github.com> Date: Mon, 5 Aug 2024 11:21:00 +0200 Subject: [PATCH 1968/2059] Add changes from rector rule WrapEncapsedVariableInCurlyBracesRector (#17783) Add changes from rector rule WrapEncapsedVariableInCurlyBracesRector --- Association/Loader/SelectWithPivotLoader.php | 2 +- Behavior/Translate/EavStrategy.php | 4 ++-- Behavior/Translate/ShadowTableStrategy.php | 10 +++++----- Behavior/TranslateBehavior.php | 2 +- Behavior/TreeBehavior.php | 12 ++++++------ Rule/IsUnique.php | 2 +- Table.php | 4 ++-- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Association/Loader/SelectWithPivotLoader.php b/Association/Loader/SelectWithPivotLoader.php index 52e1d216..f0fc9f41 100644 --- a/Association/Loader/SelectWithPivotLoader.php +++ b/Association/Loader/SelectWithPivotLoader.php @@ -111,7 +111,7 @@ protected function _buildQuery(array $options): SelectQuery foreach ($schema->typeMap() as $f => $type) { $key = $tempName . '__' . $f; - $joinFields[$key] = "$name.$f"; + $joinFields[$key] = "{$name}.{$f}"; $types[$key] = $type; } diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index 0350a3f4..042955d5 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -144,9 +144,9 @@ protected function setupAssociations(): void ]); } - $conditions = ["$targetAlias.model" => $model]; + $conditions = ["{$targetAlias}.model" => $model]; if (!$this->_config['allowEmptyTranslations']) { - $conditions["$targetAlias.content !="] = ''; + $conditions["{$targetAlias}.content !="] = ''; } if ($this->table->associations()->has($targetAlias)) { diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index dedfedf8..0598e162 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -234,7 +234,7 @@ protected function addFieldsToQuery(SelectQuery $query, array $config): bool $alias = $config['mainTableAlias']; $joinRequired = false; foreach ($this->translatedFields() as $field) { - if (array_intersect($select, [$field, "$alias.$field"])) { + if (array_intersect($select, [$field, "{$alias}.{$field}"])) { $joinRequired = true; $query->select($query->aliasField($field, $config['hasOneAlias'])); } @@ -281,9 +281,9 @@ function ($c, &$field) use ($fields, $alias, $mainTableAlias, $mainTableFields, if (in_array($field, $fields, true)) { $joinRequired = true; - $field = "$alias.$field"; + $field = "{$alias}.{$field}"; } elseif (in_array($field, $mainTableFields, true)) { - $field = "$mainTableAlias.$field"; + $field = "{$mainTableAlias}.{$field}"; } return $c; @@ -331,13 +331,13 @@ function ($expression) use ($fields, $alias, $mainTableAlias, $mainTableFields, if (in_array($field, $fields, true)) { $joinRequired = true; - $expression->setField("$alias.$field"); + $expression->setField("{$alias}.{$field}"); return; } if (in_array($field, $mainTableFields, true)) { - $expression->setField("$mainTableAlias.$field"); + $expression->setField("{$mainTableAlias}.{$field}"); } } ); diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 2009144e..f87d9f22 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -352,7 +352,7 @@ public function findTranslations(SelectQuery $query, array $locales = []): Selec return $query ->contain([$targetAlias => function (QueryInterface $query) use ($locales, $targetAlias) { if ($locales) { - $query->where(["$targetAlias.locale IN" => $locales]); + $query->where(["{$targetAlias}.locale IN" => $locales]); } return $query; diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index d7584b48..bf4b3ac2 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -403,8 +403,8 @@ function ($field) { return $this->_scope($query) ->where([ - "$left <=" => $node->get($config['left']), - "$right >=" => $node->get($config['right']), + "{$left} <=" => $node->get($config['left']), + "{$right} >=" => $node->get($config['right']), ]) ->orderBy([$left => 'ASC']); } @@ -637,7 +637,7 @@ protected function _moveUp(EntityInterface $node, int|bool $number): EntityInter /** @var \Cake\Datasource\EntityInterface|null $targetNode */ $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) - ->where(["$parent IS" => $nodeParent]) + ->where(["{$parent} IS" => $nodeParent]) ->where(fn (QueryExpression $exp) => $exp->lt($config['rightField'], $nodeLeft)) ->orderByDesc($config['leftField']) ->offset($number - 1) @@ -648,7 +648,7 @@ protected function _moveUp(EntityInterface $node, int|bool $number): EntityInter /** @var \Cake\Datasource\EntityInterface|null $targetNode */ $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) - ->where(["$parent IS" => $nodeParent]) + ->where(["{$parent} IS" => $nodeParent]) ->where(fn (QueryExpression $exp) => $exp->lt($config['rightField'], $nodeLeft)) ->orderByAsc($config['leftField']) ->limit(1) @@ -726,7 +726,7 @@ protected function _moveDown(EntityInterface $node, int|bool $number): EntityInt /** @var \Cake\Datasource\EntityInterface|null $targetNode */ $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) - ->where(["$parent IS" => $nodeParent]) + ->where(["{$parent} IS" => $nodeParent]) ->where(fn (QueryExpression $exp) => $exp->gt($config['leftField'], $nodeRight)) ->orderByAsc($config['leftField']) ->offset($number - 1) @@ -737,7 +737,7 @@ protected function _moveDown(EntityInterface $node, int|bool $number): EntityInt /** @var \Cake\Datasource\EntityInterface|null $targetNode */ $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) - ->where(["$parent IS" => $nodeParent]) + ->where(["{$parent} IS" => $nodeParent]) ->where(fn (QueryExpression $exp) => $exp->gt($config['leftField'], $nodeRight)) ->orderByDesc($config['leftField']) ->limit(1) diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index 7546ef03..e2f2665e 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -102,7 +102,7 @@ protected function _alias(string $alias, array $conditions): array { $aliased = []; foreach ($conditions as $key => $value) { - $aliased["$alias.$key IS"] = $value; + $aliased["{$alias}.{$key} IS"] = $value; } return $aliased; diff --git a/Table.php b/Table.php index 65b0eb66..5bde229b 100644 --- a/Table.php +++ b/Table.php @@ -2024,7 +2024,7 @@ protected function _processSave(EntityInterface $entity, ArrayObject $options): $alias = $this->getAlias(); $conditions = []; foreach ($entity->extract($primaryColumns) as $k => $v) { - $conditions["$alias.$k"] = $v; + $conditions["{$alias}.{$k}"] = $v; } $entity->setNew(!$this->exists($conditions)); } @@ -2246,7 +2246,7 @@ protected function _update(EntityInterface $entity, array $data): EntityInterfac if ($primaryColumns === []) { $entityClass = $entity::class; $table = $this->getTable(); - $message = "Cannot update `$entityClass`. The `$table` has no primary key."; + $message = "Cannot update `{$entityClass}`. The `{$table}` has no primary key."; throw new InvalidArgumentException($message); } From 5e98650f19a810345ec0f995d2c7cbaecf87e25b Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 5 Aug 2024 20:42:47 -0400 Subject: [PATCH 1969/2059] Fix phpcs and micro optimization for EventManager - Fix arrow function formatting to resolve phpcs build failure. - Add an early return to avoid setting `_isGlobal` each time an event manager is read. --- Table.php | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/Table.php b/Table.php index 2f6f0c65..04d982ff 100644 --- a/Table.php +++ b/Table.php @@ -1409,12 +1409,11 @@ public function findList( ['keyField', 'valueField', 'groupField'] ); - return $query->formatResults(fn (CollectionInterface $results) => - $results->combine( - $options['keyField'], - $options['valueField'], - $options['groupField'] - )); + return $query->formatResults(fn (CollectionInterface $results) => $results->combine( + $options['keyField'], + $options['valueField'], + $options['groupField'] + )); } /** @@ -1449,8 +1448,7 @@ public function findThreaded( $options = $this->_setFieldMatchers(compact('keyField', 'parentField'), ['keyField', 'parentField']); - return $query->formatResults(fn (CollectionInterface $results) => - $results->nest($options['keyField'], $options['parentField'], $nestingKey)); + return $query->formatResults(fn (CollectionInterface $results) => $results->nest($options['keyField'], $options['parentField'], $nestingKey)); } /** From 6fdc46633e5e5bad8f69e6529f625d9f0aadaece Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 5 Aug 2024 20:51:53 -0400 Subject: [PATCH 1970/2059] Fix formatting --- Table.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 04d982ff..aa1a62f1 100644 --- a/Table.php +++ b/Table.php @@ -1448,7 +1448,11 @@ public function findThreaded( $options = $this->_setFieldMatchers(compact('keyField', 'parentField'), ['keyField', 'parentField']); - return $query->formatResults(fn (CollectionInterface $results) => $results->nest($options['keyField'], $options['parentField'], $nestingKey)); + return $query->formatResults(fn (CollectionInterface $results) => $results->nest( + $options['keyField'], + $options['parentField'], + $nestingKey + )); } /** From 609a9d84aa023c82e864101f6caa8682ab958e28 Mon Sep 17 00:00:00 2001 From: Adam Haflar <47174548+Harfusha@users.noreply.github.com> Date: Tue, 6 Aug 2024 19:12:04 +0200 Subject: [PATCH 1971/2059] Add changes from rector rule ReturnEarlyIfVariableRector (#17800) * Add changes from rector rule ReturnEarlyIfVariableRector --- Association.php | 2 +- ResultSetFactory.php | 3 ++- Table.php | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Association.php b/Association.php index 4943899b..124eeb2b 100644 --- a/Association.php +++ b/Association.php @@ -1005,7 +1005,7 @@ function (CollectionInterface $results, SelectQuery $query) use ($formatters, $p $results = $results->insert($property, $extracted); if ($query->isHydrationEnabled()) { - $results = $results->map(function (EntityInterface $result) { + return $results->map(function (EntityInterface $result) { $result->clean(); return $result; diff --git a/ResultSetFactory.php b/ResultSetFactory.php index e8708090..5dac3630 100644 --- a/ResultSetFactory.php +++ b/ResultSetFactory.php @@ -235,7 +235,8 @@ protected function groupResult(array $row, array $data): EntityInterface|array $results = $results[$data['primaryAlias']]; } if ($data['hydrate'] && !($results instanceof EntityInterface)) { - $results = new $data['entityClass']($results, $options); + /** @var \Cake\Datasource\EntityInterface */ + return new $data['entityClass']($results, $options); } return $results; diff --git a/Table.php b/Table.php index aa1a62f1..50fba97d 100644 --- a/Table.php +++ b/Table.php @@ -967,7 +967,7 @@ protected function findAssociation(string $name): ?Association } if ($result !== null && $next !== null) { - $result = $result->getTarget()->getAssociation($next); + return $result->getTarget()->getAssociation($next); } return $result; From 86b28105bad11558810c14cef73349e0836fb6b0 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Thu, 8 Aug 2024 11:15:26 -0500 Subject: [PATCH 1972/2059] Fix phpcs spacing issue --- Table.php | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Table.php b/Table.php index 7f981bef..cf4dd558 100644 --- a/Table.php +++ b/Table.php @@ -1407,12 +1407,11 @@ public function findList( ['keyField', 'valueField', 'groupField'] ); - return $query->formatResults(fn (CollectionInterface $results) => - $results->combine( - $options['keyField'], - $options['valueField'], - $options['groupField'] - )); + return $query->formatResults(fn (CollectionInterface $results) => $results->combine( + $options['keyField'], + $options['valueField'], + $options['groupField'] + )); } /** @@ -1447,8 +1446,11 @@ public function findThreaded( $options = $this->_setFieldMatchers(compact('keyField', 'parentField'), ['keyField', 'parentField']); - return $query->formatResults(fn (CollectionInterface $results) => - $results->nest($options['keyField'], $options['parentField'], $nestingKey)); + return $query->formatResults(fn (CollectionInterface $results) => $results->nest( + $options['keyField'], + $options['parentField'], + $nestingKey + )); } /** From b2e7f8b2f5df66f6603707375accdc40a559f0d0 Mon Sep 17 00:00:00 2001 From: Adam Halfar Date: Thu, 22 Aug 2024 21:24:04 +0200 Subject: [PATCH 1973/2059] Rector changes after merge --- Association.php | 2 +- Behavior/Translate/EavStrategy.php | 4 ++-- Behavior/Translate/ShadowTableStrategy.php | 2 +- BehaviorRegistry.php | 2 +- EagerLoader.php | 6 +++--- Marshaller.php | 4 ++-- Query/SelectQuery.php | 2 +- RulesChecker.php | 8 ++++---- Table.php | 14 +++++++------- 9 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Association.php b/Association.php index 9a95f242..b0bb4ac5 100644 --- a/Association.php +++ b/Association.php @@ -948,7 +948,7 @@ protected function _appendFields(SelectQuery $query, SelectQuery $surrogate, arr $fields = array_merge($surrogate->clause('select'), $options['fields']); if ( - (empty($fields) && $options['includeFields']) || + ($fields === [] && $options['includeFields']) || $surrogate->isAutoFieldsEnabled() ) { $fields = array_merge($fields, $this->getTarget()->getSchema()->columns()); diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index 042955d5..46da03a4 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -264,7 +264,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array $values = $entity->extract($this->_config['fields'], true); $fields = array_keys($values); - $noFields = empty($fields); + $noFields = $fields === []; // If there are no fields and no bundled translations, or both fields // in the default locale and bundled translations we can @@ -316,7 +316,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array $new = array_diff_key($values, $modified); foreach ($new as $field => $content) { - $new[$field] = new Entity(compact('locale', 'field', 'content', 'model'), [ + $new[$field] = new Entity(['locale' => $locale, 'field' => $field, 'content' => $content, 'model' => $model], [ 'useSetters' => false, 'markNew' => true, ]); diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 54b6b858..28e2736b 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -379,7 +379,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array $values = $entity->extract($this->translatedFields(), true); $fields = array_keys($values); - $noFields = empty($fields); + $noFields = $fields === []; // If there are no fields and no bundled translations, or both fields // in the default locale and bundled translations we can diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 5eb34e6e..569e04f0 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -208,7 +208,7 @@ protected function _getMethods(Behavior $instance, string $class, string $alias) $methods[$method] = [$alias, $methodName]; } - return compact('methods', 'finders'); + return ['methods' => $methods, 'finders' => $finders]; } /** diff --git a/EagerLoader.php b/EagerLoader.php index 12ab4d9c..fa2d0425 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -290,7 +290,7 @@ public function getMatching(): array */ public function normalized(Table $repository): array { - if ($this->_normalized !== null || empty($this->_containments)) { + if ($this->_normalized !== null || $this->_containments === []) { return (array)$this->_normalized; } @@ -419,7 +419,7 @@ public function attachAssociations(SelectQuery $query, Table $repository, bool $ $newAttachable = $this->attachableAssociations($repository); $attachable = array_diff_key($newAttachable, $processed); - } while (!empty($attachable)); + } while ($attachable !== []); } /** @@ -692,7 +692,7 @@ public function associationsMap(Table $table): array { $map = []; - if (!$this->getMatching() && !$this->getContain() && empty($this->_joinsMap)) { + if (!$this->getMatching() && !$this->getContain() && $this->_joinsMap === []) { return $map; } diff --git a/Marshaller.php b/Marshaller.php index 80918982..2bee0414 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -291,7 +291,7 @@ protected function _prepareDataAndOptions(array $data, array $options): array $data = new ArrayObject($data); $options = new ArrayObject($options); - $this->_table->dispatchEvent('Model.beforeMarshal', compact('data', 'options')); + $this->_table->dispatchEvent('Model.beforeMarshal', ['data' => $data, 'options' => $options]); return [(array)$data, (array)$options]; } @@ -902,6 +902,6 @@ protected function dispatchAfterMarshal(EntityInterface $entity, array $data, ar { $data = new ArrayObject($data); $options = new ArrayObject($options); - $this->_table->dispatchEvent('Model.afterMarshal', compact('entity', 'data', 'options')); + $this->_table->dispatchEvent('Model.afterMarshal', ['entity' => $entity, 'data' => $data, 'options' => $options]); } } diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index 2a812d1c..936f85bf 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -428,7 +428,7 @@ public function mapReduce(?Closure $mapper = null, ?Closure $reducer = null, boo return $this; } - $this->_mapReduce[] = compact('mapper', 'reducer'); + $this->_mapReduce[] = ['mapper' => $mapper, 'reducer' => $reducer]; return $this; } diff --git a/RulesChecker.php b/RulesChecker.php index a7c19a41..12687f93 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -69,7 +69,7 @@ public function isUnique(array $fields, array|string|null $message = null): Rule $errorField = current($fields); - return $this->_addError(new IsUnique($fields, $options), '_isUnique', compact('errorField', 'message')); + return $this->_addError(new IsUnique($fields, $options), '_isUnique', ['errorField' => $errorField, 'message' => $message]); } /** @@ -119,7 +119,7 @@ public function existsIn( $errorField = is_string($field) ? $field : current($field); - return $this->_addError(new ExistsIn($field, $table, $options), '_existsIn', compact('errorField', 'message')); + return $this->_addError(new ExistsIn($field, $table, $options), '_existsIn', ['errorField' => $errorField, 'message' => $message]); } /** @@ -249,7 +249,7 @@ protected function _addLinkConstraintRule( $linkStatus ); - return $this->_addError($rule, $ruleName, compact('errorField', 'message')); + return $this->_addError($rule, $ruleName, ['errorField' => $errorField, 'message' => $message]); } /** @@ -280,7 +280,7 @@ public function validCount( return $this->_addError( new ValidCount($field), '_validCount', - compact('count', 'operator', 'errorField', 'message') + ['count' => $count, 'operator' => $operator, 'errorField' => $errorField, 'message' => $message] ); } } diff --git a/Table.php b/Table.php index 14aa124c..afccbb98 100644 --- a/Table.php +++ b/Table.php @@ -1405,7 +1405,7 @@ public function findList( } $options = $this->_setFieldMatchers( - compact('keyField', 'valueField', 'groupField', 'valueSeparator'), + ['keyField' => $keyField, 'valueField' => $valueField, 'groupField' => $groupField, 'valueSeparator' => $valueSeparator], ['keyField', 'valueField', 'groupField'] ); @@ -1446,7 +1446,7 @@ public function findThreaded( ): SelectQuery { $keyField ??= $this->getPrimaryKey(); - $options = $this->_setFieldMatchers(compact('keyField', 'parentField'), ['keyField', 'parentField']); + $options = $this->_setFieldMatchers(['keyField' => $keyField, 'parentField' => $parentField], ['keyField', 'parentField']); return $query->formatResults(fn (CollectionInterface $results) => $results->nest( $options['keyField'], @@ -1669,7 +1669,7 @@ public function findOrCreate( ); if ($entity && $this->_transactionCommitted($options['atomic'], true)) { - $this->dispatchEvent('Model.afterSaveCommit', compact('entity', 'options')); + $this->dispatchEvent('Model.afterSaveCommit', ['entity' => $entity, 'options' => $options]); } return $entity; @@ -1976,7 +1976,7 @@ public function save( if ($success) { if ($this->_transactionCommitted($options['atomic'], $options['_primary'])) { - $this->dispatchEvent('Model.afterSaveCommit', compact('entity', 'options')); + $this->dispatchEvent('Model.afterSaveCommit', ['entity' => $entity, 'options' => $options]); } if ($options['atomic'] || $options['_primary']) { if ($options['_cleanOnSuccess']) { @@ -2039,7 +2039,7 @@ protected function _processSave(EntityInterface $entity, ArrayObject $options): } $options['associated'] = $this->_associations->normalizeKeys($options['associated']); - $event = $this->dispatchEvent('Model.beforeSave', compact('entity', 'options')); + $event = $this->dispatchEvent('Model.beforeSave', ['entity' => $entity, 'options' => $options]); if ($event->isStopped()) { $result = $event->getResult(); @@ -2115,7 +2115,7 @@ protected function _onSaveSuccess(EntityInterface $entity, ArrayObject $options) return false; } - $this->dispatchEvent('Model.afterSave', compact('entity', 'options')); + $this->dispatchEvent('Model.afterSave', ['entity' => $entity, 'options' => $options]); if ($options['atomic'] && !$this->getConnection()->inTransaction()) { throw new RolledbackTransactionException(['table' => static::class]); @@ -2388,7 +2388,7 @@ protected function _saveMany( if ($this->_transactionCommitted($options['atomic'], $options['_primary'])) { foreach ($entities as $entity) { - $this->dispatchEvent('Model.afterSaveCommit', compact('entity', 'options')); + $this->dispatchEvent('Model.afterSaveCommit', ['entity' => $entity, 'options' => $options]); if ($options['atomic'] || $options['_primary']) { $cleanupOnSuccess($entity); } From 7c8ba78c3a9c74bfa06bb9b0712679f1801f256c Mon Sep 17 00:00:00 2001 From: Adam Halfar Date: Thu, 22 Aug 2024 21:37:26 +0200 Subject: [PATCH 1974/2059] Fix coding standard and missed tests --- Behavior/Translate/EavStrategy.php | 16 ++++++++++++---- Marshaller.php | 9 ++++++++- RulesChecker.php | 18 ++++++++++++++++-- Table.php | 12 ++++++++++-- 4 files changed, 46 insertions(+), 9 deletions(-) diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index 46da03a4..45e4e8e7 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -316,10 +316,18 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array $new = array_diff_key($values, $modified); foreach ($new as $field => $content) { - $new[$field] = new Entity(['locale' => $locale, 'field' => $field, 'content' => $content, 'model' => $model], [ - 'useSetters' => false, - 'markNew' => true, - ]); + $new[$field] = new Entity( + [ + 'locale' => $locale, + 'field' => $field, + 'content' => $content, + 'model' => $model, + ], + [ + 'useSetters' => false, + 'markNew' => true, + ] + ); } $entity->set('_i18n', array_merge($bundled, array_values($modified + $new))); diff --git a/Marshaller.php b/Marshaller.php index 2bee0414..c3a13b89 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -902,6 +902,13 @@ protected function dispatchAfterMarshal(EntityInterface $entity, array $data, ar { $data = new ArrayObject($data); $options = new ArrayObject($options); - $this->_table->dispatchEvent('Model.afterMarshal', ['entity' => $entity, 'data' => $data, 'options' => $options]); + $this->_table->dispatchEvent( + 'Model.afterMarshal', + [ + 'entity' => $entity, + 'data' => $data, + 'options' => $options, + ] + ); } } diff --git a/RulesChecker.php b/RulesChecker.php index 12687f93..a4ee3ae1 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -69,7 +69,14 @@ public function isUnique(array $fields, array|string|null $message = null): Rule $errorField = current($fields); - return $this->_addError(new IsUnique($fields, $options), '_isUnique', ['errorField' => $errorField, 'message' => $message]); + return $this->_addError( + new IsUnique($fields, $options), + '_isUnique', + [ + 'errorField' => $errorField, + 'message' => $message, + ] + ); } /** @@ -119,7 +126,14 @@ public function existsIn( $errorField = is_string($field) ? $field : current($field); - return $this->_addError(new ExistsIn($field, $table, $options), '_existsIn', ['errorField' => $errorField, 'message' => $message]); + return $this->_addError( + new ExistsIn($field, $table, $options), + '_existsIn', + [ + 'errorField' => $errorField, + 'message' => $message, + ] + ); } /** diff --git a/Table.php b/Table.php index afccbb98..4f21165e 100644 --- a/Table.php +++ b/Table.php @@ -1405,7 +1405,12 @@ public function findList( } $options = $this->_setFieldMatchers( - ['keyField' => $keyField, 'valueField' => $valueField, 'groupField' => $groupField, 'valueSeparator' => $valueSeparator], + [ + 'keyField' => $keyField, + 'valueField' => $valueField, + 'groupField' => $groupField, + 'valueSeparator' => $valueSeparator, + ], ['keyField', 'valueField', 'groupField'] ); @@ -1446,7 +1451,10 @@ public function findThreaded( ): SelectQuery { $keyField ??= $this->getPrimaryKey(); - $options = $this->_setFieldMatchers(['keyField' => $keyField, 'parentField' => $parentField], ['keyField', 'parentField']); + $options = $this->_setFieldMatchers([ + 'keyField' => $keyField, + 'parentField' => $parentField, + ], ['keyField', 'parentField']); return $query->formatResults(fn (CollectionInterface $results) => $results->nest( $options['keyField'], From a7eb0e58970f6c1ab116d5a7f616bfbcf5c5d23e Mon Sep 17 00:00:00 2001 From: Adam Halfar Date: Fri, 23 Aug 2024 15:07:24 +0200 Subject: [PATCH 1975/2059] Revert rule CompactToVariablesRector --- Behavior/Translate/EavStrategy.php | 16 ++++------------ BehaviorRegistry.php | 2 +- Marshaller.php | 11 ++--------- Query/SelectQuery.php | 2 +- RulesChecker.php | 22 ++++------------------ Table.php | 22 +++++++--------------- 6 files changed, 19 insertions(+), 56 deletions(-) diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index 45e4e8e7..b6dbf64f 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -316,18 +316,10 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array $new = array_diff_key($values, $modified); foreach ($new as $field => $content) { - $new[$field] = new Entity( - [ - 'locale' => $locale, - 'field' => $field, - 'content' => $content, - 'model' => $model, - ], - [ - 'useSetters' => false, - 'markNew' => true, - ] - ); + $new[$field] = new Entity(compact('locale', 'field', 'content', 'model'), [ + 'useSetters' => false, + 'markNew' => true, + ]); } $entity->set('_i18n', array_merge($bundled, array_values($modified + $new))); diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 569e04f0..5eb34e6e 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -208,7 +208,7 @@ protected function _getMethods(Behavior $instance, string $class, string $alias) $methods[$method] = [$alias, $methodName]; } - return ['methods' => $methods, 'finders' => $finders]; + return compact('methods', 'finders'); } /** diff --git a/Marshaller.php b/Marshaller.php index c3a13b89..80918982 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -291,7 +291,7 @@ protected function _prepareDataAndOptions(array $data, array $options): array $data = new ArrayObject($data); $options = new ArrayObject($options); - $this->_table->dispatchEvent('Model.beforeMarshal', ['data' => $data, 'options' => $options]); + $this->_table->dispatchEvent('Model.beforeMarshal', compact('data', 'options')); return [(array)$data, (array)$options]; } @@ -902,13 +902,6 @@ protected function dispatchAfterMarshal(EntityInterface $entity, array $data, ar { $data = new ArrayObject($data); $options = new ArrayObject($options); - $this->_table->dispatchEvent( - 'Model.afterMarshal', - [ - 'entity' => $entity, - 'data' => $data, - 'options' => $options, - ] - ); + $this->_table->dispatchEvent('Model.afterMarshal', compact('entity', 'data', 'options')); } } diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index 936f85bf..2a812d1c 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -428,7 +428,7 @@ public function mapReduce(?Closure $mapper = null, ?Closure $reducer = null, boo return $this; } - $this->_mapReduce[] = ['mapper' => $mapper, 'reducer' => $reducer]; + $this->_mapReduce[] = compact('mapper', 'reducer'); return $this; } diff --git a/RulesChecker.php b/RulesChecker.php index a4ee3ae1..a7c19a41 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -69,14 +69,7 @@ public function isUnique(array $fields, array|string|null $message = null): Rule $errorField = current($fields); - return $this->_addError( - new IsUnique($fields, $options), - '_isUnique', - [ - 'errorField' => $errorField, - 'message' => $message, - ] - ); + return $this->_addError(new IsUnique($fields, $options), '_isUnique', compact('errorField', 'message')); } /** @@ -126,14 +119,7 @@ public function existsIn( $errorField = is_string($field) ? $field : current($field); - return $this->_addError( - new ExistsIn($field, $table, $options), - '_existsIn', - [ - 'errorField' => $errorField, - 'message' => $message, - ] - ); + return $this->_addError(new ExistsIn($field, $table, $options), '_existsIn', compact('errorField', 'message')); } /** @@ -263,7 +249,7 @@ protected function _addLinkConstraintRule( $linkStatus ); - return $this->_addError($rule, $ruleName, ['errorField' => $errorField, 'message' => $message]); + return $this->_addError($rule, $ruleName, compact('errorField', 'message')); } /** @@ -294,7 +280,7 @@ public function validCount( return $this->_addError( new ValidCount($field), '_validCount', - ['count' => $count, 'operator' => $operator, 'errorField' => $errorField, 'message' => $message] + compact('count', 'operator', 'errorField', 'message') ); } } diff --git a/Table.php b/Table.php index 4f21165e..14aa124c 100644 --- a/Table.php +++ b/Table.php @@ -1405,12 +1405,7 @@ public function findList( } $options = $this->_setFieldMatchers( - [ - 'keyField' => $keyField, - 'valueField' => $valueField, - 'groupField' => $groupField, - 'valueSeparator' => $valueSeparator, - ], + compact('keyField', 'valueField', 'groupField', 'valueSeparator'), ['keyField', 'valueField', 'groupField'] ); @@ -1451,10 +1446,7 @@ public function findThreaded( ): SelectQuery { $keyField ??= $this->getPrimaryKey(); - $options = $this->_setFieldMatchers([ - 'keyField' => $keyField, - 'parentField' => $parentField, - ], ['keyField', 'parentField']); + $options = $this->_setFieldMatchers(compact('keyField', 'parentField'), ['keyField', 'parentField']); return $query->formatResults(fn (CollectionInterface $results) => $results->nest( $options['keyField'], @@ -1677,7 +1669,7 @@ public function findOrCreate( ); if ($entity && $this->_transactionCommitted($options['atomic'], true)) { - $this->dispatchEvent('Model.afterSaveCommit', ['entity' => $entity, 'options' => $options]); + $this->dispatchEvent('Model.afterSaveCommit', compact('entity', 'options')); } return $entity; @@ -1984,7 +1976,7 @@ public function save( if ($success) { if ($this->_transactionCommitted($options['atomic'], $options['_primary'])) { - $this->dispatchEvent('Model.afterSaveCommit', ['entity' => $entity, 'options' => $options]); + $this->dispatchEvent('Model.afterSaveCommit', compact('entity', 'options')); } if ($options['atomic'] || $options['_primary']) { if ($options['_cleanOnSuccess']) { @@ -2047,7 +2039,7 @@ protected function _processSave(EntityInterface $entity, ArrayObject $options): } $options['associated'] = $this->_associations->normalizeKeys($options['associated']); - $event = $this->dispatchEvent('Model.beforeSave', ['entity' => $entity, 'options' => $options]); + $event = $this->dispatchEvent('Model.beforeSave', compact('entity', 'options')); if ($event->isStopped()) { $result = $event->getResult(); @@ -2123,7 +2115,7 @@ protected function _onSaveSuccess(EntityInterface $entity, ArrayObject $options) return false; } - $this->dispatchEvent('Model.afterSave', ['entity' => $entity, 'options' => $options]); + $this->dispatchEvent('Model.afterSave', compact('entity', 'options')); if ($options['atomic'] && !$this->getConnection()->inTransaction()) { throw new RolledbackTransactionException(['table' => static::class]); @@ -2396,7 +2388,7 @@ protected function _saveMany( if ($this->_transactionCommitted($options['atomic'], $options['_primary'])) { foreach ($entities as $entity) { - $this->dispatchEvent('Model.afterSaveCommit', ['entity' => $entity, 'options' => $options]); + $this->dispatchEvent('Model.afterSaveCommit', compact('entity', 'options')); if ($options['atomic'] || $options['_primary']) { $cleanupOnSuccess($entity); } From 1662fc10de42d1d34db366a42effe7817828e900 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 28 Aug 2024 17:39:49 +0530 Subject: [PATCH 1976/2059] Pin phpstan to 1.12.0 (#17875) --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index c29f4853..93575e1e 100644 --- a/composer.json +++ b/composer.json @@ -47,6 +47,6 @@ "require-dev": { "cakephp/cache": "^5.0", "cakephp/i18n": "^5.0", - "phpstan/phpstan": "^1.10" + "phpstan/phpstan": "1.12.0" } } From 9258b40e8a88c7ce5d4a1440e7bf88eee0d7dfce Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 28 Aug 2024 11:42:25 +0530 Subject: [PATCH 1977/2059] Allow preserving keys when using select loaders. Setting the `preserveKeys` option for the association's finder query now preserves the record keys when populating the associated records array for the parent records. Refs #10118 --- Association/Loader/SelectLoader.php | 15 ++++++++++++--- Association/Loader/SelectWithPivotLoader.php | 9 ++++++++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 4dc682e5..358f69df 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -485,16 +485,25 @@ protected function _buildResultMap(SelectQuery $fetchQuery, array $options): arr $this->bindingKey; $key = (array)$keys; - foreach ($fetchQuery->all() as $result) { + $preserveKeys = $fetchQuery->getOptions()['preserveKeys'] ?? false; + + foreach ($fetchQuery->all() as $i => $result) { $values = []; foreach ($key as $k) { $values[] = $result[$k]; } + if ($singleResult) { $resultMap[implode(';', $values)] = $result; - } else { - $resultMap[implode(';', $values)][] = $result; + continue; } + + if ($preserveKeys) { + $resultMap[implode(';', $values)][$i] = $result; + continue; + } + + $resultMap[implode(';', $values)][] = $result; } return $resultMap; diff --git a/Association/Loader/SelectWithPivotLoader.php b/Association/Loader/SelectWithPivotLoader.php index 33b398b1..4c6399f4 100644 --- a/Association/Loader/SelectWithPivotLoader.php +++ b/Association/Loader/SelectWithPivotLoader.php @@ -178,8 +178,9 @@ protected function _buildResultMap(SelectQuery $fetchQuery, array $options): arr { $resultMap = []; $key = (array)$options['foreignKey']; + $preserveKeys = $fetchQuery->getOptions()['preserveKeys'] ?? false; - foreach ($fetchQuery->all() as $result) { + foreach ($fetchQuery->all() as $i => $result) { if (!isset($result[$this->junctionProperty])) { throw new DatabaseException(sprintf( '`%s` is missing from the belongsToMany results. Results cannot be created.', @@ -191,6 +192,12 @@ protected function _buildResultMap(SelectQuery $fetchQuery, array $options): arr foreach ($key as $k) { $values[] = $result[$this->junctionProperty][$k]; } + + if ($preserveKeys) { + $resultMap[implode(';', $values)][$i] = $result; + continue; + } + $resultMap[implode(';', $values)][] = $result; } From ad09a1d143143a26d28632f1bbc81100c75deaa3 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 29 Aug 2024 13:06:38 +0530 Subject: [PATCH 1978/2059] Remove phpstan as dev dependency from split packages. Use the phpstan version from the main composer.json for split package checks for consistency. --- composer.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 93575e1e..ba931623 100644 --- a/composer.json +++ b/composer.json @@ -46,7 +46,6 @@ }, "require-dev": { "cakephp/cache": "^5.0", - "cakephp/i18n": "^5.0", - "phpstan/phpstan": "1.12.0" + "cakephp/i18n": "^5.0" } } From 7ce497d49eaa4b606187f5ed245bf1522d7bea28 Mon Sep 17 00:00:00 2001 From: Alejandro Ibarra Date: Mon, 2 Sep 2024 20:21:06 +0200 Subject: [PATCH 1979/2059] 17857 - Add support for INTERSECT and INTERSECT ALL clauses in ORM --- Query/SelectQuery.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index 2a812d1c..afc3e418 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -1418,6 +1418,7 @@ protected function _performCount(): int $query->clause('distinct') || count($query->clause('group')) || count($query->clause('union')) || + count($query->clause('intersect')) || $query->clause('having') ); From 51647aa2be4f8ec62a9d693e7e33f36b219ecaaa Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 20 Jul 2024 19:37:40 +0530 Subject: [PATCH 1980/2059] Bump up Cake dependencies versions in split packages. --- composer.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index ba931623..456315ac 100644 --- a/composer.json +++ b/composer.json @@ -24,13 +24,13 @@ }, "require": { "php": ">=8.1", - "cakephp/collection": "^5.0", - "cakephp/core": "^5.0", - "cakephp/datasource": "^5.0", - "cakephp/database": "^5.0", - "cakephp/event": "^5.0", - "cakephp/utility": "^5.0", - "cakephp/validation": "^5.0" + "cakephp/collection": "^5.1", + "cakephp/core": "^5.1", + "cakephp/datasource": "^5.1", + "cakephp/database": "^5.1", + "cakephp/event": "^5.1", + "cakephp/utility": "^5.1", + "cakephp/validation": "^5.1" }, "suggest": { "cakephp/cache": "If you decide to use Query caching.", @@ -45,7 +45,7 @@ ] }, "require-dev": { - "cakephp/cache": "^5.0", - "cakephp/i18n": "^5.0" + "cakephp/cache": "^5.1", + "cakephp/i18n": "^5.1" } } From a8266550bea70952c7574b631c6256ce2e908adf Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Mon, 9 Sep 2024 10:55:34 +0200 Subject: [PATCH 1981/2059] replace @method docbloc with @mixin where possible --- Behavior/TranslateBehavior.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index f87d9f22..0048b032 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -41,6 +41,8 @@ * * If you want to bring all or certain languages for each of the fetched records, * you can use the custom `translations` finders that is exposed to the table. + * + * @mixin \Cake\ORM\Behavior\Translate\TranslateStrategyInterface */ class TranslateBehavior extends Behavior implements PropertyMarshalInterface { From 45d4e654274c1d9da056241e8ae257520fe2e14a Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Tue, 10 Sep 2024 21:29:01 +0200 Subject: [PATCH 1982/2059] Revert "replace @method docbloc with @mixin where possible" This reverts commit 6881cbc4098f345864b3a622708707dff7f7c73b. --- Behavior/TranslateBehavior.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 0048b032..f87d9f22 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -41,8 +41,6 @@ * * If you want to bring all or certain languages for each of the fetched records, * you can use the custom `translations` finders that is exposed to the table. - * - * @mixin \Cake\ORM\Behavior\Translate\TranslateStrategyInterface */ class TranslateBehavior extends Behavior implements PropertyMarshalInterface { From f126fd2ae74b947b7fc042429dda2ffeebc5bddd Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 14 Sep 2024 14:07:28 +0530 Subject: [PATCH 1983/2059] Update phpstan config for ORM package --- phpstan.neon.dist | 2 -- 1 file changed, 2 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index bd0af9cd..7e4dc886 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -16,5 +16,3 @@ parameters: - "#^Method Cake\\\\ORM\\\\Behavior\\\\TreeBehavior\\:\\:_scope\\(\\) should return T of Cake\\\\ORM\\\\Query\\\\DeleteQuery\\|Cake\\\\ORM\\\\Query\\\\SelectQuery\\|Cake\\\\ORM\\\\Query\\\\UpdateQuery but returns Cake\\\\ORM\\\\Query\\\\DeleteQuery\\|Cake\\\\ORM\\\\Query\\\\SelectQuery\\|Cake\\\\ORM\\\\Query\\\\UpdateQuery\\.$#" - "#^PHPDoc tag @return with type Cake\\\\ORM\\\\Query\\\\SelectQuery\\ is not subtype of native type static\\(Cake\\\\ORM\\\\Query\\\\SelectQuery\\\\)\\.$#" - "#^Method Cake\\\\ORM\\\\Query\\\\SelectQuery\\:\\:find\\(\\) should return static\\(Cake\\\\ORM\\\\Query\\\\SelectQuery\\\\) but returns Cake\\\\ORM\\\\Query\\\\SelectQuery\\\\.$#" - - "#^Access to an undefined property Cake\\\\ORM\\\\Query\\\\SelectQuery\\\\:\\:\\$bufferedResults\\.$#" - - "#^Parameter \\#2 \\$results of method Cake\\\\ORM\\\\EagerLoader\\:\\:loadExternal\\(\\) expects iterable, Cake\\\\Database\\\\StatementInterface\\|iterable given\\.$#" From 3f1c674cf145a3de2ab7397827fbf33f2f9c0574 Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Fri, 4 Oct 2024 14:32:01 +0200 Subject: [PATCH 1984/2059] fix usage of constant in EavStrategy --- Behavior/Translate/EavStrategy.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index b6dbf64f..957b24a1 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -138,7 +138,7 @@ protected function setupAssociations(): void $this->table->hasOne($name, [ 'targetTable' => $fieldTable, 'foreignKey' => 'foreign_key', - 'joinType' => $filter ? SelectQuery::JOIN_TYPE_INNER : SelectQuery ::JOIN_TYPE_LEFT, + 'joinType' => $filter ? SelectQuery::JOIN_TYPE_INNER : SelectQuery::JOIN_TYPE_LEFT, 'conditions' => $conditions, 'propertyName' => $field . '_translation', ]); @@ -218,7 +218,7 @@ public function beforeFind(EventInterface $event, SelectQuery $query, ArrayObjec if ($changeFilter) { $filter = $options['filterByCurrentLocale'] ? SelectQuery::JOIN_TYPE_INNER - : SelectQuery ::JOIN_TYPE_LEFT; + : SelectQuery::JOIN_TYPE_LEFT; $contain[$name]['joinType'] = $filter; } } From 80eb68fe2f5b0e6ab8e4ca570777f1c8100a9a9d Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Fri, 4 Oct 2024 15:03:19 +0200 Subject: [PATCH 1985/2059] cleanup --- Association/BelongsToMany.php | 4 ++-- Behavior.php | 2 +- Marshaller.php | 2 +- Table.php | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 589d3bcb..7b816db1 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -805,7 +805,7 @@ protected function _saveLinks(EntityInterface $sourceEntity, array $targetEntiti foreach ($targetEntities as $e) { $joint = $e->get($jointProperty); - if (!$joint || !($joint instanceof EntityInterface)) { + if (!($joint instanceof EntityInterface)) { $joint = new $entityClass([], ['markNew' => true, 'source' => $junctionRegistryAlias]); } $sourceKeys = array_combine($foreignKey, $sourceEntity->extract($bindingKey)); @@ -1405,7 +1405,7 @@ protected function _collectJointEntities(EntityInterface $sourceEntity, array $t } $joint = $entity->get($jointProperty); - if (!$joint || !($joint instanceof EntityInterface)) { + if (!($joint instanceof EntityInterface)) { $missing[] = $entity->extract($primary); continue; } diff --git a/Behavior.php b/Behavior.php index b0b7c977..58dbaa40 100644 --- a/Behavior.php +++ b/Behavior.php @@ -203,7 +203,7 @@ protected function _resolveMethodAliases(string $key, array $defaults, array $co if (!isset($defaults[$key], $config[$key])) { return $config; } - if (isset($config[$key]) && $config[$key] === []) { + if ($config[$key] === []) { $this->setConfig($key, [], false); unset($config[$key]); diff --git a/Marshaller.php b/Marshaller.php index 80918982..4b331720 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -847,7 +847,7 @@ protected function _mergeJoinData(array $original, BelongsToMany $assoc, array $ $entity->setAccess('_joinData', true); $joinData = $entity->get('_joinData'); - if ($joinData && $joinData instanceof EntityInterface) { + if ($joinData instanceof EntityInterface) { $extra[spl_object_hash($entity)] = $joinData; } } diff --git a/Table.php b/Table.php index 14aa124c..cc768a64 100644 --- a/Table.php +++ b/Table.php @@ -2551,7 +2551,7 @@ public function deleteOrFail(EntityInterface $entity, array $options = []): bool throw new PersistenceFailedException($entity, ['delete']); } - return $deleted; + return true; } /** From 0b1734da9e617d69771e8881f19c65e1da261b36 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 12 Oct 2024 14:49:12 +0530 Subject: [PATCH 1986/2059] Optimize count updation in CounterCache behavior. Use sub query to update the count instead of separate queries to fetch and update the count. --- Behavior/CounterCacheBehavior.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 292b2c8f..b8475d52 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -21,6 +21,7 @@ use Cake\Event\EventInterface; use Cake\ORM\Association; use Cake\ORM\Behavior; +use Cake\ORM\Query\SelectQuery; use Closure; /** @@ -296,9 +297,10 @@ protected function _shouldUpdateCount(array $conditions): bool * * @param array $config The counter cache configuration for a single field * @param array $conditions Additional conditions given to the query - * @return int The number of relations matching the given config and conditions + * @return \Cake\ORM\Query\SelectQuery|int The query to fetch the number of + * relations matching the given config and conditions or the number itself. */ - protected function _getCount(array $config, array $conditions): int + protected function _getCount(array $config, array $conditions): SelectQuery|int { $finder = 'all'; if (!empty($config['finder'])) { @@ -309,6 +311,12 @@ protected function _getCount(array $config, array $conditions): int $config['conditions'] = array_merge($conditions, $config['conditions'] ?? []); $query = $this->_table->find($finder, ...$config); - return $query->count(); + if (isset($config['useSubQuery']) && $config['useSubQuery'] === false) { + return $query->count(); + } + + return $query + ->select(['count' => $query->func()->count('*')], true) + ->orderBy([], true); } } From 8f3f6bf00a6286fd0b7623b13792431f8f5950c7 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 12 Oct 2024 18:33:02 +0530 Subject: [PATCH 1987/2059] Add method to update counter cache values for all records. --- Behavior/CounterCacheBehavior.php | 81 +++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index b8475d52..f07f827d 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -20,9 +20,11 @@ use Cake\Datasource\EntityInterface; use Cake\Event\EventInterface; use Cake\ORM\Association; +use Cake\ORM\Association\BelongsTo; use Cake\ORM\Behavior; use Cake\ORM\Query\SelectQuery; use Closure; +use InvalidArgumentException; /** * CounterCache behavior @@ -191,6 +193,85 @@ public function afterDelete(EventInterface $event, EntityInterface $entity, Arra $this->_processAssociations($event, $entity); } + /** + * Update counter cache. + * + * @param string|null $assocName The association name to update counter cache for. + * If null, all configured associations will be updated. + * @throws \InvalidArgumentException If specified association is not configured. + * @return void + */ + public function updateCounterCache(?string $assocName = null): void + { + $config = $this->_config; + if ($assocName !== null) { + if (!isset($config[$assocName])) { + throw new InvalidArgumentException(sprintf( + 'Association `%s` is not configured for counter cache behavior of `%s`', + $assocName, + $this->_table->getAlias() + )); + } + + $config = [$assocName => $config[$assocName]]; + } + + foreach ($config as $assoc => $settings) { + /** @var \Cake\ORM\Association\BelongsTo $belongsTo */ + $belongsTo = $this->_table->getAssociation($assoc); + + foreach ($settings as $field => $config) { + if ($config instanceof Closure) { + // Cannot update counter cache which use a closure + return; + } + + if (is_int($field)) { + $field = $config; + $config = []; + } + + $this->updateCountForAssociation($belongsTo, $field, $config); + } + } + } + + /** + * Update counter cache for the given association. + * + * @param \Cake\ORM\Association\BelongsTo $assoc The association object. + * @param string $field Counter cache field. + * @param array $config Config array. + * @return void + */ + protected function updateCountForAssociation(BelongsTo $assoc, string $field, array $config): void + { + $primaryKeys = (array)$assoc->getBindingKey(); + /** @var list $foreignKeys */ + $foreignKeys = (array)$assoc->getForeignKey(); + + $results = $assoc->getTarget()->find() + ->select($primaryKeys) + ->all(); + + /** @var \Cake\Datasource\EntityInterface $entity */ + foreach ($results as $entity) { + $updateConditions = $entity->extract($primaryKeys); + + foreach ($updateConditions as $f => $value) { + if ($value === null) { + $updateConditions[$f . ' IS'] = $value; + unset($updateConditions[$f]); + } + } + + $countConditions = array_combine($foreignKeys, $updateConditions); + + $count = $this->_getCount($config, $countConditions); + $assoc->getTarget()->updateAll([$field => $count], $updateConditions); + } + } + /** * Iterate all associations and update counter caches. * From 6753d70265457864d206919b5e07dc5c6b61f3c4 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 14 Oct 2024 17:22:16 +0530 Subject: [PATCH 1988/2059] Update counter cache records in batches to limit memory utilization --- Behavior/CounterCacheBehavior.php | 55 +++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index f07f827d..f06b6913 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -198,10 +198,13 @@ public function afterDelete(EventInterface $event, EntityInterface $entity, Arra * * @param string|null $assocName The association name to update counter cache for. * If null, all configured associations will be updated. + * @param int $limit The number of records to fetch per page/iteration. + * @param int|null $page The page/iteration number. If null (default), all + * records will be updated one page at a time. * @throws \InvalidArgumentException If specified association is not configured. * @return void */ - public function updateCounterCache(?string $assocName = null): void + public function updateCounterCache(?string $assocName = null, int $limit = 100, ?int $page = null): void { $config = $this->_config; if ($assocName !== null) { @@ -231,7 +234,7 @@ public function updateCounterCache(?string $assocName = null): void $config = []; } - $this->updateCountForAssociation($belongsTo, $field, $config); + $this->updateCountForAssociation($belongsTo, $field, $config, $limit, $page); } } } @@ -242,34 +245,50 @@ public function updateCounterCache(?string $assocName = null): void * @param \Cake\ORM\Association\BelongsTo $assoc The association object. * @param string $field Counter cache field. * @param array $config Config array. + * @param int $limit Limit. + * @param int|null $page Page number. * @return void */ - protected function updateCountForAssociation(BelongsTo $assoc, string $field, array $config): void - { + protected function updateCountForAssociation( + BelongsTo $assoc, + string $field, + array $config, + int $limit = 100, + ?int $page = null + ): void { $primaryKeys = (array)$assoc->getBindingKey(); /** @var list $foreignKeys */ $foreignKeys = (array)$assoc->getForeignKey(); - $results = $assoc->getTarget()->find() + $query = $assoc->getTarget()->find() ->select($primaryKeys) - ->all(); + ->limit($limit); + + $singlePage = $page !== null; + $page ??= 1; + + do { + $results = $query + ->page($page++) + ->all(); - /** @var \Cake\Datasource\EntityInterface $entity */ - foreach ($results as $entity) { - $updateConditions = $entity->extract($primaryKeys); + /** @var \Cake\Datasource\EntityInterface $entity */ + foreach ($results as $entity) { + $updateConditions = $entity->extract($primaryKeys); - foreach ($updateConditions as $f => $value) { - if ($value === null) { - $updateConditions[$f . ' IS'] = $value; - unset($updateConditions[$f]); + foreach ($updateConditions as $f => $value) { + if ($value === null) { + $updateConditions[$f . ' IS'] = $value; + unset($updateConditions[$f]); + } } - } - $countConditions = array_combine($foreignKeys, $updateConditions); + $countConditions = array_combine($foreignKeys, $updateConditions); - $count = $this->_getCount($config, $countConditions); - $assoc->getTarget()->updateAll([$field => $count], $updateConditions); - } + $count = $this->_getCount($config, $countConditions); + $assoc->getTarget()->updateAll([$field => $count], $updateConditions); + } + } while (!$singlePage && $results->count() === $limit); } /** From 7edeca66cd3a216608d1da19d49a6c3da875ba62 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 14 Oct 2024 17:55:16 +0530 Subject: [PATCH 1989/2059] Remove unneeded check --- Behavior/CounterCacheBehavior.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index f06b6913..1b5b5525 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -24,7 +24,6 @@ use Cake\ORM\Behavior; use Cake\ORM\Query\SelectQuery; use Closure; -use InvalidArgumentException; /** * CounterCache behavior @@ -208,14 +207,6 @@ public function updateCounterCache(?string $assocName = null, int $limit = 100, { $config = $this->_config; if ($assocName !== null) { - if (!isset($config[$assocName])) { - throw new InvalidArgumentException(sprintf( - 'Association `%s` is not configured for counter cache behavior of `%s`', - $assocName, - $this->_table->getAlias() - )); - } - $config = [$assocName => $config[$assocName]]; } From 691faed2e25f41f02a81db78579eb88dfa864271 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 15 Oct 2024 08:46:27 +0530 Subject: [PATCH 1990/2059] Use deterministic ordering when fetching records --- Behavior/CounterCacheBehavior.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 1b5b5525..72888cc6 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -193,7 +193,7 @@ public function afterDelete(EventInterface $event, EntityInterface $entity, Arra } /** - * Update counter cache. + * Update counter cache for a batch of records. * * @param string|null $assocName The association name to update counter cache for. * If null, all configured associations will be updated. @@ -255,6 +255,10 @@ protected function updateCountForAssociation( ->select($primaryKeys) ->limit($limit); + foreach ($primaryKeys as $key) { + $query->orderByAsc($key); + } + $singlePage = $page !== null; $page ??= 1; From 56853c7315fc584bfef8016922a933343fc280a8 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 19 Oct 2024 17:25:48 +0530 Subject: [PATCH 1991/2059] Improve docblock --- Behavior/CounterCacheBehavior.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index 72888cc6..b6c80661 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -195,13 +195,15 @@ public function afterDelete(EventInterface $event, EntityInterface $entity, Arra /** * Update counter cache for a batch of records. * + * Counter caches configured to use closures will not be updated by the method. + * * @param string|null $assocName The association name to update counter cache for. - * If null, all configured associations will be updated. - * @param int $limit The number of records to fetch per page/iteration. + * If null, all configured associations will be processed. + * @param int $limit The number of records to update per page/iteration. * @param int|null $page The page/iteration number. If null (default), all * records will be updated one page at a time. - * @throws \InvalidArgumentException If specified association is not configured. * @return void + * @since 5.2.0 */ public function updateCounterCache(?string $assocName = null, int $limit = 100, ?int $page = null): void { From b9d63a4e91e575f518679e6df8ac8ffbaafe8279 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 25 Oct 2024 14:47:32 +0530 Subject: [PATCH 1992/2059] Fix context array being passed for optional argument of validation method. Prior to this patch validation providers had to be used through the RulesProvider class to handle properly passing the context array. Fixes #17631 --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index cc768a64..db7ce74b 100644 --- a/Table.php +++ b/Table.php @@ -3132,7 +3132,7 @@ public function patchEntities(iterable $entities, array $data, array $options = * @param array|null $context Either the validation context or null. * @return bool True if the value is unique, or false if a non-scalar, non-unique value was given. */ - public function validateUnique(mixed $value, array $options, ?array $context = null): bool + public function validateUnique(mixed $value, array $options = [], ?array $context = null): bool { if ($context === null) { $context = $options; From 817274b57b7a675bc0ade79527930793342bfe83 Mon Sep 17 00:00:00 2001 From: mscherer Date: Sun, 17 Nov 2024 16:19:16 +0100 Subject: [PATCH 1993/2059] Allow input param to be a normal array list instead of strict phpstan one. --- Association.php | 4 ++-- Association/BelongsTo.php | 2 +- Association/BelongsToMany.php | 2 +- Association/HasOne.php | 2 +- Association/Loader/SelectLoader.php | 10 +++++----- AssociationCollection.php | 2 +- Behavior/TranslateBehavior.php | 2 +- Exception/PersistenceFailedException.php | 2 +- LazyEagerLoader.php | 4 ++-- Locator/TableLocator.php | 2 +- Query/SelectQuery.php | 2 +- Rule/ExistsIn.php | 2 +- Rule/IsUnique.php | 2 +- Rule/LinkConstraint.php | 4 ++-- RulesChecker.php | 4 ++-- Table.php | 8 ++++---- 16 files changed, 27 insertions(+), 27 deletions(-) diff --git a/Association.php b/Association.php index b0bb4ac5..13a76cbb 100644 --- a/Association.php +++ b/Association.php @@ -428,7 +428,7 @@ public function getConditions(): Closure|array * Sets the name of the field representing the binding field with the target table. * When not manually specified the primary key of the owning side table is used. * - * @param list|string $key the table field or fields to be used to link both tables together + * @param array|string $key the table field or fields to be used to link both tables together * @return $this */ public function setBindingKey(array|string $key) @@ -468,7 +468,7 @@ public function getForeignKey(): array|string|false /** * Sets the name of the field representing the foreign key to the target table. * - * @param list|string $key the key or keys to be used to link both tables together + * @param array|string $key the key or keys to be used to link both tables together * @return $this */ public function setForeignKey(array|string $key) diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 01555394..a71c3497 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -62,7 +62,7 @@ public function getForeignKey(): array|string|false /** * Sets the name of the field representing the foreign key to the target table. * - * @param list|string|false $key the key or keys to be used to link both tables together, if set to `false` + * @param array|string|false $key the key or keys to be used to link both tables together, if set to `false` * no join conditions will be generated automatically. * @return $this */ diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 7b816db1..a3c7253b 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -166,7 +166,7 @@ class BelongsToMany extends Association /** * Sets the name of the field representing the foreign key to the target table. * - * @param list|string $key the key to be used to link both tables together + * @param array|string $key the key to be used to link both tables together * @return $this */ public function setTargetForeignKey(array|string $key) diff --git a/Association/HasOne.php b/Association/HasOne.php index 9da1390e..9b455b27 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -60,7 +60,7 @@ public function getForeignKey(): array|string|false /** * Sets the name of the field representing the foreign key to the target table. * - * @param list|string|false $key the key or keys to be used to link both tables together, if set to `false` + * @param array|string|false $key the key or keys to be used to link both tables together, if set to `false` * no join conditions will be generated automatically. * @return $this */ diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 358f69df..c0410bf2 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -243,7 +243,7 @@ protected function _extractFinder(array|string $finderData): array * If the required fields are missing, throws an exception. * * @param \Cake\ORM\Query\SelectQuery $fetchQuery The association fetching query - * @param list $key The foreign key fields to check + * @param array $key The foreign key fields to check * @return void * @throws \InvalidArgumentException */ @@ -290,7 +290,7 @@ protected function _assertFieldsPresent(SelectQuery $fetchQuery, array $key): vo * filtering needs to be done using a subquery. * * @param \Cake\ORM\Query\SelectQuery $query Target table's query - * @param list|string $key the fields that should be used for filtering + * @param array|string $key the fields that should be used for filtering * @param \Cake\ORM\Query\SelectQuery $subquery The Subquery to use for filtering * @return \Cake\ORM\Query\SelectQuery */ @@ -326,7 +326,7 @@ protected function _addFilteringJoin(SelectQuery $query, array|string $key, Sele * target table query given a filter key and some filtering values. * * @param \Cake\ORM\Query\SelectQuery $query Target table's query - * @param list|string $key The fields that should be used for filtering + * @param array|string $key The fields that should be used for filtering * @param mixed $filter The value that should be used to match for $key * @return \Cake\ORM\Query\SelectQuery */ @@ -346,7 +346,7 @@ protected function _addFilteringCondition(SelectQuery $query, array|string $key, * from $keys with the tuple values in $filter using the provided operator. * * @param \Cake\ORM\Query\SelectQuery $query Target table's query - * @param list $keys the fields that should be used for filtering + * @param array $keys the fields that should be used for filtering * @param mixed $filter the value that should be used to match for $key * @param string $operator The operator for comparing the tuples * @return \Cake\Database\Expression\TupleComparison @@ -553,7 +553,7 @@ protected function _resultInjector(SelectQuery $fetchQuery, array $resultMap, ar * be done with multiple foreign keys * * @param array $resultMap A keyed arrays containing the target table - * @param list $sourceKeys An array with aliased keys to match + * @param array $sourceKeys An array with aliased keys to match * @param string $nestKey The key under which results should be nested * @return \Closure */ diff --git a/AssociationCollection.php b/AssociationCollection.php index 080ddbcf..80d5bb4b 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -162,7 +162,7 @@ public function keys(): array /** * Get an array of associations matching a specific type. * - * @param list|string $class The type of associations you want. + * @param array|string $class The type of associations you want. * For example 'BelongsTo' or array like ['BelongsTo', 'HasOne'] * @return array<\Cake\ORM\Association> An array of Association objects. * @since 3.5.3 diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index f87d9f22..433857ef 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -342,7 +342,7 @@ public function translationField(string $field): string * for each record. * * @param \Cake\ORM\Query\SelectQuery $query The original query to modify - * @param list $locales A list of locales or options with the `locales` key defined + * @param array $locales A list of locales or options with the `locales` key defined * @return \Cake\ORM\Query\SelectQuery */ public function findTranslations(SelectQuery $query, array $locales = []): SelectQuery diff --git a/Exception/PersistenceFailedException.php b/Exception/PersistenceFailedException.php index 534f88b9..68881429 100644 --- a/Exception/PersistenceFailedException.php +++ b/Exception/PersistenceFailedException.php @@ -40,7 +40,7 @@ class PersistenceFailedException extends CakeException * Constructor. * * @param \Cake\Datasource\EntityInterface $entity The entity on which the persistence operation failed - * @param list|string $message Either the string of the error message, or an array of attributes + * @param array|string $message Either the string of the error message, or an array of attributes * that are made available in the view, and sprintf()'d into Exception::$_messageTemplate * @param int|null $code The code of the error, is also the HTTP status code for the error. * @param \Throwable|null $previous the previous exception. diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 3dbb3201..84e6456d 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -111,7 +111,7 @@ protected function _getQuery(array $entities, array $contain, Table $source): Se * in the top level entities. * * @param \Cake\ORM\Table $source The table having the top level associations - * @param list $associations The name of the top level associations + * @param array $associations The name of the top level associations * @return array */ protected function _getPropertyMap(Table $source, array $associations): array @@ -133,7 +133,7 @@ protected function _getPropertyMap(Table $source, array $associations): array * * @param array<\Cake\Datasource\EntityInterface> $entities The original list of entities * @param \Cake\ORM\Query\SelectQuery $query The query to load results - * @param list $associations The top level associations that were loaded + * @param array $associations The top level associations that were loaded * @param \Cake\ORM\Table $source The table where the entities came from * @return array<\Cake\Datasource\EntityInterface> */ diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 30efe1c9..743e30a3 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -83,7 +83,7 @@ class TableLocator extends AbstractLocator implements LocatorInterface /** * Constructor. * - * @param list|null $locations Locations where tables should be looked for. + * @param array|null $locations Locations where tables should be looked for. * If none provided, the default `Model\Table` under your app's namespace is used. */ public function __construct(?array $locations = null, ?QueryFactory $queryFactory = null) diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index afc3e418..9577002e 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -845,7 +845,7 @@ public function selectAlso( * pass overwrite boolean true which will reset the select clause removing all previous additions. * * @param \Cake\ORM\Table|\Cake\ORM\Association $table The table to use to get an array of columns - * @param list $excludedFields The un-aliased column names you do not want selected from $table + * @param array $excludedFields The un-aliased column names you do not want selected from $table * @param bool $overwrite Whether to reset/remove previous selected fields * @return $this */ diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index d4d99316..132cd44c 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -54,7 +54,7 @@ class ExistsIn * Available option for $options is 'allowNullableNulls' flag. * Set to true to accept composite foreign keys where one or more nullable columns are null. * - * @param list|string $fields The field or fields to check existence as primary key. + * @param array|string $fields The field or fields to check existence as primary key. * @param \Cake\ORM\Table|\Cake\ORM\Association|string $repository The repository where the * field will be looked for, or the association name for the repository. * @param array $options The options that modify the rule's behavior. diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index e2f2665e..ffacbea2 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -47,7 +47,7 @@ class IsUnique * * - `allowMultipleNulls` Allows any field to have multiple null values. Defaults to true. * - * @param list $fields The list of fields to check uniqueness for + * @param array $fields The list of fields to check uniqueness for * @param array $options The options for unique checks. */ public function __construct(array $fields, array $options = []) diff --git a/Rule/LinkConstraint.php b/Rule/LinkConstraint.php index 98f62adc..9a5cf7a4 100644 --- a/Rule/LinkConstraint.php +++ b/Rule/LinkConstraint.php @@ -118,7 +118,7 @@ public function __invoke(EntityInterface $entity, array $options): bool /** * Alias fields. * - * @param list $fields The fields that should be aliased. + * @param array $fields The fields that should be aliased. * @param \Cake\ORM\Table $source The object to use for aliasing. * @return list The aliased fields */ @@ -134,7 +134,7 @@ protected function _aliasFields(array $fields, Table $source): array /** * Build conditions. * - * @param list $fields The condition fields. + * @param array $fields The condition fields. * @param array $values The condition values. * @return array A conditions array combined from the passed fields and values. */ diff --git a/RulesChecker.php b/RulesChecker.php index a7c19a41..3dcaf15f 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -48,7 +48,7 @@ class RulesChecker extends BaseRulesChecker * * - `allowMultipleNulls` Allows any field to have multiple null values. Defaults to false. * - * @param list $fields The list of fields to check for uniqueness. + * @param array $fields The list of fields to check for uniqueness. * @param array|string|null $message The error message to show in case the rule does not pass. Can * also be an array of options. When an array, the 'message' key can be used to provide a message. * @return \Cake\Datasource\RuleInvoker @@ -90,7 +90,7 @@ public function isUnique(array $fields, array|string|null $message = null): Rule * 'message' sets a custom error message. * Set 'allowNullableNulls' to true to accept composite foreign keys where one or more nullable columns are null. * - * @param list|string $field The field or list of fields to check for existence by + * @param array|string $field The field or list of fields to check for existence by * primary key lookup in the other table. * @param \Cake\ORM\Table|\Cake\ORM\Association|string $table The table name where the fields existence will be checked. * @param array|string|null $message The error message to show in case the rule does not pass. Can diff --git a/Table.php b/Table.php index cc768a64..e09fdc3e 100644 --- a/Table.php +++ b/Table.php @@ -639,7 +639,7 @@ public function hasField(string $field): bool /** * Sets the primary key field name. * - * @param list|string $key Sets a new name to be used as primary key + * @param array|string $key Sets a new name to be used as primary key * @return $this */ public function setPrimaryKey(array|string $key) @@ -670,7 +670,7 @@ public function getPrimaryKey(): array|string /** * Sets the display field. * - * @param list|string $field Name to be used as display field. + * @param array|string $field Name to be used as display field. * @return $this */ public function setDisplayField(array|string $field) @@ -1464,7 +1464,7 @@ public function findThreaded( * composite keys when comparing values. * * @param array $options the original options passed to a finder - * @param list $keys the keys to check in $options to build matchers from + * @param array $keys the keys to check in $options to build matchers from * the associated value * @return array */ @@ -2214,7 +2214,7 @@ protected function _insert(EntityInterface $entity, array $data): EntityInterfac * Note: The ORM will not generate primary key values for composite primary keys. * You can overwrite _newId() in your table class. * - * @param list $primary The primary key columns to get a new ID for. + * @param array $primary The primary key columns to get a new ID for. * @return string|null Either null or the primary key value or a list of primary key values. */ protected function _newId(array $primary): ?string From 5bec49453fd64dd60100bb6dde89d1e8a34593b3 Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Sun, 17 Nov 2024 18:37:29 +0100 Subject: [PATCH 1994/2059] phpstan 2.0 (#18014) * get ready for phpstan 2.0 * undo array_values fix * add review changes * fix stan * phpstan 2.0 * fix phpstan for split packages * fix phpstan for tests * re-add method_exists * Apply suggestions from code review Co-authored-by: ADmad --------- Co-authored-by: ADmad --- Behavior/Translate/ShadowTableStrategy.php | 9 +++------ Locator/TableLocator.php | 2 +- Marshaller.php | 12 +++++------- phpstan.neon.dist | 7 ++++++- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 28e2736b..b6e7f24d 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -486,7 +486,6 @@ protected function rowMapper(CollectionInterface $results, string $locale): Coll $allowEmpty = $this->_config['allowEmptyTranslations']; return $results->map(function ($row) use ($allowEmpty, $locale) { - /** @var \Cake\Datasource\EntityInterface|array|null $row */ if ($row === null) { return $row; } @@ -505,14 +504,14 @@ protected function rowMapper(CollectionInterface $results, string $locale): Coll return $row; } - /** @var \Cake\Datasource\EntityInterface|array $translation */ $translation = $row['translation']; + assert($translation instanceof EntityInterface || is_array($translation)); if ($hydrated) { /** @var \Cake\Datasource\EntityInterface $translation */ $keys = $translation->getVisible(); } else { - /** @var array $translation */ + /** @var non-empty-array $translation */ $keys = array_keys($translation); } @@ -569,9 +568,7 @@ public function groupTranslations(CollectionInterface $results): CollectionInter $row['_translations'] = $result; unset($row['_i18n']); - if ($row instanceof EntityInterface) { - $row->setDirty('_translations', false); - } + $row->setDirty('_translations', false); return $row; }); diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 30efe1c9..c4fa53b6 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -254,7 +254,7 @@ protected function createInstance(string $alias, array $options): Table if (!empty($options['connectionName'])) { $connectionName = $options['connectionName']; } else { - /** @var \Cake\ORM\Table $className */ + /** @var class-string<\Cake\ORM\Table> $className */ $className = $options['className']; $connectionName = $className::defaultConnectionName(); } diff --git a/Marshaller.php b/Marshaller.php index 4b331720..f917e94c 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -746,7 +746,7 @@ public function mergeMany(iterable $entities, array $data, array $options = []): /** * Creates a new sub-marshaller and merges the associated data. * - * @param \Cake\Datasource\EntityInterface|array<\Cake\Datasource\EntityInterface>|null $original The original entity + * @param \Cake\Datasource\EntityInterface|non-empty-array<\Cake\Datasource\EntityInterface>|null $original The original entity * @param \Cake\ORM\Association $assoc The association to merge * @param mixed $value The array of data to hydrate. If not an array, this method will return null. * @param array $options List of options. @@ -773,11 +773,9 @@ protected function _mergeAssociation( /** @var \Cake\Datasource\EntityInterface $original */ return $marshaller->merge($original, $value, $options); } - if ($type === Association::MANY_TO_MANY) { - /** - * @var array<\Cake\Datasource\EntityInterface> $original - * @var \Cake\ORM\Association\BelongsToMany $assoc - */ + if ($type === Association::MANY_TO_MANY && is_array($original)) { + assert($assoc instanceof BelongsToMany); + return $marshaller->_mergeBelongsToMany($original, $assoc, $value, $options); } @@ -793,7 +791,7 @@ protected function _mergeAssociation( } /** - * @var array<\Cake\Datasource\EntityInterface> $original + * @var non-empty-array<\Cake\Datasource\EntityInterface> $original */ return $marshaller->mergeMany($original, $value, $options); } diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 7e4dc886..3dec5c81 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -8,11 +8,16 @@ parameters: excludePaths: - vendor/ ignoreErrors: + - + identifier: trait.unused - identifier: missingType.iterableValue - identifier: missingType.generics - '#Unsafe usage of new static\(\).#' - - "#^Method Cake\\\\ORM\\\\Behavior\\\\TreeBehavior\\:\\:_scope\\(\\) should return T of Cake\\\\ORM\\\\Query\\\\DeleteQuery\\|Cake\\\\ORM\\\\Query\\\\SelectQuery\\|Cake\\\\ORM\\\\Query\\\\UpdateQuery but returns Cake\\\\ORM\\\\Query\\\\DeleteQuery\\|Cake\\\\ORM\\\\Query\\\\SelectQuery\\|Cake\\\\ORM\\\\Query\\\\UpdateQuery\\.$#" - "#^PHPDoc tag @return with type Cake\\\\ORM\\\\Query\\\\SelectQuery\\ is not subtype of native type static\\(Cake\\\\ORM\\\\Query\\\\SelectQuery\\\\)\\.$#" - "#^Method Cake\\\\ORM\\\\Query\\\\SelectQuery\\:\\:find\\(\\) should return static\\(Cake\\\\ORM\\\\Query\\\\SelectQuery\\\\) but returns Cake\\\\ORM\\\\Query\\\\SelectQuery\\\\.$#" + - '#^PHPDoc tag @var with type callable\(\): mixed is not subtype of native type Closure\(string\): string\.$#' + - '#^Parameter \#1 \$fields of method Cake\\Datasource\\EntityInterface::extract\(\) expects list, array, string> given\.$#' + - '#^Method Cake\\ORM\\Rule\\LinkConstraint::_aliasFields\(\) should return list but returns array, string>\.$#' + - '#^Parameter \#1 \$fields of method Cake\\ORM\\Entity::extract\(\) expects list, non-empty-array given\.$#' From c69cc2ffd1e9e7f9fc60fbe781a7e4aa0dc1bacc Mon Sep 17 00:00:00 2001 From: mscherer Date: Wed, 20 Nov 2024 00:13:19 +0100 Subject: [PATCH 1995/2059] Merge 5.x into 5.next --- Association.php | 4 ++-- Association/BelongsTo.php | 2 +- Association/BelongsToMany.php | 2 +- Association/HasOne.php | 2 +- Association/Loader/SelectLoader.php | 10 +++++----- AssociationCollection.php | 2 +- Behavior/TranslateBehavior.php | 2 +- Exception/PersistenceFailedException.php | 2 +- LazyEagerLoader.php | 4 ++-- Locator/TableLocator.php | 2 +- Query/SelectQuery.php | 2 +- Rule/ExistsIn.php | 2 +- Rule/IsUnique.php | 2 +- Rule/LinkConstraint.php | 4 ++-- RulesChecker.php | 4 ++-- Table.php | 8 ++++---- 16 files changed, 27 insertions(+), 27 deletions(-) diff --git a/Association.php b/Association.php index b0bb4ac5..13a76cbb 100644 --- a/Association.php +++ b/Association.php @@ -428,7 +428,7 @@ public function getConditions(): Closure|array * Sets the name of the field representing the binding field with the target table. * When not manually specified the primary key of the owning side table is used. * - * @param list|string $key the table field or fields to be used to link both tables together + * @param array|string $key the table field or fields to be used to link both tables together * @return $this */ public function setBindingKey(array|string $key) @@ -468,7 +468,7 @@ public function getForeignKey(): array|string|false /** * Sets the name of the field representing the foreign key to the target table. * - * @param list|string $key the key or keys to be used to link both tables together + * @param array|string $key the key or keys to be used to link both tables together * @return $this */ public function setForeignKey(array|string $key) diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 01555394..a71c3497 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -62,7 +62,7 @@ public function getForeignKey(): array|string|false /** * Sets the name of the field representing the foreign key to the target table. * - * @param list|string|false $key the key or keys to be used to link both tables together, if set to `false` + * @param array|string|false $key the key or keys to be used to link both tables together, if set to `false` * no join conditions will be generated automatically. * @return $this */ diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 7b816db1..a3c7253b 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -166,7 +166,7 @@ class BelongsToMany extends Association /** * Sets the name of the field representing the foreign key to the target table. * - * @param list|string $key the key to be used to link both tables together + * @param array|string $key the key to be used to link both tables together * @return $this */ public function setTargetForeignKey(array|string $key) diff --git a/Association/HasOne.php b/Association/HasOne.php index 9da1390e..9b455b27 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -60,7 +60,7 @@ public function getForeignKey(): array|string|false /** * Sets the name of the field representing the foreign key to the target table. * - * @param list|string|false $key the key or keys to be used to link both tables together, if set to `false` + * @param array|string|false $key the key or keys to be used to link both tables together, if set to `false` * no join conditions will be generated automatically. * @return $this */ diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 358f69df..c0410bf2 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -243,7 +243,7 @@ protected function _extractFinder(array|string $finderData): array * If the required fields are missing, throws an exception. * * @param \Cake\ORM\Query\SelectQuery $fetchQuery The association fetching query - * @param list $key The foreign key fields to check + * @param array $key The foreign key fields to check * @return void * @throws \InvalidArgumentException */ @@ -290,7 +290,7 @@ protected function _assertFieldsPresent(SelectQuery $fetchQuery, array $key): vo * filtering needs to be done using a subquery. * * @param \Cake\ORM\Query\SelectQuery $query Target table's query - * @param list|string $key the fields that should be used for filtering + * @param array|string $key the fields that should be used for filtering * @param \Cake\ORM\Query\SelectQuery $subquery The Subquery to use for filtering * @return \Cake\ORM\Query\SelectQuery */ @@ -326,7 +326,7 @@ protected function _addFilteringJoin(SelectQuery $query, array|string $key, Sele * target table query given a filter key and some filtering values. * * @param \Cake\ORM\Query\SelectQuery $query Target table's query - * @param list|string $key The fields that should be used for filtering + * @param array|string $key The fields that should be used for filtering * @param mixed $filter The value that should be used to match for $key * @return \Cake\ORM\Query\SelectQuery */ @@ -346,7 +346,7 @@ protected function _addFilteringCondition(SelectQuery $query, array|string $key, * from $keys with the tuple values in $filter using the provided operator. * * @param \Cake\ORM\Query\SelectQuery $query Target table's query - * @param list $keys the fields that should be used for filtering + * @param array $keys the fields that should be used for filtering * @param mixed $filter the value that should be used to match for $key * @param string $operator The operator for comparing the tuples * @return \Cake\Database\Expression\TupleComparison @@ -553,7 +553,7 @@ protected function _resultInjector(SelectQuery $fetchQuery, array $resultMap, ar * be done with multiple foreign keys * * @param array $resultMap A keyed arrays containing the target table - * @param list $sourceKeys An array with aliased keys to match + * @param array $sourceKeys An array with aliased keys to match * @param string $nestKey The key under which results should be nested * @return \Closure */ diff --git a/AssociationCollection.php b/AssociationCollection.php index 080ddbcf..80d5bb4b 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -162,7 +162,7 @@ public function keys(): array /** * Get an array of associations matching a specific type. * - * @param list|string $class The type of associations you want. + * @param array|string $class The type of associations you want. * For example 'BelongsTo' or array like ['BelongsTo', 'HasOne'] * @return array<\Cake\ORM\Association> An array of Association objects. * @since 3.5.3 diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index f87d9f22..433857ef 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -342,7 +342,7 @@ public function translationField(string $field): string * for each record. * * @param \Cake\ORM\Query\SelectQuery $query The original query to modify - * @param list $locales A list of locales or options with the `locales` key defined + * @param array $locales A list of locales or options with the `locales` key defined * @return \Cake\ORM\Query\SelectQuery */ public function findTranslations(SelectQuery $query, array $locales = []): SelectQuery diff --git a/Exception/PersistenceFailedException.php b/Exception/PersistenceFailedException.php index 534f88b9..68881429 100644 --- a/Exception/PersistenceFailedException.php +++ b/Exception/PersistenceFailedException.php @@ -40,7 +40,7 @@ class PersistenceFailedException extends CakeException * Constructor. * * @param \Cake\Datasource\EntityInterface $entity The entity on which the persistence operation failed - * @param list|string $message Either the string of the error message, or an array of attributes + * @param array|string $message Either the string of the error message, or an array of attributes * that are made available in the view, and sprintf()'d into Exception::$_messageTemplate * @param int|null $code The code of the error, is also the HTTP status code for the error. * @param \Throwable|null $previous the previous exception. diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 3dbb3201..84e6456d 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -111,7 +111,7 @@ protected function _getQuery(array $entities, array $contain, Table $source): Se * in the top level entities. * * @param \Cake\ORM\Table $source The table having the top level associations - * @param list $associations The name of the top level associations + * @param array $associations The name of the top level associations * @return array */ protected function _getPropertyMap(Table $source, array $associations): array @@ -133,7 +133,7 @@ protected function _getPropertyMap(Table $source, array $associations): array * * @param array<\Cake\Datasource\EntityInterface> $entities The original list of entities * @param \Cake\ORM\Query\SelectQuery $query The query to load results - * @param list $associations The top level associations that were loaded + * @param array $associations The top level associations that were loaded * @param \Cake\ORM\Table $source The table where the entities came from * @return array<\Cake\Datasource\EntityInterface> */ diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index c4fa53b6..1495698c 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -83,7 +83,7 @@ class TableLocator extends AbstractLocator implements LocatorInterface /** * Constructor. * - * @param list|null $locations Locations where tables should be looked for. + * @param array|null $locations Locations where tables should be looked for. * If none provided, the default `Model\Table` under your app's namespace is used. */ public function __construct(?array $locations = null, ?QueryFactory $queryFactory = null) diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index afc3e418..9577002e 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -845,7 +845,7 @@ public function selectAlso( * pass overwrite boolean true which will reset the select clause removing all previous additions. * * @param \Cake\ORM\Table|\Cake\ORM\Association $table The table to use to get an array of columns - * @param list $excludedFields The un-aliased column names you do not want selected from $table + * @param array $excludedFields The un-aliased column names you do not want selected from $table * @param bool $overwrite Whether to reset/remove previous selected fields * @return $this */ diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index d4d99316..132cd44c 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -54,7 +54,7 @@ class ExistsIn * Available option for $options is 'allowNullableNulls' flag. * Set to true to accept composite foreign keys where one or more nullable columns are null. * - * @param list|string $fields The field or fields to check existence as primary key. + * @param array|string $fields The field or fields to check existence as primary key. * @param \Cake\ORM\Table|\Cake\ORM\Association|string $repository The repository where the * field will be looked for, or the association name for the repository. * @param array $options The options that modify the rule's behavior. diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index e2f2665e..ffacbea2 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -47,7 +47,7 @@ class IsUnique * * - `allowMultipleNulls` Allows any field to have multiple null values. Defaults to true. * - * @param list $fields The list of fields to check uniqueness for + * @param array $fields The list of fields to check uniqueness for * @param array $options The options for unique checks. */ public function __construct(array $fields, array $options = []) diff --git a/Rule/LinkConstraint.php b/Rule/LinkConstraint.php index 98f62adc..9a5cf7a4 100644 --- a/Rule/LinkConstraint.php +++ b/Rule/LinkConstraint.php @@ -118,7 +118,7 @@ public function __invoke(EntityInterface $entity, array $options): bool /** * Alias fields. * - * @param list $fields The fields that should be aliased. + * @param array $fields The fields that should be aliased. * @param \Cake\ORM\Table $source The object to use for aliasing. * @return list The aliased fields */ @@ -134,7 +134,7 @@ protected function _aliasFields(array $fields, Table $source): array /** * Build conditions. * - * @param list $fields The condition fields. + * @param array $fields The condition fields. * @param array $values The condition values. * @return array A conditions array combined from the passed fields and values. */ diff --git a/RulesChecker.php b/RulesChecker.php index a7c19a41..3dcaf15f 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -48,7 +48,7 @@ class RulesChecker extends BaseRulesChecker * * - `allowMultipleNulls` Allows any field to have multiple null values. Defaults to false. * - * @param list $fields The list of fields to check for uniqueness. + * @param array $fields The list of fields to check for uniqueness. * @param array|string|null $message The error message to show in case the rule does not pass. Can * also be an array of options. When an array, the 'message' key can be used to provide a message. * @return \Cake\Datasource\RuleInvoker @@ -90,7 +90,7 @@ public function isUnique(array $fields, array|string|null $message = null): Rule * 'message' sets a custom error message. * Set 'allowNullableNulls' to true to accept composite foreign keys where one or more nullable columns are null. * - * @param list|string $field The field or list of fields to check for existence by + * @param array|string $field The field or list of fields to check for existence by * primary key lookup in the other table. * @param \Cake\ORM\Table|\Cake\ORM\Association|string $table The table name where the fields existence will be checked. * @param array|string|null $message The error message to show in case the rule does not pass. Can diff --git a/Table.php b/Table.php index db7ce74b..0f8ba1b6 100644 --- a/Table.php +++ b/Table.php @@ -639,7 +639,7 @@ public function hasField(string $field): bool /** * Sets the primary key field name. * - * @param list|string $key Sets a new name to be used as primary key + * @param array|string $key Sets a new name to be used as primary key * @return $this */ public function setPrimaryKey(array|string $key) @@ -670,7 +670,7 @@ public function getPrimaryKey(): array|string /** * Sets the display field. * - * @param list|string $field Name to be used as display field. + * @param array|string $field Name to be used as display field. * @return $this */ public function setDisplayField(array|string $field) @@ -1464,7 +1464,7 @@ public function findThreaded( * composite keys when comparing values. * * @param array $options the original options passed to a finder - * @param list $keys the keys to check in $options to build matchers from + * @param array $keys the keys to check in $options to build matchers from * the associated value * @return array */ @@ -2214,7 +2214,7 @@ protected function _insert(EntityInterface $entity, array $data): EntityInterfac * Note: The ORM will not generate primary key values for composite primary keys. * You can overwrite _newId() in your table class. * - * @param list $primary The primary key columns to get a new ID for. + * @param array $primary The primary key columns to get a new ID for. * @return string|null Either null or the primary key value or a list of primary key values. */ protected function _newId(array $primary): ?string From 392f92e9c7848177110a5b12ba3976545de022b4 Mon Sep 17 00:00:00 2001 From: mscherer Date: Wed, 20 Nov 2024 00:54:30 +0100 Subject: [PATCH 1996/2059] Fix up list array. --- Association.php | 10 +++++----- Association/BelongsTo.php | 4 ++-- Association/BelongsToMany.php | 20 ++++++++++---------- Association/HasMany.php | 6 +++--- Association/HasOne.php | 4 ++-- Association/Loader/SelectLoader.php | 2 +- Association/Loader/SelectWithPivotLoader.php | 2 +- AssociationCollection.php | 2 +- Behavior/CounterCacheBehavior.php | 4 ++-- Behavior/Translate/ShadowTableStrategy.php | 6 +++--- Locator/TableLocator.php | 2 +- Rule/ExistsIn.php | 2 +- Rule/IsUnique.php | 2 +- Rule/LinkConstraint.php | 4 ++-- Table.php | 8 ++++---- phpstan.neon.dist | 7 +++++-- 16 files changed, 44 insertions(+), 41 deletions(-) diff --git a/Association.php b/Association.php index 13a76cbb..04390b95 100644 --- a/Association.php +++ b/Association.php @@ -110,14 +110,14 @@ abstract class Association /** * The field name in the owning side table that is used to match with the foreignKey * - * @var list|string + * @var array|string */ protected array|string $_bindingKey; /** * The name of the field representing the foreign key to the table to load * - * @var list|string|false + * @var array|string|false */ protected array|string|false $_foreignKey; @@ -193,7 +193,7 @@ abstract class Association /** * Valid strategies for this association. Subclasses can narrow this down. * - * @var list + * @var array */ protected array $_validStrategies = [ self::STRATEGY_JOIN, @@ -442,7 +442,7 @@ public function setBindingKey(array|string $key) * Gets the name of the field representing the binding field with the target table. * When not manually specified the primary key of the owning side table is used. * - * @return list|string + * @return array|string */ public function getBindingKey(): array|string { @@ -458,7 +458,7 @@ public function getBindingKey(): array|string /** * Gets the name of the field representing the foreign key to the target table. * - * @return list|string|false + * @return array|string|false */ public function getForeignKey(): array|string|false { diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index a71c3497..0a584716 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -40,7 +40,7 @@ class BelongsTo extends Association /** * Valid strategies for this type of association * - * @var list + * @var array */ protected array $_validStrategies = [ self::STRATEGY_JOIN, @@ -147,7 +147,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []): En return false; } - /** @var list $foreignKeys */ + /** @var array $foreignKeys */ $foreignKeys = (array)$this->getForeignKey(); $properties = array_combine( $foreignKeys, diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index a3c7253b..a5eed00a 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -111,7 +111,7 @@ class BelongsToMany extends Association /** * The name of the field representing the foreign key to the target table * - * @var list|string|null + * @var array|string|null */ protected array|string|null $_targetForeignKey = null; @@ -125,7 +125,7 @@ class BelongsToMany extends Association /** * Valid strategies for this type of association * - * @var list + * @var array */ protected array $_validStrategies = [ self::STRATEGY_SELECT, @@ -179,7 +179,7 @@ public function setTargetForeignKey(array|string $key) /** * Gets the name of the field representing the foreign key to the target table. * - * @return list|string + * @return array|string */ public function getTargetForeignKey(): array|string { @@ -584,7 +584,7 @@ public function cascadeDelete(EntityInterface $entity, array $options = []): boo return true; } - /** @var list $foreignKeys */ + /** @var array $foreignKeys */ $foreignKeys = (array)$this->getForeignKey(); $bindingKeys = (array)$this->getBindingKey(); $conditions = []; @@ -794,9 +794,9 @@ protected function _saveLinks(EntityInterface $sourceEntity, array $targetEntiti $junction = $this->junction(); $entityClass = $junction->getEntityClass(); $belongsTo = $junction->getAssociation($target->getAlias()); - /** @var list $foreignKey */ + /** @var array $foreignKey */ $foreignKey = (array)$this->getForeignKey(); - /** @var list $assocForeignKey */ + /** @var array $assocForeignKey */ $assocForeignKey = (array)$belongsTo->getForeignKey(); $targetBindingKey = (array)$belongsTo->getBindingKey(); $bindingKey = (array)$this->getBindingKey(); @@ -1195,7 +1195,7 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { $junction = $this->junction(); $target = $this->getTarget(); - /** @var list $foreignKey */ + /** @var array $foreignKey */ $foreignKey = (array)$this->getForeignKey(); $assocForeignKey = (array)$junction->getAssociation($target->getAlias())->getForeignKey(); $prefixedForeignKey = array_map($junction->aliasField(...), $foreignKey); @@ -1277,9 +1277,9 @@ protected function _diffLinks( $junction = $this->junction(); $target = $this->getTarget(); $belongsTo = $junction->getAssociation($target->getAlias()); - /** @var list $foreignKey */ + /** @var array $foreignKey */ $foreignKey = (array)$this->getForeignKey(); - /** @var list $assocForeignKey */ + /** @var array $assocForeignKey */ $assocForeignKey = (array)$belongsTo->getForeignKey(); $keys = array_merge($foreignKey, $assocForeignKey); @@ -1419,7 +1419,7 @@ protected function _collectJointEntities(EntityInterface $sourceEntity, array $t $belongsTo = $junction->getAssociation($target->getAlias()); $hasMany = $source->getAssociation($junction->getAlias()); - /** @var list $foreignKey */ + /** @var array $foreignKey */ $foreignKey = (array)$this->getForeignKey(); $foreignKey = array_map(function ($key) { return $key . ' IS'; diff --git a/Association/HasMany.php b/Association/HasMany.php index d95bed90..9c2c4f04 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -64,7 +64,7 @@ class HasMany extends Association /** * Valid strategies for this type of association * - * @var list + * @var array */ protected array $_validStrategies = [ self::STRATEGY_SELECT, @@ -169,7 +169,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []): En throw new InvalidArgumentException($message); } - /** @var list $foreignKeys */ + /** @var array $foreignKeys */ $foreignKeys = (array)$this->getForeignKey(); $foreignKeyReference = array_combine( $foreignKeys, @@ -368,7 +368,7 @@ public function unlink(EntityInterface $sourceEntity, array $targetEntities, arr 'OR' => (new Collection($targetEntities)) ->map(function (EntityInterface $entity) use ($targetPrimaryKey) { /** @psalm-suppress InvalidArgument,UnusedPsalmSuppress */ - /** @var list $targetPrimaryKey */ + /** @var array $targetPrimaryKey */ return $entity->extract($targetPrimaryKey); }) ->toList(), diff --git a/Association/HasOne.php b/Association/HasOne.php index 9b455b27..fff3b284 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -38,7 +38,7 @@ class HasOne extends Association /** * Valid strategies for this type of association * - * @var list + * @var array */ protected array $_validStrategies = [ self::STRATEGY_JOIN, @@ -125,7 +125,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []): En return $entity; } - /** @var list $foreignKeys */ + /** @var array $foreignKeys */ $foreignKeys = (array)$this->getForeignKey(); $properties = array_combine( $foreignKeys, diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index c0410bf2..b0de00f7 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -373,7 +373,7 @@ protected function _createTupleCondition( * which the filter should be applied * * @param array $options The options for getting the link field. - * @return list|string + * @return array|string * @throws \Cake\Database\Exception\DatabaseException */ protected function _linkField(array $options): array|string diff --git a/Association/Loader/SelectWithPivotLoader.php b/Association/Loader/SelectWithPivotLoader.php index 4c6399f4..75918c82 100644 --- a/Association/Loader/SelectWithPivotLoader.php +++ b/Association/Loader/SelectWithPivotLoader.php @@ -147,7 +147,7 @@ protected function _assertFieldsPresent(SelectQuery $fetchQuery, array $key): vo * which the filter should be applied * * @param array $options the options to use for getting the link field. - * @return list|string + * @return array|string */ protected function _linkField(array $options): array|string { diff --git a/AssociationCollection.php b/AssociationCollection.php index 80d5bb4b..03c31cb8 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -152,7 +152,7 @@ public function has(string $alias): bool /** * Get the names of all the associations in the collection. * - * @return list + * @return array */ public function keys(): array { diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index b6c80661..fd55dc6c 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -250,7 +250,7 @@ protected function updateCountForAssociation( ?int $page = null ): void { $primaryKeys = (array)$assoc->getBindingKey(); - /** @var list $foreignKeys */ + /** @var array $foreignKeys */ $foreignKeys = (array)$assoc->getForeignKey(); $query = $assoc->getTarget()->find() @@ -319,7 +319,7 @@ protected function _processAssociation( Association $assoc, array $settings ): void { - /** @var list $foreignKeys */ + /** @var array $foreignKeys */ $foreignKeys = (array)$assoc->getForeignKey(); $countConditions = $entity->extract($foreignKeys); diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index b6e7f24d..01d276a1 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -610,11 +610,11 @@ protected function bundleTranslatedFields(EntityInterface $entity): void /** * Lazy define and return the main table fields. * - * @return list + * @return array */ protected function mainFields(): array { - /** @var list $fields */ + /** @var array $fields */ $fields = $this->getConfig('mainTableFields'); if ($fields) { @@ -631,7 +631,7 @@ protected function mainFields(): array /** * Lazy define and return the translation table fields. * - * @return list + * @return array */ protected function translatedFields(): array { diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 1495698c..baf1c181 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -36,7 +36,7 @@ class TableLocator extends AbstractLocator implements LocatorInterface /** * Contains a list of locations where table classes should be looked for. * - * @var list + * @var array */ protected array $locations = []; diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 132cd44c..15ac455d 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -30,7 +30,7 @@ class ExistsIn /** * The list of fields to check * - * @var list + * @var array */ protected array $_fields; diff --git a/Rule/IsUnique.php b/Rule/IsUnique.php index ffacbea2..5c3d4ad4 100644 --- a/Rule/IsUnique.php +++ b/Rule/IsUnique.php @@ -27,7 +27,7 @@ class IsUnique /** * The list of fields to check * - * @var list + * @var array */ protected array $_fields; diff --git a/Rule/LinkConstraint.php b/Rule/LinkConstraint.php index 9a5cf7a4..33c5e420 100644 --- a/Rule/LinkConstraint.php +++ b/Rule/LinkConstraint.php @@ -120,7 +120,7 @@ public function __invoke(EntityInterface $entity, array $options): bool * * @param array $fields The fields that should be aliased. * @param \Cake\ORM\Table $source The object to use for aliasing. - * @return list The aliased fields + * @return array The aliased fields */ protected function _aliasFields(array $fields, Table $source): array { @@ -162,7 +162,7 @@ protected function _countLinks(Association $association, EntityInterface $entity { $source = $association->getSource(); - /** @var list $primaryKey */ + /** @var array $primaryKey */ $primaryKey = (array)$source->getPrimaryKey(); if (!$entity->has($primaryKey)) { throw new DatabaseException(sprintf( diff --git a/Table.php b/Table.php index 0f8ba1b6..dabbd602 100644 --- a/Table.php +++ b/Table.php @@ -232,14 +232,14 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc /** * The name of the field that represents the primary key in the table * - * @var list|string|null + * @var array|string|null */ protected array|string|null $_primaryKey = null; /** * The name of the field that represents a human-readable representation of a row * - * @var list|string|null + * @var array|string|null */ protected array|string|null $_displayField = null; @@ -652,7 +652,7 @@ public function setPrimaryKey(array|string $key) /** * Returns the primary key field name. * - * @return list|string + * @return array|string */ public function getPrimaryKey(): array|string { @@ -683,7 +683,7 @@ public function setDisplayField(array|string $field) /** * Returns the display field. * - * @return list|string + * @return array|string */ public function getDisplayField(): array|string|null { diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 3dec5c81..d01208c4 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -18,6 +18,9 @@ parameters: - "#^PHPDoc tag @return with type Cake\\\\ORM\\\\Query\\\\SelectQuery\\ is not subtype of native type static\\(Cake\\\\ORM\\\\Query\\\\SelectQuery\\\\)\\.$#" - "#^Method Cake\\\\ORM\\\\Query\\\\SelectQuery\\:\\:find\\(\\) should return static\\(Cake\\\\ORM\\\\Query\\\\SelectQuery\\\\) but returns Cake\\\\ORM\\\\Query\\\\SelectQuery\\\\.$#" - '#^PHPDoc tag @var with type callable\(\): mixed is not subtype of native type Closure\(string\): string\.$#' - - '#^Parameter \#1 \$fields of method Cake\\Datasource\\EntityInterface::extract\(\) expects list, array, string> given\.$#' - - '#^Method Cake\\ORM\\Rule\\LinkConstraint::_aliasFields\(\) should return list but returns array, string>\.$#' - '#^Parameter \#1 \$fields of method Cake\\ORM\\Entity::extract\(\) expects list, non-empty-array given\.$#' + - '#^Parameter \#1 \$fields of method Cake\\ORM\\Entity::extract\(\) expects list, array given\.$#' + - '#^Parameter \#1 \$fields of method Cake\\Datasource\\EntityInterface::.*\(\) expects list, non-empty-array given\.$#' + - '#^Parameter \#1 \$fields of method Cake\\Datasource\\EntityInterface::.*\(\) expects list, array given\.$#' + - '#^Parameter \#1 \$field of method Cake\\Datasource\\EntityInterface::has\(\) expects list\|string, .+ given\.$#' + - '#^Parameter \#1 \$field of method Cake\\Datasource\\EntityInterface::unset\(\) expects list\|string, array\|string given\.$#' From 3088cf180fdd4fae80dd1d568cd4301073ffacea Mon Sep 17 00:00:00 2001 From: mscherer Date: Wed, 20 Nov 2024 02:25:27 +0100 Subject: [PATCH 1997/2059] Fix up array docblock. --- Query/SelectQuery.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index 9577002e..2040c939 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -337,9 +337,9 @@ public function aliasField(string $field, ?string $alias = null): array * Runs `aliasField()` for each field in the provided list and returns * the result under a single array. * - * @param array $fields The fields to alias + * @param array $fields The fields to alias * @param string|null $defaultAlias The default alias - * @return array + * @return array */ public function aliasFields(array $fields, ?string $defaultAlias = null): array { From 2dcd313cf6ac33767fcc3fcd100b14c291f8dbf8 Mon Sep 17 00:00:00 2001 From: mscherer Date: Wed, 20 Nov 2024 13:02:01 +0100 Subject: [PATCH 1998/2059] Fix up docblocks and code cleanup. --- Query/SelectQuery.php | 17 ++++++----------- Table.php | 3 +-- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index 9577002e..1c3556be 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -376,15 +376,10 @@ public function all(): ResultSetInterface return $this->_results; } - $results = null; - if ($this->_cache) { - $results = $this->_cache->fetch($this); - } + $results = $this->_cache?->fetch($this); if ($results === null) { $results = $this->_decorateResults($this->_execute()); - if ($this->_cache) { - $this->_cache->store($this, $results); - } + $this->_cache?->store($this, $results); } $this->_results = $results; @@ -771,7 +766,7 @@ protected function _decorateResults(iterable $result): ResultSetInterface * If a callback is passed, the returning array of the function will * be used as the list of fields. * - * By default this function will append any passed argument to the list of fields + * By default, this function will append any passed argument to the list of fields * to be selected, unless the second argument is set to true. * * ### Examples: @@ -786,7 +781,7 @@ protected function _decorateResults(iterable $result): ResultSetInterface * }) * ``` * - * By default no fields are selected, if you have an instance of `Cake\ORM\Query` and try to append + * By default, no fields are selected, if you have an instance of `Cake\ORM\Query` and try to append * fields you should also call `Cake\ORM\Query::enableAutoFields()` to select the default fields * from the table. * @@ -1748,7 +1743,7 @@ public function jsonSerialize(): ResultSetInterface /** * Sets whether the ORM should automatically append fields. * - * By default calling select() will disable auto-fields. You can re-enable + * By default, calling select() will disable auto-fields. You can re-enable * auto-fields with this method. * * @param bool $value Set true to enable, false to disable. @@ -1776,7 +1771,7 @@ public function disableAutoFields() /** * Gets whether the ORM should automatically append fields. * - * By default calling select() will disable auto-fields. You can re-enable + * By default, calling select() will disable auto-fields. You can re-enable * auto-fields with enableAutoFields(). * * @return bool|null The current value. Returns null if neither enabled or disabled yet. diff --git a/Table.php b/Table.php index e09fdc3e..b5613a85 100644 --- a/Table.php +++ b/Table.php @@ -683,7 +683,7 @@ public function setDisplayField(array|string $field) /** * Returns the display field. * - * @return list|string + * @return list|string|null */ public function getDisplayField(): array|string|null { @@ -3156,7 +3156,6 @@ public function validateUnique(mixed $value, array $options, ?array $context = n } } $class = static::IS_UNIQUE_CLASS; - /** @var \Cake\ORM\Rule\IsUnique $rule */ $rule = new $class($fields, $options); return $rule($entity, ['repository' => $this]); From be307fde6674fe501bc81064b61ce0c2eaad125c Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Fri, 22 Nov 2024 13:03:02 +0100 Subject: [PATCH 1999/2059] CS: Trailing comma on function calls (#18032) * CS: Trailing comma on function calls * Fix CS. * Fix CS. * Remove debug call --------- Co-authored-by: Mark Story --- Association.php | 18 +++--- Association/BelongsTo.php | 4 +- Association/BelongsToMany.php | 14 ++-- Association/HasMany.php | 18 +++--- Association/HasOne.php | 2 +- Association/Loader/SelectLoader.php | 6 +- Association/Loader/SelectWithPivotLoader.php | 2 +- AssociationCollection.php | 2 +- Behavior.php | 6 +- Behavior/TimestampBehavior.php | 4 +- Behavior/Translate/EavStrategy.php | 6 +- Behavior/Translate/ShadowTableStrategy.php | 12 ++-- Behavior/TranslateBehavior.php | 2 +- Behavior/TreeBehavior.php | 20 +++--- BehaviorRegistry.php | 8 +-- EagerLoader.php | 10 +-- Locator/LocatorAwareTrait.php | 4 +- Locator/TableLocator.php | 2 +- Marshaller.php | 2 +- Query/CommonQueryTrait.php | 2 +- Query/SelectQuery.php | 4 +- ResultSetFactory.php | 4 +- Rule/ExistsIn.php | 6 +- Rule/LinkConstraint.php | 10 +-- RulesChecker.php | 12 ++-- Table.php | 68 ++++++++++---------- 26 files changed, 124 insertions(+), 124 deletions(-) diff --git a/Association.php b/Association.php index 13a76cbb..3295da44 100644 --- a/Association.php +++ b/Association.php @@ -295,7 +295,7 @@ public function setClassName(string $className) throw new InvalidArgumentException(sprintf( "The class name `%s` doesn't match the target table class name of `%s`.", $className, - $this->_targetTable::class + $this->_targetTable::class, )); } @@ -388,7 +388,7 @@ public function getTarget(): Table $this->getName(), $this->type(), $this->_targetTable::class, - $className + $className, )); } } @@ -574,7 +574,7 @@ public function getProperty(): string ' You should explicitly specify the `propertyName` option.'; trigger_error( sprintf($msg, $this->_propertyName, $this->_sourceTable->getTable()), - E_USER_WARNING + E_USER_WARNING, ); } } @@ -609,7 +609,7 @@ public function setStrategy(string $name) throw new InvalidArgumentException(sprintf( 'Invalid strategy `%s` was provided. Valid options are `(%s)`.', $name, - implode(', ', $this->_validStrategies) + implode(', ', $this->_validStrategies), )); } $this->_strategy = $name; @@ -727,7 +727,7 @@ public function attachTo(SelectQuery $query, array $options = []): void if (!($dummy instanceof SelectQuery)) { throw new DatabaseException(sprintf( 'Query builder for association `%s` did not return a query.', - $this->getName() + $this->getName(), )); } } @@ -739,7 +739,7 @@ public function attachTo(SelectQuery $query, array $options = []): void ) { throw new DatabaseException(sprintf( '`%s` association cannot contain() associations when using JOIN strategy.', - $this->getName() + $this->getName(), )); } @@ -1014,7 +1014,7 @@ function (CollectionInterface $results, SelectQuery $query) use ($formatters, $p return $results; }, - SelectQuery::PREPEND + SelectQuery::PREPEND, ); } @@ -1055,7 +1055,7 @@ protected function _bindNewAssociations(SelectQuery $query, SelectQuery $surroga $eagerLoader->setMatching( $options['aliasPath'] . '.' . $alias, $value['queryBuilder'], - $value + $value, ); } } @@ -1092,7 +1092,7 @@ protected function _joinCondition(array $options): array $msg, $this->_name, implode(', ', $foreignKey), - implode(', ', $bindingKey) + implode(', ', $bindingKey), )); } diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index a71c3497..9c3ab889 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -151,7 +151,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []): En $foreignKeys = (array)$this->getForeignKey(); $properties = array_combine( $foreignKeys, - $targetEntity->extract((array)$this->getBindingKey()) + $targetEntity->extract((array)$this->getBindingKey()), ); $entity->set($properties, ['guard' => false]); @@ -186,7 +186,7 @@ protected function _joinCondition(array $options): array $msg, $this->_name, implode(', ', $foreignKey), - implode(', ', $bindingKey) + implode(', ', $bindingKey), )); } diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index a3c7253b..2d5c102e 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -289,7 +289,7 @@ public function junction(Table|string|null $table = null): Table throw new InvalidArgumentException(sprintf( 'The `%s` association on `%s` cannot target the same table.', $this->getName(), - $source->getAlias() + $source->getAlias(), )); } @@ -417,7 +417,7 @@ protected function _generateJunctionAssociations(Table $junction, Table $source, ) { throw new InvalidArgumentException( "The existing `{$tAlias}` association on `{$junction->getAlias()}` " . - "is incompatible with the `{$this->getName()}` association on `{$source->getAlias()}`" + "is incompatible with the `{$this->getName()}` association on `{$source->getAlias()}`", ); } } @@ -876,7 +876,7 @@ public function link(EntityInterface $sourceEntity, array $targetEntities, array return $this->junction()->getConnection()->transactional( function () use ($sourceEntity, $targetEntities, $options) { return $this->_saveLinks($sourceEntity, $targetEntities, $options); - } + }, ); } @@ -935,7 +935,7 @@ function () use ($sourceEntity, $targetEntities, $options): void { foreach ($links as $entity) { $this->_junctionTable->delete($entity, $options); } - } + }, ); /** @var array<\Cake\Datasource\EntityInterface> $existing */ @@ -1223,7 +1223,7 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { ->from([$junctionQueryAlias => $matches]) ->innerJoin( [$junction->getAlias() => $junction->getTable()], - $matchesConditions + $matchesConditions, ); $jointEntities = $this->_collectJointEntities($sourceEntity, $targetEntities); @@ -1242,7 +1242,7 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { /** @psalm-suppress RedundantConditionGivenDocblockType */ $inserted = array_combine( array_keys($inserts), - (array)$sourceEntity->get($property) + (array)$sourceEntity->get($property), ) ?: []; $targetEntities = $inserted + $targetEntities; } @@ -1252,7 +1252,7 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { $sourceEntity->setDirty($property, false); return true; - } + }, ); } diff --git a/Association/HasMany.php b/Association/HasMany.php index d95bed90..efbafc32 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -173,7 +173,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []): En $foreignKeys = (array)$this->getForeignKey(); $foreignKeyReference = array_combine( $foreignKeys, - $entity->extract((array)$this->getBindingKey()) + $entity->extract((array)$this->getBindingKey()), ); $options['_sourceTable'] = $this->getSource(); @@ -287,8 +287,8 @@ public function link(EntityInterface $sourceEntity, array $targetEntities, array $currentEntities = array_unique( array_merge( (array)$sourceEntity->get($property), - $targetEntities - ) + $targetEntities, + ), ); $sourceEntity->set($property, $currentEntities); @@ -384,9 +384,9 @@ public function unlink(EntityInterface $sourceEntity, array $targetEntities, arr ->reject( function ($assoc) use ($targetEntities) { return in_array($assoc, $targetEntities); - } + }, ) - ->toList() + ->toList(), ); } @@ -478,12 +478,12 @@ protected function _unlinkAssociated( $exclusions = $exclusions->map( function (EntityInterface $ent) use ($primaryKey) { return $ent->extract($primaryKey); - } + }, ) ->filter( function ($v) { return !in_array(null, $v, true); - } + }, ) ->toList(); @@ -563,8 +563,8 @@ protected function _foreignKeyAcceptsNull(Table $table, array $properties): bool function ($prop) use ($table) { return $table->getSchema()->isNullable($prop); }, - $properties - ) + $properties, + ), ); } diff --git a/Association/HasOne.php b/Association/HasOne.php index 9b455b27..cfe06778 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -129,7 +129,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []): En $foreignKeys = (array)$this->getForeignKey(); $properties = array_combine( $foreignKeys, - $entity->extract((array)$this->getBindingKey()) + $entity->extract((array)$this->getBindingKey()), ); $targetEntity->set($properties, ['guard' => false]); diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index c0410bf2..5ee6b8ed 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -278,8 +278,8 @@ protected function _assertFieldsPresent(SelectQuery $fetchQuery, array $key): vo throw new InvalidArgumentException( sprintf( 'You are required to select the "%s" field(s)', - implode(', ', $key) - ) + implode(', ', $key), + ), ); } } @@ -317,7 +317,7 @@ protected function _addFilteringJoin(SelectQuery $query, array|string $key, Sele return $query->innerJoin( [$aliasedTable => $subquery], - $conditions + $conditions, ); } diff --git a/Association/Loader/SelectWithPivotLoader.php b/Association/Loader/SelectWithPivotLoader.php index 4c6399f4..309ae7ea 100644 --- a/Association/Loader/SelectWithPivotLoader.php +++ b/Association/Loader/SelectWithPivotLoader.php @@ -184,7 +184,7 @@ protected function _buildResultMap(SelectQuery $fetchQuery, array $options): arr if (!isset($result[$this->junctionProperty])) { throw new DatabaseException(sprintf( '`%s` is missing from the belongsToMany results. Results cannot be created.', - $this->junctionProperty + $this->junctionProperty, )); } diff --git a/AssociationCollection.php b/AssociationCollection.php index 80d5bb4b..1044bb3f 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -281,7 +281,7 @@ protected function _saveAssociations( $msg = sprintf( 'Cannot save `%s`, it is not associated to `%s`.', $alias, - $table->getAlias() + $table->getAlias(), ); throw new InvalidArgumentException($msg); } diff --git a/Behavior.php b/Behavior.php index 58dbaa40..2e20ee22 100644 --- a/Behavior.php +++ b/Behavior.php @@ -155,12 +155,12 @@ public function __construct(Table $table, array $config = []) $config = $this->_resolveMethodAliases( 'implementedFinders', $this->_defaultConfig, - $config + $config, ); $config = $this->_resolveMethodAliases( 'implementedMethods', $this->_defaultConfig, - $config + $config, ); $this->_table = $table; $this->setConfig($config); @@ -244,7 +244,7 @@ public function verifyConfig(): void throw new CakeException(sprintf( 'The method `%s` is not callable on class `%s`.', $method, - static::class + static::class, )); } } diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 5c1521ca..6dfcc58d 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -104,7 +104,7 @@ public function handleEvent(EventInterface $event, EntityInterface $entity): voi if (!in_array($when, ['always', 'new', 'existing'], true)) { throw new UnexpectedValueException(sprintf( 'When should be one of "always", "new" or "existing". The passed value `%s` is invalid.', - $when + $when, )); } if ( @@ -217,7 +217,7 @@ protected function _updateField(EntityInterface $entity, string $field, bool $re $type = TypeFactory::build($columnType); assert( $type instanceof DateTimeType, - sprintf('TimestampBehavior only supports columns of type `%s`.', DateTimeType::class) + sprintf('TimestampBehavior only supports columns of type `%s`.', DateTimeType::class), ); $class = $type->getDateTimeClassName(); diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index 957b24a1..f93a6c71 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -82,7 +82,7 @@ public function __construct(Table $table, array $config = []) $this->table = $table; $this->translationTable = $this->getTableLocator()->get( $this->_config['translationTable'], - ['allowFallbackClass' => true] + ['allowFallbackClass' => true], ); $this->setupAssociations(); @@ -212,7 +212,7 @@ public function beforeFind(EventInterface $event, SelectQuery $query, ArrayObjec $field, $locale, $query, - $select + $select, ); if ($changeFilter) { @@ -226,7 +226,7 @@ public function beforeFind(EventInterface $event, SelectQuery $query, ArrayObjec $query->contain($contain); $query->formatResults( fn (CollectionInterface $results) => $this->rowMapper($results, $locale), - $query::PREPEND + $query::PREPEND, ); } diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 28e2736b..6ba555ee 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -86,7 +86,7 @@ public function __construct(Table $table, array $config = []) $this->table = $table; $this->translationTable = $this->getTableLocator()->get( $this->_config['translationTable'], - ['allowFallbackClass' => true] + ['allowFallbackClass' => true], ); $this->setupAssociations(); @@ -155,7 +155,7 @@ public function beforeFind(EventInterface $event, SelectQuery $query, ArrayObjec $query->formatResults( fn (CollectionInterface $results) => $this->rowMapper($results, $locale), - $query::PREPEND + $query::PREPEND, ); } @@ -179,7 +179,7 @@ protected function setupHasOneAssociation(string $locale, ArrayObject $options): [ 'className' => $config['translationTable'], 'allowFallbackClass' => true, - ] + ], ); } @@ -287,7 +287,7 @@ function ($c, &$field) use ($fields, $alias, $mainTableAlias, $mainTableFields, } return $c; - } + }, ); return $joinRequired; @@ -339,7 +339,7 @@ function ($expression) use ($fields, $alias, $mainTableAlias, $mainTableFields, if (in_array($field, $mainTableFields, true)) { $expression->setField("{$mainTableAlias}.{$field}"); } - } + }, ); return $joinRequired; @@ -426,7 +426,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array [ 'useSetters' => false, 'markNew' => true, - ] + ], ); } diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 433857ef..bc726198 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -182,7 +182,7 @@ protected function createStrategy(): TranslateStrategyInterface { $config = array_diff_key( $this->_config, - ['implementedFinders', 'implementedMethods', 'strategyClass'] + ['implementedFinders', 'implementedMethods', 'strategyClass'], ); /** @var class-string<\Cake\ORM\Behavior\Translate\TranslateStrategyInterface> $className */ $className = $this->getConfig('strategyClass', static::$defaultStrategyClass); diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 13ec84d7..d2c64d01 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -212,7 +212,7 @@ protected function _setChildrenLevel(EntityInterface $entity): void $this->_table->updateAll( [$config['level'] => $depth], - [$primaryKey => $node->get($primaryKey)] + [$primaryKey => $node->get($primaryKey)], ); } } @@ -238,7 +238,7 @@ public function beforeDelete(EventInterface $event, EntityInterface $entity): vo ->where( fn (QueryExpression $exp) => $exp ->gte($config['leftField'], $left + 1) - ->lte($config['leftField'], $right - 1) + ->lte($config['leftField'], $right - 1), ); $entities = $query->toArray(); @@ -250,7 +250,7 @@ public function beforeDelete(EventInterface $event, EntityInterface $entity): vo ->where( fn (QueryExpression $exp) => $exp ->gte($config['leftField'], $left + 1) - ->lte($config['leftField'], $right - 1) + ->lte($config['leftField'], $right - 1), ) ->execute(); } @@ -283,7 +283,7 @@ protected function _setParent(EntityInterface $entity, mixed $parent): void throw new DatabaseException(sprintf( 'Cannot use node `%s` as parent for entity `%s`.', $parent, - $entity->get($this->_getPrimaryKey()) + $entity->get($this->_getPrimaryKey()), )); } @@ -375,7 +375,7 @@ function (QueryExpression $exp) use ($config) { ->eq($config['leftField'], $leftInverse->add($config['leftField'])) ->eq($config['rightField'], $rightInverse->add($config['rightField'])); }, - fn (QueryExpression $exp) => $exp->lt($config['leftField'], 0) + fn (QueryExpression $exp) => $exp->lt($config['leftField'], 0), ); } @@ -396,7 +396,7 @@ public function findPath(SelectQuery $query, string|int $for): SelectQuery function ($field) { return $this->_table->aliasField($field); }, - [$config['left'], $config['right']] + [$config['left'], $config['right']], ); $node = $this->_table->get($for, select: [$left, $right]); @@ -452,7 +452,7 @@ public function findChildren(SelectQuery $query, int|string $for, bool $direct = function ($field) { return $this->_table->aliasField($field); }, - [$config['parent'], $config['left'], $config['right']] + [$config['parent'], $config['left'], $config['right']], ); if ($query->clause('order') === null) { @@ -529,7 +529,7 @@ function (CollectionInterface $results) use ($keyPath, $valuePath, $spacer) { assert(is_callable($valuePath) || is_string($valuePath)); return $nested->printer($valuePath, $keyPath, $spacer); - } + }, ); } @@ -576,7 +576,7 @@ protected function _removeFromTree(EntityInterface $node): EntityInterface|false $primary = $this->_getPrimaryKey(); $this->_table->updateAll( [$config['parent'] => $parent], - [$config['parent'] => $node->get($primary)] + [$config['parent'] => $node->get($primary)], ); $this->_sync(1, '-', 'BETWEEN ' . ($left + 1) . ' AND ' . ($right - 1)); $this->_sync(2, '-', "> {$right}"); @@ -844,7 +844,7 @@ protected function _recoverTree(int $lftRght = 1, mixed $parentId = null, int $l $this->_table->updateAll( $fields, - [$primaryKey => $node[$primaryKey]] + [$primaryKey => $node[$primaryKey]], ); } diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 5eb34e6e..c531e4a4 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -187,7 +187,7 @@ protected function _getMethods(Behavior $instance, string $class, string $alias) '`%s` contains duplicate finder `%s` which is already provided by `%s`.', $class, $finder, - $duplicate[0] + $duplicate[0], ); throw new LogicException($error); } @@ -201,7 +201,7 @@ protected function _getMethods(Behavior $instance, string $class, string $alias) '`%s` contains duplicate method `%s` which is already provided by `%s`.', $class, $method, - $duplicate[0] + $duplicate[0], ); throw new LogicException($error); } @@ -286,7 +286,7 @@ public function call(string $method, array $args = []): mixed } throw new BadMethodCallException( - sprintf('Cannot call `%s`, it does not belong to any attached behavior.', $method) + sprintf('Cannot call `%s`, it does not belong to any attached behavior.', $method), ); } @@ -313,7 +313,7 @@ public function callFinder(string $type, SelectQuery $query, mixed ...$args): Se } throw new BadMethodCallException( - sprintf('Cannot call finder `%s`, it does not belong to any attached behavior.', $type) + sprintf('Cannot call finder `%s`, it does not belong to any attached behavior.', $type), ); } } diff --git a/EagerLoader.php b/EagerLoader.php index fa2d0425..7c17cf3c 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -134,7 +134,7 @@ public function contain(array|string $associations, ?Closure $queryBuilder = nul if ($queryBuilder) { if (!is_string($associations)) { throw new InvalidArgumentException( - 'Cannot set containments. To use $queryBuilder, $associations must be a string' + 'Cannot set containments. To use $queryBuilder, $associations must be a string', ); } @@ -304,7 +304,7 @@ public function normalized(Table $repository): array $repository, $alias, $options, - ['root' => null] + ['root' => null], ); } @@ -358,7 +358,7 @@ protected function _reformatContain(array $associations, array $original): array $options; $options = $this->_reformatContain( $options, - $pointer[$table] ?? [] + $pointer[$table] ?? [], ); } @@ -514,7 +514,7 @@ protected function _normalizeContain(Table $parent, string $alias, array $option foreach ($extra as $t => $assoc) { $eagerLoadable->addAssociation( $t, - $this->_normalizeContain($table, $t, $assoc, $paths) + $this->_normalizeContain($table, $t, $assoc, $paths), ); } @@ -665,7 +665,7 @@ public function loadExternal(SelectQuery $query, iterable $results): iterable 'contain' => $contain, 'keys' => $keys, 'nestKey' => $meta->aliasPath(), - ] + ], ); $results = array_map($callback, $results); } diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index 1788b915..204934cc 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -66,7 +66,7 @@ public function getTableLocator(): LocatorInterface $locator = FactoryLocator::get('Table'); assert( $locator instanceof LocatorInterface, - '`FactoryLocator` must return an instance of Cake\ORM\LocatorInterface for type `Table`.' + '`FactoryLocator` must return an instance of Cake\ORM\LocatorInterface for type `Table`.', ); return $this->_tableLocator = $locator; @@ -89,7 +89,7 @@ public function fetchTable(?string $alias = null, array $options = []): Table $alias ??= $this->defaultTable; if (!$alias) { throw new UnexpectedValueException( - 'You must provide an `$alias` or set the `$defaultTable` property to a non empty string.' + 'You must provide an `$alias` or set the `$defaultTable` property to a non empty string.', ); } diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 743e30a3..5af1d602 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -149,7 +149,7 @@ public function setConfig(array|string $alias, ?array $options = null) if (isset($this->instances[$alias])) { throw new DatabaseException(sprintf( 'You cannot configure `%s`, it has already been constructed.', - $alias + $alias, )); } diff --git a/Marshaller.php b/Marshaller.php index 4b331720..0dc8f020 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -95,7 +95,7 @@ protected function _buildPropertyMap(array $data, array $options): array throw new InvalidArgumentException(sprintf( 'Cannot marshal data for `%s` association. It is not associated with `%s`.', (string)$key, - $this->_table->getAlias() + $this->_table->getAlias(), )); } continue; diff --git a/Query/CommonQueryTrait.php b/Query/CommonQueryTrait.php index 3b824930..7ac6a6a0 100644 --- a/Query/CommonQueryTrait.php +++ b/Query/CommonQueryTrait.php @@ -66,7 +66,7 @@ public function setRepository(RepositoryInterface $repository) { assert( $repository instanceof Table, - '`$repository` must be an instance of `' . Table::class . '`.' + '`$repository` must be an instance of `' . Table::class . '`.', ); $this->_repository = $repository; diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index e839074a..0fd8bd0a 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -598,7 +598,7 @@ public function firstOrFail(): mixed $table = $this->getRepository(); throw new RecordNotFoundException(sprintf( 'Record not found in table `%s`.', - $table->getTable() + $table->getTable(), )); } @@ -1016,7 +1016,7 @@ public function contain(array|string $associations, Closure|bool $override = fal $this->_addAssociationsToTypeMap( $this->getRepository(), $this->getTypeMap(), - $loader->getContain() + $loader->getContain(), ); return $this; diff --git a/ResultSetFactory.php b/ResultSetFactory.php index 5dac3630..95da2632 100644 --- a/ResultSetFactory.php +++ b/ResultSetFactory.php @@ -147,7 +147,7 @@ protected function groupResult(array $row, array $data): EntityInterface|array $matching = $data['matchingAssoc'][$alias]; $results['_matchingData'][$alias] = array_combine( $keys, - array_intersect_key($row, $keys) + array_intersect_key($row, $keys), ); if ($data['hydrate']) { $table = $matching['instance']; @@ -254,7 +254,7 @@ public function setResultSetClass(string $resultSetClass) throw new InvalidArgumentException(sprintf( 'Invalid ResultSet class `%s`. It must implement `%s`', $resultSetClass, - ResultSetInterface::class + ResultSetInterface::class, )); } diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 132cd44c..2f41bc7d 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -90,7 +90,7 @@ public function __invoke(EntityInterface $entity, array $options): bool 'ExistsIn rule for `%s` is invalid. `%s` is not associated with `%s`.', implode(', ', $this->_fields), $this->_repository, - $options['repository']::class + $options['repository']::class, )); } $repository = $table->getAssociation($this->_repository); @@ -139,11 +139,11 @@ public function __invoke(EntityInterface $entity, array $options): bool $primary = array_map( fn ($key) => $target->aliasField($key) . ' IS', - $bindingKey + $bindingKey, ); $conditions = array_combine( $primary, - $entity->extract($fields) + $entity->extract($fields), ); return $target->exists($conditions); diff --git a/Rule/LinkConstraint.php b/Rule/LinkConstraint.php index 9a5cf7a4..6b77c77b 100644 --- a/Rule/LinkConstraint.php +++ b/Rule/LinkConstraint.php @@ -66,7 +66,7 @@ public function __construct(Association|string $association, string $requiredLin { if (!in_array($requiredLinkStatus, [static::STATUS_LINKED, static::STATUS_NOT_LINKED], true)) { throw new InvalidArgumentException( - 'Argument 2 is expected to match one of the `\Cake\ORM\Rule\LinkConstraint::STATUS_*` constants.' + 'Argument 2 is expected to match one of the `\Cake\ORM\Rule\LinkConstraint::STATUS_*` constants.', ); } @@ -88,7 +88,7 @@ public function __invoke(EntityInterface $entity, array $options): bool $table = $options['repository'] ?? null; if (!($table instanceof Table)) { throw new InvalidArgumentException( - 'Argument 2 is expected to have a `repository` key that holds an instance of `\Cake\ORM\Table`.' + 'Argument 2 is expected to have a `repository` key that holds an instance of `\Cake\ORM\Table`.', ); } @@ -144,7 +144,7 @@ protected function _buildConditions(array $fields, array $values): array throw new InvalidArgumentException(sprintf( 'The number of fields is expected to match the number of values, got %d field(s) and %d value(s).', count($fields), - count($values) + count($values), )); } @@ -170,14 +170,14 @@ protected function _countLinks(Association $association, EntityInterface $entity 'conditions, expected values for `(%s)`, got `(%s)`.', $source->getAlias(), implode(', ', $primaryKey), - implode(', ', $entity->extract($primaryKey)) + implode(', ', $entity->extract($primaryKey)), )); } $aliasedPrimaryKey = $this->_aliasFields($primaryKey, $source); $conditions = $this->_buildConditions( $aliasedPrimaryKey, - $entity->extract($primaryKey) + $entity->extract($primaryKey), ); return $source diff --git a/RulesChecker.php b/RulesChecker.php index 3dcaf15f..00cca995 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -151,7 +151,7 @@ public function isLinkedTo( $field, $message, LinkConstraint::STATUS_LINKED, - '_isLinkedTo' + '_isLinkedTo', ); } @@ -184,7 +184,7 @@ public function isNotLinkedTo( $field, $message, LinkConstraint::STATUS_NOT_LINKED, - '_isNotLinkedTo' + '_isNotLinkedTo', ); } @@ -234,19 +234,19 @@ protected function _addLinkConstraintRule( $message = __d( 'cake', 'Cannot modify row: a constraint for the `{0}` association fails.', - $associationAlias + $associationAlias, ); } else { $message = sprintf( 'Cannot modify row: a constraint for the `%s` association fails.', - $associationAlias + $associationAlias, ); } } $rule = new LinkConstraint( $association, - $linkStatus + $linkStatus, ); return $this->_addError($rule, $ruleName, compact('errorField', 'message')); @@ -280,7 +280,7 @@ public function validCount( return $this->_addError( new ValidCount($field), '_validCount', - compact('count', 'operator', 'errorField', 'message') + compact('count', 'operator', 'errorField', 'message'), ); } } diff --git a/Table.php b/Table.php index b5613a85..7e987576 100644 --- a/Table.php +++ b/Table.php @@ -421,7 +421,7 @@ public function getTable(): string $table = substr((string)end($table), 0, -5) ?: $this->_alias; if (!$table) { throw new CakeException( - 'You must specify either the `alias` or the `table` option for the constructor.' + 'You must specify either the `alias` or the `table` option for the constructor.', ); } $this->_table = Inflector::underscore($table); @@ -455,7 +455,7 @@ public function getAlias(): string $alias = substr((string)end($alias), 0, -5) ?: $this->_table; if (!$alias) { throw new CakeException( - 'You must specify either the `alias` or the `table` option for the constructor.' + 'You must specify either the `alias` or the `table` option for the constructor.', ); } $this->_alias = $alias; @@ -599,7 +599,7 @@ protected function checkAliasLengths(): void if ($this->_schema === null) { throw new DatabaseException(sprintf( 'Unable to check max alias lengths for `%s` without schema.', - $this->getAlias() + $this->getAlias(), )); } @@ -616,7 +616,7 @@ protected function checkAliasLengths(): void 'ORM queries generate field aliases using the table name/alias and column name. ' . "The table alias `{$table}` and column `{$name}` create an alias longer than ({$nameLength}). " . 'You must change the table schema in the database and shorten either the table or column ' . - 'identifier so they fit within the database alias limits.' + 'identifier so they fit within the database alias limits.', ); } } @@ -873,7 +873,7 @@ public function getBehavior(string $name): Behavior throw new InvalidArgumentException(sprintf( 'The `%s` behavior is not defined on `%s`.', $name, - static::class + static::class, )); } @@ -1396,7 +1396,7 @@ public function findList( $fields = array_merge( (array)$keyField, (array)$valueField, - (array)$groupField + (array)$groupField, ); $columns = $this->getSchema()->columns(); if (count($fields) === count(array_intersect($fields, $columns))) { @@ -1406,13 +1406,13 @@ public function findList( $options = $this->_setFieldMatchers( compact('keyField', 'valueField', 'groupField', 'valueSeparator'), - ['keyField', 'valueField', 'groupField'] + ['keyField', 'valueField', 'groupField'], ); return $query->formatResults(fn (CollectionInterface $results) => $results->combine( $options['keyField'], $options['valueField'], - $options['groupField'] + $options['groupField'], )); } @@ -1451,7 +1451,7 @@ public function findThreaded( return $query->formatResults(fn (CollectionInterface $results) => $results->nest( $options['keyField'], $options['parentField'], - $nestingKey + $nestingKey, )); } @@ -1530,7 +1530,7 @@ public function get( if ($primaryKey === null) { throw new InvalidPrimaryKeyException(sprintf( 'Record not found in table `%s` with primary key `[NULL]`.', - $this->getTable() + $this->getTable(), )); } @@ -1551,7 +1551,7 @@ public function get( throw new InvalidPrimaryKeyException(sprintf( 'Record not found in table `%s` with primary key `[%s]`.', $this->getTable(), - implode(', ', $primaryKey) + implode(', ', $primaryKey), )); } $conditions = array_combine($key, $primaryKey); @@ -1560,7 +1560,7 @@ public function get( deprecationWarning( '5.0.0', 'Calling Table::get() with options array is deprecated.' - . ' Use named arguments instead.' + . ' Use named arguments instead.', ); $args += $finder; @@ -1582,7 +1582,7 @@ public function get( 'get-%s-%s-%s', $this->getConnection()->configName(), $this->getTable(), - json_encode($primaryKey, JSON_THROW_ON_ERROR) + json_encode($primaryKey, JSON_THROW_ON_ERROR), ); } $query->cache($cacheKey, $cache); @@ -1665,7 +1665,7 @@ public function findOrCreate( $entity = $this->_executeTransaction( fn () => $this->_processFindOrCreate($search, $callback, $options->getArrayCopy()), - $options['atomic'] + $options['atomic'], ); if ($entity && $this->_transactionCommitted($options['atomic'], true)) { @@ -1858,7 +1858,7 @@ public function exists(QueryExpression|Closure|array|string|null $conditions): b ->where($conditions) ->limit(1) ->disableHydration() - ->toArray() + ->toArray(), ); } @@ -1971,7 +1971,7 @@ public function save( $success = $this->_executeTransaction( fn () => $this->_processSave($entity, $options), - $options['atomic'] + $options['atomic'], ); if ($success) { @@ -2052,8 +2052,8 @@ protected function _processSave(EntityInterface $entity, ArrayObject $options): $result instanceof EntityInterface, sprintf( 'The beforeSave callback must return `false` or `EntityInterface` instance. Got `%s` instead.', - get_debug_type($result) - ) + get_debug_type($result), + ), ); } @@ -2064,7 +2064,7 @@ protected function _processSave(EntityInterface $entity, ArrayObject $options): $this, $entity, $options['associated'], - ['_primary' => false] + $options->getArrayCopy() + ['_primary' => false] + $options->getArrayCopy(), ); if (!$saved && $options['atomic']) { @@ -2108,7 +2108,7 @@ protected function _onSaveSuccess(EntityInterface $entity, ArrayObject $options) $this, $entity, $options['associated'], - ['_primary' => false] + $options->getArrayCopy() + ['_primary' => false] + $options->getArrayCopy(), ); if (!$success && $options['atomic']) { @@ -2145,7 +2145,7 @@ protected function _insert(EntityInterface $entity, array $data): EntityInterfac if (!$primary) { $msg = sprintf( 'Cannot insert row in `%s` table, it has no primary key.', - $this->getTable() + $this->getTable(), ); throw new DatabaseException($msg); } @@ -2169,7 +2169,7 @@ protected function _insert(EntityInterface $entity, array $data): EntityInterfac $msg .= sprintf( 'Got (%s), expecting (%s)', implode(', ', $filteredKeys + $entity->extract(array_keys($primary))), - implode(', ', array_keys($primary)) + implode(', ', array_keys($primary)), ); throw new DatabaseException($msg); } @@ -2325,7 +2325,7 @@ protected function _saveMany( 'atomic' => true, 'checkRules' => true, '_primary' => true, - ] + ], ); $options['_cleanOnSuccess'] = false; @@ -2438,7 +2438,7 @@ public function delete(EntityInterface $entity, array $options = []): bool $success = $this->_executeTransaction( fn () => $this->_processDelete($entity, $options), - $options['atomic'] + $options['atomic'], ); if ($success && $this->_transactionCommitted($options['atomic'], $options['_primary'])) { @@ -2593,7 +2593,7 @@ protected function _processDelete(EntityInterface $entity, ArrayObject $options) $success = $this->_associations->cascadeDelete( $entity, - ['_primary' => false] + $options->getArrayCopy() + ['_primary' => false] + $options->getArrayCopy(), ); if (!$success) { return $success; @@ -2656,7 +2656,7 @@ public function callFinder(string $type, SelectQuery $query, mixed ...$args): Se throw new BadMethodCallException(sprintf( 'Unknown finder method `%s` on `%s`.', $type, - static::class + static::class, )); } @@ -2692,7 +2692,7 @@ public function invokeFinder(Closure $callable, SelectQuery $query, array $args) deprecationWarning( '5.0.0', 'Calling finders with options arrays is deprecated.' - . ' Update your finder methods to used named arguments instead.' + . ' Update your finder methods to used named arguments instead.', ); $args = $args[0]; } @@ -2717,7 +2717,7 @@ public function invokeFinder(Closure $callable, SelectQuery $query, array $args) deprecationWarning( '5.0.0', "Calling `{$reflected->getName()}` finder with options array is deprecated." - . ' Use named arguments instead.' + . ' Use named arguments instead.', ); $args = $args[0]; @@ -2779,7 +2779,7 @@ protected function _dynamicFinder(string $method, array $args): SelectQuery throw new BadMethodCallException(sprintf( 'Not enough arguments for magic finder. Got %s required %s', count($args), - count($fields) + count($fields), )); } foreach ($fields as $field) { @@ -2791,7 +2791,7 @@ protected function _dynamicFinder(string $method, array $args): SelectQuery if ($hasOr && $hasAnd) { throw new BadMethodCallException( - 'Cannot mix "and" & "or" in a magic finder. Use find() instead.' + 'Cannot mix "and" & "or" in a magic finder. Use find() instead.', ); } @@ -2831,7 +2831,7 @@ public function __call(string $method, array $args): mixed } throw new BadMethodCallException( - sprintf('Unknown method `%s` called on `%s`', $method, static::class) + sprintf('Unknown method `%s` called on `%s`', $method, static::class), ); } @@ -2852,7 +2852,7 @@ public function __get(string $property): Association 'You have not defined the `%s` association on `%s`.', $property, $property, - static::class + static::class, )); } @@ -3143,11 +3143,11 @@ public function validateUnique(mixed $value, array $options, ?array $context = n 'useSetters' => false, 'markNew' => $context['newRecord'], 'source' => $this->getRegistryAlias(), - ] + ], ); $fields = array_merge( [$context['field']], - isset($options['scope']) ? (array)$options['scope'] : [] + isset($options['scope']) ? (array)$options['scope'] : [], ); $values = $entity->extract($fields); foreach ($values as $field) { From f37c3d520d20bd842fc81e0eed87b83ff07c623e Mon Sep 17 00:00:00 2001 From: mscherer Date: Fri, 22 Nov 2024 13:17:38 +0100 Subject: [PATCH 2000/2059] Fix typos and small cleanup. --- Association.php | 2 +- Behavior/TimestampBehavior.php | 4 ++-- Behavior/Translate/TranslateStrategyTrait.php | 2 +- Behavior/TranslateBehavior.php | 2 +- PropertyMarshalInterface.php | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Association.php b/Association.php index 3295da44..c3d5cfa0 100644 --- a/Association.php +++ b/Association.php @@ -791,7 +791,7 @@ protected function _appendNotMatching(SelectQuery $query, array $options): void * @param bool $joined Whether the row is a result of a direct join * with this association * @param string|null $targetProperty The property name in the source results where the association - * data shuld be nested in. Will use the default one if not provided. + * data should be nested in. Will use the default one if not provided. * @return array */ public function transformRow(array $row, string $nestKey, bool $joined, ?string $targetProperty = null): array diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 6dfcc58d..9ce67b6d 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -88,9 +88,9 @@ public function initialize(array $config): void * * @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event Event instance. * @param \Cake\Datasource\EntityInterface $entity Entity instance. - * @throws \UnexpectedValueException if a field's when value is misdefined + * @throws \UnexpectedValueException If a field's value is misdefined. + * @throws \UnexpectedValueException When the value for an event is not 'always', 'new' or 'existing'. * @return void - * @throws \UnexpectedValueException When the value for an event is not 'always', 'new' or 'existing' */ public function handleEvent(EventInterface $event, EntityInterface $entity): void { diff --git a/Behavior/Translate/TranslateStrategyTrait.php b/Behavior/Translate/TranslateStrategyTrait.php index be9b1f95..afb730da 100644 --- a/Behavior/Translate/TranslateStrategyTrait.php +++ b/Behavior/Translate/TranslateStrategyTrait.php @@ -139,7 +139,7 @@ protected function unsetEmptyFields(EntityInterface $entity): void * of translations by setting `'translations' => false` in the options * provided to `Table::newEntity()` or `Table::patchEntity()`. * - * @param \Cake\ORM\Marshaller $marshaller The marhshaller of the table the behavior is attached to. + * @param \Cake\ORM\Marshaller $marshaller The marshaller of the table the behavior is attached to. * @param array $map The property map being built. * @param array $options The options array used in the marshalling call. * @return array A map of `[property => callable]` of additional properties to marshal. diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index bc726198..fb866f58 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -256,7 +256,7 @@ public function beforeMarshal(EventInterface $event, ArrayObject $data, ArrayObj * of translations by setting `'translations' => false` in the options * provided to `Table::newEntity()` or `Table::patchEntity()`. * - * @param \Cake\ORM\Marshaller $marshaller The marhshaller of the table the behavior is attached to. + * @param \Cake\ORM\Marshaller $marshaller The marshaller of the table the behavior is attached to. * @param array $map The property map being built. * @param array $options The options array used in the marshalling call. * @return array A map of `[property => callable]` of additional properties to marshal. diff --git a/PropertyMarshalInterface.php b/PropertyMarshalInterface.php index 8317d9eb..ce3fbbbe 100644 --- a/PropertyMarshalInterface.php +++ b/PropertyMarshalInterface.php @@ -27,7 +27,7 @@ interface PropertyMarshalInterface /** * Build a set of properties that should be included in the marshalling process. * - * @param \Cake\ORM\Marshaller $marshaller The marhshaller of the table the behavior is attached to. + * @param \Cake\ORM\Marshaller $marshaller The marshaller of the table the behavior is attached to. * @param array $map The property map being built. * @param array $options The options array used in the marshalling call. * @return array A map of `[property => callable]` of additional properties to marshal. From 6423e29fd8766ce441cbe84cac6b4030d71b094d Mon Sep 17 00:00:00 2001 From: mscherer Date: Fri, 22 Nov 2024 17:07:25 +0100 Subject: [PATCH 2001/2059] Merge 5.x into 5.next --- Association.php | 20 +++--- Association/BelongsTo.php | 4 +- Association/BelongsToMany.php | 14 ++-- Association/HasMany.php | 18 ++--- Association/HasOne.php | 2 +- Association/Loader/SelectLoader.php | 6 +- Association/Loader/SelectWithPivotLoader.php | 2 +- AssociationCollection.php | 2 +- Behavior.php | 6 +- Behavior/TimestampBehavior.php | 8 +-- Behavior/Translate/EavStrategy.php | 6 +- Behavior/Translate/ShadowTableStrategy.php | 12 ++-- Behavior/Translate/TranslateStrategyTrait.php | 2 +- Behavior/TranslateBehavior.php | 4 +- Behavior/TreeBehavior.php | 20 +++--- BehaviorRegistry.php | 8 +-- EagerLoader.php | 10 +-- Locator/LocatorAwareTrait.php | 4 +- Locator/TableLocator.php | 2 +- Marshaller.php | 2 +- PropertyMarshalInterface.php | 2 +- Query/CommonQueryTrait.php | 2 +- Query/SelectQuery.php | 25 +++---- ResultSetFactory.php | 4 +- Rule/ExistsIn.php | 6 +- Rule/LinkConstraint.php | 10 +-- RulesChecker.php | 12 ++-- Table.php | 71 +++++++++---------- 28 files changed, 139 insertions(+), 145 deletions(-) diff --git a/Association.php b/Association.php index 04390b95..4553baa5 100644 --- a/Association.php +++ b/Association.php @@ -295,7 +295,7 @@ public function setClassName(string $className) throw new InvalidArgumentException(sprintf( "The class name `%s` doesn't match the target table class name of `%s`.", $className, - $this->_targetTable::class + $this->_targetTable::class, )); } @@ -388,7 +388,7 @@ public function getTarget(): Table $this->getName(), $this->type(), $this->_targetTable::class, - $className + $className, )); } } @@ -574,7 +574,7 @@ public function getProperty(): string ' You should explicitly specify the `propertyName` option.'; trigger_error( sprintf($msg, $this->_propertyName, $this->_sourceTable->getTable()), - E_USER_WARNING + E_USER_WARNING, ); } } @@ -609,7 +609,7 @@ public function setStrategy(string $name) throw new InvalidArgumentException(sprintf( 'Invalid strategy `%s` was provided. Valid options are `(%s)`.', $name, - implode(', ', $this->_validStrategies) + implode(', ', $this->_validStrategies), )); } $this->_strategy = $name; @@ -727,7 +727,7 @@ public function attachTo(SelectQuery $query, array $options = []): void if (!($dummy instanceof SelectQuery)) { throw new DatabaseException(sprintf( 'Query builder for association `%s` did not return a query.', - $this->getName() + $this->getName(), )); } } @@ -739,7 +739,7 @@ public function attachTo(SelectQuery $query, array $options = []): void ) { throw new DatabaseException(sprintf( '`%s` association cannot contain() associations when using JOIN strategy.', - $this->getName() + $this->getName(), )); } @@ -791,7 +791,7 @@ protected function _appendNotMatching(SelectQuery $query, array $options): void * @param bool $joined Whether the row is a result of a direct join * with this association * @param string|null $targetProperty The property name in the source results where the association - * data shuld be nested in. Will use the default one if not provided. + * data should be nested in. Will use the default one if not provided. * @return array */ public function transformRow(array $row, string $nestKey, bool $joined, ?string $targetProperty = null): array @@ -1014,7 +1014,7 @@ function (CollectionInterface $results, SelectQuery $query) use ($formatters, $p return $results; }, - SelectQuery::PREPEND + SelectQuery::PREPEND, ); } @@ -1055,7 +1055,7 @@ protected function _bindNewAssociations(SelectQuery $query, SelectQuery $surroga $eagerLoader->setMatching( $options['aliasPath'] . '.' . $alias, $value['queryBuilder'], - $value + $value, ); } } @@ -1092,7 +1092,7 @@ protected function _joinCondition(array $options): array $msg, $this->_name, implode(', ', $foreignKey), - implode(', ', $bindingKey) + implode(', ', $bindingKey), )); } diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 0a584716..a9bb6ff0 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -151,7 +151,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []): En $foreignKeys = (array)$this->getForeignKey(); $properties = array_combine( $foreignKeys, - $targetEntity->extract((array)$this->getBindingKey()) + $targetEntity->extract((array)$this->getBindingKey()), ); $entity->set($properties, ['guard' => false]); @@ -186,7 +186,7 @@ protected function _joinCondition(array $options): array $msg, $this->_name, implode(', ', $foreignKey), - implode(', ', $bindingKey) + implode(', ', $bindingKey), )); } diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index a5eed00a..3efe7736 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -289,7 +289,7 @@ public function junction(Table|string|null $table = null): Table throw new InvalidArgumentException(sprintf( 'The `%s` association on `%s` cannot target the same table.', $this->getName(), - $source->getAlias() + $source->getAlias(), )); } @@ -417,7 +417,7 @@ protected function _generateJunctionAssociations(Table $junction, Table $source, ) { throw new InvalidArgumentException( "The existing `{$tAlias}` association on `{$junction->getAlias()}` " . - "is incompatible with the `{$this->getName()}` association on `{$source->getAlias()}`" + "is incompatible with the `{$this->getName()}` association on `{$source->getAlias()}`", ); } } @@ -876,7 +876,7 @@ public function link(EntityInterface $sourceEntity, array $targetEntities, array return $this->junction()->getConnection()->transactional( function () use ($sourceEntity, $targetEntities, $options) { return $this->_saveLinks($sourceEntity, $targetEntities, $options); - } + }, ); } @@ -935,7 +935,7 @@ function () use ($sourceEntity, $targetEntities, $options): void { foreach ($links as $entity) { $this->_junctionTable->delete($entity, $options); } - } + }, ); /** @var array<\Cake\Datasource\EntityInterface> $existing */ @@ -1223,7 +1223,7 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { ->from([$junctionQueryAlias => $matches]) ->innerJoin( [$junction->getAlias() => $junction->getTable()], - $matchesConditions + $matchesConditions, ); $jointEntities = $this->_collectJointEntities($sourceEntity, $targetEntities); @@ -1242,7 +1242,7 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { /** @psalm-suppress RedundantConditionGivenDocblockType */ $inserted = array_combine( array_keys($inserts), - (array)$sourceEntity->get($property) + (array)$sourceEntity->get($property), ) ?: []; $targetEntities = $inserted + $targetEntities; } @@ -1252,7 +1252,7 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { $sourceEntity->setDirty($property, false); return true; - } + }, ); } diff --git a/Association/HasMany.php b/Association/HasMany.php index 9c2c4f04..94b30bb9 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -173,7 +173,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []): En $foreignKeys = (array)$this->getForeignKey(); $foreignKeyReference = array_combine( $foreignKeys, - $entity->extract((array)$this->getBindingKey()) + $entity->extract((array)$this->getBindingKey()), ); $options['_sourceTable'] = $this->getSource(); @@ -287,8 +287,8 @@ public function link(EntityInterface $sourceEntity, array $targetEntities, array $currentEntities = array_unique( array_merge( (array)$sourceEntity->get($property), - $targetEntities - ) + $targetEntities, + ), ); $sourceEntity->set($property, $currentEntities); @@ -384,9 +384,9 @@ public function unlink(EntityInterface $sourceEntity, array $targetEntities, arr ->reject( function ($assoc) use ($targetEntities) { return in_array($assoc, $targetEntities); - } + }, ) - ->toList() + ->toList(), ); } @@ -478,12 +478,12 @@ protected function _unlinkAssociated( $exclusions = $exclusions->map( function (EntityInterface $ent) use ($primaryKey) { return $ent->extract($primaryKey); - } + }, ) ->filter( function ($v) { return !in_array(null, $v, true); - } + }, ) ->toList(); @@ -563,8 +563,8 @@ protected function _foreignKeyAcceptsNull(Table $table, array $properties): bool function ($prop) use ($table) { return $table->getSchema()->isNullable($prop); }, - $properties - ) + $properties, + ), ); } diff --git a/Association/HasOne.php b/Association/HasOne.php index fff3b284..2bac5936 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -129,7 +129,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []): En $foreignKeys = (array)$this->getForeignKey(); $properties = array_combine( $foreignKeys, - $entity->extract((array)$this->getBindingKey()) + $entity->extract((array)$this->getBindingKey()), ); $targetEntity->set($properties, ['guard' => false]); diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index b0de00f7..81a6dbf7 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -278,8 +278,8 @@ protected function _assertFieldsPresent(SelectQuery $fetchQuery, array $key): vo throw new InvalidArgumentException( sprintf( 'You are required to select the "%s" field(s)', - implode(', ', $key) - ) + implode(', ', $key), + ), ); } } @@ -317,7 +317,7 @@ protected function _addFilteringJoin(SelectQuery $query, array|string $key, Sele return $query->innerJoin( [$aliasedTable => $subquery], - $conditions + $conditions, ); } diff --git a/Association/Loader/SelectWithPivotLoader.php b/Association/Loader/SelectWithPivotLoader.php index 75918c82..f3d5baf1 100644 --- a/Association/Loader/SelectWithPivotLoader.php +++ b/Association/Loader/SelectWithPivotLoader.php @@ -184,7 +184,7 @@ protected function _buildResultMap(SelectQuery $fetchQuery, array $options): arr if (!isset($result[$this->junctionProperty])) { throw new DatabaseException(sprintf( '`%s` is missing from the belongsToMany results. Results cannot be created.', - $this->junctionProperty + $this->junctionProperty, )); } diff --git a/AssociationCollection.php b/AssociationCollection.php index 03c31cb8..00376670 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -281,7 +281,7 @@ protected function _saveAssociations( $msg = sprintf( 'Cannot save `%s`, it is not associated to `%s`.', $alias, - $table->getAlias() + $table->getAlias(), ); throw new InvalidArgumentException($msg); } diff --git a/Behavior.php b/Behavior.php index 58dbaa40..2e20ee22 100644 --- a/Behavior.php +++ b/Behavior.php @@ -155,12 +155,12 @@ public function __construct(Table $table, array $config = []) $config = $this->_resolveMethodAliases( 'implementedFinders', $this->_defaultConfig, - $config + $config, ); $config = $this->_resolveMethodAliases( 'implementedMethods', $this->_defaultConfig, - $config + $config, ); $this->_table = $table; $this->setConfig($config); @@ -244,7 +244,7 @@ public function verifyConfig(): void throw new CakeException(sprintf( 'The method `%s` is not callable on class `%s`.', $method, - static::class + static::class, )); } } diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 5c1521ca..9ce67b6d 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -88,9 +88,9 @@ public function initialize(array $config): void * * @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event Event instance. * @param \Cake\Datasource\EntityInterface $entity Entity instance. - * @throws \UnexpectedValueException if a field's when value is misdefined + * @throws \UnexpectedValueException If a field's value is misdefined. + * @throws \UnexpectedValueException When the value for an event is not 'always', 'new' or 'existing'. * @return void - * @throws \UnexpectedValueException When the value for an event is not 'always', 'new' or 'existing' */ public function handleEvent(EventInterface $event, EntityInterface $entity): void { @@ -104,7 +104,7 @@ public function handleEvent(EventInterface $event, EntityInterface $entity): voi if (!in_array($when, ['always', 'new', 'existing'], true)) { throw new UnexpectedValueException(sprintf( 'When should be one of "always", "new" or "existing". The passed value `%s` is invalid.', - $when + $when, )); } if ( @@ -217,7 +217,7 @@ protected function _updateField(EntityInterface $entity, string $field, bool $re $type = TypeFactory::build($columnType); assert( $type instanceof DateTimeType, - sprintf('TimestampBehavior only supports columns of type `%s`.', DateTimeType::class) + sprintf('TimestampBehavior only supports columns of type `%s`.', DateTimeType::class), ); $class = $type->getDateTimeClassName(); diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index 957b24a1..f93a6c71 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -82,7 +82,7 @@ public function __construct(Table $table, array $config = []) $this->table = $table; $this->translationTable = $this->getTableLocator()->get( $this->_config['translationTable'], - ['allowFallbackClass' => true] + ['allowFallbackClass' => true], ); $this->setupAssociations(); @@ -212,7 +212,7 @@ public function beforeFind(EventInterface $event, SelectQuery $query, ArrayObjec $field, $locale, $query, - $select + $select, ); if ($changeFilter) { @@ -226,7 +226,7 @@ public function beforeFind(EventInterface $event, SelectQuery $query, ArrayObjec $query->contain($contain); $query->formatResults( fn (CollectionInterface $results) => $this->rowMapper($results, $locale), - $query::PREPEND + $query::PREPEND, ); } diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 01d276a1..18477d47 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -86,7 +86,7 @@ public function __construct(Table $table, array $config = []) $this->table = $table; $this->translationTable = $this->getTableLocator()->get( $this->_config['translationTable'], - ['allowFallbackClass' => true] + ['allowFallbackClass' => true], ); $this->setupAssociations(); @@ -155,7 +155,7 @@ public function beforeFind(EventInterface $event, SelectQuery $query, ArrayObjec $query->formatResults( fn (CollectionInterface $results) => $this->rowMapper($results, $locale), - $query::PREPEND + $query::PREPEND, ); } @@ -179,7 +179,7 @@ protected function setupHasOneAssociation(string $locale, ArrayObject $options): [ 'className' => $config['translationTable'], 'allowFallbackClass' => true, - ] + ], ); } @@ -287,7 +287,7 @@ function ($c, &$field) use ($fields, $alias, $mainTableAlias, $mainTableFields, } return $c; - } + }, ); return $joinRequired; @@ -339,7 +339,7 @@ function ($expression) use ($fields, $alias, $mainTableAlias, $mainTableFields, if (in_array($field, $mainTableFields, true)) { $expression->setField("{$mainTableAlias}.{$field}"); } - } + }, ); return $joinRequired; @@ -426,7 +426,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array [ 'useSetters' => false, 'markNew' => true, - ] + ], ); } diff --git a/Behavior/Translate/TranslateStrategyTrait.php b/Behavior/Translate/TranslateStrategyTrait.php index be9b1f95..afb730da 100644 --- a/Behavior/Translate/TranslateStrategyTrait.php +++ b/Behavior/Translate/TranslateStrategyTrait.php @@ -139,7 +139,7 @@ protected function unsetEmptyFields(EntityInterface $entity): void * of translations by setting `'translations' => false` in the options * provided to `Table::newEntity()` or `Table::patchEntity()`. * - * @param \Cake\ORM\Marshaller $marshaller The marhshaller of the table the behavior is attached to. + * @param \Cake\ORM\Marshaller $marshaller The marshaller of the table the behavior is attached to. * @param array $map The property map being built. * @param array $options The options array used in the marshalling call. * @return array A map of `[property => callable]` of additional properties to marshal. diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 433857ef..fb866f58 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -182,7 +182,7 @@ protected function createStrategy(): TranslateStrategyInterface { $config = array_diff_key( $this->_config, - ['implementedFinders', 'implementedMethods', 'strategyClass'] + ['implementedFinders', 'implementedMethods', 'strategyClass'], ); /** @var class-string<\Cake\ORM\Behavior\Translate\TranslateStrategyInterface> $className */ $className = $this->getConfig('strategyClass', static::$defaultStrategyClass); @@ -256,7 +256,7 @@ public function beforeMarshal(EventInterface $event, ArrayObject $data, ArrayObj * of translations by setting `'translations' => false` in the options * provided to `Table::newEntity()` or `Table::patchEntity()`. * - * @param \Cake\ORM\Marshaller $marshaller The marhshaller of the table the behavior is attached to. + * @param \Cake\ORM\Marshaller $marshaller The marshaller of the table the behavior is attached to. * @param array $map The property map being built. * @param array $options The options array used in the marshalling call. * @return array A map of `[property => callable]` of additional properties to marshal. diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 13ec84d7..d2c64d01 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -212,7 +212,7 @@ protected function _setChildrenLevel(EntityInterface $entity): void $this->_table->updateAll( [$config['level'] => $depth], - [$primaryKey => $node->get($primaryKey)] + [$primaryKey => $node->get($primaryKey)], ); } } @@ -238,7 +238,7 @@ public function beforeDelete(EventInterface $event, EntityInterface $entity): vo ->where( fn (QueryExpression $exp) => $exp ->gte($config['leftField'], $left + 1) - ->lte($config['leftField'], $right - 1) + ->lte($config['leftField'], $right - 1), ); $entities = $query->toArray(); @@ -250,7 +250,7 @@ public function beforeDelete(EventInterface $event, EntityInterface $entity): vo ->where( fn (QueryExpression $exp) => $exp ->gte($config['leftField'], $left + 1) - ->lte($config['leftField'], $right - 1) + ->lte($config['leftField'], $right - 1), ) ->execute(); } @@ -283,7 +283,7 @@ protected function _setParent(EntityInterface $entity, mixed $parent): void throw new DatabaseException(sprintf( 'Cannot use node `%s` as parent for entity `%s`.', $parent, - $entity->get($this->_getPrimaryKey()) + $entity->get($this->_getPrimaryKey()), )); } @@ -375,7 +375,7 @@ function (QueryExpression $exp) use ($config) { ->eq($config['leftField'], $leftInverse->add($config['leftField'])) ->eq($config['rightField'], $rightInverse->add($config['rightField'])); }, - fn (QueryExpression $exp) => $exp->lt($config['leftField'], 0) + fn (QueryExpression $exp) => $exp->lt($config['leftField'], 0), ); } @@ -396,7 +396,7 @@ public function findPath(SelectQuery $query, string|int $for): SelectQuery function ($field) { return $this->_table->aliasField($field); }, - [$config['left'], $config['right']] + [$config['left'], $config['right']], ); $node = $this->_table->get($for, select: [$left, $right]); @@ -452,7 +452,7 @@ public function findChildren(SelectQuery $query, int|string $for, bool $direct = function ($field) { return $this->_table->aliasField($field); }, - [$config['parent'], $config['left'], $config['right']] + [$config['parent'], $config['left'], $config['right']], ); if ($query->clause('order') === null) { @@ -529,7 +529,7 @@ function (CollectionInterface $results) use ($keyPath, $valuePath, $spacer) { assert(is_callable($valuePath) || is_string($valuePath)); return $nested->printer($valuePath, $keyPath, $spacer); - } + }, ); } @@ -576,7 +576,7 @@ protected function _removeFromTree(EntityInterface $node): EntityInterface|false $primary = $this->_getPrimaryKey(); $this->_table->updateAll( [$config['parent'] => $parent], - [$config['parent'] => $node->get($primary)] + [$config['parent'] => $node->get($primary)], ); $this->_sync(1, '-', 'BETWEEN ' . ($left + 1) . ' AND ' . ($right - 1)); $this->_sync(2, '-', "> {$right}"); @@ -844,7 +844,7 @@ protected function _recoverTree(int $lftRght = 1, mixed $parentId = null, int $l $this->_table->updateAll( $fields, - [$primaryKey => $node[$primaryKey]] + [$primaryKey => $node[$primaryKey]], ); } diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 5eb34e6e..c531e4a4 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -187,7 +187,7 @@ protected function _getMethods(Behavior $instance, string $class, string $alias) '`%s` contains duplicate finder `%s` which is already provided by `%s`.', $class, $finder, - $duplicate[0] + $duplicate[0], ); throw new LogicException($error); } @@ -201,7 +201,7 @@ protected function _getMethods(Behavior $instance, string $class, string $alias) '`%s` contains duplicate method `%s` which is already provided by `%s`.', $class, $method, - $duplicate[0] + $duplicate[0], ); throw new LogicException($error); } @@ -286,7 +286,7 @@ public function call(string $method, array $args = []): mixed } throw new BadMethodCallException( - sprintf('Cannot call `%s`, it does not belong to any attached behavior.', $method) + sprintf('Cannot call `%s`, it does not belong to any attached behavior.', $method), ); } @@ -313,7 +313,7 @@ public function callFinder(string $type, SelectQuery $query, mixed ...$args): Se } throw new BadMethodCallException( - sprintf('Cannot call finder `%s`, it does not belong to any attached behavior.', $type) + sprintf('Cannot call finder `%s`, it does not belong to any attached behavior.', $type), ); } } diff --git a/EagerLoader.php b/EagerLoader.php index fa2d0425..7c17cf3c 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -134,7 +134,7 @@ public function contain(array|string $associations, ?Closure $queryBuilder = nul if ($queryBuilder) { if (!is_string($associations)) { throw new InvalidArgumentException( - 'Cannot set containments. To use $queryBuilder, $associations must be a string' + 'Cannot set containments. To use $queryBuilder, $associations must be a string', ); } @@ -304,7 +304,7 @@ public function normalized(Table $repository): array $repository, $alias, $options, - ['root' => null] + ['root' => null], ); } @@ -358,7 +358,7 @@ protected function _reformatContain(array $associations, array $original): array $options; $options = $this->_reformatContain( $options, - $pointer[$table] ?? [] + $pointer[$table] ?? [], ); } @@ -514,7 +514,7 @@ protected function _normalizeContain(Table $parent, string $alias, array $option foreach ($extra as $t => $assoc) { $eagerLoadable->addAssociation( $t, - $this->_normalizeContain($table, $t, $assoc, $paths) + $this->_normalizeContain($table, $t, $assoc, $paths), ); } @@ -665,7 +665,7 @@ public function loadExternal(SelectQuery $query, iterable $results): iterable 'contain' => $contain, 'keys' => $keys, 'nestKey' => $meta->aliasPath(), - ] + ], ); $results = array_map($callback, $results); } diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index 1788b915..204934cc 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -66,7 +66,7 @@ public function getTableLocator(): LocatorInterface $locator = FactoryLocator::get('Table'); assert( $locator instanceof LocatorInterface, - '`FactoryLocator` must return an instance of Cake\ORM\LocatorInterface for type `Table`.' + '`FactoryLocator` must return an instance of Cake\ORM\LocatorInterface for type `Table`.', ); return $this->_tableLocator = $locator; @@ -89,7 +89,7 @@ public function fetchTable(?string $alias = null, array $options = []): Table $alias ??= $this->defaultTable; if (!$alias) { throw new UnexpectedValueException( - 'You must provide an `$alias` or set the `$defaultTable` property to a non empty string.' + 'You must provide an `$alias` or set the `$defaultTable` property to a non empty string.', ); } diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index baf1c181..14d6c9e7 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -149,7 +149,7 @@ public function setConfig(array|string $alias, ?array $options = null) if (isset($this->instances[$alias])) { throw new DatabaseException(sprintf( 'You cannot configure `%s`, it has already been constructed.', - $alias + $alias, )); } diff --git a/Marshaller.php b/Marshaller.php index f917e94c..a19ad95c 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -95,7 +95,7 @@ protected function _buildPropertyMap(array $data, array $options): array throw new InvalidArgumentException(sprintf( 'Cannot marshal data for `%s` association. It is not associated with `%s`.', (string)$key, - $this->_table->getAlias() + $this->_table->getAlias(), )); } continue; diff --git a/PropertyMarshalInterface.php b/PropertyMarshalInterface.php index 8317d9eb..ce3fbbbe 100644 --- a/PropertyMarshalInterface.php +++ b/PropertyMarshalInterface.php @@ -27,7 +27,7 @@ interface PropertyMarshalInterface /** * Build a set of properties that should be included in the marshalling process. * - * @param \Cake\ORM\Marshaller $marshaller The marhshaller of the table the behavior is attached to. + * @param \Cake\ORM\Marshaller $marshaller The marshaller of the table the behavior is attached to. * @param array $map The property map being built. * @param array $options The options array used in the marshalling call. * @return array A map of `[property => callable]` of additional properties to marshal. diff --git a/Query/CommonQueryTrait.php b/Query/CommonQueryTrait.php index 3b824930..7ac6a6a0 100644 --- a/Query/CommonQueryTrait.php +++ b/Query/CommonQueryTrait.php @@ -66,7 +66,7 @@ public function setRepository(RepositoryInterface $repository) { assert( $repository instanceof Table, - '`$repository` must be an instance of `' . Table::class . '`.' + '`$repository` must be an instance of `' . Table::class . '`.', ); $this->_repository = $repository; diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index 9577002e..0fd8bd0a 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -337,9 +337,9 @@ public function aliasField(string $field, ?string $alias = null): array * Runs `aliasField()` for each field in the provided list and returns * the result under a single array. * - * @param array $fields The fields to alias + * @param array $fields The fields to alias * @param string|null $defaultAlias The default alias - * @return array + * @return array */ public function aliasFields(array $fields, ?string $defaultAlias = null): array { @@ -376,15 +376,10 @@ public function all(): ResultSetInterface return $this->_results; } - $results = null; - if ($this->_cache) { - $results = $this->_cache->fetch($this); - } + $results = $this->_cache?->fetch($this); if ($results === null) { $results = $this->_decorateResults($this->_execute()); - if ($this->_cache) { - $this->_cache->store($this, $results); - } + $this->_cache?->store($this, $results); } $this->_results = $results; @@ -603,7 +598,7 @@ public function firstOrFail(): mixed $table = $this->getRepository(); throw new RecordNotFoundException(sprintf( 'Record not found in table `%s`.', - $table->getTable() + $table->getTable(), )); } @@ -771,7 +766,7 @@ protected function _decorateResults(iterable $result): ResultSetInterface * If a callback is passed, the returning array of the function will * be used as the list of fields. * - * By default this function will append any passed argument to the list of fields + * By default, this function will append any passed argument to the list of fields * to be selected, unless the second argument is set to true. * * ### Examples: @@ -786,7 +781,7 @@ protected function _decorateResults(iterable $result): ResultSetInterface * }) * ``` * - * By default no fields are selected, if you have an instance of `Cake\ORM\Query` and try to append + * By default, no fields are selected, if you have an instance of `Cake\ORM\Query` and try to append * fields you should also call `Cake\ORM\Query::enableAutoFields()` to select the default fields * from the table. * @@ -1021,7 +1016,7 @@ public function contain(array|string $associations, Closure|bool $override = fal $this->_addAssociationsToTypeMap( $this->getRepository(), $this->getTypeMap(), - $loader->getContain() + $loader->getContain(), ); return $this; @@ -1748,7 +1743,7 @@ public function jsonSerialize(): ResultSetInterface /** * Sets whether the ORM should automatically append fields. * - * By default calling select() will disable auto-fields. You can re-enable + * By default, calling select() will disable auto-fields. You can re-enable * auto-fields with this method. * * @param bool $value Set true to enable, false to disable. @@ -1776,7 +1771,7 @@ public function disableAutoFields() /** * Gets whether the ORM should automatically append fields. * - * By default calling select() will disable auto-fields. You can re-enable + * By default, calling select() will disable auto-fields. You can re-enable * auto-fields with enableAutoFields(). * * @return bool|null The current value. Returns null if neither enabled or disabled yet. diff --git a/ResultSetFactory.php b/ResultSetFactory.php index 5dac3630..95da2632 100644 --- a/ResultSetFactory.php +++ b/ResultSetFactory.php @@ -147,7 +147,7 @@ protected function groupResult(array $row, array $data): EntityInterface|array $matching = $data['matchingAssoc'][$alias]; $results['_matchingData'][$alias] = array_combine( $keys, - array_intersect_key($row, $keys) + array_intersect_key($row, $keys), ); if ($data['hydrate']) { $table = $matching['instance']; @@ -254,7 +254,7 @@ public function setResultSetClass(string $resultSetClass) throw new InvalidArgumentException(sprintf( 'Invalid ResultSet class `%s`. It must implement `%s`', $resultSetClass, - ResultSetInterface::class + ResultSetInterface::class, )); } diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 15ac455d..5a41b064 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -90,7 +90,7 @@ public function __invoke(EntityInterface $entity, array $options): bool 'ExistsIn rule for `%s` is invalid. `%s` is not associated with `%s`.', implode(', ', $this->_fields), $this->_repository, - $options['repository']::class + $options['repository']::class, )); } $repository = $table->getAssociation($this->_repository); @@ -139,11 +139,11 @@ public function __invoke(EntityInterface $entity, array $options): bool $primary = array_map( fn ($key) => $target->aliasField($key) . ' IS', - $bindingKey + $bindingKey, ); $conditions = array_combine( $primary, - $entity->extract($fields) + $entity->extract($fields), ); return $target->exists($conditions); diff --git a/Rule/LinkConstraint.php b/Rule/LinkConstraint.php index 33c5e420..1a512ab3 100644 --- a/Rule/LinkConstraint.php +++ b/Rule/LinkConstraint.php @@ -66,7 +66,7 @@ public function __construct(Association|string $association, string $requiredLin { if (!in_array($requiredLinkStatus, [static::STATUS_LINKED, static::STATUS_NOT_LINKED], true)) { throw new InvalidArgumentException( - 'Argument 2 is expected to match one of the `\Cake\ORM\Rule\LinkConstraint::STATUS_*` constants.' + 'Argument 2 is expected to match one of the `\Cake\ORM\Rule\LinkConstraint::STATUS_*` constants.', ); } @@ -88,7 +88,7 @@ public function __invoke(EntityInterface $entity, array $options): bool $table = $options['repository'] ?? null; if (!($table instanceof Table)) { throw new InvalidArgumentException( - 'Argument 2 is expected to have a `repository` key that holds an instance of `\Cake\ORM\Table`.' + 'Argument 2 is expected to have a `repository` key that holds an instance of `\Cake\ORM\Table`.', ); } @@ -144,7 +144,7 @@ protected function _buildConditions(array $fields, array $values): array throw new InvalidArgumentException(sprintf( 'The number of fields is expected to match the number of values, got %d field(s) and %d value(s).', count($fields), - count($values) + count($values), )); } @@ -170,14 +170,14 @@ protected function _countLinks(Association $association, EntityInterface $entity 'conditions, expected values for `(%s)`, got `(%s)`.', $source->getAlias(), implode(', ', $primaryKey), - implode(', ', $entity->extract($primaryKey)) + implode(', ', $entity->extract($primaryKey)), )); } $aliasedPrimaryKey = $this->_aliasFields($primaryKey, $source); $conditions = $this->_buildConditions( $aliasedPrimaryKey, - $entity->extract($primaryKey) + $entity->extract($primaryKey), ); return $source diff --git a/RulesChecker.php b/RulesChecker.php index 3dcaf15f..00cca995 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -151,7 +151,7 @@ public function isLinkedTo( $field, $message, LinkConstraint::STATUS_LINKED, - '_isLinkedTo' + '_isLinkedTo', ); } @@ -184,7 +184,7 @@ public function isNotLinkedTo( $field, $message, LinkConstraint::STATUS_NOT_LINKED, - '_isNotLinkedTo' + '_isNotLinkedTo', ); } @@ -234,19 +234,19 @@ protected function _addLinkConstraintRule( $message = __d( 'cake', 'Cannot modify row: a constraint for the `{0}` association fails.', - $associationAlias + $associationAlias, ); } else { $message = sprintf( 'Cannot modify row: a constraint for the `%s` association fails.', - $associationAlias + $associationAlias, ); } } $rule = new LinkConstraint( $association, - $linkStatus + $linkStatus, ); return $this->_addError($rule, $ruleName, compact('errorField', 'message')); @@ -280,7 +280,7 @@ public function validCount( return $this->_addError( new ValidCount($field), '_validCount', - compact('count', 'operator', 'errorField', 'message') + compact('count', 'operator', 'errorField', 'message'), ); } } diff --git a/Table.php b/Table.php index dabbd602..7feb4bb3 100644 --- a/Table.php +++ b/Table.php @@ -421,7 +421,7 @@ public function getTable(): string $table = substr((string)end($table), 0, -5) ?: $this->_alias; if (!$table) { throw new CakeException( - 'You must specify either the `alias` or the `table` option for the constructor.' + 'You must specify either the `alias` or the `table` option for the constructor.', ); } $this->_table = Inflector::underscore($table); @@ -455,7 +455,7 @@ public function getAlias(): string $alias = substr((string)end($alias), 0, -5) ?: $this->_table; if (!$alias) { throw new CakeException( - 'You must specify either the `alias` or the `table` option for the constructor.' + 'You must specify either the `alias` or the `table` option for the constructor.', ); } $this->_alias = $alias; @@ -599,7 +599,7 @@ protected function checkAliasLengths(): void if ($this->_schema === null) { throw new DatabaseException(sprintf( 'Unable to check max alias lengths for `%s` without schema.', - $this->getAlias() + $this->getAlias(), )); } @@ -616,7 +616,7 @@ protected function checkAliasLengths(): void 'ORM queries generate field aliases using the table name/alias and column name. ' . "The table alias `{$table}` and column `{$name}` create an alias longer than ({$nameLength}). " . 'You must change the table schema in the database and shorten either the table or column ' . - 'identifier so they fit within the database alias limits.' + 'identifier so they fit within the database alias limits.', ); } } @@ -683,7 +683,7 @@ public function setDisplayField(array|string $field) /** * Returns the display field. * - * @return array|string + * @return array|string|null */ public function getDisplayField(): array|string|null { @@ -873,7 +873,7 @@ public function getBehavior(string $name): Behavior throw new InvalidArgumentException(sprintf( 'The `%s` behavior is not defined on `%s`.', $name, - static::class + static::class, )); } @@ -1396,7 +1396,7 @@ public function findList( $fields = array_merge( (array)$keyField, (array)$valueField, - (array)$groupField + (array)$groupField, ); $columns = $this->getSchema()->columns(); if (count($fields) === count(array_intersect($fields, $columns))) { @@ -1406,13 +1406,13 @@ public function findList( $options = $this->_setFieldMatchers( compact('keyField', 'valueField', 'groupField', 'valueSeparator'), - ['keyField', 'valueField', 'groupField'] + ['keyField', 'valueField', 'groupField'], ); return $query->formatResults(fn (CollectionInterface $results) => $results->combine( $options['keyField'], $options['valueField'], - $options['groupField'] + $options['groupField'], )); } @@ -1451,7 +1451,7 @@ public function findThreaded( return $query->formatResults(fn (CollectionInterface $results) => $results->nest( $options['keyField'], $options['parentField'], - $nestingKey + $nestingKey, )); } @@ -1530,7 +1530,7 @@ public function get( if ($primaryKey === null) { throw new InvalidPrimaryKeyException(sprintf( 'Record not found in table `%s` with primary key `[NULL]`.', - $this->getTable() + $this->getTable(), )); } @@ -1551,7 +1551,7 @@ public function get( throw new InvalidPrimaryKeyException(sprintf( 'Record not found in table `%s` with primary key `[%s]`.', $this->getTable(), - implode(', ', $primaryKey) + implode(', ', $primaryKey), )); } $conditions = array_combine($key, $primaryKey); @@ -1560,7 +1560,7 @@ public function get( deprecationWarning( '5.0.0', 'Calling Table::get() with options array is deprecated.' - . ' Use named arguments instead.' + . ' Use named arguments instead.', ); $args += $finder; @@ -1582,7 +1582,7 @@ public function get( 'get-%s-%s-%s', $this->getConnection()->configName(), $this->getTable(), - json_encode($primaryKey, JSON_THROW_ON_ERROR) + json_encode($primaryKey, JSON_THROW_ON_ERROR), ); } $query->cache($cacheKey, $cache); @@ -1665,7 +1665,7 @@ public function findOrCreate( $entity = $this->_executeTransaction( fn () => $this->_processFindOrCreate($search, $callback, $options->getArrayCopy()), - $options['atomic'] + $options['atomic'], ); if ($entity && $this->_transactionCommitted($options['atomic'], true)) { @@ -1858,7 +1858,7 @@ public function exists(QueryExpression|Closure|array|string|null $conditions): b ->where($conditions) ->limit(1) ->disableHydration() - ->toArray() + ->toArray(), ); } @@ -1971,7 +1971,7 @@ public function save( $success = $this->_executeTransaction( fn () => $this->_processSave($entity, $options), - $options['atomic'] + $options['atomic'], ); if ($success) { @@ -2052,8 +2052,8 @@ protected function _processSave(EntityInterface $entity, ArrayObject $options): $result instanceof EntityInterface, sprintf( 'The beforeSave callback must return `false` or `EntityInterface` instance. Got `%s` instead.', - get_debug_type($result) - ) + get_debug_type($result), + ), ); } @@ -2064,7 +2064,7 @@ protected function _processSave(EntityInterface $entity, ArrayObject $options): $this, $entity, $options['associated'], - ['_primary' => false] + $options->getArrayCopy() + ['_primary' => false] + $options->getArrayCopy(), ); if (!$saved && $options['atomic']) { @@ -2108,7 +2108,7 @@ protected function _onSaveSuccess(EntityInterface $entity, ArrayObject $options) $this, $entity, $options['associated'], - ['_primary' => false] + $options->getArrayCopy() + ['_primary' => false] + $options->getArrayCopy(), ); if (!$success && $options['atomic']) { @@ -2145,7 +2145,7 @@ protected function _insert(EntityInterface $entity, array $data): EntityInterfac if (!$primary) { $msg = sprintf( 'Cannot insert row in `%s` table, it has no primary key.', - $this->getTable() + $this->getTable(), ); throw new DatabaseException($msg); } @@ -2169,7 +2169,7 @@ protected function _insert(EntityInterface $entity, array $data): EntityInterfac $msg .= sprintf( 'Got (%s), expecting (%s)', implode(', ', $filteredKeys + $entity->extract(array_keys($primary))), - implode(', ', array_keys($primary)) + implode(', ', array_keys($primary)), ); throw new DatabaseException($msg); } @@ -2325,7 +2325,7 @@ protected function _saveMany( 'atomic' => true, 'checkRules' => true, '_primary' => true, - ] + ], ); $options['_cleanOnSuccess'] = false; @@ -2438,7 +2438,7 @@ public function delete(EntityInterface $entity, array $options = []): bool $success = $this->_executeTransaction( fn () => $this->_processDelete($entity, $options), - $options['atomic'] + $options['atomic'], ); if ($success && $this->_transactionCommitted($options['atomic'], $options['_primary'])) { @@ -2593,7 +2593,7 @@ protected function _processDelete(EntityInterface $entity, ArrayObject $options) $success = $this->_associations->cascadeDelete( $entity, - ['_primary' => false] + $options->getArrayCopy() + ['_primary' => false] + $options->getArrayCopy(), ); if (!$success) { return $success; @@ -2656,7 +2656,7 @@ public function callFinder(string $type, SelectQuery $query, mixed ...$args): Se throw new BadMethodCallException(sprintf( 'Unknown finder method `%s` on `%s`.', $type, - static::class + static::class, )); } @@ -2692,7 +2692,7 @@ public function invokeFinder(Closure $callable, SelectQuery $query, array $args) deprecationWarning( '5.0.0', 'Calling finders with options arrays is deprecated.' - . ' Update your finder methods to used named arguments instead.' + . ' Update your finder methods to used named arguments instead.', ); $args = $args[0]; } @@ -2717,7 +2717,7 @@ public function invokeFinder(Closure $callable, SelectQuery $query, array $args) deprecationWarning( '5.0.0', "Calling `{$reflected->getName()}` finder with options array is deprecated." - . ' Use named arguments instead.' + . ' Use named arguments instead.', ); $args = $args[0]; @@ -2779,7 +2779,7 @@ protected function _dynamicFinder(string $method, array $args): SelectQuery throw new BadMethodCallException(sprintf( 'Not enough arguments for magic finder. Got %s required %s', count($args), - count($fields) + count($fields), )); } foreach ($fields as $field) { @@ -2791,7 +2791,7 @@ protected function _dynamicFinder(string $method, array $args): SelectQuery if ($hasOr && $hasAnd) { throw new BadMethodCallException( - 'Cannot mix "and" & "or" in a magic finder. Use find() instead.' + 'Cannot mix "and" & "or" in a magic finder. Use find() instead.', ); } @@ -2831,7 +2831,7 @@ public function __call(string $method, array $args): mixed } throw new BadMethodCallException( - sprintf('Unknown method `%s` called on `%s`', $method, static::class) + sprintf('Unknown method `%s` called on `%s`', $method, static::class), ); } @@ -2852,7 +2852,7 @@ public function __get(string $property): Association 'You have not defined the `%s` association on `%s`.', $property, $property, - static::class + static::class, )); } @@ -3143,11 +3143,11 @@ public function validateUnique(mixed $value, array $options = [], ?array $contex 'useSetters' => false, 'markNew' => $context['newRecord'], 'source' => $this->getRegistryAlias(), - ] + ], ); $fields = array_merge( [$context['field']], - isset($options['scope']) ? (array)$options['scope'] : [] + isset($options['scope']) ? (array)$options['scope'] : [], ); $values = $entity->extract($fields); foreach ($values as $field) { @@ -3156,7 +3156,6 @@ public function validateUnique(mixed $value, array $options = [], ?array $contex } } $class = static::IS_UNIQUE_CLASS; - /** @var \Cake\ORM\Rule\IsUnique $rule */ $rule = new $class($fields, $options); return $rule($entity, ['repository' => $this]); From efe340aeaf111da3ba067af49d86d89e3509d056 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Sat, 30 Nov 2024 16:39:48 +0100 Subject: [PATCH 2002/2059] Fix CS as per trailing comma rules. (#18047) --- Association.php | 2 +- Association/BelongsToMany.php | 4 ++-- Association/HasMany.php | 4 ++-- Association/Loader/SelectLoader.php | 2 +- AssociationCollection.php | 4 ++-- Behavior/CounterCacheBehavior.php | 2 +- Behavior/TreeBehavior.php | 4 ++-- EagerLoader.php | 2 +- Exception/PersistenceFailedException.php | 2 +- LazyEagerLoader.php | 2 +- Marshaller.php | 6 +++--- Query/SelectQuery.php | 4 ++-- RulesChecker.php | 10 +++++----- Table.php | 18 +++++++++--------- 14 files changed, 33 insertions(+), 33 deletions(-) diff --git a/Association.php b/Association.php index c3d5cfa0..ac3d30d2 100644 --- a/Association.php +++ b/Association.php @@ -878,7 +878,7 @@ public function exists(ExpressionInterface|Closure|array|string|null $conditions */ public function updateAll( QueryExpression|Closure|array|string $fields, - QueryExpression|Closure|array|string|null $conditions + QueryExpression|Closure|array|string|null $conditions, ): int { $expression = $this->find() ->where($conditions) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 2d5c102e..0a05f8c6 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -724,7 +724,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []): En protected function _saveTarget( EntityInterface $parentEntity, array $entities, - array $options + array $options, ): EntityInterface|false { $joinAssociations = false; if (isset($options['associated']) && is_array($options['associated'])) { @@ -1272,7 +1272,7 @@ protected function _diffLinks( SelectQuery $existing, array $jointEntities, array $targetEntities, - array $options = [] + array $options = [], ): array|false { $junction = $this->junction(); $target = $this->getTarget(); diff --git a/Association/HasMany.php b/Association/HasMany.php index efbafc32..e7780c8e 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -212,7 +212,7 @@ protected function _saveTarget( array $foreignKeyReference, EntityInterface $parentEntity, array $entities, - array $options + array $options, ): bool { $foreignKey = array_keys($foreignKeyReference); $table = $this->getTarget(); @@ -471,7 +471,7 @@ protected function _unlinkAssociated( EntityInterface $entity, Table $target, iterable $remainingEntities = [], - array $options = [] + array $options = [], ): bool { $primaryKey = (array)$target->getPrimaryKey(); $exclusions = new Collection($remainingEntities); diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 5ee6b8ed..37eed5b5 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -355,7 +355,7 @@ protected function _createTupleCondition( SelectQuery $query, array $keys, mixed $filter, - string $operator + string $operator, ): TupleComparison { $types = []; $defaults = $query->getDefaultTypes(); diff --git a/AssociationCollection.php b/AssociationCollection.php index 1044bb3f..b5788041 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -268,7 +268,7 @@ protected function _saveAssociations( EntityInterface $entity, array $associations, array $options, - bool $owningSide + bool $owningSide, ): bool { unset($options['associated']); foreach ($associations as $alias => $nested) { @@ -309,7 +309,7 @@ protected function _save( Association $association, EntityInterface $entity, array $nested, - array $options + array $options, ): bool { if (!$entity->isDirty($association->getProperty())) { return true; diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index b8475d52..73255d20 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -220,7 +220,7 @@ protected function _processAssociation( EventInterface $event, EntityInterface $entity, Association $assoc, - array $settings + array $settings, ): void { /** @var list $foreignKeys */ $foreignKeys = (array)$assoc->getForeignKey(); diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index d2c64d01..8303c98d 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -489,7 +489,7 @@ public function findTreeList( SelectQuery $query, Closure|string|null $keyPath = null, Closure|string|null $valuePath = null, - ?string $spacer = null + ?string $spacer = null, ): SelectQuery { $left = $this->_table->aliasField($this->getConfig('left')); @@ -516,7 +516,7 @@ public function formatTreeList( SelectQuery $query, Closure|string|null $keyPath = null, Closure|string|null $valuePath = null, - ?string $spacer = null + ?string $spacer = null, ): SelectQuery { return $query->formatResults( function (CollectionInterface $results) use ($keyPath, $valuePath, $spacer) { diff --git a/EagerLoader.php b/EagerLoader.php index 7c17cf3c..d1323481 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -755,7 +755,7 @@ public function addToJoinsMap( string $alias, Association $assoc, bool $asMatching = false, - ?string $targetProperty = null + ?string $targetProperty = null, ): void { $this->_joinsMap[$alias] = new EagerLoadable($alias, [ 'aliasPath' => $alias, diff --git a/Exception/PersistenceFailedException.php b/Exception/PersistenceFailedException.php index 68881429..1e56c6a8 100644 --- a/Exception/PersistenceFailedException.php +++ b/Exception/PersistenceFailedException.php @@ -49,7 +49,7 @@ public function __construct( EntityInterface $entity, array|string $message, ?int $code = null, - ?Throwable $previous = null + ?Throwable $previous = null, ) { $this->_entity = $entity; if (is_array($message)) { diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 84e6456d..7a0b0f86 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -141,7 +141,7 @@ protected function _injectResults( array $entities, SelectQuery $query, array $associations, - Table $source + Table $source, ): array { $injected = []; $properties = $this->_getPropertyMap($source, $associations); diff --git a/Marshaller.php b/Marshaller.php index 0dc8f020..304f4b4c 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -108,10 +108,10 @@ protected function _buildPropertyMap(array $data, array $options): array if (isset($options['isMerge'])) { $callback = function ( $value, - EntityInterface $entity + EntityInterface $entity, ) use ( $assoc, - $nested + $nested, ): array|EntityInterface|null { $options = $nested + ['associated' => [], 'association' => $assoc]; @@ -756,7 +756,7 @@ protected function _mergeAssociation( EntityInterface|array|null $original, Association $assoc, mixed $value, - array $options + array $options, ): EntityInterface|array|null { if (!$original) { return $this->_marshalAssociation($assoc, $value, $options); diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index 0fd8bd0a..6b506ad2 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -796,7 +796,7 @@ protected function _decorateResults(iterable $result): ResultSetInterface */ public function select( ExpressionInterface|Table|Association|Closure|array|string|float|int $fields = [], - bool $overwrite = false + bool $overwrite = false, ) { if ($fields instanceof Association) { $fields = $fields->getTarget(); @@ -824,7 +824,7 @@ public function select( * @return $this */ public function selectAlso( - ExpressionInterface|Table|Association|Closure|array|string|float|int $fields + ExpressionInterface|Table|Association|Closure|array|string|float|int $fields, ) { $this->select($fields); $this->_autoFields = true; diff --git a/RulesChecker.php b/RulesChecker.php index 00cca995..15c0cf96 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -100,7 +100,7 @@ public function isUnique(array $fields, array|string|null $message = null): Rule public function existsIn( array|string $field, Table|Association|string $table, - array|string|null $message = null + array|string|null $message = null, ): RuleInvoker { $options = []; if (is_array($message)) { @@ -144,7 +144,7 @@ public function existsIn( public function isLinkedTo( Association|string $association, ?string $field = null, - ?string $message = null + ?string $message = null, ): RuleInvoker { return $this->_addLinkConstraintRule( $association, @@ -177,7 +177,7 @@ public function isLinkedTo( public function isNotLinkedTo( Association|string $association, ?string $field = null, - ?string $message = null + ?string $message = null, ): RuleInvoker { return $this->_addLinkConstraintRule( $association, @@ -210,7 +210,7 @@ protected function _addLinkConstraintRule( ?string $errorField, ?string $message, string $linkStatus, - string $ruleName + string $ruleName, ): RuleInvoker { if ($association instanceof Association) { $associationAlias = $association->getName(); @@ -265,7 +265,7 @@ public function validCount( string $field, int $count = 0, string $operator = '>', - ?string $message = null + ?string $message = null, ): RuleInvoker { if (!$message) { if ($this->_useI18n) { diff --git a/Table.php b/Table.php index 7e987576..fe521862 100644 --- a/Table.php +++ b/Table.php @@ -1382,7 +1382,7 @@ public function findList( Closure|array|string|null $keyField = null, Closure|array|string|null $valueField = null, Closure|array|string|null $groupField = null, - string $valueSeparator = ' ' + string $valueSeparator = ' ', ): SelectQuery { $keyField ??= $this->getPrimaryKey(); $valueField ??= $this->getDisplayField(); @@ -1442,7 +1442,7 @@ public function findThreaded( SelectQuery $query, Closure|array|string|null $keyField = null, Closure|array|string $parentField = 'parent_id', - string $nestingKey = 'children' + string $nestingKey = 'children', ): SelectQuery { $keyField ??= $this->getPrimaryKey(); @@ -1525,7 +1525,7 @@ public function get( array|string $finder = 'all', CacheInterface|string|null $cache = null, Closure|string|null $cacheKey = null, - mixed ...$args + mixed ...$args, ): EntityInterface { if ($primaryKey === null) { throw new InvalidPrimaryKeyException(sprintf( @@ -1656,7 +1656,7 @@ protected function _transactionCommitted(bool $atomic, bool $primary): bool public function findOrCreate( SelectQuery|callable|array $search, ?callable $callback = null, - array $options = [] + array $options = [], ): EntityInterface { $options = new ArrayObject($options + [ 'atomic' => true, @@ -1691,7 +1691,7 @@ public function findOrCreate( protected function _processFindOrCreate( SelectQuery|callable|array $search, ?callable $callback = null, - array $options = [] + array $options = [], ): EntityInterface|array { $query = $this->_getFindOrCreateQuery($search); @@ -1814,7 +1814,7 @@ public function subquery(): SelectQuery */ public function updateAll( QueryExpression|Closure|array|string $fields, - QueryExpression|Closure|array|string|null $conditions + QueryExpression|Closure|array|string|null $conditions, ): int { $statement = $this->updateQuery() ->set($fields) @@ -1950,7 +1950,7 @@ public function exists(QueryExpression|Closure|array|string|null $conditions): b */ public function save( EntityInterface $entity, - array $options = [] + array $options = [], ): EntityInterface|false { $options = new ArrayObject($options + [ 'atomic' => true, @@ -2282,7 +2282,7 @@ protected function _update(EntityInterface $entity, array $data): EntityInterfac */ public function saveMany( iterable $entities, - array $options = [] + array $options = [], ): iterable|false { try { return $this->_saveMany($entities, $options); @@ -2318,7 +2318,7 @@ public function saveManyOrFail(iterable $entities, array $options = []): iterabl */ protected function _saveMany( iterable $entities, - array $options = [] + array $options = [], ): iterable { $options = new ArrayObject( $options + [ From 2f5a302748c5944938c436551b857553d562a83f Mon Sep 17 00:00:00 2001 From: mscherer Date: Sat, 30 Nov 2024 16:48:48 +0100 Subject: [PATCH 2003/2059] Merge 5.x into 5.next --- Association.php | 2 +- Association/BelongsToMany.php | 4 ++-- Association/HasMany.php | 4 ++-- Association/Loader/SelectLoader.php | 2 +- AssociationCollection.php | 4 ++-- Behavior/CounterCacheBehavior.php | 4 ++-- Behavior/TreeBehavior.php | 4 ++-- EagerLoader.php | 2 +- Exception/PersistenceFailedException.php | 2 +- LazyEagerLoader.php | 2 +- Marshaller.php | 6 +++--- Query/SelectQuery.php | 4 ++-- RulesChecker.php | 10 +++++----- Table.php | 18 +++++++++--------- 14 files changed, 34 insertions(+), 34 deletions(-) diff --git a/Association.php b/Association.php index 4553baa5..1f5730b2 100644 --- a/Association.php +++ b/Association.php @@ -878,7 +878,7 @@ public function exists(ExpressionInterface|Closure|array|string|null $conditions */ public function updateAll( QueryExpression|Closure|array|string $fields, - QueryExpression|Closure|array|string|null $conditions + QueryExpression|Closure|array|string|null $conditions, ): int { $expression = $this->find() ->where($conditions) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 3efe7736..24c0b7c2 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -724,7 +724,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []): En protected function _saveTarget( EntityInterface $parentEntity, array $entities, - array $options + array $options, ): EntityInterface|false { $joinAssociations = false; if (isset($options['associated']) && is_array($options['associated'])) { @@ -1272,7 +1272,7 @@ protected function _diffLinks( SelectQuery $existing, array $jointEntities, array $targetEntities, - array $options = [] + array $options = [], ): array|false { $junction = $this->junction(); $target = $this->getTarget(); diff --git a/Association/HasMany.php b/Association/HasMany.php index 94b30bb9..43b094d4 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -212,7 +212,7 @@ protected function _saveTarget( array $foreignKeyReference, EntityInterface $parentEntity, array $entities, - array $options + array $options, ): bool { $foreignKey = array_keys($foreignKeyReference); $table = $this->getTarget(); @@ -471,7 +471,7 @@ protected function _unlinkAssociated( EntityInterface $entity, Table $target, iterable $remainingEntities = [], - array $options = [] + array $options = [], ): bool { $primaryKey = (array)$target->getPrimaryKey(); $exclusions = new Collection($remainingEntities); diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 81a6dbf7..35113ab6 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -355,7 +355,7 @@ protected function _createTupleCondition( SelectQuery $query, array $keys, mixed $filter, - string $operator + string $operator, ): TupleComparison { $types = []; $defaults = $query->getDefaultTypes(); diff --git a/AssociationCollection.php b/AssociationCollection.php index 00376670..b91295a5 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -268,7 +268,7 @@ protected function _saveAssociations( EntityInterface $entity, array $associations, array $options, - bool $owningSide + bool $owningSide, ): bool { unset($options['associated']); foreach ($associations as $alias => $nested) { @@ -309,7 +309,7 @@ protected function _save( Association $association, EntityInterface $entity, array $nested, - array $options + array $options, ): bool { if (!$entity->isDirty($association->getProperty())) { return true; diff --git a/Behavior/CounterCacheBehavior.php b/Behavior/CounterCacheBehavior.php index fd55dc6c..67464821 100644 --- a/Behavior/CounterCacheBehavior.php +++ b/Behavior/CounterCacheBehavior.php @@ -247,7 +247,7 @@ protected function updateCountForAssociation( string $field, array $config, int $limit = 100, - ?int $page = null + ?int $page = null, ): void { $primaryKeys = (array)$assoc->getBindingKey(); /** @var array $foreignKeys */ @@ -317,7 +317,7 @@ protected function _processAssociation( EventInterface $event, EntityInterface $entity, Association $assoc, - array $settings + array $settings, ): void { /** @var array $foreignKeys */ $foreignKeys = (array)$assoc->getForeignKey(); diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index d2c64d01..8303c98d 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -489,7 +489,7 @@ public function findTreeList( SelectQuery $query, Closure|string|null $keyPath = null, Closure|string|null $valuePath = null, - ?string $spacer = null + ?string $spacer = null, ): SelectQuery { $left = $this->_table->aliasField($this->getConfig('left')); @@ -516,7 +516,7 @@ public function formatTreeList( SelectQuery $query, Closure|string|null $keyPath = null, Closure|string|null $valuePath = null, - ?string $spacer = null + ?string $spacer = null, ): SelectQuery { return $query->formatResults( function (CollectionInterface $results) use ($keyPath, $valuePath, $spacer) { diff --git a/EagerLoader.php b/EagerLoader.php index 7c17cf3c..d1323481 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -755,7 +755,7 @@ public function addToJoinsMap( string $alias, Association $assoc, bool $asMatching = false, - ?string $targetProperty = null + ?string $targetProperty = null, ): void { $this->_joinsMap[$alias] = new EagerLoadable($alias, [ 'aliasPath' => $alias, diff --git a/Exception/PersistenceFailedException.php b/Exception/PersistenceFailedException.php index 68881429..1e56c6a8 100644 --- a/Exception/PersistenceFailedException.php +++ b/Exception/PersistenceFailedException.php @@ -49,7 +49,7 @@ public function __construct( EntityInterface $entity, array|string $message, ?int $code = null, - ?Throwable $previous = null + ?Throwable $previous = null, ) { $this->_entity = $entity; if (is_array($message)) { diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 84e6456d..7a0b0f86 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -141,7 +141,7 @@ protected function _injectResults( array $entities, SelectQuery $query, array $associations, - Table $source + Table $source, ): array { $injected = []; $properties = $this->_getPropertyMap($source, $associations); diff --git a/Marshaller.php b/Marshaller.php index a19ad95c..702ea990 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -108,10 +108,10 @@ protected function _buildPropertyMap(array $data, array $options): array if (isset($options['isMerge'])) { $callback = function ( $value, - EntityInterface $entity + EntityInterface $entity, ) use ( $assoc, - $nested + $nested, ): array|EntityInterface|null { $options = $nested + ['associated' => [], 'association' => $assoc]; @@ -756,7 +756,7 @@ protected function _mergeAssociation( EntityInterface|array|null $original, Association $assoc, mixed $value, - array $options + array $options, ): EntityInterface|array|null { if (!$original) { return $this->_marshalAssociation($assoc, $value, $options); diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index 0fd8bd0a..6b506ad2 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -796,7 +796,7 @@ protected function _decorateResults(iterable $result): ResultSetInterface */ public function select( ExpressionInterface|Table|Association|Closure|array|string|float|int $fields = [], - bool $overwrite = false + bool $overwrite = false, ) { if ($fields instanceof Association) { $fields = $fields->getTarget(); @@ -824,7 +824,7 @@ public function select( * @return $this */ public function selectAlso( - ExpressionInterface|Table|Association|Closure|array|string|float|int $fields + ExpressionInterface|Table|Association|Closure|array|string|float|int $fields, ) { $this->select($fields); $this->_autoFields = true; diff --git a/RulesChecker.php b/RulesChecker.php index 00cca995..15c0cf96 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -100,7 +100,7 @@ public function isUnique(array $fields, array|string|null $message = null): Rule public function existsIn( array|string $field, Table|Association|string $table, - array|string|null $message = null + array|string|null $message = null, ): RuleInvoker { $options = []; if (is_array($message)) { @@ -144,7 +144,7 @@ public function existsIn( public function isLinkedTo( Association|string $association, ?string $field = null, - ?string $message = null + ?string $message = null, ): RuleInvoker { return $this->_addLinkConstraintRule( $association, @@ -177,7 +177,7 @@ public function isLinkedTo( public function isNotLinkedTo( Association|string $association, ?string $field = null, - ?string $message = null + ?string $message = null, ): RuleInvoker { return $this->_addLinkConstraintRule( $association, @@ -210,7 +210,7 @@ protected function _addLinkConstraintRule( ?string $errorField, ?string $message, string $linkStatus, - string $ruleName + string $ruleName, ): RuleInvoker { if ($association instanceof Association) { $associationAlias = $association->getName(); @@ -265,7 +265,7 @@ public function validCount( string $field, int $count = 0, string $operator = '>', - ?string $message = null + ?string $message = null, ): RuleInvoker { if (!$message) { if ($this->_useI18n) { diff --git a/Table.php b/Table.php index 7feb4bb3..d7c72610 100644 --- a/Table.php +++ b/Table.php @@ -1382,7 +1382,7 @@ public function findList( Closure|array|string|null $keyField = null, Closure|array|string|null $valueField = null, Closure|array|string|null $groupField = null, - string $valueSeparator = ' ' + string $valueSeparator = ' ', ): SelectQuery { $keyField ??= $this->getPrimaryKey(); $valueField ??= $this->getDisplayField(); @@ -1442,7 +1442,7 @@ public function findThreaded( SelectQuery $query, Closure|array|string|null $keyField = null, Closure|array|string $parentField = 'parent_id', - string $nestingKey = 'children' + string $nestingKey = 'children', ): SelectQuery { $keyField ??= $this->getPrimaryKey(); @@ -1525,7 +1525,7 @@ public function get( array|string $finder = 'all', CacheInterface|string|null $cache = null, Closure|string|null $cacheKey = null, - mixed ...$args + mixed ...$args, ): EntityInterface { if ($primaryKey === null) { throw new InvalidPrimaryKeyException(sprintf( @@ -1656,7 +1656,7 @@ protected function _transactionCommitted(bool $atomic, bool $primary): bool public function findOrCreate( SelectQuery|callable|array $search, ?callable $callback = null, - array $options = [] + array $options = [], ): EntityInterface { $options = new ArrayObject($options + [ 'atomic' => true, @@ -1691,7 +1691,7 @@ public function findOrCreate( protected function _processFindOrCreate( SelectQuery|callable|array $search, ?callable $callback = null, - array $options = [] + array $options = [], ): EntityInterface|array { $query = $this->_getFindOrCreateQuery($search); @@ -1814,7 +1814,7 @@ public function subquery(): SelectQuery */ public function updateAll( QueryExpression|Closure|array|string $fields, - QueryExpression|Closure|array|string|null $conditions + QueryExpression|Closure|array|string|null $conditions, ): int { $statement = $this->updateQuery() ->set($fields) @@ -1950,7 +1950,7 @@ public function exists(QueryExpression|Closure|array|string|null $conditions): b */ public function save( EntityInterface $entity, - array $options = [] + array $options = [], ): EntityInterface|false { $options = new ArrayObject($options + [ 'atomic' => true, @@ -2282,7 +2282,7 @@ protected function _update(EntityInterface $entity, array $data): EntityInterfac */ public function saveMany( iterable $entities, - array $options = [] + array $options = [], ): iterable|false { try { return $this->_saveMany($entities, $options); @@ -2318,7 +2318,7 @@ public function saveManyOrFail(iterable $entities, array $options = []): iterabl */ protected function _saveMany( iterable $entities, - array $options = [] + array $options = [], ): iterable { $options = new ArrayObject( $options + [ From 86fea71a1b679ceee554682f1e071c0455abbcaf Mon Sep 17 00:00:00 2001 From: mscherer Date: Sat, 30 Nov 2024 19:47:29 +0100 Subject: [PATCH 2004/2059] Fix some more typos. --- Behavior/Translate/TranslateStrategyInterface.php | 2 +- Behavior/Translate/TranslateStrategyTrait.php | 2 +- Behavior/TranslateBehavior.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Behavior/Translate/TranslateStrategyInterface.php b/Behavior/Translate/TranslateStrategyInterface.php index bd39605d..7d36e11f 100644 --- a/Behavior/Translate/TranslateStrategyInterface.php +++ b/Behavior/Translate/TranslateStrategyInterface.php @@ -49,7 +49,7 @@ public function getTranslationTable(): Table; * * @param string|null $locale The locale to use for fetching and saving * records. Pass `null` in order to unset the current locale, and to make - * the behavior fall back to using the globally configured locale. + * the behavior falls back to using the globally configured locale. * @return $this */ public function setLocale(?string $locale); diff --git a/Behavior/Translate/TranslateStrategyTrait.php b/Behavior/Translate/TranslateStrategyTrait.php index afb730da..15bdfd95 100644 --- a/Behavior/Translate/TranslateStrategyTrait.php +++ b/Behavior/Translate/TranslateStrategyTrait.php @@ -71,7 +71,7 @@ public function getTranslationTable(): Table * * @param string|null $locale The locale to use for fetching and saving * records. Pass `null` in order to unset the current locale, and to make - * the behavior fall back to using the globally configured locale. + * the behavior falls back to using the globally configured locale. * @return $this */ public function setLocale(?string $locale) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index fb866f58..df46212c 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -279,7 +279,7 @@ public function buildMarshalMap(Marshaller $marshaller, array $map, array $optio * that matter)! * * @param string|null $locale The locale to use for fetching and saving records. Pass `null` - * in order to unset the current locale, and to make the behavior fall back to using the + * in order to unset the current locale, and to make the behavior falls back to using the * globally configured locale. * @return $this * @see \Cake\ORM\Behavior\TranslateBehavior::getLocale() From 110f24f3be11f8466dcebe4651d29798c268e3a5 Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Fri, 20 Dec 2024 22:03:03 +0100 Subject: [PATCH 2005/2059] update stan --- phpstan.neon.dist | 6 ------ 1 file changed, 6 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index d01208c4..684a761c 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -18,9 +18,3 @@ parameters: - "#^PHPDoc tag @return with type Cake\\\\ORM\\\\Query\\\\SelectQuery\\ is not subtype of native type static\\(Cake\\\\ORM\\\\Query\\\\SelectQuery\\\\)\\.$#" - "#^Method Cake\\\\ORM\\\\Query\\\\SelectQuery\\:\\:find\\(\\) should return static\\(Cake\\\\ORM\\\\Query\\\\SelectQuery\\\\) but returns Cake\\\\ORM\\\\Query\\\\SelectQuery\\\\.$#" - '#^PHPDoc tag @var with type callable\(\): mixed is not subtype of native type Closure\(string\): string\.$#' - - '#^Parameter \#1 \$fields of method Cake\\ORM\\Entity::extract\(\) expects list, non-empty-array given\.$#' - - '#^Parameter \#1 \$fields of method Cake\\ORM\\Entity::extract\(\) expects list, array given\.$#' - - '#^Parameter \#1 \$fields of method Cake\\Datasource\\EntityInterface::.*\(\) expects list, non-empty-array given\.$#' - - '#^Parameter \#1 \$fields of method Cake\\Datasource\\EntityInterface::.*\(\) expects list, array given\.$#' - - '#^Parameter \#1 \$field of method Cake\\Datasource\\EntityInterface::has\(\) expects list\|string, .+ given\.$#' - - '#^Parameter \#1 \$field of method Cake\\Datasource\\EntityInterface::unset\(\) expects list\|string, array\|string given\.$#' From 89c7358a4117c51b07b31934eadddb6513f55bc1 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 28 Dec 2024 10:52:11 +0530 Subject: [PATCH 2006/2059] Add branch alias for 5.next. Update composer.json for sub packages. --- composer.json | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/composer.json b/composer.json index 456315ac..e4d96a65 100644 --- a/composer.json +++ b/composer.json @@ -24,17 +24,17 @@ }, "require": { "php": ">=8.1", - "cakephp/collection": "^5.1", - "cakephp/core": "^5.1", - "cakephp/datasource": "^5.1", - "cakephp/database": "^5.1", - "cakephp/event": "^5.1", - "cakephp/utility": "^5.1", - "cakephp/validation": "^5.1" + "cakephp/collection": "^5.2", + "cakephp/core": "^5.2", + "cakephp/datasource": "^5.2", + "cakephp/database": "^5.2", + "cakephp/event": "^5.2", + "cakephp/utility": "^5.2", + "cakephp/validation": "^5.2" }, - "suggest": { - "cakephp/cache": "If you decide to use Query caching.", - "cakephp/i18n": "If you are using Translate/TimestampBehavior or Chronos types." + "require-dev": { + "cakephp/cache": "^5.2", + "cakephp/i18n": "^5.2" }, "autoload": { "psr-4": { @@ -44,8 +44,10 @@ "bootstrap.php" ] }, - "require-dev": { - "cakephp/cache": "^5.1", - "cakephp/i18n": "^5.1" - } + "suggest": { + "cakephp/cache": "If you decide to use Query caching.", + "cakephp/i18n": "If you are using Translate/TimestampBehavior or Chronos types." + }, + "minimum-stability": "dev", + "prefer-stable": true } From acd6c30e28c7003c9a385dec51848b06527cd9e9 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 6 Jan 2025 12:40:18 +0530 Subject: [PATCH 2007/2059] Avoid empty() usage --- Entity.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Entity.php b/Entity.php index b5ca0f92..210bca69 100644 --- a/Entity.php +++ b/Entity.php @@ -57,7 +57,7 @@ public function __construct(array $properties = [], array $options = []) 'source' => null, ]; - if (!empty($options['source'])) { + if ($options['source'] !== null) { $this->setSource($options['source']); } From 8eac25d954a2c6cec82a4bdad37b430f14571367 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 8 Jan 2025 13:42:40 +0530 Subject: [PATCH 2008/2059] Use the entity class configured for the table instead of ORM\Entity. A table could be using a custom implementation of EntityInterface instead of ORM\Entity. --- Behavior/Translate/EavStrategy.php | 9 +++++---- Table.php | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index f93a6c71..efcb07aa 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -22,7 +22,6 @@ use Cake\Core\InstanceConfigTrait; use Cake\Datasource\EntityInterface; use Cake\Event\EventInterface; -use Cake\ORM\Entity; use Cake\ORM\Locator\LocatorAwareTrait; use Cake\ORM\Query\SelectQuery; use Cake\ORM\Table; @@ -314,9 +313,10 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array $modified[$field] = $translation; } + $entityClass = $this->translationTable->getEntityClass(); $new = array_diff_key($values, $modified); foreach ($new as $field => $content) { - $new[$field] = new Entity(compact('locale', 'field', 'content', 'model'), [ + $new[$field] = new $entityClass(compact('locale', 'field', 'content', 'model'), [ 'useSetters' => false, 'markNew' => true, ]); @@ -424,9 +424,9 @@ public function groupTranslations(CollectionInterface $results): CollectionInter } $grouped = new Collection($translations); + $entityClass = $this->table->getEntityClass(); $result = []; foreach ($grouped->combine('field', 'content', 'locale') as $locale => $keys) { - $entityClass = $this->table->getEntityClass(); $translation = new $entityClass($keys + ['locale' => $locale], [ 'markNew' => false, 'useSetters' => false, @@ -466,6 +466,7 @@ protected function bundleTranslatedFields(EntityInterface $entity): void $key = $entity->get((string)current($primaryKey)); $find = []; $contents = []; + $entityClass = $this->translationTable->getEntityClass(); foreach ($translations as $lang => $translation) { foreach ($fields as $field) { @@ -473,7 +474,7 @@ protected function bundleTranslatedFields(EntityInterface $entity): void continue; } $find[] = ['locale' => $lang, 'field' => $field, 'foreign_key IS' => $key]; - $contents[] = new Entity(['content' => $translation->get($field)], [ + $contents[] = new $entityClass(['content' => $translation->get($field)], [ 'useSetters' => false, ]); } diff --git a/Table.php b/Table.php index fe521862..92754eef 100644 --- a/Table.php +++ b/Table.php @@ -3137,7 +3137,7 @@ public function validateUnique(mixed $value, array $options, ?array $context = n if ($context === null) { $context = $options; } - $entity = new Entity( + $entity = new ($this->getEntityClass())( $context['data'], [ 'useSetters' => false, From 1976f2b0d26a2824faee6a2e3436bc38ff6f8c5d Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 8 Jan 2025 17:43:35 +0530 Subject: [PATCH 2009/2059] Fix creation of new entity for the shadow table. Incorrect options were being passed for the `newEntity()` call. --- Behavior/Translate/ShadowTableStrategy.php | 2 +- Behavior/TranslateBehavior.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 6ba555ee..4540263d 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -421,7 +421,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array if ($translation) { $translation->set($values); } else { - $translation = $this->translationTable->newEntity( + $translation = new ($this->translationTable->getEntityClass())( $where + $values, [ 'useSetters' => false, diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index df46212c..0198fddb 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -57,6 +57,7 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface 'setLocale' => 'setLocale', 'getLocale' => 'getLocale', 'translationField' => 'translationField', + 'getStrategy' => 'getStrategy', ], 'fields' => [], 'defaultLocale' => null, From 9e2d8bcca737d474e27547f8a7bf479c5fcbb29d Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 12 Jan 2025 19:57:39 +0530 Subject: [PATCH 2010/2059] Avoid empty() calls --- Association.php | 6 ++---- Table.php | 54 +++++++++++++++---------------------------------- 2 files changed, 18 insertions(+), 42 deletions(-) diff --git a/Association.php b/Association.php index ac3d30d2..63600e2a 100644 --- a/Association.php +++ b/Association.php @@ -230,9 +230,7 @@ public function __construct(string $alias, array $options = []) } } - if (empty($this->_className)) { - $this->_className = $alias; - } + $this->_className ??= $alias; [, $name] = pluginSplit($alias); $this->_name = $name; @@ -710,7 +708,7 @@ public function attachTo(SelectQuery $query, array $options = []): void $options['includeFields'] = false; } - if (!empty($options['foreignKey'])) { + if ($options['foreignKey']) { $joinCondition = $this->_joinCondition($options); if ($joinCondition) { $options['conditions'][] = $joinCondition; diff --git a/Table.php b/Table.php index fe521862..38fbb9f1 100644 --- a/Table.php +++ b/Table.php @@ -297,38 +297,18 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc */ public function __construct(array $config = []) { - if (!empty($config['registryAlias'])) { - $this->setRegistryAlias($config['registryAlias']); - } - if (!empty($config['table'])) { - $this->setTable($config['table']); - } - if (!empty($config['alias'])) { - $this->setAlias($config['alias']); - } - if (!empty($config['connection'])) { - $this->setConnection($config['connection']); - } - if (!empty($config['queryFactory'])) { - $this->queryFactory = $config['queryFactory']; - } - if (!empty($config['schema'])) { - $this->setSchema($config['schema']); - } - if (!empty($config['entityClass'])) { - $this->setEntityClass($config['entityClass']); - } - $eventManager = null; - $behaviors = null; - $associations = null; - if (!empty($config['eventManager'])) { - $eventManager = $config['eventManager']; - } - if (!empty($config['behaviors'])) { - $behaviors = $config['behaviors']; - } - if (!empty($config['associations'])) { - $associations = $config['associations']; + $methodConfigs = [ + 'registryAlias', + 'table', + 'alias', + 'connection', + 'schema', + 'entityClass', + ]; + foreach ($methodConfigs as $cfg) { + if (isset($config[$cfg])) { + $this->{'set' . $cfg}($config[$cfg]); + } } if (!empty($config['validator'])) { if (!is_array($config['validator'])) { @@ -339,13 +319,11 @@ public function __construct(array $config = []) } } } - $this->_eventManager = $eventManager ?: new EventManager(); - /** @var \Cake\ORM\BehaviorRegistry $behaviors */ - $this->_behaviors = $behaviors ?: new BehaviorRegistry(); + $this->_eventManager = $config['eventManager'] ?? new EventManager(); + $this->_behaviors = $config['behaviors'] ?? new BehaviorRegistry(); $this->_behaviors->setTable($this); - $this->_associations = $associations ?: new AssociationCollection(); - /** @psalm-suppress TypeDoesNotContainType */ - $this->queryFactory ??= new QueryFactory(); + $this->_associations = $config['associations'] ?? new AssociationCollection(); + $this->queryFactory = $config['queryFactory'] ?? new QueryFactory(); $this->initialize($config); From 0e5543821e8d2c1e469bb6a01d0c7a894b134670 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 12 Jan 2025 20:19:09 +0530 Subject: [PATCH 2011/2059] Avoid negation --- Table.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Table.php b/Table.php index 38fbb9f1..94b6fdfe 100644 --- a/Table.php +++ b/Table.php @@ -310,13 +310,13 @@ public function __construct(array $config = []) $this->{'set' . $cfg}($config[$cfg]); } } - if (!empty($config['validator'])) { - if (!is_array($config['validator'])) { - $this->setValidator(static::DEFAULT_VALIDATOR, $config['validator']); - } else { + if (isset($config['validator'])) { + if (is_array($config['validator'])) { foreach ($config['validator'] as $name => $validator) { $this->setValidator($name, $validator); } + } else { + $this->setValidator(static::DEFAULT_VALIDATOR, $config['validator']); } } $this->_eventManager = $config['eventManager'] ?? new EventManager(); From d9f7c0ea75d54ef0f771cba0b7b4845426478ad1 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 12 Jan 2025 22:47:15 +0530 Subject: [PATCH 2012/2059] Allow customizing the field which holds the junction table data --- Association/BelongsToMany.php | 30 ++++++++++++++++++++- Marshaller.php | 51 ++++++++++++++++++++--------------- 2 files changed, 59 insertions(+), 22 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 24c0b7c2..ff941d2d 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -300,6 +300,29 @@ public function junction(Table|string|null $table = null): Table return $this->_junctionTable = $table; } + /** + * Set the junction property name. + * + * @param string $junctionProperty Property name. + * @return $this + */ + public function setJunctionProperty(string $junctionProperty) + { + $this->_junctionProperty = $junctionProperty; + + return $this; + } + + /** + * Get the junction property naeme. + * + * @return string + */ + public function getJunctionProperty(): string + { + return $this->_junctionProperty; + } + /** * Generate reciprocal associations as necessary. * @@ -463,7 +486,7 @@ public function attachTo(SelectQuery $query, array $options = []): void $includeFields = $options['includeFields'] ?? null; - // Attach the junction table as well we need it to populate _joinData. + // Attach the junction table as well we need it to populate junction property (_joinData). $assoc = $this->getTarget()->getAssociation($junction->getAlias()); $newOptions = array_intersect_key($options, ['joinType' => 1, 'fields' => 1]); $newOptions += [ @@ -1512,5 +1535,10 @@ protected function _options(array $options): void if (isset($options['sort'])) { $this->setSort($options['sort']); } + if (isset($options['junctionProperty'])) { + assert(is_string($options['junctionProperty']), '`junctionProperty` must be a string'); + + $this->_junctionProperty = $options['junctionProperty']; + } } } diff --git a/Marshaller.php b/Marshaller.php index 702ea990..27d37c20 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -88,19 +88,24 @@ protected function _buildPropertyMap(array $data, array $options): array $key = $nested; $nested = []; } + + $stringifiedKey = (string)$key; // If the key is not a special field like _ids or _joinData // it is a missing association that we should error on. - if (!$this->_table->hasAssociation((string)$key)) { - if (!str_starts_with((string)$key, '_')) { + if (!$this->_table->hasAssociation($stringifiedKey)) { + if ( + !str_starts_with($stringifiedKey, '_') + && (!isset($options['junctionProperty']) || $options['junctionProperty'] !== $stringifiedKey) + ) { throw new InvalidArgumentException(sprintf( 'Cannot marshal data for `%s` association. It is not associated with `%s`.', - (string)$key, + $stringifiedKey, $this->_table->getAlias(), )); } continue; } - $assoc = $this->_table->getAssociation((string)$key); + $assoc = $this->_table->getAssociation($stringifiedKey); if (isset($options['forceNew'])) { $nested['forceNew'] = $options['forceNew']; @@ -396,6 +401,8 @@ protected function _belongsToMany(BelongsToMany $assoc, array $data, array $opti $records = []; $conditions = []; $primaryCount = count($primaryKey); + $junctionProperty = $assoc->getJunctionProperty(); + $options += ['junctionProperty' => $junctionProperty]; foreach ($data as $i => $row) { if (!is_array($row)) { @@ -453,15 +460,15 @@ protected function _belongsToMany(BelongsToMany $assoc, array $data, array $opti $jointMarshaller = $assoc->junction()->marshaller(); $nested = []; - if (isset($associated['_joinData'])) { - $nested = (array)$associated['_joinData']; + if (isset($associated[$junctionProperty])) { + $nested = (array)$associated[$junctionProperty]; } foreach ($records as $i => $record) { - // Update junction table data in _joinData. - if (isset($data[$i]['_joinData'])) { - $joinData = $jointMarshaller->one($data[$i]['_joinData'], $nested); - $record->set('_joinData', $joinData); + // Update junction table data in the junction property (_joinData). + if (isset($data[$i][$junctionProperty])) { + $joinData = $jointMarshaller->one($data[$i][$junctionProperty], $nested); + $record->set($junctionProperty, $joinData); } } @@ -820,7 +827,8 @@ protected function _mergeBelongsToMany(array $original, BelongsToMany $assoc, ar return []; } - if ($associated && !in_array('_joinData', $associated, true) && !isset($associated['_joinData'])) { + $junctionProperty = $assoc->getJunctionProperty(); + if ($associated && !in_array($junctionProperty, $associated, true) && !isset($associated[$junctionProperty])) { return $this->mergeMany($original, $value, $options); } @@ -828,7 +836,7 @@ protected function _mergeBelongsToMany(array $original, BelongsToMany $assoc, ar } /** - * Merge the special _joinData property into the entity set. + * Merge the special junction property (_joinData) into the entity set. * * @param array<\Cake\Datasource\EntityInterface> $original The original entities list. * @param \Cake\ORM\Association\BelongsToMany $assoc The association to marshall @@ -840,11 +848,12 @@ protected function _mergeJoinData(array $original, BelongsToMany $assoc, array $ { $associated = $options['associated'] ?? []; $extra = []; + $junctionProperty = $assoc->getJunctionProperty(); foreach ($original as $entity) { // Mark joinData as accessible so we can marshal it properly. - $entity->setAccess('_joinData', true); + $entity->setAccess($junctionProperty, true); - $joinData = $entity->get('_joinData'); + $joinData = $entity->get($junctionProperty); if ($joinData instanceof EntityInterface) { $extra[spl_object_hash($entity)] = $joinData; } @@ -854,16 +863,16 @@ protected function _mergeJoinData(array $original, BelongsToMany $assoc, array $ $marshaller = $joint->marshaller(); $nested = []; - if (isset($associated['_joinData'])) { - $nested = (array)$associated['_joinData']; + if (isset($associated[$junctionProperty])) { + $nested = (array)$associated[$junctionProperty]; } - $options['accessibleFields'] = ['_joinData' => true]; + $options['accessibleFields'] = [$junctionProperty => true]; $records = $this->mergeMany($original, $value, $options); foreach ($records as $record) { $hash = spl_object_hash($record); - $value = $record->get('_joinData'); + $value = $record->get($junctionProperty); // Already an entity, no further marshalling required. if ($value instanceof EntityInterface) { @@ -872,16 +881,16 @@ protected function _mergeJoinData(array $original, BelongsToMany $assoc, array $ // Scalar data can't be handled if (!is_array($value)) { - $record->unset('_joinData'); + $record->unset($junctionProperty); continue; } // Marshal data into the old object, or make a new joinData object. if (isset($extra[$hash])) { - $record->set('_joinData', $marshaller->merge($extra[$hash], $value, $nested)); + $record->set($junctionProperty, $marshaller->merge($extra[$hash], $value, $nested)); } else { $joinData = $marshaller->one($value, $nested); - $record->set('_joinData', $joinData); + $record->set($junctionProperty, $joinData); } } From 8eb15bae6d5bbbac5f50f3749e770314122172f6 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 20 Jan 2025 19:32:22 +0530 Subject: [PATCH 2013/2059] Update dependency constraints for the split packages. This should allow using/testing the 5.next branch of the split packages. --- composer.json | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index e4d96a65..179333c1 100644 --- a/composer.json +++ b/composer.json @@ -24,16 +24,16 @@ }, "require": { "php": ">=8.1", - "cakephp/collection": "^5.2", - "cakephp/core": "^5.2", - "cakephp/datasource": "^5.2", - "cakephp/database": "^5.2", - "cakephp/event": "^5.2", - "cakephp/utility": "^5.2", + "cakephp/collection": "5.2.*@dev", + "cakephp/core": "5.2.*@dev", + "cakephp/datasource": "5.2.*@dev", + "cakephp/database": "5.2.*@dev", + "cakephp/event": "5.2.*@dev", + "cakephp/utility": "5.2.*@dev", "cakephp/validation": "^5.2" }, "require-dev": { - "cakephp/cache": "^5.2", + "cakephp/cache": "5.2.*@dev", "cakephp/i18n": "^5.2" }, "autoload": { @@ -48,6 +48,10 @@ "cakephp/cache": "If you decide to use Query caching.", "cakephp/i18n": "If you are using Translate/TimestampBehavior or Chronos types." }, - "minimum-stability": "dev", - "prefer-stable": true + "prefer-stable": true, + "extra": { + "branch-alias": { + "dev-5.next": "5.2.x-dev" + } + } } From bffa3c07a56c1315c25dc0def39dec85107bd390 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 27 Jan 2025 12:13:42 +0530 Subject: [PATCH 2014/2059] Code cleanup. Avoid negation and use of static string. --- Query/SelectQuery.php | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index 6b506ad2..3569012a 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -35,6 +35,7 @@ use Closure; use InvalidArgumentException; use JsonSerializable; +use PDO; use Psr\SimpleCache\CacheInterface; /** @@ -1435,26 +1436,22 @@ protected function _performCount(): int $count = ['count' => $query->func()->count('*')]; - if (!$complex) { + if ($complex) { + $statement = $this->getConnection()->selectQuery() + ->select($count) + ->from(['count_source' => $query]) + ->execute(); + } else { $query->getEagerLoader()->disableAutoFields(); $statement = $query ->select($count, true) ->disableAutoFields() ->execute(); - } else { - $statement = $this->getConnection()->selectQuery() - ->select($count) - ->from(['count_source' => $query]) - ->execute(); } - $result = $statement->fetch('assoc'); - - if ($result === false) { - return 0; - } + $result = $statement->fetch(PDO::FETCH_ASSOC); - return (int)$result['count']; + return $result === false ? 0 : (int)$result['count']; } /** From c5ad16f2346a875c4f51d839e2f461ec7aa391f0 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 31 Jan 2025 12:03:07 +0530 Subject: [PATCH 2015/2059] Avoid MissingRequiredField exception in Marshaller Refs #18164 --- Marshaller.php | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 304f4b4c..e9d69c32 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -115,7 +115,12 @@ protected function _buildPropertyMap(array $data, array $options): array ): array|EntityInterface|null { $options = $nested + ['associated' => [], 'association' => $assoc]; - return $this->_mergeAssociation($entity->get($assoc->getProperty()), $assoc, $value, $options); + return $this->_mergeAssociation( + $this->fieldValue($entity, $assoc->getProperty()), + $assoc, + $value, + $options + ); }; } else { $callback = function ($value, $entity) use ($assoc, $nested): array|EntityInterface|null { @@ -582,7 +587,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) } continue; } - $original = $entity->get($key); + $original = $this->fieldValue($entity, $key); if (isset($propertyMap[$key])) { $value = $propertyMap[$key]($value, $entity); @@ -846,7 +851,7 @@ protected function _mergeJoinData(array $original, BelongsToMany $assoc, array $ // Mark joinData as accessible so we can marshal it properly. $entity->setAccess('_joinData', true); - $joinData = $entity->get('_joinData'); + $joinData = $this->fieldValue($entity, '_joinData'); if ($joinData instanceof EntityInterface) { $extra[spl_object_hash($entity)] = $joinData; } @@ -865,7 +870,7 @@ protected function _mergeJoinData(array $original, BelongsToMany $assoc, array $ $records = $this->mergeMany($original, $value, $options); foreach ($records as $record) { $hash = spl_object_hash($record); - $value = $record->get('_joinData'); + $value = $this->fieldValue($record, '_joinData'); // Already an entity, no further marshalling required. if ($value instanceof EntityInterface) { @@ -904,4 +909,19 @@ protected function dispatchAfterMarshal(EntityInterface $entity, array $data, ar $options = new ArrayObject($options); $this->_table->dispatchEvent('Model.afterMarshal', compact('entity', 'data', 'options')); } + + /** + * Get the value of a field from an entity. + * + * It checks whether the field exists in the entity before getting the value + * to avoid MissingPropertyException if `requireFieldPresence` is enabled. + * + * @param \Cake\Datasource\EntityInterface $entity The entity to extract the field from. + * @param string $field The field to extract. + * @return mixed + */ + protected function fieldValue(EntityInterface $entity, string $field): mixed + { + return $entity->has($field) ? $entity->get($field) : null; + } } From 0af49f34bdab890eaf1767654220894e508b44b9 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 31 Jan 2025 23:23:13 +0530 Subject: [PATCH 2016/2059] Bump up phpstan to v2.1 --- Behavior/TimestampBehavior.php | 1 + Query/SelectQuery.php | 1 + 2 files changed, 2 insertions(+) diff --git a/Behavior/TimestampBehavior.php b/Behavior/TimestampBehavior.php index 9ce67b6d..99932bae 100644 --- a/Behavior/TimestampBehavior.php +++ b/Behavior/TimestampBehavior.php @@ -220,6 +220,7 @@ protected function _updateField(EntityInterface $entity, string $field, bool $re sprintf('TimestampBehavior only supports columns of type `%s`.', DateTimeType::class), ); + /** @var class-string<\Cake\I18n\DateTime> $class */ $class = $type->getDateTimeClassName(); $entity->set($field, new $class($ts)); diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index 3569012a..c3796452 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -384,6 +384,7 @@ public function all(): ResultSetInterface } $this->_results = $results; + /** @phpstan-ignore-next-line */ return $this->_results; } From f671a580418b1f1e1afeee7ca4c04f512add4df9 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 31 Jan 2025 22:48:28 +0530 Subject: [PATCH 2017/2059] Optimize/cleanup marshaller code. Now `Entity::set()` itself compares the new and existing value to avoid setting a field as dirty. --- Marshaller.php | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/Marshaller.php b/Marshaller.php index 2066ff5b..e8b06d04 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -594,32 +594,9 @@ public function merge(EntityInterface $entity, array $data, array $options = []) } continue; } - $original = $this->fieldValue($entity, $key); if (isset($propertyMap[$key])) { $value = $propertyMap[$key]($value, $entity); - - // Don't dirty scalar values and objects that didn't - // change. Arrays will always be marked as dirty because - // the original/updated list could contain references to the - // same objects, even though those objects may have changed internally. - if ( - ( - is_scalar($value) - && $original === $value - ) - || ( - $value === null - && $original === $value - ) - || ( - is_object($value) - && !($value instanceof EntityInterface) - && $original == $value - ) - ) { - continue; - } } $properties[$key] = $value; } From aa5e333e287e899ae69e64527e7f5bdf2159c32a Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 2 Feb 2025 02:08:35 +0530 Subject: [PATCH 2018/2059] Fix errors in TranslateBehavior with Entity::$requireFieldPresence enabled Refs #18164 --- Behavior/Translate/EavStrategy.php | 17 +++++++++----- Behavior/Translate/ShadowTableStrategy.php | 22 ++++++++++++------- Behavior/Translate/TranslateStrategyTrait.php | 2 +- Behavior/Translate/TranslateTrait.php | 2 +- 4 files changed, 27 insertions(+), 16 deletions(-) diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index efcb07aa..ca34dfb5 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -240,7 +240,7 @@ public function beforeFind(EventInterface $event, SelectQuery $query, ArrayObjec */ public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void { - $locale = $entity->get('_locale') ?: $this->getLocale(); + $locale = $entity->has('_locale') ? $entity->get('_locale') : $this->getLocale(); $newOptions = [$this->translationTable->getAlias() => ['validate' => false]]; $options['associated'] = $newOptions + $options['associated']; @@ -252,7 +252,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array } $this->bundleTranslatedFields($entity); - $bundled = $entity->get('_i18n') ?: []; + $bundled = $entity->has('_i18n') ? $entity->get('_i18n') : []; $noBundled = count($bundled) === 0; // No additional translation records need to be saved, @@ -418,7 +418,7 @@ public function groupTranslations(CollectionInterface $results): CollectionInter if (!$row instanceof EntityInterface) { return $row; } - $translations = (array)$row->get('_i18n'); + $translations = $row->has('_i18n') ? $row->get('_i18n') : null; if (!$translations && $row->get('_translations')) { return $row; } @@ -455,15 +455,19 @@ public function groupTranslations(CollectionInterface $results): CollectionInter protected function bundleTranslatedFields(EntityInterface $entity): void { /** @var array $translations */ - $translations = (array)$entity->get('_translations'); + $translations = $entity->has('_translations') ? $entity->get('_translations') : null; if (!$translations && !$entity->isDirty('_translations')) { return; } $fields = $this->_config['fields']; - $primaryKey = (array)$this->table->getPrimaryKey(); - $key = $entity->get((string)current($primaryKey)); + if ($entity->isNew()) { + $key = null; + } else { + $primaryKey = (array)$this->table->getPrimaryKey(); + $key = $entity->get((string)current($primaryKey)); + } $find = []; $contents = []; $entityClass = $this->translationTable->getEntityClass(); @@ -492,6 +496,7 @@ protected function bundleTranslatedFields(EntityInterface $entity): void $contents[$i]->setNew(false); } else { $translation['model'] = $this->_config['referenceName']; + unset($translation['foreign_key IS']); $contents[$i]->set($translation, ['setter' => false, 'guard' => false]); $contents[$i]->setNew(true); } diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 4540263d..9bb3d6da 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -356,7 +356,7 @@ function ($expression) use ($fields, $alias, $mainTableAlias, $mainTableFields, */ public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void { - $locale = $entity->get('_locale') ?: $this->getLocale(); + $locale = $entity->has('_locale') ? $entity->get('_locale') : $this->getLocale(); $newOptions = [$this->translationTable->getAlias() => ['validate' => false]]; $options['associated'] = $newOptions + $options['associated']; @@ -368,7 +368,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array } $this->bundleTranslatedFields($entity); - $bundled = $entity->get('_i18n') ?: []; + $bundled = $entity->has('_i18n') ? $entity->get('_i18n') : []; $noBundled = count($bundled) === 0; // No additional translation records need to be saved, @@ -556,7 +556,7 @@ public function groupTranslations(CollectionInterface $results): CollectionInter if (!($row instanceof EntityInterface)) { return $row; } - $translations = (array)$row->get('_i18n'); + $translations = $row->has('_i18n') ? $row->get('_i18n') : null; if (!$translations && $row->get('_translations')) { return $row; } @@ -588,21 +588,27 @@ public function groupTranslations(CollectionInterface $results): CollectionInter protected function bundleTranslatedFields(EntityInterface $entity): void { /** @var array $translations */ - $translations = (array)$entity->get('_translations'); + $translations = $entity->has('_translations') ? $entity->get('_translations') : null; if (!$translations && !$entity->isDirty('_translations')) { return; } - $primaryKey = (array)$this->table->getPrimaryKey(); - $key = $entity->get((string)current($primaryKey)); + if ($entity->isNew()) { + $key = null; + } else { + $primaryKey = (array)$this->table->getPrimaryKey(); + $key = $entity->get((string)current($primaryKey)); + } foreach ($translations as $lang => $translation) { - if (!$translation->id) { + if ($translation->isNew()) { $update = [ - 'id' => $key, 'locale' => $lang, ]; + if ($key !== null) { + $update['id'] = $key; + } $translation->set($update, ['guard' => false]); } } diff --git a/Behavior/Translate/TranslateStrategyTrait.php b/Behavior/Translate/TranslateStrategyTrait.php index 15bdfd95..c2ee3890 100644 --- a/Behavior/Translate/TranslateStrategyTrait.php +++ b/Behavior/Translate/TranslateStrategyTrait.php @@ -157,7 +157,7 @@ public function buildMarshalMap(Marshaller $marshaller, array $map, array $optio } /** @var array $translations */ - $translations = $entity->get('_translations') ?? []; + $translations = $entity->has('_translations') ? $entity->get('_translations') : []; $options['validate'] = $this->_config['validator']; $errors = []; diff --git a/Behavior/Translate/TranslateTrait.php b/Behavior/Translate/TranslateTrait.php index 6c4c5e18..69f25cc7 100644 --- a/Behavior/Translate/TranslateTrait.php +++ b/Behavior/Translate/TranslateTrait.php @@ -39,7 +39,7 @@ public function translation(string $language) return $this; } - $i18n = $this->get('_translations'); + $i18n = $this->has('_translations') ? $this->get('_translations') : null; $created = false; if (!$i18n) { From 59cf65b01106aacdbca66472097a3e68a5b107da Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 14 Feb 2025 12:30:59 +0530 Subject: [PATCH 2019/2059] Deprecate the ability to cast entities to string. --- Association/HasMany.php | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index 43b094d4..61159cfd 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -284,12 +284,31 @@ public function link(EntityInterface $sourceEntity, array $targetEntities, array $this->setSaveStrategy(self::SAVE_APPEND); $property = $this->getProperty(); - $currentEntities = array_unique( - array_merge( - (array)$sourceEntity->get($property), - $targetEntities, - ), - ); + $currentEntities = (array)$sourceEntity->get($property); + if ($currentEntities === []) { + $currentEntities = $targetEntities; + } else { + $pkFields = (array)$this->getTarget()->getPrimaryKey(); + $targetEntities = (new Collection($targetEntities)) + ->reject( + function (EntityInterface $entity) use ($currentEntities, $pkFields) { + if ($entity->isNew()) { + return false; + } + + foreach ($currentEntities as $cEntity) { + if ($entity->extract($pkFields) === $cEntity->extract($pkFields)) { + return true; + } + } + + return false; + }, + ) + ->toList(); + + $currentEntities = array_merge($currentEntities, $targetEntities); + } $sourceEntity->set($property, $currentEntities); From 99de33fdb51a0f5f025568e550965f4775fb245c Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 23 Feb 2025 02:36:50 +0530 Subject: [PATCH 2020/2059] Deprecate returning values from event listeners. (#18196) This allows callback methods to have a consistent return type `void`. --- Table.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Table.php b/Table.php index f66ea313..8d748a4a 100644 --- a/Table.php +++ b/Table.php @@ -2029,7 +2029,8 @@ protected function _processSave(EntityInterface $entity, ArrayObject $options): assert( $result instanceof EntityInterface, sprintf( - 'The beforeSave callback must return `false` or `EntityInterface` instance. Got `%s` instead.', + 'The result for the `Model.beforeSave` event must be `false` or `EntityInterface` instance.' + . ' Got `%s` instead.', get_debug_type($result), ), ); From 046b384cbad6b27b3f481e8e7547c6a20cbf3cc3 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Sun, 23 Feb 2025 11:50:29 -0500 Subject: [PATCH 2021/2059] Add findOrCreate() support for array data. (#18197) Add findOrCreate() support for array data. --- Table.php | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/Table.php b/Table.php index 8d748a4a..21646f68 100644 --- a/Table.php +++ b/Table.php @@ -1624,8 +1624,8 @@ protected function _transactionCommitted(bool $atomic, bool $primary): bool * @param \Cake\ORM\Query\SelectQuery|callable|array $search The criteria to find existing * records by. Note that when you pass a query object you'll have to use * the 2nd arg of the method to modify the entity data before saving. - * @param callable|null $callback A callback that will be invoked for newly - * created entities. This callback will be called *before* the entity + * @param callable|array|null $callback An array of data key/value pairs or a callback that will + * be invoked for newly created entities. This callback will be called *before* the entity * is persisted. * @param array $options The options to use when saving. * @return \Cake\Datasource\EntityInterface An entity. @@ -1633,7 +1633,7 @@ protected function _transactionCommitted(bool $atomic, bool $primary): bool */ public function findOrCreate( SelectQuery|callable|array $search, - ?callable $callback = null, + callable|array|null $callback = null, array $options = [], ): EntityInterface { $options = new ArrayObject($options + [ @@ -1658,7 +1658,7 @@ public function findOrCreate( * * @param \Cake\ORM\Query\SelectQuery|callable|array $search The criteria to find an existing record by, or a callable tha will * customize the find query. - * @param callable|null $callback A callback that will be invoked for newly + * @param callable|array|null $callback Data or a callback that will be invoked for newly * created entities. This callback will be called *before* the entity * is persisted. * @param array $options The options to use when saving. @@ -1668,7 +1668,7 @@ public function findOrCreate( */ protected function _processFindOrCreate( SelectQuery|callable|array $search, - ?callable $callback = null, + callable|array|null $callback = null, array $options = [], ): EntityInterface|array { $query = $this->_getFindOrCreateQuery($search); @@ -1678,10 +1678,16 @@ protected function _processFindOrCreate( return $row; } + $data = $search; + if (is_array($callback) && !is_callable($callback)) { + $data = $callback + $search; + $callback = null; + } + $entity = $this->newEmptyEntity(); - if ($options['defaults'] && is_array($search)) { - $accessibleFields = array_combine(array_keys($search), array_fill(0, count($search), true)); - $entity = $this->patchEntity($entity, $search, ['accessibleFields' => $accessibleFields]); + if ($options['defaults'] && is_array($data)) { + $accessibleFields = array_combine(array_keys($data), array_fill(0, count($data), true)); + $entity = $this->patchEntity($entity, $data, ['accessibleFields' => $accessibleFields]); } if ($callback !== null) { $entity = $callback($entity) ?: $entity; From c55ade59c91b121b18ec97b86d426112a95e0ad2 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 25 Feb 2025 22:09:50 +0530 Subject: [PATCH 2022/2059] Fix checking of entity fields in Translate behavior strategies. (#18207) Refs #18174. Closes #18206. --- Behavior/Translate/EavStrategy.php | 26 +++++++++++++------- Behavior/Translate/ShadowTableStrategy.php | 28 ++++++++++++++-------- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index ca34dfb5..6e68661c 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -272,8 +272,9 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array return; } - $primaryKey = (array)$this->table->getPrimaryKey(); - $key = $entity->get((string)current($primaryKey)); + /** @var string $primaryKey */ + $primaryKey = current((array)$this->table->getPrimaryKey()); + $key = $entity->has($primaryKey) ? $entity->get($primaryKey) : null; // When we have no key and bundled translations, we // need to mark the entity dirty so the root @@ -418,10 +419,20 @@ public function groupTranslations(CollectionInterface $results): CollectionInter if (!$row instanceof EntityInterface) { return $row; } - $translations = $row->has('_i18n') ? $row->get('_i18n') : null; - if (!$translations && $row->get('_translations')) { + + $translations = $row->has('_i18n') ? $row->get('_i18n') : []; + if ($translations === []) { + if ($row->has('_translations')) { + return $row; + } + + $row->set('_translations', []) + ->setDirty('_translations', false); + unset($row['_i18n']); + return $row; } + $grouped = new Collection($translations); $entityClass = $this->table->getEntityClass(); @@ -435,9 +446,8 @@ public function groupTranslations(CollectionInterface $results): CollectionInter $result[$locale] = $translation; } - $options = ['setter' => false, 'guard' => false]; - $row->set('_translations', $result, $options); - $row->setDirty('_translations', false); + $row->set('_translations', $result, ['setter' => false, 'guard' => false]) + ->setDirty('_translations', false); unset($row['_i18n']); return $row; @@ -455,7 +465,7 @@ public function groupTranslations(CollectionInterface $results): CollectionInter protected function bundleTranslatedFields(EntityInterface $entity): void { /** @var array $translations */ - $translations = $entity->has('_translations') ? $entity->get('_translations') : null; + $translations = $entity->has('_translations') ? $entity->get('_translations') : []; if (!$translations && !$entity->isDirty('_translations')) { return; diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 9bb3d6da..60e2e87b 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -388,8 +388,9 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array return; } - $primaryKey = (array)$this->table->getPrimaryKey(); - $id = $entity->get((string)current($primaryKey)); + /** @var string $primaryKey */ + $primaryKey = current((array)$this->table->getPrimaryKey()); + $id = $entity->has($primaryKey) ? $entity->get($primaryKey) : null; // When we have no key and bundled translations, we // need to mark the entity dirty so the root @@ -553,11 +554,20 @@ protected function rowMapper(CollectionInterface $results, string $locale): Coll public function groupTranslations(CollectionInterface $results): CollectionInterface { return $results->map(function ($row) { - if (!($row instanceof EntityInterface)) { + if (!$row instanceof EntityInterface) { return $row; } - $translations = $row->has('_i18n') ? $row->get('_i18n') : null; - if (!$translations && $row->get('_translations')) { + + $translations = $row->has('_i18n') ? $row->get('_i18n') : []; + if ($translations === []) { + if ($row->has('_translations')) { + return $row; + } + + $row->set('_translations', []) + ->setDirty('_translations', false); + unset($row['_i18n']); + return $row; } @@ -567,11 +577,9 @@ public function groupTranslations(CollectionInterface $results): CollectionInter $result[$translation['locale']] = $translation; } - $row['_translations'] = $result; + $row->set('_translations', $result) + ->setDirty('_translations', false); unset($row['_i18n']); - if ($row instanceof EntityInterface) { - $row->setDirty('_translations', false); - } return $row; }); @@ -588,7 +596,7 @@ public function groupTranslations(CollectionInterface $results): CollectionInter protected function bundleTranslatedFields(EntityInterface $entity): void { /** @var array $translations */ - $translations = $entity->has('_translations') ? $entity->get('_translations') : null; + $translations = $entity->has('_translations') ? $entity->get('_translations') : []; if (!$translations && !$entity->isDirty('_translations')) { return; From af16f7d6ad7bb57f3dd102d5a0b0881b87fe3d75 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 1 Mar 2025 23:37:57 +0530 Subject: [PATCH 2023/2059] Code cleanup (#18215) * Code cleanup * Improve code coverage --- Behavior/Translate/TranslateStrategyTrait.php | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/Behavior/Translate/TranslateStrategyTrait.php b/Behavior/Translate/TranslateStrategyTrait.php index c2ee3890..559174d7 100644 --- a/Behavior/Translate/TranslateStrategyTrait.php +++ b/Behavior/Translate/TranslateStrategyTrait.php @@ -106,8 +106,12 @@ public function getLocale(): string */ protected function unsetEmptyFields(EntityInterface $entity): void { - /** @var array<\Cake\ORM\Entity> $translations */ - $translations = (array)$entity->get('_translations'); + if (!$entity->has('_translations')) { + return; + } + + /** @var array<\Cake\Datasource\EntityInterface> $translations */ + $translations = $entity->get('_translations'); foreach ($translations as $locale => $translation) { $fields = $translation->extract($this->_config['fields'], false); foreach ($fields as $field => $value) { @@ -120,15 +124,16 @@ protected function unsetEmptyFields(EntityInterface $entity): void // If now, the current locale property is empty, // unset it completely. - if (empty(array_filter($translation))) { - unset($entity->get('_translations')[$locale]); + if (array_filter($translation) === []) { + unset($translations[$locale]); } } - // If now, the whole _translations property is empty, - // unset it completely and return - if (empty($entity->get('_translations'))) { + // If now, the whole $translations is empty, unset _translations property completely + if ($translations === []) { $entity->unset('_translations'); + } else { + $entity->set('_translations', $translations); } } From c1858652b793c2e7775dcc65f2e05b3d60693cea Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 22 Feb 2025 17:55:39 +0530 Subject: [PATCH 2024/2059] Add EntityTrait::patch() for mass assigning fields. Passing multiple field values to EntityTrait::set() is deprecated. --- Association/BelongsTo.php | 7 ++++++- Association/BelongsToMany.php | 8 ++++++-- Association/HasMany.php | 6 +++++- Association/HasOne.php | 6 +++++- Behavior/Translate/EavStrategy.php | 6 +++++- Behavior/Translate/ShadowTableStrategy.php | 12 ++++++++++-- Behavior/TreeBehavior.php | 6 +++++- Entity.php | 2 +- Marshaller.php | 12 ++++++++++-- Table.php | 8 +++++++- 10 files changed, 60 insertions(+), 13 deletions(-) diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index a9bb6ff0..2ecfce12 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -153,7 +153,12 @@ public function saveAssociated(EntityInterface $entity, array $options = []): En $foreignKeys, $targetEntity->extract((array)$this->getBindingKey()), ); - $entity->set($properties, ['guard' => false]); + + if (method_exists($entity, 'patch')) { + $entity = $entity->patch($properties, ['guard' => false]); + } else { + $entity->set($properties, ['guard' => false]); + } return $entity; } diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index ff941d2d..bd84c20d 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -843,8 +843,12 @@ protected function _saveLinks(EntityInterface $sourceEntity, array $targetEntiti // or if we are updating an existing link. if ($changedKeys) { $joint->setNew(true); - $joint->unset($junction->getPrimaryKey()) - ->set(array_merge($sourceKeys, $targetKeys), ['guard' => false]); + $joint->unset($junction->getPrimaryKey()); + if (method_exists($joint, 'patch')) { + $joint->patch(array_merge($sourceKeys, $targetKeys), ['guard' => false]); + } else { + $joint->set(array_merge($sourceKeys, $targetKeys), ['guard' => false]); + } } $saved = $junction->save($joint, $options); diff --git a/Association/HasMany.php b/Association/HasMany.php index 61159cfd..f810ed3c 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -228,7 +228,11 @@ protected function _saveTarget( } if ($foreignKeyReference !== $entity->extract($foreignKey)) { - $entity->set($foreignKeyReference, ['guard' => false]); + if (method_exists($entity, 'patch')) { + $entity->patch($foreignKeyReference, ['guard' => false]); + } else { + $entity->set($foreignKeyReference, ['guard' => false]); + } } if ($table->save($entity, $options)) { diff --git a/Association/HasOne.php b/Association/HasOne.php index 2bac5936..accdb1b9 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -131,7 +131,11 @@ public function saveAssociated(EntityInterface $entity, array $options = []): En $foreignKeys, $entity->extract((array)$this->getBindingKey()), ); - $targetEntity->set($properties, ['guard' => false]); + if (method_exists($targetEntity, 'patch')) { + $targetEntity = $targetEntity->patch($properties, ['guard' => false]); + } else { + $targetEntity->set($properties, ['guard' => false]); + } if (!$this->getTarget()->save($targetEntity, $options)) { $targetEntity->unset(array_keys($properties)); diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index 6e68661c..ce933277 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -507,7 +507,11 @@ protected function bundleTranslatedFields(EntityInterface $entity): void } else { $translation['model'] = $this->_config['referenceName']; unset($translation['foreign_key IS']); - $contents[$i]->set($translation, ['setter' => false, 'guard' => false]); + if (method_exists($contents[$i], 'patch')) { + $contents[$i]->patch($translation, ['setter' => false, 'guard' => false]); + } else { + $contents[$i]->set($translation, ['setter' => false, 'guard' => false]); + } $contents[$i]->setNew(true); } } diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 834b5fae..040e899c 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -420,7 +420,11 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array } if ($translation) { - $translation->set($values); + if (method_exists($translation, 'patch')) { + $translation->patch($values); + } else { + $translation->set($values); + } } else { $translation = new ($this->translationTable->getEntityClass())( $where + $values, @@ -616,7 +620,11 @@ protected function bundleTranslatedFields(EntityInterface $entity): void if ($key !== null) { $update['id'] = $key; } - $translation->set($update, ['guard' => false]); + if (method_exists($translation, 'patch')) { + $translation->patch($update, ['guard' => false]); + } else { + $translation->set($update, ['guard' => false]); + } } } diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 8303c98d..4617a948 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -949,7 +949,11 @@ protected function _ensureFields(EntityInterface $entity): void } $fresh = $this->_table->get($entity->get($this->_getPrimaryKey())); - $entity->set($fresh->extract($fields), ['guard' => false]); + if (method_exists($entity, 'patch')) { + $entity->patch($fresh->extract($fields), ['guard' => false]); + } else { + $entity->set($fresh->extract($fields), ['guard' => false]); + } foreach ($fields as $field) { $entity->setDirty($field, false); diff --git a/Entity.php b/Entity.php index 210bca69..c924e4d8 100644 --- a/Entity.php +++ b/Entity.php @@ -75,7 +75,7 @@ public function __construct(array $properties = [], array $options = []) return; } - $this->set($properties, [ + $this->patch($properties, [ 'setter' => $options['useSetters'], 'guard' => $options['guard'], ]); diff --git a/Marshaller.php b/Marshaller.php index e8b06d04..eef3416e 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -243,7 +243,11 @@ public function one(array $data, array $options = []): EntityInterface } } } else { - $entity->set($properties, ['asOriginal' => true]); + if (method_exists($entity, 'patch')) { + $entity->patch($properties, ['asOriginal' => true]); + } else { + $entity->set($properties, ['asOriginal' => true]); + } } // Don't flag clean association entities as @@ -603,7 +607,11 @@ public function merge(EntityInterface $entity, array $data, array $options = []) $entity->setErrors($errors); if (!isset($options['fields'])) { - $entity->set($properties); + if (method_exists($entity, 'patch')) { + $entity->patch($properties); + } else { + $entity->set($properties); + } foreach ($properties as $field => $value) { if ($value instanceof EntityInterface) { diff --git a/Table.php b/Table.php index 21646f68..678f36dd 100644 --- a/Table.php +++ b/Table.php @@ -2172,7 +2172,13 @@ protected function _insert(EntityInterface $entity, array $data): EntityInterfac $success = false; if ($statement->rowCount() !== 0) { $success = $entity; - $entity->set($filteredKeys, ['guard' => false]); + + if (method_exists($entity, 'patch')) { + $entity = $entity->patch($filteredKeys, ['guard' => false]); + } else { + $entity->set($filteredKeys, ['guard' => false]); + } + $schema = $this->getSchema(); $driver = $this->getConnection()->getDriver(); foreach ($primary as $key => $v) { From 25acd43fb6ea192fc32006a2512c3ee57100c7de Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 2 Mar 2025 11:26:16 +0530 Subject: [PATCH 2025/2059] Fix constraints --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 179333c1..cddef59f 100644 --- a/composer.json +++ b/composer.json @@ -30,11 +30,11 @@ "cakephp/database": "5.2.*@dev", "cakephp/event": "5.2.*@dev", "cakephp/utility": "5.2.*@dev", - "cakephp/validation": "^5.2" + "cakephp/validation": "5.2.*@dev" }, "require-dev": { "cakephp/cache": "5.2.*@dev", - "cakephp/i18n": "^5.2" + "cakephp/i18n": "5.2.*@dev" }, "autoload": { "psr-4": { From 6d5eada8e57891180b6aa6a78c13a98f22c7e574 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 17 Mar 2025 17:24:21 +0530 Subject: [PATCH 2026/2059] Fix issue iterating ResultSet with xdebug breakpoints. Closes #18234 --- ResultSet.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/ResultSet.php b/ResultSet.php index a3ed77fd..103d913c 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -38,8 +38,18 @@ class ResultSet extends Collection implements ResultSetInterface */ public function __debugInfo(): array { + $key = $this->key(); + $items = $this->toArray(); + + $this->rewind(); + // Move the internal pointer to the previous position otherwise it creates problems with Xdebug + // https://github.com/cakephp/cakephp/issues/18234 + while ($this->key() !== $key) { + $this->next(); + } + return [ - 'items' => $this->toArray(), + 'items' => $items, ]; } } From f23ec56a51ea7fe2094160ec43628b886538e8f4 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 20 Mar 2025 01:00:48 +0530 Subject: [PATCH 2027/2059] Bump up codesniffer version and fix reported issues. (#18240) --- Association/HasMany.php | 2 +- Behavior/Translate/EavStrategy.php | 2 +- Behavior/Translate/ShadowTableStrategy.php | 2 +- Behavior/TreeBehavior.php | 14 +++++++------- EagerLoader.php | 2 +- LazyEagerLoader.php | 4 ++-- Marshaller.php | 8 ++++---- Rule/ExistsIn.php | 2 +- Table.php | 12 ++++++------ 9 files changed, 24 insertions(+), 24 deletions(-) diff --git a/Association/HasMany.php b/Association/HasMany.php index e7780c8e..9a1aca7f 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -293,7 +293,7 @@ public function link(EntityInterface $sourceEntity, array $targetEntities, array $sourceEntity->set($property, $currentEntities); - $savedEntity = $this->getConnection()->transactional(fn () => $this->saveAssociated($sourceEntity, $options)); + $savedEntity = $this->getConnection()->transactional(fn() => $this->saveAssociated($sourceEntity, $options)); $ok = ($savedEntity instanceof EntityInterface); $this->setSaveStrategy($saveStrategy); diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index 6e68661c..45ba4792 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -224,7 +224,7 @@ public function beforeFind(EventInterface $event, SelectQuery $query, ArrayObjec $query->contain($contain); $query->formatResults( - fn (CollectionInterface $results) => $this->rowMapper($results, $locale), + fn(CollectionInterface $results) => $this->rowMapper($results, $locale), $query::PREPEND, ); } diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 60e2e87b..aa1e5864 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -154,7 +154,7 @@ public function beforeFind(EventInterface $event, SelectQuery $query, ArrayObjec $query->contain([$config['hasOneAlias']]); $query->formatResults( - fn (CollectionInterface $results) => $this->rowMapper($results, $locale), + fn(CollectionInterface $results) => $this->rowMapper($results, $locale), $query::PREPEND, ); } diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 8303c98d..d78b2b0d 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -236,7 +236,7 @@ public function beforeDelete(EventInterface $event, EntityInterface $entity): vo if ($this->getConfig('cascadeCallbacks')) { $query = $this->_scope($this->_table->query()) ->where( - fn (QueryExpression $exp) => $exp + fn(QueryExpression $exp) => $exp ->gte($config['leftField'], $left + 1) ->lte($config['leftField'], $right - 1), ); @@ -248,7 +248,7 @@ public function beforeDelete(EventInterface $event, EntityInterface $entity): vo } else { $this->_scope($this->_table->deleteQuery()) ->where( - fn (QueryExpression $exp) => $exp + fn(QueryExpression $exp) => $exp ->gte($config['leftField'], $left + 1) ->lte($config['leftField'], $right - 1), ) @@ -375,7 +375,7 @@ function (QueryExpression $exp) use ($config) { ->eq($config['leftField'], $leftInverse->add($config['leftField'])) ->eq($config['rightField'], $rightInverse->add($config['rightField'])); }, - fn (QueryExpression $exp) => $exp->lt($config['leftField'], 0), + fn(QueryExpression $exp) => $exp->lt($config['leftField'], 0), ); } @@ -638,7 +638,7 @@ protected function _moveUp(EntityInterface $node, int|bool $number): EntityInter $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) ->where(["{$parent} IS" => $nodeParent]) - ->where(fn (QueryExpression $exp) => $exp->lt($config['rightField'], $nodeLeft)) + ->where(fn(QueryExpression $exp) => $exp->lt($config['rightField'], $nodeLeft)) ->orderByDesc($config['leftField']) ->offset($number - 1) ->limit(1) @@ -649,7 +649,7 @@ protected function _moveUp(EntityInterface $node, int|bool $number): EntityInter $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) ->where(["{$parent} IS" => $nodeParent]) - ->where(fn (QueryExpression $exp) => $exp->lt($config['rightField'], $nodeLeft)) + ->where(fn(QueryExpression $exp) => $exp->lt($config['rightField'], $nodeLeft)) ->orderByAsc($config['leftField']) ->limit(1) ->first(); @@ -727,7 +727,7 @@ protected function _moveDown(EntityInterface $node, int|bool $number): EntityInt $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) ->where(["{$parent} IS" => $nodeParent]) - ->where(fn (QueryExpression $exp) => $exp->gt($config['leftField'], $nodeRight)) + ->where(fn(QueryExpression $exp) => $exp->gt($config['leftField'], $nodeRight)) ->orderByAsc($config['leftField']) ->offset($number - 1) ->limit(1) @@ -738,7 +738,7 @@ protected function _moveDown(EntityInterface $node, int|bool $number): EntityInt $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) ->where(["{$parent} IS" => $nodeParent]) - ->where(fn (QueryExpression $exp) => $exp->gt($config['leftField'], $nodeRight)) + ->where(fn(QueryExpression $exp) => $exp->gt($config['leftField'], $nodeRight)) ->orderByDesc($config['leftField']) ->limit(1) ->first(); diff --git a/EagerLoader.php b/EagerLoader.php index d1323481..636dbc6a 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -371,7 +371,7 @@ protected function _reformatContain(array $associations, array $original): array if (isset($options['queryBuilder'], $pointer[$table]['queryBuilder'])) { $first = $pointer[$table]['queryBuilder']; $second = $options['queryBuilder']; - $options['queryBuilder'] = fn ($query) => $second($first($query)); + $options['queryBuilder'] = fn($query) => $second($first($query)); } if (!is_array($options)) { diff --git a/LazyEagerLoader.php b/LazyEagerLoader.php index 7a0b0f86..431459f5 100644 --- a/LazyEagerLoader.php +++ b/LazyEagerLoader.php @@ -75,7 +75,7 @@ protected function _getQuery(array $entities, array $contain, Table $source): Se $primaryKey = $source->getPrimaryKey(); $method = is_string($primaryKey) ? 'get' : 'extract'; - $keys = Hash::map($entities, '{*}', fn (EntityInterface $entity) => $entity->{$method}($primaryKey)); + $keys = Hash::map($entities, '{*}', fn(EntityInterface $entity) => $entity->{$method}($primaryKey)); $query = $source ->find() @@ -149,7 +149,7 @@ protected function _injectResults( /** @var array<\Cake\Datasource\EntityInterface> $results */ $results = $query ->all() - ->indexBy(fn (EntityInterface $e) => implode(';', $e->extract($primaryKey))) + ->indexBy(fn(EntityInterface $e) => implode(';', $e->extract($primaryKey))) ->toArray(); foreach ($entities as $k => $object) { diff --git a/Marshaller.php b/Marshaller.php index e9d69c32..97581b8e 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -76,7 +76,7 @@ protected function _buildPropertyMap(array $data, array $options): array $prop = (string)$prop; $columnType = $schema->getColumnType($prop); if ($columnType) { - $map[$prop] = fn ($value) => TypeFactory::build($columnType)->marshal($value); + $map[$prop] = fn($value) => TypeFactory::build($columnType)->marshal($value); } } @@ -119,7 +119,7 @@ protected function _buildPropertyMap(array $data, array $options): array $this->fieldValue($entity, $assoc->getProperty()), $assoc, $value, - $options + $options, ); }; } else { @@ -428,7 +428,7 @@ protected function _belongsToMany(BelongsToMany $assoc, array $data, array $opti if ($conditions) { /** @var \Traversable<\Cake\Datasource\EntityInterface> $results */ $results = $target->find() - ->andWhere(fn (QueryExpression $exp) => $exp->or($conditions)) + ->andWhere(fn(QueryExpression $exp) => $exp->or($conditions)) ->all(); $keyFields = array_keys($primaryKey); @@ -717,7 +717,7 @@ public function mergeMany(iterable $entities, array $data, array $options = []): ->map(function ($data, $key) { return explode(';', (string)$key); }) - ->filter(fn ($keys) => count(Hash::filter($keys)) === count($primary)) + ->filter(fn($keys) => count(Hash::filter($keys)) === count($primary)) ->reduce(function ($conditions, $keys) use ($primary) { $fields = array_map($this->_table->aliasField(...), $primary); $conditions['OR'][] = array_combine($fields, $keys); diff --git a/Rule/ExistsIn.php b/Rule/ExistsIn.php index 2f41bc7d..a7e3c0a8 100644 --- a/Rule/ExistsIn.php +++ b/Rule/ExistsIn.php @@ -138,7 +138,7 @@ public function __invoke(EntityInterface $entity, array $options): bool } $primary = array_map( - fn ($key) => $target->aliasField($key) . ' IS', + fn($key) => $target->aliasField($key) . ' IS', $bindingKey, ); $conditions = array_combine( diff --git a/Table.php b/Table.php index 34811edd..a2323641 100644 --- a/Table.php +++ b/Table.php @@ -1387,7 +1387,7 @@ public function findList( ['keyField', 'valueField', 'groupField'], ); - return $query->formatResults(fn (CollectionInterface $results) => $results->combine( + return $query->formatResults(fn(CollectionInterface $results) => $results->combine( $options['keyField'], $options['valueField'], $options['groupField'], @@ -1426,7 +1426,7 @@ public function findThreaded( $options = $this->_setFieldMatchers(compact('keyField', 'parentField'), ['keyField', 'parentField']); - return $query->formatResults(fn (CollectionInterface $results) => $results->nest( + return $query->formatResults(fn(CollectionInterface $results) => $results->nest( $options['keyField'], $options['parentField'], $nestingKey, @@ -1579,7 +1579,7 @@ public function get( protected function _executeTransaction(callable $worker, bool $atomic = true): mixed { if ($atomic) { - return $this->getConnection()->transactional(fn () => $worker()); + return $this->getConnection()->transactional(fn() => $worker()); } return $worker(); @@ -1642,7 +1642,7 @@ public function findOrCreate( ]); $entity = $this->_executeTransaction( - fn () => $this->_processFindOrCreate($search, $callback, $options->getArrayCopy()), + fn() => $this->_processFindOrCreate($search, $callback, $options->getArrayCopy()), $options['atomic'], ); @@ -1948,7 +1948,7 @@ public function save( } $success = $this->_executeTransaction( - fn () => $this->_processSave($entity, $options), + fn() => $this->_processSave($entity, $options), $options['atomic'], ); @@ -2415,7 +2415,7 @@ public function delete(EntityInterface $entity, array $options = []): bool ]); $success = $this->_executeTransaction( - fn () => $this->_processDelete($entity, $options), + fn() => $this->_processDelete($entity, $options), $options['atomic'], ); From bb0bb3716102e487cea1a111dea18699d56baa78 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 31 Mar 2025 11:26:24 +0530 Subject: [PATCH 2028/2059] Fix errors reported by phpstan --- phpstan.neon.dist | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 684a761c..239541a0 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -18,3 +18,38 @@ parameters: - "#^PHPDoc tag @return with type Cake\\\\ORM\\\\Query\\\\SelectQuery\\ is not subtype of native type static\\(Cake\\\\ORM\\\\Query\\\\SelectQuery\\\\)\\.$#" - "#^Method Cake\\\\ORM\\\\Query\\\\SelectQuery\\:\\:find\\(\\) should return static\\(Cake\\\\ORM\\\\Query\\\\SelectQuery\\\\) but returns Cake\\\\ORM\\\\Query\\\\SelectQuery\\\\.$#" - '#^PHPDoc tag @var with type callable\(\): mixed is not subtype of native type Closure\(string\): string\.$#' + - + message: '#^Call to function method_exists\(\) with Cake\\Datasource\\EntityInterface and ''patch'' will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 1 + path: Association/BelongsTo.php + + - + message: '#^Call to function method_exists\(\) with Cake\\Datasource\\EntityInterface and ''patch'' will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 1 + path: Association/HasMany.php + + - + message: '#^Call to function method_exists\(\) with Cake\\Datasource\\EntityInterface and ''patch'' will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 1 + path: Association/HasOne.php + + - + message: '#^Call to function method_exists\(\) with Cake\\Datasource\\EntityInterface and ''patch'' will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 1 + path: Behavior/TreeBehavior.php + + - + message: '#^Call to function method_exists\(\) with Cake\\Datasource\\EntityInterface and ''patch'' will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 2 + path: Marshaller.php + + - + message: '#^Call to function method_exists\(\) with Cake\\Datasource\\EntityInterface and ''patch'' will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 1 + path: Table.php From c3f7d821d6a265930dbc85a8ffe35f40a643d2f5 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 1 Apr 2025 00:37:16 +0530 Subject: [PATCH 2029/2059] Update composer branch aliases for split packages. (#18259) --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index cddef59f..c3f67a12 100644 --- a/composer.json +++ b/composer.json @@ -51,7 +51,7 @@ "prefer-stable": true, "extra": { "branch-alias": { - "dev-5.next": "5.2.x-dev" + "dev-5.x": "5.2.x-dev" } } } From 28da3b2ac5f565f833059497eea196500d5feaf1 Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 2 Apr 2025 13:57:47 +0530 Subject: [PATCH 2030/2059] Fix method and finder map population when using BehaviorRegistry::set() Closes #18263 --- BehaviorRegistry.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index c531e4a4..82db5cff 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -211,6 +211,24 @@ protected function _getMethods(Behavior $instance, string $class, string $alias) return compact('methods', 'finders'); } + /** + * Set an object directly into the registry by name. + * + * @param string $name The name of the object to set in the registry. + * @param \Cake\ORM\Behavior $object instance to store in the registry + * @return $this + */ + public function set(string $name, object $object) + { + parent::set($name, $object); + + $methods = $this->_getMethods($object, $object::class, $name); + $this->_methodMap += $methods['methods']; + $this->_finderMap += $methods['finders']; + + return $this; + } + /** * Remove an object from the registry. * From f88a4f88759410eb923e2d574e650fb22f51567d Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 4 Apr 2025 10:49:33 +0530 Subject: [PATCH 2031/2059] Update info regarding `Table::buildRules()` and `Model.buildRules` event. Refs #18270 --- Table.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 24c7b730..92252f8d 100644 --- a/Table.php +++ b/Table.php @@ -110,6 +110,8 @@ * for the provided named validator. * * - `Model.buildRules` Allows listeners to modify the rules checker by adding more rules. + * Behaviors or custom listerners can subscribe to this even. For tables you don't + * need to subscribe to this event, simply override the `Table::buildRules()` method. * * - `Model.beforeRules` Fired before an entity is validated using the rules checker. * By stopping this event, you can return the final value of the rules checking operation. @@ -142,7 +144,6 @@ * - `beforeMarshal(EventInterface $event, ArrayObject $data, ArrayObject $options)` * - `afterMarshal(EventInterface $event, EntityInterface $entity, ArrayObject $options)` * - `buildValidator(EventInterface $event, Validator $validator, string $name)` - * - `buildRules(RulesChecker $rules)` * - `beforeRules(EventInterface $event, EntityInterface $entity, ArrayObject $options, string $operation)` * - `afterRules(EventInterface $event, EntityInterface $entity, ArrayObject $options, bool $result, string $operation)` * - `beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options)` From ef16953b6cecdfc8b45bfb08d91d7a0c9f074e0b Mon Sep 17 00:00:00 2001 From: mscherer Date: Sat, 5 Apr 2025 04:12:27 +0200 Subject: [PATCH 2032/2059] Fixed typos and cleanup. --- Behavior/TranslateBehavior.php | 2 +- Query/SelectQuery.php | 6 +++--- ResultSetFactory.php | 2 +- Table.php | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 0198fddb..bf32e2d6 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -95,7 +95,7 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface * using `ShadowTableStrategy` then the list will be auto generated based on * shadow table schema. * - `defaultLocale`: The locale which is treated as default by the behavior. - * Fields values for defaut locale will be stored in the primary table itself + * Fields values for default locale will be stored in the primary table itself * and the rest in translation table. If not explicitly set the value of * `I18n::getDefaultLocale()` will be used to get default locale. * If you do not want any default locale and want translated fields diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index c3796452..e276b2ed 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -142,7 +142,7 @@ class SelectQuery extends DbSelectQuery implements JsonSerializable, QueryInterf protected ?int $_resultsCount = null; /** - * Resultset factory + * Result set factory * * @var \Cake\ORM\ResultSetFactory<\Cake\Datasource\EntityInterface|array> */ @@ -205,7 +205,7 @@ public function __construct(Table $table) /** * Set the result set for a query. * - * Setting the resultset of a query will make execute() a no-op. Instead + * Setting the result set of a query will make execute() a no-op. Instead * of executing the SQL query and fetching results, the ResultSet provided to this * method will be returned. * @@ -1578,7 +1578,7 @@ protected function _execute(): iterable } /** - * Get resultset factory. + * Get result set factory. * * @return \Cake\ORM\ResultSetFactory */ diff --git a/ResultSetFactory.php b/ResultSetFactory.php index 95da2632..171fc5aa 100644 --- a/ResultSetFactory.php +++ b/ResultSetFactory.php @@ -39,7 +39,7 @@ class ResultSetFactory protected string $resultSetClass = ResultSet::class; /** - * Create a resultset instance. + * Create a result set instance. * * @param iterable $results Results. * @param \Cake\ORM\Query\SelectQuery|null $query Query from where results came. diff --git a/Table.php b/Table.php index 92252f8d..fc4c6012 100644 --- a/Table.php +++ b/Table.php @@ -892,11 +892,11 @@ public function getAssociation(string $name): Association { $association = $this->findAssociation($name); if (!$association) { - $assocations = $this->associations()->keys(); + $associations = $this->associations()->keys(); $message = "The `{$name}` association is not defined on `{$this->getAlias()}`."; - if ($assocations) { - $message .= "\nValid associations are: " . implode(', ', $assocations); + if ($associations) { + $message .= "\nValid associations are: " . implode(', ', $associations); } throw new InvalidArgumentException($message); } From 7502663fd99fbddb1013fa832208e86f2f02480d Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 7 Apr 2025 17:17:28 +0530 Subject: [PATCH 2033/2059] Fix method and finder map cleanup when unloading a behavior. Closes #18311 --- BehaviorRegistry.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 82db5cff..cca7bbcf 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -242,11 +242,11 @@ public function unload(string $name) $instance = $this->get($name); $result = parent::unload($name); - $methods = $instance->implementedMethods(); + $methods = array_map('strtolower', array_keys($instance->implementedMethods())); foreach ($methods as $method) { unset($this->_methodMap[$method]); } - $finders = $instance->implementedFinders(); + $finders = array_map('strtolower', array_keys($instance->implementedFinders())); foreach ($finders as $finder) { unset($this->_finderMap[$finder]); } From 07f147b89f7912abaa856f5e81e3d4c638f8b060 Mon Sep 17 00:00:00 2001 From: Marcelo Rocha Date: Sun, 13 Apr 2025 17:42:43 -0300 Subject: [PATCH 2034/2059] =?UTF-8?q?Update=20getBehavior=20annotation=20t?= =?UTF-8?q?o=20allow=20better=20code=20analyse=20on=20applica=E2=80=A6=20(?= =?UTF-8?q?#18323)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update getBehavior annotation to allow better code analyse on application. * Update annotation to use phpstan prefixed phpdoc * Don't use prefix phpstan for template phpdoc * use base return type for psalm --- Table.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Table.php b/Table.php index fc4c6012..dacc731f 100644 --- a/Table.php +++ b/Table.php @@ -155,6 +155,7 @@ * * @see \Cake\Event\EventManager for reference on the events system. * @link https://book.cakephp.org/5/en/orm/table-objects.html#event-list + * @template TBehaviors of array * @implements \Cake\Event\EventDispatcherInterface<\Cake\ORM\Table> */ class Table implements RepositoryInterface, EventListenerInterface, EventDispatcherInterface, ValidatorAwareInterface @@ -844,6 +845,10 @@ public function behaviors(): BehaviorRegistry * * @param string $name The behavior alias to get from the registry. * @return \Cake\ORM\Behavior + * @template TName of key-of + * @phpstan-param TName $name The behavior alias to get from the registry. + * @phpstan-return TBehaviors[TName] + * @psalm-return \Cake\ORM\Behavior * @throws \InvalidArgumentException If the behavior does not exist. */ public function getBehavior(string $name): Behavior From 50162e893b4c7f67d6d5b84a56f519cca86614f8 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 15 Apr 2025 12:41:23 +0530 Subject: [PATCH 2035/2059] Update phpstan --- phpstan.neon.dist | 1 - 1 file changed, 1 deletion(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 239541a0..a2621ab1 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -15,7 +15,6 @@ parameters: - identifier: missingType.generics - '#Unsafe usage of new static\(\).#' - - "#^PHPDoc tag @return with type Cake\\\\ORM\\\\Query\\\\SelectQuery\\ is not subtype of native type static\\(Cake\\\\ORM\\\\Query\\\\SelectQuery\\\\)\\.$#" - "#^Method Cake\\\\ORM\\\\Query\\\\SelectQuery\\:\\:find\\(\\) should return static\\(Cake\\\\ORM\\\\Query\\\\SelectQuery\\\\) but returns Cake\\\\ORM\\\\Query\\\\SelectQuery\\\\.$#" - '#^PHPDoc tag @var with type callable\(\): mixed is not subtype of native type Closure\(string\): string\.$#' - From e459cbf9aec97c1052310f18e511b300c702229b Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 15 Apr 2025 21:22:17 +0530 Subject: [PATCH 2036/2059] Drop psalm --- Association/BelongsToMany.php | 1 - Association/HasMany.php | 1 - AssociationCollection.php | 8 ++++---- Behavior/TranslateBehavior.php | 6 +++--- Behavior/TreeBehavior.php | 4 ++-- BehaviorRegistry.php | 2 +- EagerLoader.php | 1 - Locator/LocatorInterface.php | 1 - Locator/TableLocator.php | 6 ++---- Query/SelectQuery.php | 2 -- Table.php | 3 +-- 11 files changed, 13 insertions(+), 22 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index bd84c20d..5dde8bd3 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -1266,7 +1266,6 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { $property = $this->getProperty(); if ($inserts !== []) { - /** @psalm-suppress RedundantConditionGivenDocblockType */ $inserted = array_combine( array_keys($inserts), (array)$sourceEntity->get($property), diff --git a/Association/HasMany.php b/Association/HasMany.php index 7803f52f..2e53236e 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -390,7 +390,6 @@ public function unlink(EntityInterface $sourceEntity, array $targetEntities, arr $conditions = [ 'OR' => (new Collection($targetEntities)) ->map(function (EntityInterface $entity) use ($targetPrimaryKey) { - /** @psalm-suppress InvalidArgument,UnusedPsalmSuppress */ /** @var array $targetPrimaryKey */ return $entity->extract($targetPrimaryKey); }) diff --git a/AssociationCollection.php b/AssociationCollection.php index b91295a5..0818746b 100644 --- a/AssociationCollection.php +++ b/AssociationCollection.php @@ -73,8 +73,8 @@ public function __construct(?LocatorInterface $tableLocator = null) * @return \Cake\ORM\Association The association object being added. * @throws \Cake\Core\Exception\CakeException If the alias is already added. * @template T of \Cake\ORM\Association - * @psalm-param T $association - * @psalm-return T + * @phpstan-param T $association + * @phpstan-return T */ public function add(string $alias, Association $association): Association { @@ -96,8 +96,8 @@ public function add(string $alias, Association $association): Association * @return \Cake\ORM\Association * @throws \InvalidArgumentException * @template T of \Cake\ORM\Association - * @psalm-param class-string $className - * @psalm-return T + * @phpstan-param class-string $className + * @phpstan-return T */ public function load(string $className, string $associated, array $options = []): Association { diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index bf32e2d6..62111989 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -74,7 +74,7 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface * Default strategy class name. * * @var string - * @psalm-var class-string<\Cake\ORM\Behavior\Translate\TranslateStrategyInterface> + * @phpstan-var class-string<\Cake\ORM\Behavior\Translate\TranslateStrategyInterface> */ protected static string $defaultStrategyClass = ShadowTableStrategy::class; @@ -139,7 +139,7 @@ public function initialize(array $config): void * @param string $class Class name. * @return void * @since 4.0.0 - * @psalm-param class-string<\Cake\ORM\Behavior\Translate\TranslateStrategyInterface> $class + * @phpstan-param class-string<\Cake\ORM\Behavior\Translate\TranslateStrategyInterface> $class */ public static function setDefaultStrategyClass(string $class): void { @@ -151,7 +151,7 @@ public static function setDefaultStrategyClass(string $class): void * * @return string * @since 4.0.0 - * @psalm-return class-string<\Cake\ORM\Behavior\Translate\TranslateStrategyInterface> + * @phpstan-return class-string<\Cake\ORM\Behavior\Translate\TranslateStrategyInterface> */ public static function getDefaultStrategyClass(): string { diff --git a/Behavior/TreeBehavior.php b/Behavior/TreeBehavior.php index 48504f69..c489af00 100644 --- a/Behavior/TreeBehavior.php +++ b/Behavior/TreeBehavior.php @@ -918,8 +918,8 @@ protected function _sync(int $shift, string $dir, string $conditions, bool $mark * @param \Cake\ORM\Query\SelectQuery|\Cake\ORM\Query\UpdateQuery|\Cake\ORM\Query\DeleteQuery $query the Query to modify * @return \Cake\ORM\Query\SelectQuery|\Cake\ORM\Query\UpdateQuery|\Cake\ORM\Query\DeleteQuery * @template T of \Cake\ORM\Query\SelectQuery|\Cake\ORM\Query\UpdateQuery|\Cake\ORM\Query\DeleteQuery - * @psalm-param T $query - * @psalm-return T + * @phpstan-param T $query + * @phpstan-return T */ protected function _scope(SelectQuery|UpdateQuery|DeleteQuery $query): SelectQuery|UpdateQuery|DeleteQuery { diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index cca7bbcf..3984c0bf 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -91,7 +91,7 @@ public function setTable(Table $table): void * * @param string $class Partial classname to resolve. * @return string|null Either the correct classname or null. - * @psalm-return class-string|null + * @phpstan-return class-string|null */ public static function className(string $class): ?string { diff --git a/EagerLoader.php b/EagerLoader.php index 636dbc6a..18e93b9c 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -375,7 +375,6 @@ protected function _reformatContain(array $associations, array $original): array } if (!is_array($options)) { - /** @psalm-suppress InvalidArrayOffset */ $options = [$options => []]; } diff --git a/Locator/LocatorInterface.php b/Locator/LocatorInterface.php index 81ad5a9c..15d0ba2c 100644 --- a/Locator/LocatorInterface.php +++ b/Locator/LocatorInterface.php @@ -62,7 +62,6 @@ public function get(string $alias, array $options = []): Table; * @param string $alias The alias to set. * @param \Cake\ORM\Table $repository The table to set. * @return \Cake\ORM\Table - * @psalm-suppress MoreSpecificImplementedParamType */ public function set(string $alias, RepositoryInterface $repository): Table; } diff --git a/Locator/TableLocator.php b/Locator/TableLocator.php index 14d6c9e7..0f9a7f65 100644 --- a/Locator/TableLocator.php +++ b/Locator/TableLocator.php @@ -51,7 +51,6 @@ class TableLocator extends AbstractLocator implements LocatorInterface * Instances that belong to the registry. * * @var array - * @psalm-suppress NonInvariantDocblockPropertyType */ protected array $instances = []; @@ -67,7 +66,7 @@ class TableLocator extends AbstractLocator implements LocatorInterface * Fallback class to use * * @var string - * @psalm-var class-string<\Cake\ORM\Table> + * @phpstan-var class-string<\Cake\ORM\Table> */ protected string $fallbackClassName = Table::class; @@ -126,7 +125,7 @@ public function allowFallbackClass(bool $allow) * * @param string $className Fallback class name * @return $this - * @psalm-param class-string<\Cake\ORM\Table> $className + * @phpstan-param class-string<\Cake\ORM\Table> $className */ public function setFallbackClassName(string $className) { @@ -325,7 +324,6 @@ protected function _create(array $options): Table * @param string $alias The alias to set. * @param \Cake\ORM\Table $repository The Table to set. * @return \Cake\ORM\Table - * @psalm-suppress MoreSpecificImplementedParamType */ public function set(string $alias, RepositoryInterface $repository): Table { diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index e276b2ed..26efc49b 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -1673,13 +1673,11 @@ protected function _addDefaultSelectTypes(): void * @param string $finder The finder method to use. * @param mixed ...$args Arguments that match up to finder-specific parameters * @return static Returns a modified query. - * @psalm-suppress MoreSpecificReturnType */ public function find(string $finder, mixed ...$args): static { $table = $this->getRepository(); - /** @psalm-suppress LessSpecificReturnStatement */ return $table->callFinder($finder, $this, ...$args); } diff --git a/Table.php b/Table.php index dacc731f..36833d48 100644 --- a/Table.php +++ b/Table.php @@ -263,7 +263,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc * The name of the class that represent a single row for this table * * @var string|null - * @psalm-var class-string<\Cake\Datasource\EntityInterface>|null + * @phpstan-var class-string<\Cake\Datasource\EntityInterface>|null */ protected ?string $_entityClass = null; @@ -848,7 +848,6 @@ public function behaviors(): BehaviorRegistry * @template TName of key-of * @phpstan-param TName $name The behavior alias to get from the registry. * @phpstan-return TBehaviors[TName] - * @psalm-return \Cake\ORM\Behavior * @throws \InvalidArgumentException If the behavior does not exist. */ public function getBehavior(string $name): Behavior From 347350f2450e83140335b114d77da89e301c9fa1 Mon Sep 17 00:00:00 2001 From: mscherer Date: Wed, 16 Apr 2025 13:37:36 +0200 Subject: [PATCH 2037/2059] Remove one phpstan silencing. --- Query/SelectQuery.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index 26efc49b..2f27ddc3 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -384,8 +384,7 @@ public function all(): ResultSetInterface } $this->_results = $results; - /** @phpstan-ignore-next-line */ - return $this->_results; + return $results; } /** From f8a066ce6a3e65915b0af8d57eb311688832669b Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 18 Apr 2025 09:17:06 +0530 Subject: [PATCH 2038/2059] Ensure a field's setter is not run if the value is unchanged. (#18348) Fixes #18346 --- Entity.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Entity.php b/Entity.php index c924e4d8..48c55fac 100644 --- a/Entity.php +++ b/Entity.php @@ -76,6 +76,7 @@ public function __construct(array $properties = [], array $options = []) } $this->patch($properties, [ + 'asOriginal' => true, 'setter' => $options['useSetters'], 'guard' => $options['guard'], ]); From d8bd07b69aee435fd50d311273d354c7e137cf81 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Fri, 18 Apr 2025 22:32:48 +0200 Subject: [PATCH 2039/2059] Fix up template annotation. (#18351) --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 36833d48..489b9a63 100644 --- a/Table.php +++ b/Table.php @@ -155,7 +155,7 @@ * * @see \Cake\Event\EventManager for reference on the events system. * @link https://book.cakephp.org/5/en/orm/table-objects.html#event-list - * @template TBehaviors of array + * @template TBehaviors of array = array{} * @implements \Cake\Event\EventDispatcherInterface<\Cake\ORM\Table> */ class Table implements RepositoryInterface, EventListenerInterface, EventDispatcherInterface, ValidatorAwareInterface From 3402c99aba222e5da8074671cd333c46b0e106bc Mon Sep 17 00:00:00 2001 From: mscherer Date: Sat, 19 Apr 2025 13:52:51 +0200 Subject: [PATCH 2040/2059] Small cleanups and typo fixes. --- Association.php | 16 ++++++++-------- Behavior.php | 4 ++-- Behavior/Translate/TranslateStrategyTrait.php | 8 ++++---- Behavior/TranslateBehavior.php | 6 +++--- Marshaller.php | 18 +++++++++--------- PropertyMarshalInterface.php | 10 +++++----- Table.php | 4 ++-- 7 files changed, 33 insertions(+), 33 deletions(-) diff --git a/Association.php b/Association.php index 96c8ba45..bc0f9bb9 100644 --- a/Association.php +++ b/Association.php @@ -665,7 +665,7 @@ protected function _options(array $options): void * Alters a Query object to include the associated target table data in the final * result * - * The options array accept the following keys: + * The options array accepts the following keys: * * - includeFields: Whether to include target model fields in the result or not * - foreignKey: The name of the field to use as foreign key, if false none @@ -683,7 +683,7 @@ protected function _options(array $options): void * with this association. * * @param \Cake\ORM\Query\SelectQuery $query the query to be altered to include the target table data - * @param array $options Any extra options or overrides to be taken in account + * @param array $options Any extra options or overrides to be taken into account * @return void * @throws \RuntimeException Unable to build the query or associations. */ @@ -830,7 +830,7 @@ public function defaultRowValue(array $row, bool $joined): array * and modifies the query accordingly based of this association * configuration * - * @param array|string|null $type the type of query to perform, if an array is passed, + * @param array|string|null $type the type of query to perform if an array is passed, * it will be interpreted as the `$args` parameter * @param mixed ...$args Arguments that match up to finder-specific parameters * @see \Cake\ORM\Table::find() @@ -917,7 +917,7 @@ public function requiresKeys(array $options = []): bool } /** - * Triggers beforeFind on the target table for the query this association is + * Triggers `beforeFind` on the target table for the query this association is * attaching to * * @param \Cake\ORM\Query\SelectQuery $query the query this association is attaching itself to @@ -958,10 +958,10 @@ protected function _appendFields(SelectQuery $query, SelectQuery $surrogate, arr /** * Adds a formatter function to the passed `$query` if the `$surrogate` query - * declares any other formatter. Since the `$surrogate` query correspond to + * declares any other formatter. Since the `$surrogate` query corresponds to * the associated target table, the resulting formatter will be the result of * applying the surrogate formatters to only the property corresponding to - * such table. + * such a table. * * @param \Cake\ORM\Query\SelectQuery $query the query that will get the formatter applied to * @param \Cake\ORM\Query\SelectQuery $surrogate the query having formatters for the associated @@ -1021,7 +1021,7 @@ function (CollectionInterface $results, SelectQuery $query) use ($formatters, $p * in the `$surrogate` query. * * Copies all contained associations from the `$surrogate` query into the - * passed `$query`. Containments are altered so that they respect the associations + * passed `$query`. Containments are altered so that they respect the association * chain from which they originated. * * @param \Cake\ORM\Query\SelectQuery $query the query that will get the associations attached to @@ -1136,7 +1136,7 @@ protected function _extractFinder(array|string $finderData): array * * @param string $property the property name * @return self - * @throws \RuntimeException if no association with such name exists + * @throws \RuntimeException if no association with such a name exists */ public function __get(string $property): Association { diff --git a/Behavior.php b/Behavior.php index 2e20ee22..5c688409 100644 --- a/Behavior.php +++ b/Behavior.php @@ -54,7 +54,7 @@ * Fired before each find operation. By stopping the event and supplying a * return value you can bypass the find operation entirely. Any changes done * to the $query instance will be retained for the rest of the find. The - * $primary parameter indicates whether this is the root query, + * $primary parameter indicates whether this is the root query * or an associated query. * * - `buildValidator(EventInterface $event, Validator $validator, string $name)` @@ -91,7 +91,7 @@ * event fired from your Table classes including custom application * specific ones. * - * You can set the priority of a behaviors callbacks by using the + * You can set the priority of behaviors' callbacks by using the * `priority` setting when attaching a behavior. This will set the * priority for all the callbacks a behavior provides. * diff --git a/Behavior/Translate/TranslateStrategyTrait.php b/Behavior/Translate/TranslateStrategyTrait.php index 559174d7..6681b197 100644 --- a/Behavior/Translate/TranslateStrategyTrait.php +++ b/Behavior/Translate/TranslateStrategyTrait.php @@ -138,15 +138,15 @@ protected function unsetEmptyFields(EntityInterface $entity): void } /** - * Build a set of properties that should be included in the marshalling process. + * Build a set of properties that should be included in the marshaling process. - * Add in `_translations` marshalling handlers. You can disable marshalling + * Add in `_translations` marshaling handlers. You can disable marshaling * of translations by setting `'translations' => false` in the options * provided to `Table::newEntity()` or `Table::patchEntity()`. * - * @param \Cake\ORM\Marshaller $marshaller The marshaller of the table the behavior is attached to. + * @param \Cake\ORM\Marshaller $marshaller The marshaler of the table the behavior is attached to. * @param array $map The property map being built. - * @param array $options The options array used in the marshalling call. + * @param array $options The options array used in the marshaling call. * @return array A map of `[property => callable]` of additional properties to marshal. */ public function buildMarshalMap(Marshaller $marshaller, array $map, array $options): array diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 62111989..8b930c16 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -253,13 +253,13 @@ public function beforeMarshal(EventInterface $event, ArrayObject $data, ArrayObj /** * {@inheritDoc} * - * Add in `_translations` marshalling handlers. You can disable marshalling + * Add in `_translations` marshaling handlers. You can disable marshaling * of translations by setting `'translations' => false` in the options * provided to `Table::newEntity()` or `Table::patchEntity()`. * - * @param \Cake\ORM\Marshaller $marshaller The marshaller of the table the behavior is attached to. + * @param \Cake\ORM\Marshaller $marshaller The marshaler of the table the behavior is attached to. * @param array $map The property map being built. - * @param array $options The options array used in the marshalling call. + * @param array $options The options array used in the marshaling call. * @return array A map of `[property => callable]` of additional properties to marshal. */ public function buildMarshalMap(Marshaller $marshaller, array $map, array $options): array diff --git a/Marshaller.php b/Marshaller.php index 3593700d..0a5f58db 100644 --- a/Marshaller.php +++ b/Marshaller.php @@ -59,9 +59,9 @@ public function __construct(Table $table) } /** - * Build the map of property => marshalling callable. + * Build the map of property => marshaling callable. * - * @param array $data The data being marshalled. + * @param array $data The data being marshaled. * @param array $options List of options containing the 'associated' key. * @throws \InvalidArgumentException When associations do not exist. * @return array @@ -155,7 +155,7 @@ protected function _buildPropertyMap(array $data, array $options): array * * - validate: Set to false to disable validation. Can also be a string of the validator ruleset to be applied. * Defaults to true/default. - * - associated: Associations listed here will be marshalled as well. Defaults to null. + * - associated: Associations listed here will be marshaled as well. Defaults to null. * - fields: An allowed list of fields to be assigned to the entity. If not present, * the accessible fields list in the entity will be used. Defaults to null. * - accessibleFields: A list of fields to allow or deny in entity accessible fields. Defaults to null @@ -226,7 +226,7 @@ public function one(array $data, array $options = []): EntityInterface } if ($value === '' && in_array($key, $primaryKey, true)) { - // Skip marshalling '' for pk fields. + // Skip marshaling '' for pk fields. continue; } if (isset($propertyMap[$key])) { @@ -357,7 +357,7 @@ protected function _marshalAssociation(Association $assoc, mixed $value, array $ * * - validate: Set to false to disable validation. Can also be a string of the validator ruleset to be applied. * Defaults to true/default. - * - associated: Associations listed here will be marshalled as well. Defaults to null. + * - associated: Associations listed here will be marshaled as well. Defaults to null. * - fields: An allowed list of fields to be assigned to the entity. If not present, * the accessible fields list in the entity will be used. Defaults to null. * - accessibleFields: A list of fields to allow or deny in entity accessible fields. Defaults to null @@ -533,7 +533,7 @@ protected function _loadAssociatedByIds(Association $assoc, array $ids): array * * ### Options: * - * - associated: Associations listed here will be marshalled as well. + * - associated: Associations listed here will be marshaled as well. * - validate: Whether to validate data before hydrating the entities. Can * also be set to a string to use a specific validator. Defaults to true/default. * - fields: An allowed list of fields to be assigned to the entity. If not present @@ -647,7 +647,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) * Records in `$data` are matched against the entities using the primary key * column. Entries in `$entities` that cannot be matched to any record in * `$data` will be discarded. Records in `$data` that could not be matched will - * be marshalled as a new entity. + * be marshaled as a new entity. * * When merging HasMany or BelongsToMany associations, all the entities in the * `$data` array will appear, those that can be matched by primary key will get @@ -657,7 +657,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) * * - validate: Whether to validate data before hydrating the entities. Can * also be set to a string to use a specific validator. Defaults to true/default. - * - associated: Associations listed here will be marshalled as well. + * - associated: Associations listed here will be marshaled as well. * - fields: An allowed list of fields to be assigned to the entity. If not present, * the accessible fields list in the entity will be used. * - accessibleFields: A list of fields to allow or deny in entity accessible fields. @@ -864,7 +864,7 @@ protected function _mergeJoinData(array $original, BelongsToMany $assoc, array $ $hash = spl_object_hash($record); $value = $this->fieldValue($record, $junctionProperty); - // Already an entity, no further marshalling required. + // Already an entity, no further marshaling required. if ($value instanceof EntityInterface) { continue; } diff --git a/PropertyMarshalInterface.php b/PropertyMarshalInterface.php index ce3fbbbe..d7510ba8 100644 --- a/PropertyMarshalInterface.php +++ b/PropertyMarshalInterface.php @@ -17,19 +17,19 @@ namespace Cake\ORM; /** - * Behaviors implementing this interface can participate in entity marshalling. + * Behaviors implementing this interface can participate in entity marshaling. * * This enables behaviors to define behavior for how the properties they provide/manage - * should be marshalled. + * should be marshaled. */ interface PropertyMarshalInterface { /** - * Build a set of properties that should be included in the marshalling process. + * Build a set of properties that should be included in the marshaling process. * - * @param \Cake\ORM\Marshaller $marshaller The marshaller of the table the behavior is attached to. + * @param \Cake\ORM\Marshaller $marshaller The marshaler of the table the behavior is attached to. * @param array $map The property map being built. - * @param array $options The options array used in the marshalling call. + * @param array $options The options array used in the marshaling call. * @return array A map of `[property => callable]` of additional properties to marshal. */ public function buildMarshalMap(Marshaller $marshaller, array $map, array $options): array; diff --git a/Table.php b/Table.php index 489b9a63..9ab58619 100644 --- a/Table.php +++ b/Table.php @@ -2871,7 +2871,7 @@ public function __isset(string $property): bool * Get the object used to marshal/convert array data into objects. * * Override this method if you want a table object to use custom - * marshalling logic. + * marshaling logic. * * @return \Cake\ORM\Marshaller * @see \Cake\ORM\Marshaller @@ -3061,7 +3061,7 @@ public function patchEntity(EntityInterface $entity, array $data, array $options * * Those entries in `$entities` that cannot be matched to any record in * `$data` will be discarded. Records in `$data` that could not be matched will - * be marshalled as a new entity. + * be marshaled as a new entity. * * When merging HasMany or BelongsToMany associations, all the entities in the * `$data` array will appear, those that can be matched by primary key will get From 441e318218d76ec4253804ae1ac9544984d5bc93 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Sun, 27 Apr 2025 01:00:26 +0200 Subject: [PATCH 2041/2059] Fix regression for EntityTrait::isModified() (#18389) * Fixed regression for `EntityTrait::isModified()` * Fix tests. --------- Co-authored-by: mirko-pagliai --- Behavior/Translate/EavStrategy.php | 2 +- Behavior/Translate/ShadowTableStrategy.php | 4 ++-- Behavior/Translate/TranslateStrategyTrait.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index f1df6f09..6fd6131b 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -465,7 +465,7 @@ public function groupTranslations(CollectionInterface $results): CollectionInter protected function bundleTranslatedFields(EntityInterface $entity): void { /** @var array $translations */ - $translations = $entity->has('_translations') ? $entity->get('_translations') : []; + $translations = $entity->has('_translations') ? (array)$entity->get('_translations') : []; if (!$translations && !$entity->isDirty('_translations')) { return; diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index a395bed5..06ac22bb 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -368,7 +368,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array } $this->bundleTranslatedFields($entity); - $bundled = $entity->has('_i18n') ? $entity->get('_i18n') : []; + $bundled = $entity->has('_i18n') ? (array)$entity->get('_i18n') : []; $noBundled = count($bundled) === 0; // No additional translation records need to be saved, @@ -599,7 +599,7 @@ public function groupTranslations(CollectionInterface $results): CollectionInter protected function bundleTranslatedFields(EntityInterface $entity): void { /** @var array $translations */ - $translations = $entity->has('_translations') ? $entity->get('_translations') : []; + $translations = $entity->has('_translations') ? (array)$entity->get('_translations') : []; if (!$translations && !$entity->isDirty('_translations')) { return; diff --git a/Behavior/Translate/TranslateStrategyTrait.php b/Behavior/Translate/TranslateStrategyTrait.php index 6681b197..e2f48ca2 100644 --- a/Behavior/Translate/TranslateStrategyTrait.php +++ b/Behavior/Translate/TranslateStrategyTrait.php @@ -162,7 +162,7 @@ public function buildMarshalMap(Marshaller $marshaller, array $map, array $optio } /** @var array $translations */ - $translations = $entity->has('_translations') ? $entity->get('_translations') : []; + $translations = $entity->has('_translations') ? (array)$entity->get('_translations') : []; $options['validate'] = $this->_config['validator']; $errors = []; From 8cf21264155ea2bce3de70aed8088a20f8865271 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Wed, 30 Apr 2025 05:12:55 +0200 Subject: [PATCH 2042/2059] Fix up missing string cast. (#18392) --- ResultSetFactory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ResultSetFactory.php b/ResultSetFactory.php index 171fc5aa..072510db 100644 --- a/ResultSetFactory.php +++ b/ResultSetFactory.php @@ -99,7 +99,7 @@ protected function collectData(SelectQuery $query): array $fields = []; foreach ($query->clause('select') as $key => $field) { - $key = trim($key, '"`[]'); + $key = trim((string)$key, '"`[]'); if (strpos($key, '__') <= 0) { $fields[$data['primaryAlias']][$key] = $key; From 1272f22a19176069e6cfae1a548e882ea972b7c7 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 9 May 2025 18:28:40 +0530 Subject: [PATCH 2043/2059] Remove redundant check. The finder map is appropriately updated when a behavior is loaded/unloaded making the additional check redundant. --- BehaviorRegistry.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BehaviorRegistry.php b/BehaviorRegistry.php index 3984c0bf..fe0c7c71 100644 --- a/BehaviorRegistry.php +++ b/BehaviorRegistry.php @@ -323,7 +323,7 @@ public function callFinder(string $type, SelectQuery $query, mixed ...$args): Se { $type = strtolower($type); - if ($this->hasFinder($type) && $this->has($this->_finderMap[$type][0])) { + if ($this->hasFinder($type)) { [$behavior, $callMethod] = $this->_finderMap[$type]; $callable = $this->_loaded[$behavior]->$callMethod(...); From 027ab46baa6ac1c38650ddafb95961c2e70fb826 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 18 May 2025 09:46:26 -0400 Subject: [PATCH 2044/2059] Fix API doc example This example should use named parameters instead of the deprecated options array. --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 9ab58619..45728cca 100644 --- a/Table.php +++ b/Table.php @@ -1486,7 +1486,7 @@ protected function _setFieldMatchers(array $options, array $keys): array * Get an article and some relationships: * * ``` - * $article = $articles->get(1, ['contain' => ['Users', 'Comments']]); + * $article = $articles->get(1, contain: ['Users', 'Comments']]); * ``` * * @param mixed $primaryKey primary key value to find From f4d3cb2892f40a620b570ce051912863fd5979cd Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 4 Jun 2025 08:24:18 +0530 Subject: [PATCH 2045/2059] Fix problem with chained find() calls and overlapping argument names. (#18717) Fix problem with changed find() calls and overlapping argument names. Closes #18716 --- Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Table.php b/Table.php index 45728cca..cdae390d 100644 --- a/Table.php +++ b/Table.php @@ -2722,7 +2722,7 @@ public function invokeFinder(Closure $callable, SelectQuery $query, array $args) if ($args) { $query->applyOptions($args); // Fetch custom args without the query options. - $args = $query->getOptions(); + $args = array_intersect_key($args, $query->getOptions()); unset($params[0]); $lastParam = end($params); From 712594be65500dee18c25f42ba906aea931421a2 Mon Sep 17 00:00:00 2001 From: othercorey Date: Sat, 28 Jun 2025 11:06:57 -0500 Subject: [PATCH 2046/2059] Fix translate behavior with I18n locale with options (#18772) Fix translate strategies with I18n locale with options --- Behavior/Translate/TranslateStrategyTrait.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Behavior/Translate/TranslateStrategyTrait.php b/Behavior/Translate/TranslateStrategyTrait.php index e2f48ca2..72ab0f12 100644 --- a/Behavior/Translate/TranslateStrategyTrait.php +++ b/Behavior/Translate/TranslateStrategyTrait.php @@ -82,7 +82,7 @@ public function setLocale(?string $locale) } /** - * Returns the current locale. + * Returns the current locale excluding any options set with @. * * If no locale has been explicitly set via `setLocale()`, this method will return * the currently configured global locale. @@ -93,7 +93,7 @@ public function setLocale(?string $locale) */ public function getLocale(): string { - return $this->locale ?: I18n::getLocale(); + return $this->locale ?: explode('@', I18n::getLocale())[0]; } /** From 0624ad8c52e3597af2552fa475b165aca7976fb4 Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Sat, 28 Jun 2025 21:32:15 -0500 Subject: [PATCH 2047/2059] Add shadow table tests for locale options --- Behavior/Translate/TranslateStrategyTrait.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Behavior/Translate/TranslateStrategyTrait.php b/Behavior/Translate/TranslateStrategyTrait.php index 72ab0f12..197da640 100644 --- a/Behavior/Translate/TranslateStrategyTrait.php +++ b/Behavior/Translate/TranslateStrategyTrait.php @@ -82,10 +82,10 @@ public function setLocale(?string $locale) } /** - * Returns the current locale excluding any options set with @. + * Returns the current locale. * * If no locale has been explicitly set via `setLocale()`, this method will return - * the currently configured global locale. + * the currently configured global locale excluding any options set after @. * * @return string * @see \Cake\I18n\I18n::getLocale() From fe736073dd402587229efce60ed0854a83558864 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 4 Jul 2025 09:15:30 +0530 Subject: [PATCH 2048/2059] Check whether properties are initialized before accessing. (#18766) This avoids errors with Xdebug which somehow manages to call __debugInfo() without calling the constructor which initializes these properties. Closes #18760 --- Table.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Table.php b/Table.php index cdae390d..ea7d681a 100644 --- a/Table.php +++ b/Table.php @@ -3280,8 +3280,10 @@ public function __debugInfo(): array 'table' => $this->getTable(), 'alias' => $this->getAlias(), 'entityClass' => $this->getEntityClass(), - 'associations' => $this->_associations->keys(), - 'behaviors' => $this->_behaviors->loaded(), + /** @phpstan-ignore isset.initializedProperty */ + 'associations' => isset($this->_associations) ? $this->_associations->keys() : [], + /** @phpstan-ignore isset.initializedProperty */ + 'behaviors' => isset($this->_behaviors) ? $this->_behaviors->loaded() : [], 'defaultConnection' => static::defaultConnectionName(), 'connectionName' => $conn->configName(), ]; From 1019d5e0bb1b335643608983f227227b66fca0b5 Mon Sep 17 00:00:00 2001 From: Eriknag Date: Fri, 4 Jul 2025 17:04:30 +0200 Subject: [PATCH 2049/2059] Fixes InvalidArgumentException (#16362) (#18763) * Fixes InvalidArgumentException when joining and containing the same table in a loadInto call * Fix phpcs Errors * Add assertions on article and author in TableTest::testloadBelongsToDoubleJoin --------- Co-authored-by: Erik Nagelkerke --- EagerLoader.php | 1 + 1 file changed, 1 insertion(+) diff --git a/EagerLoader.php b/EagerLoader.php index 18e93b9c..665483fe 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -62,6 +62,7 @@ class EagerLoader 'joinType' => 1, 'strategy' => 1, 'negateMatch' => 1, + 'includeFields' => 1, ]; /** From af926432720895ef584fbb075292fe719e4bafe8 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Sun, 20 Jul 2025 04:02:49 +0200 Subject: [PATCH 2050/2059] Fix some spelling and broken code snippets. (#18791) --- Behavior/Translate/EavStrategy.php | 2 +- Behavior/Translate/ShadowTableStrategy.php | 2 +- Query/CommonQueryTrait.php | 2 +- RulesChecker.php | 2 +- Table.php | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Behavior/Translate/EavStrategy.php b/Behavior/Translate/EavStrategy.php index 6fd6131b..cc357d97 100644 --- a/Behavior/Translate/EavStrategy.php +++ b/Behavior/Translate/EavStrategy.php @@ -267,7 +267,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array // If there are no fields and no bundled translations, or both fields // in the default locale and bundled translations we can - // skip the remaining logic as its not necessary. + // skip the remaining logic as it is not necessary. if ($noFields && $noBundled || ($fields && $bundled)) { return; } diff --git a/Behavior/Translate/ShadowTableStrategy.php b/Behavior/Translate/ShadowTableStrategy.php index 06ac22bb..4d787378 100644 --- a/Behavior/Translate/ShadowTableStrategy.php +++ b/Behavior/Translate/ShadowTableStrategy.php @@ -383,7 +383,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array // If there are no fields and no bundled translations, or both fields // in the default locale and bundled translations we can - // skip the remaining logic as its not necessary. + // skip the remaining logic as it is not necessary. if ($noFields && $noBundled || ($fields && $bundled)) { return; } diff --git a/Query/CommonQueryTrait.php b/Query/CommonQueryTrait.php index 7ac6a6a0..1b45f992 100644 --- a/Query/CommonQueryTrait.php +++ b/Query/CommonQueryTrait.php @@ -76,7 +76,7 @@ public function setRepository(RepositoryInterface $repository) /** * Returns the default repository object that will be used by this query, - * that is, the table that will appear in the from clause. + * that is, the table that will appear in the "from" clause. * * @return \Cake\ORM\Table */ diff --git a/RulesChecker.php b/RulesChecker.php index 15c0cf96..7fef0635 100644 --- a/RulesChecker.php +++ b/RulesChecker.php @@ -26,7 +26,7 @@ use function Cake\I18n\__d; /** - * ORM flavoured rules checker. + * ORM flavored rules checker. * * Adds ORM related features to the RulesChecker class. * diff --git a/Table.php b/Table.php index ea7d681a..f8df15f5 100644 --- a/Table.php +++ b/Table.php @@ -447,7 +447,7 @@ public function getAlias(): string /** * Alias a field with the table's current alias. * - * If field is already aliased it will result in no-op. + * If field is already aliased, it will result in no-op. * * @param string $field The field to alias. * @return string The field prefixed with the table alias. @@ -1486,7 +1486,7 @@ protected function _setFieldMatchers(array $options, array $keys): array * Get an article and some relationships: * * ``` - * $article = $articles->get(1, contain: ['Users', 'Comments']]); + * $article = $articles->get(1, contain: ['Users', 'Comments']); * ``` * * @param mixed $primaryKey primary key value to find From c680aa9cbcc4aa1230b422af63c86055510525f8 Mon Sep 17 00:00:00 2001 From: Arshid Date: Sat, 23 Aug 2025 12:31:20 +0530 Subject: [PATCH 2051/2059] Fix PHP 8.5 deprecations: replace SplObjectStorage::contains() and remove ReflectionProperty::setAccessible() (#18831) * Fix PHP 8.5 deprecations: replace SplObjectStorage::contains() and remove ReflectionProperty::setAccessible() * Update Debugger.php * Update ci.yml * Temporarily ignore PHP version constraint for 8.5 * Remove curl_close() call as it has no effect in PHP 8.0+ * Replace DATE_RFC7231 * Define DATE_RFC7231 * Define DATE_RFC7231 * Define DATE_RFC7231 * CAKE_DATE_RFC7231 * CAKE_DATE_RFC7231 * CAKE_DATE_RFC7231 * CAKE_DATE_RFC7231 * CAKE_DATE_RFC7231 * fix the he split packages phpstan failure * fix the split packages phpstan failure * Undo --------- Co-authored-by: Arshid Co-authored-by: ADmad --- Association/BelongsToMany.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 5dde8bd3..1ad93513 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -974,11 +974,11 @@ function () use ($sourceEntity, $targetEntities, $options): void { /** @var \SplObjectStorage<\Cake\Datasource\EntityInterface, null> $storage */ $storage = new SplObjectStorage(); foreach ($targetEntities as $e) { - $storage->attach($e); + $storage->offsetSet($e); } foreach ($existing as $k => $e) { - if ($storage->contains($e)) { + if ($storage->offsetExists($e)) { unset($existing[$k]); } } From c319ec5ddce23ce90563972ebd5b0e3b9f927338 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 31 Aug 2025 22:40:59 +0530 Subject: [PATCH 2052/2059] Use null coalescing operator --- Association/BelongsTo.php | 6 +----- Association/BelongsToMany.php | 6 +----- Association/HasMany.php | 6 +----- Association/HasOne.php | 6 +----- Behavior.php | 4 +--- Behavior/Translate/TranslateStrategyTrait.php | 4 +--- 6 files changed, 6 insertions(+), 26 deletions(-) diff --git a/Association/BelongsTo.php b/Association/BelongsTo.php index 2ecfce12..35f0eb12 100644 --- a/Association/BelongsTo.php +++ b/Association/BelongsTo.php @@ -52,11 +52,7 @@ class BelongsTo extends Association */ public function getForeignKey(): array|string|false { - if (!isset($this->_foreignKey)) { - $this->_foreignKey = $this->_modelKey($this->getTarget()->getAlias()); - } - - return $this->_foreignKey; + return $this->_foreignKey ??= $this->_modelKey($this->getTarget()->getAlias()); } /** diff --git a/Association/BelongsToMany.php b/Association/BelongsToMany.php index 1ad93513..b4b91988 100644 --- a/Association/BelongsToMany.php +++ b/Association/BelongsToMany.php @@ -203,11 +203,7 @@ public function canBeJoined(array $options = []): bool */ public function getForeignKey(): array|string|false { - if (!isset($this->_foreignKey)) { - $this->_foreignKey = $this->_modelKey($this->getSource()->getTable()); - } - - return $this->_foreignKey; + return $this->_foreignKey ??= $this->_modelKey($this->getSource()->getTable()); } /** diff --git a/Association/HasMany.php b/Association/HasMany.php index 2e53236e..d8de160b 100644 --- a/Association/HasMany.php +++ b/Association/HasMany.php @@ -617,11 +617,7 @@ public function canBeJoined(array $options = []): bool */ public function getForeignKey(): array|string|false { - if (!isset($this->_foreignKey)) { - $this->_foreignKey = $this->_modelKey($this->getSource()->getTable()); - } - - return $this->_foreignKey; + return $this->_foreignKey ??= $this->_modelKey($this->getSource()->getTable()); } /** diff --git a/Association/HasOne.php b/Association/HasOne.php index accdb1b9..51012c77 100644 --- a/Association/HasOne.php +++ b/Association/HasOne.php @@ -50,11 +50,7 @@ class HasOne extends Association */ public function getForeignKey(): array|string|false { - if (!isset($this->_foreignKey)) { - $this->_foreignKey = $this->_modelKey($this->getSource()->getAlias()); - } - - return $this->_foreignKey; + return $this->_foreignKey ??= $this->_modelKey($this->getSource()->getAlias()); } /** diff --git a/Behavior.php b/Behavior.php index 5c688409..7dcfbed9 100644 --- a/Behavior.php +++ b/Behavior.php @@ -213,9 +213,7 @@ protected function _resolveMethodAliases(string $key, array $defaults, array $co $indexed = array_flip($defaults[$key]); $indexedCustom = array_flip($config[$key]); foreach ($indexed as $method => $alias) { - if (!isset($indexedCustom[$method])) { - $indexedCustom[$method] = $alias; - } + $indexedCustom[$method] ??= $alias; } $this->setConfig($key, array_flip($indexedCustom), false); unset($config[$key]); diff --git a/Behavior/Translate/TranslateStrategyTrait.php b/Behavior/Translate/TranslateStrategyTrait.php index 197da640..9512229b 100644 --- a/Behavior/Translate/TranslateStrategyTrait.php +++ b/Behavior/Translate/TranslateStrategyTrait.php @@ -167,9 +167,7 @@ public function buildMarshalMap(Marshaller $marshaller, array $map, array $optio $options['validate'] = $this->_config['validator']; $errors = []; foreach ($value as $language => $fields) { - if (!isset($translations[$language])) { - $translations[$language] = $this->table->newEmptyEntity(); - } + $translations[$language] ??= $this->table->newEmptyEntity(); $marshaller->merge($translations[$language], $fields, $options); $translationErrors = $translations[$language]->getErrors(); From 326314841f760bce52c374c66cffdd80627daf17 Mon Sep 17 00:00:00 2001 From: Marcelo Rocha Date: Wed, 3 Sep 2025 21:13:11 -0300 Subject: [PATCH 2053/2059] Update annotation for IDE autocomplete (#18806) * Update annotation for IDE autocomplete * Update annotation for IDE autocomplete * Update annotation for IDE autocomplete * fix phpstan issues withcorrect annotations for IDE autocomplete * Use generic return for collection inteface * Re-added extends to avoid PHPStan error * Remove phpdoc causing error on IDE * Fixing typo * Use generic for return types * Use generic for return types * IteratoIterator extends * Fix phpstan issues * Fix phpstan issues * Ignore phpstan error generics.interfaceConflict * Fix phpstan issues * Use array instead of list, lists has caused issues before * Moved conditional return to phpstan specific annotation * Annotations must be compatible with Traversable * Annotations must be compatible with Traversable. Improved phpdoc with TKey and TValue * PHPStan fixes * Fix git conflicts * PHPCS fix * PHPCS fixes --- ResultSet.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ResultSet.php b/ResultSet.php index 103d913c..a38b4127 100644 --- a/ResultSet.php +++ b/ResultSet.php @@ -25,8 +25,10 @@ * the query, casting each field to the correct type and executing the extra * queries required for eager loading external associations. * - * @template T - * @implements \Cake\Datasource\ResultSetInterface + * @template TKey + * @template TValue + * @template-implemements \Cake\Datasource\ResultSetInterface + * @template-extends \Cake\Collection\Collection */ class ResultSet extends Collection implements ResultSetInterface { From 479f37e1c4ced0ed1e2aefed63d1103aac0ccf91 Mon Sep 17 00:00:00 2001 From: Arshid Date: Sat, 6 Sep 2025 00:01:47 +0530 Subject: [PATCH 2054/2059] PHP 8.5 (#18888) * PHP 8.5 * Update src/Collection/Iterator/MapReduce.php Co-authored-by: ADmad --------- Co-authored-by: Arshid Co-authored-by: ADmad --- EagerLoader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EagerLoader.php b/EagerLoader.php index 665483fe..27db386f 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -305,7 +305,7 @@ public function normalized(Table $repository): array $repository, $alias, $options, - ['root' => null], + ['root' => ''], ); } From 31def8702aabbff4f7551b083256e563130071a3 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Sun, 7 Sep 2025 18:17:16 +0200 Subject: [PATCH 2055/2059] Fix up loadInto() (#18876) * Fix up loadInto() * Add additional assertions and remove conditions in tests --------- Co-authored-by: Mark Story --- EagerLoader.php | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/EagerLoader.php b/EagerLoader.php index 27db386f..5251112f 100644 --- a/EagerLoader.php +++ b/EagerLoader.php @@ -354,13 +354,22 @@ protected function _reformatContain(array $associations, array $original): array } if (is_array($options)) { - $options = isset($options['config']) ? - $options['config'] + $options['associations'] : - $options; - $options = $this->_reformatContain( - $options, - $pointer[$table] ?? [], - ); + // When options come from asContainArray(), they have 'config' and 'associations' keys + // We need to keep them separate to avoid config options being treated as associations + if (isset($options['config']) && isset($options['associations'])) { + // Process associations recursively, but keep config separate + $associations = $this->_reformatContain( + $options['associations'], + $pointer[$table] ?? [], + ); + // Merge config with associations, ensuring config options stay as options + $options = $options['config'] + $associations; + } else { + $options = $this->_reformatContain( + $options, + $pointer[$table] ?? [], + ); + } } if ($options instanceof Closure) { From 04dab9addcd63772449702d2657e653f80fa7d39 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 8 Sep 2025 20:48:32 +0530 Subject: [PATCH 2056/2059] Improve/cleanup type inference (#18892) --- Behavior/TranslateBehavior.php | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Behavior/TranslateBehavior.php b/Behavior/TranslateBehavior.php index 8b930c16..2bf2cf26 100644 --- a/Behavior/TranslateBehavior.php +++ b/Behavior/TranslateBehavior.php @@ -166,11 +166,7 @@ public static function getDefaultStrategyClass(): string */ public function getStrategy(): TranslateStrategyInterface { - if ($this->strategy !== null) { - return $this->strategy; - } - - return $this->strategy = $this->createStrategy(); + return $this->strategy ??= $this->createStrategy(); } /** @@ -370,7 +366,7 @@ public function findTranslations(SelectQuery $query, array $locales = []): Selec */ public function __call(string $method, array $args): mixed { - return $this->strategy->{$method}(...$args); + return $this->getStrategy()->{$method}(...$args); } /** @@ -387,7 +383,7 @@ public function __call(string $method, array $args): mixed protected function referenceName(Table $table): string { $name = namespaceSplit($table::class); - $name = substr((string)end($name), 0, -5); + $name = substr(end($name), 0, -5); if (!$name) { $name = $table->getTable() ?: $table->getAlias(); $name = Inflector::camelize($name); From fa163f8557acae460187f0be74c5cadc255ded0f Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Wed, 10 Sep 2025 04:35:50 +0200 Subject: [PATCH 2057/2059] Fix up eager loading. (#18883) * Fix up eager loading. * Fix CS --- Association/Loader/SelectLoader.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Association/Loader/SelectLoader.php b/Association/Loader/SelectLoader.php index 35113ab6..dff8b195 100644 --- a/Association/Loader/SelectLoader.php +++ b/Association/Loader/SelectLoader.php @@ -419,11 +419,19 @@ protected function _buildSubquery(SelectQuery $query): SelectQuery $filterQuery->contain([], true); $filterQuery->setValueBinder(new ValueBinder()); - // Ignore limit if there is no order since we need all rows to find matches - if (!$filterQuery->clause('limit') || !$filterQuery->clause('order')) { + // Only remove limit and order when BOTH are missing or when order exists without limit + // When limit exists with order, preserve both for proper subquery results + $hasLimit = $filterQuery->clause('limit') !== null; + $hasOrder = $filterQuery->clause('order') !== null; + + // Remove order if there's no limit to avoid SQL grouping errors + // But preserve both when they exist together + if (!$hasLimit) { $filterQuery->limit(null); - $filterQuery->orderBy([], true); $filterQuery->offset(null); + if ($hasOrder) { + $filterQuery->orderBy([], true); + } } $fields = $this->_subqueryFields($query); From 111df88f700ef4274e3fddb71834d3ba70d92411 Mon Sep 17 00:00:00 2001 From: kolorafa Date: Wed, 24 Sep 2025 16:57:28 +0200 Subject: [PATCH 2058/2059] Update SelectQuery return typehints to match updated ResultSetInterface --- Query/SelectQuery.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Query/SelectQuery.php b/Query/SelectQuery.php index 2f27ddc3..4fa180d0 100644 --- a/Query/SelectQuery.php +++ b/Query/SelectQuery.php @@ -227,7 +227,7 @@ public function setResult(iterable $results) * iterated without having to call execute() manually, thus making it look like * a result set instead of the query itself. * - * @return \Cake\Datasource\ResultSetInterface<\Cake\Datasource\EntityInterface|array> + * @return \Cake\Datasource\ResultSetInterface */ public function getIterator(): ResultSetInterface { @@ -365,7 +365,7 @@ public function aliasFields(array $fields, ?string $defaultAlias = null): array * ResultSetDecorator is a traversable object that implements the methods found * on Cake\Collection\Collection. * - * @return \Cake\Datasource\ResultSetInterface + * @return \Cake\Datasource\ResultSetInterface */ public function all(): ResultSetInterface { @@ -725,7 +725,7 @@ public function applyOptions(array $options) * Decorates the results iterator with MapReduce routines and formatters * * @param iterable $result Original results - * @return \Cake\Datasource\ResultSetInterface<\Cake\Datasource\EntityInterface|mixed> + * @return \Cake\Datasource\ResultSetInterface */ protected function _decorateResults(iterable $result): ResultSetInterface { @@ -1728,7 +1728,7 @@ public function __debugInfo(): array * * Part of JsonSerializable interface. * - * @return \Cake\Datasource\ResultSetInterface<(\Cake\Datasource\EntityInterface|mixed)> The data to convert to JSON. + * @return \Cake\Datasource\ResultSetInterface The data to convert to JSON. */ public function jsonSerialize(): ResultSetInterface { From 7491cf5811aa5de68359ee8c014a3e9a9bd5de5a Mon Sep 17 00:00:00 2001 From: Adam Halfar <47174548+Harfusha@users.noreply.github.com> Date: Fri, 10 Oct 2025 12:04:50 +0200 Subject: [PATCH 2059/2059] Add TableLocator get by FQCN (#18957) * Add TableLocator get by FQCN * Add missing docblock, add phpcs ignore * Fix whitespace * Revert TableLocator, Add fetchTableByClass * Merge fetchTable and fetchTableByClass --------- Co-authored-by: Adam Halfar --- Locator/LocatorAwareTrait.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Locator/LocatorAwareTrait.php b/Locator/LocatorAwareTrait.php index 204934cc..7f216f29 100644 --- a/Locator/LocatorAwareTrait.php +++ b/Locator/LocatorAwareTrait.php @@ -75,11 +75,12 @@ public function getTableLocator(): LocatorInterface /** * Convenience method to get a table instance. * - * @param string|null $alias The alias name you want to get. Should be in CamelCase format. + * @template T of \Cake\ORM\Table + * @param class-string|string|null $alias The alias name you want to get. Should be in CamelCase format. * If `null` then the value of $defaultTable property is used. * @param array $options The options you want to build the table with. * If a table has already been loaded the registry options will be ignored. - * @return \Cake\ORM\Table + * @return ($alias is class-string ? T : \Cake\ORM\Table) * @throws \Cake\Core\Exception\CakeException If `$alias` argument and `$defaultTable` property both are `null`. * @see \Cake\ORM\TableLocator::get() * @since 4.3.0 @@ -93,6 +94,8 @@ public function fetchTable(?string $alias = null, array $options = []): Table ); } + // phpcs:ignore + /** @var T */ return $this->getTableLocator()->get($alias, $options); } }