1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org) |
4
|
|
|
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
5
|
|
|
* |
6
|
|
|
* Licensed under The MIT License |
7
|
|
|
* For full copyright and license information, please see the LICENSE.txt |
8
|
|
|
* Redistributions of files must retain the above copyright notice. |
9
|
|
|
* |
10
|
|
|
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
11
|
|
|
* @link https://cakephp.org CakePHP(tm) Project |
12
|
|
|
* @since 3.0.0 |
13
|
|
|
* @license https://opensource.org/licenses/mit-license.php MIT License |
14
|
|
|
*/ |
15
|
|
|
namespace Cake\ORM; |
16
|
|
|
|
17
|
|
|
use ArrayObject; |
18
|
|
|
use BadMethodCallException; |
19
|
|
|
use Cake\Core\App; |
20
|
|
|
use Cake\Database\Schema\TableSchema; |
21
|
|
|
use Cake\Database\Type; |
22
|
|
|
use Cake\Datasource\ConnectionInterface; |
23
|
|
|
use Cake\Datasource\EntityInterface; |
24
|
|
|
use Cake\Datasource\Exception\InvalidPrimaryKeyException; |
25
|
|
|
use Cake\Datasource\RepositoryInterface; |
26
|
|
|
use Cake\Datasource\RulesAwareTrait; |
27
|
|
|
use Cake\Event\EventDispatcherInterface; |
28
|
|
|
use Cake\Event\EventDispatcherTrait; |
29
|
|
|
use Cake\Event\EventListenerInterface; |
30
|
|
|
use Cake\Event\EventManager; |
31
|
|
|
use Cake\ORM\Association\BelongsTo; |
32
|
|
|
use Cake\ORM\Association\BelongsToMany; |
33
|
|
|
use Cake\ORM\Association\HasMany; |
34
|
|
|
use Cake\ORM\Association\HasOne; |
35
|
|
|
use Cake\ORM\Exception\MissingEntityException; |
36
|
|
|
use Cake\ORM\Exception\PersistenceFailedException; |
37
|
|
|
use Cake\ORM\Exception\RolledbackTransactionException; |
38
|
|
|
use Cake\ORM\Rule\IsUnique; |
39
|
|
|
use Cake\Utility\Inflector; |
40
|
|
|
use Cake\Validation\ValidatorAwareInterface; |
41
|
|
|
use Cake\Validation\ValidatorAwareTrait; |
42
|
|
|
use InvalidArgumentException; |
43
|
|
|
use RuntimeException; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* Represents a single database table. |
47
|
|
|
* |
48
|
|
|
* Exposes methods for retrieving data out of it, and manages the associations |
49
|
|
|
* this table has to other tables. Multiple instances of this class can be created |
50
|
|
|
* for the same database table with different aliases, this allows you to address |
51
|
|
|
* your database structure in a richer and more expressive way. |
52
|
|
|
* |
53
|
|
|
* ### Retrieving data |
54
|
|
|
* |
55
|
|
|
* The primary way to retrieve data is using Table::find(). See that method |
56
|
|
|
* for more information. |
57
|
|
|
* |
58
|
|
|
* ### Dynamic finders |
59
|
|
|
* |
60
|
|
|
* In addition to the standard find($type) finder methods, CakePHP provides dynamic |
61
|
|
|
* finder methods. These methods allow you to easily set basic conditions up. For example |
62
|
|
|
* to filter users by username you would call |
63
|
|
|
* |
64
|
|
|
* ``` |
65
|
|
|
* $query = $users->findByUsername('mark'); |
66
|
|
|
* ``` |
67
|
|
|
* |
68
|
|
|
* You can also combine conditions on multiple fields using either `Or` or `And`: |
69
|
|
|
* |
70
|
|
|
* ``` |
71
|
|
|
* $query = $users->findByUsernameOrEmail('mark', '[email protected]'); |
72
|
|
|
* ``` |
73
|
|
|
* |
74
|
|
|
* ### Bulk updates/deletes |
75
|
|
|
* |
76
|
|
|
* You can use Table::updateAll() and Table::deleteAll() to do bulk updates/deletes. |
77
|
|
|
* You should be aware that events will *not* be fired for bulk updates/deletes. |
78
|
|
|
* |
79
|
|
|
* ### Callbacks/events |
80
|
|
|
* |
81
|
|
|
* Table objects provide a few callbacks/events you can hook into to augment/replace |
82
|
|
|
* find operations. Each event uses the standard event subsystem in CakePHP |
83
|
|
|
* |
84
|
|
|
* - `beforeFind(Event $event, Query $query, ArrayObject $options, boolean $primary)` |
85
|
|
|
* Fired before each find operation. By stopping the event and supplying a |
86
|
|
|
* return value you can bypass the find operation entirely. Any changes done |
87
|
|
|
* to the $query instance will be retained for the rest of the find. The |
88
|
|
|
* $primary parameter indicates whether or not this is the root query, |
89
|
|
|
* or an associated query. |
90
|
|
|
* |
91
|
|
|
* - `buildValidator(Event $event, Validator $validator, string $name)` |
92
|
|
|
* Allows listeners to modify validation rules for the provided named validator. |
93
|
|
|
* |
94
|
|
|
* - `buildRules(Event $event, RulesChecker $rules)` |
95
|
|
|
* Allows listeners to modify the rules checker by adding more rules. |
96
|
|
|
* |
97
|
|
|
* - `beforeRules(Event $event, EntityInterface $entity, ArrayObject $options, string $operation)` |
98
|
|
|
* Fired before an entity is validated using the rules checker. By stopping this event, |
99
|
|
|
* you can return the final value of the rules checking operation. |
100
|
|
|
* |
101
|
|
|
* - `afterRules(Event $event, EntityInterface $entity, ArrayObject $options, bool $result, string $operation)` |
102
|
|
|
* Fired after the rules have been checked on the entity. By stopping this event, |
103
|
|
|
* you can return the final value of the rules checking operation. |
104
|
|
|
* |
105
|
|
|
* - `beforeSave(Event $event, EntityInterface $entity, ArrayObject $options)` |
106
|
|
|
* Fired before each entity is saved. Stopping this event will abort the save |
107
|
|
|
* operation. When the event is stopped the result of the event will be returned. |
108
|
|
|
* |
109
|
|
|
* - `afterSave(Event $event, EntityInterface $entity, ArrayObject $options)` |
110
|
|
|
* Fired after an entity is saved. |
111
|
|
|
* |
112
|
|
|
* - `afterSaveCommit(Event $event, EntityInterface $entity, ArrayObject $options)` |
113
|
|
|
* Fired after the transaction in which the save operation is wrapped has been committed. |
114
|
|
|
* It’s also triggered for non atomic saves where database operations are implicitly committed. |
115
|
|
|
* The event is triggered only for the primary table on which save() is directly called. |
116
|
|
|
* The event is not triggered if a transaction is started before calling save. |
117
|
|
|
* |
118
|
|
|
* - `beforeDelete(Event $event, EntityInterface $entity, ArrayObject $options)` |
119
|
|
|
* Fired before an entity is deleted. By stopping this event you will abort |
120
|
|
|
* the delete operation. |
121
|
|
|
* |
122
|
|
|
* - `afterDelete(Event $event, EntityInterface $entity, ArrayObject $options)` |
123
|
|
|
* Fired after an entity has been deleted. |
124
|
|
|
* |
125
|
|
|
* @see \Cake\Event\EventManager for reference on the events system. |
126
|
|
|
*/ |
127
|
|
|
class Table implements RepositoryInterface, EventListenerInterface, EventDispatcherInterface, ValidatorAwareInterface |
128
|
|
|
{ |
129
|
|
|
use EventDispatcherTrait; |
130
|
|
|
use RulesAwareTrait; |
131
|
|
|
use ValidatorAwareTrait; |
132
|
|
|
|
133
|
|
|
/** |
134
|
|
|
* The alias this object is assigned to validators as. |
135
|
|
|
* |
136
|
|
|
* @var string |
137
|
|
|
*/ |
138
|
|
|
const VALIDATOR_PROVIDER_NAME = 'table'; |
139
|
|
|
|
140
|
|
|
/** |
141
|
|
|
* The name of the event dispatched when a validator has been built. |
142
|
|
|
* |
143
|
|
|
* @var string |
144
|
|
|
*/ |
145
|
|
|
const BUILD_VALIDATOR_EVENT = 'Model.buildValidator'; |
146
|
|
|
|
147
|
|
|
/** |
148
|
|
|
* The rules class name that is used. |
149
|
|
|
* |
150
|
|
|
* @var string |
151
|
|
|
*/ |
152
|
|
|
const RULES_CLASS = RulesChecker::class; |
153
|
|
|
|
154
|
|
|
/** |
155
|
|
|
* The IsUnique class name that is used. |
156
|
|
|
* |
157
|
|
|
* @var string |
158
|
|
|
*/ |
159
|
|
|
const IS_UNIQUE_CLASS = IsUnique::class; |
160
|
|
|
|
161
|
|
|
/** |
162
|
|
|
* Name of the table as it can be found in the database |
163
|
|
|
* |
164
|
|
|
* @var string |
165
|
|
|
*/ |
166
|
|
|
protected $_table; |
167
|
|
|
|
168
|
|
|
/** |
169
|
|
|
* Human name giving to this particular instance. Multiple objects representing |
170
|
|
|
* the same database table can exist by using different aliases. |
171
|
|
|
* |
172
|
|
|
* @var string |
173
|
|
|
*/ |
174
|
|
|
protected $_alias; |
175
|
|
|
|
176
|
|
|
/** |
177
|
|
|
* Connection instance |
178
|
|
|
* |
179
|
|
|
* @var \Cake\Database\Connection |
180
|
|
|
*/ |
181
|
|
|
protected $_connection; |
182
|
|
|
|
183
|
|
|
/** |
184
|
|
|
* The schema object containing a description of this table fields |
185
|
|
|
* |
186
|
|
|
* @var \Cake\Database\Schema\TableSchema |
187
|
|
|
*/ |
188
|
|
|
protected $_schema; |
189
|
|
|
|
190
|
|
|
/** |
191
|
|
|
* The name of the field that represents the primary key in the table |
192
|
|
|
* |
193
|
|
|
* @var string|string[] |
194
|
|
|
*/ |
195
|
|
|
protected $_primaryKey; |
196
|
|
|
|
197
|
|
|
/** |
198
|
|
|
* The name of the field that represents a human readable representation of a row |
199
|
|
|
* |
200
|
|
|
* @var string |
201
|
|
|
*/ |
202
|
|
|
protected $_displayField; |
203
|
|
|
|
204
|
|
|
/** |
205
|
|
|
* The associations container for this Table. |
206
|
|
|
* |
207
|
|
|
* @var \Cake\ORM\AssociationCollection |
208
|
|
|
*/ |
209
|
|
|
protected $_associations; |
210
|
|
|
|
211
|
|
|
/** |
212
|
|
|
* BehaviorRegistry for this table |
213
|
|
|
* |
214
|
|
|
* @var \Cake\ORM\BehaviorRegistry |
215
|
|
|
*/ |
216
|
|
|
protected $_behaviors; |
217
|
|
|
|
218
|
|
|
/** |
219
|
|
|
* The name of the class that represent a single row for this table |
220
|
|
|
* |
221
|
|
|
* @var string |
222
|
|
|
*/ |
223
|
|
|
protected $_entityClass; |
224
|
|
|
|
225
|
|
|
/** |
226
|
|
|
* Registry key used to create this table object |
227
|
|
|
* |
228
|
|
|
* @var string |
229
|
|
|
*/ |
230
|
|
|
protected $_registryAlias; |
231
|
|
|
|
232
|
|
|
/** |
233
|
|
|
* Initializes a new instance |
234
|
|
|
* |
235
|
|
|
* The $config array understands the following keys: |
236
|
|
|
* |
237
|
|
|
* - table: Name of the database table to represent |
238
|
|
|
* - alias: Alias to be assigned to this table (default to table name) |
239
|
|
|
* - connection: The connection instance to use |
240
|
|
|
* - entityClass: The fully namespaced class name of the entity class that will |
241
|
|
|
* represent rows in this table. |
242
|
|
|
* - schema: A \Cake\Database\Schema\TableSchema object or an array that can be |
243
|
|
|
* passed to it. |
244
|
|
|
* - eventManager: An instance of an event manager to use for internal events |
245
|
|
|
* - behaviors: A BehaviorRegistry. Generally not used outside of tests. |
246
|
|
|
* - associations: An AssociationCollection instance. |
247
|
|
|
* - validator: A Validator instance which is assigned as the "default" |
248
|
|
|
* validation set, or an associative array, where key is the name of the |
249
|
|
|
* validation set and value the Validator instance. |
250
|
|
|
* |
251
|
|
|
* @param array $config List of options for this table |
252
|
|
|
*/ |
253
|
|
|
public function __construct(array $config = []) |
254
|
|
|
{ |
255
|
|
|
if (!empty($config['registryAlias'])) { |
256
|
|
|
$this->setRegistryAlias($config['registryAlias']); |
257
|
|
|
} |
258
|
|
|
if (!empty($config['table'])) { |
259
|
|
|
$this->setTable($config['table']); |
260
|
|
|
} |
261
|
|
|
if (!empty($config['alias'])) { |
262
|
|
|
$this->setAlias($config['alias']); |
263
|
|
|
} |
264
|
|
|
if (!empty($config['connection'])) { |
265
|
|
|
$this->setConnection($config['connection']); |
266
|
|
|
} |
267
|
|
|
if (!empty($config['schema'])) { |
268
|
|
|
$this->setSchema($config['schema']); |
269
|
|
|
} |
270
|
|
|
if (!empty($config['entityClass'])) { |
271
|
|
|
$this->setEntityClass($config['entityClass']); |
272
|
|
|
} |
273
|
|
|
$eventManager = $behaviors = $associations = null; |
274
|
|
|
if (!empty($config['eventManager'])) { |
275
|
|
|
$eventManager = $config['eventManager']; |
276
|
|
|
} |
277
|
|
|
if (!empty($config['behaviors'])) { |
278
|
|
|
$behaviors = $config['behaviors']; |
279
|
|
|
} |
280
|
|
|
if (!empty($config['associations'])) { |
281
|
|
|
$associations = $config['associations']; |
282
|
|
|
} |
283
|
|
|
if (!empty($config['validator'])) { |
284
|
|
|
if (!is_array($config['validator'])) { |
285
|
|
|
$this->setValidator(static::DEFAULT_VALIDATOR, $config['validator']); |
286
|
|
|
} else { |
287
|
|
|
foreach ($config['validator'] as $name => $validator) { |
288
|
|
|
$this->setValidator($name, $validator); |
289
|
|
|
} |
290
|
|
|
} |
291
|
|
|
} |
292
|
|
|
$this->_eventManager = $eventManager ?: new EventManager(); |
293
|
|
|
$this->_behaviors = $behaviors ?: new BehaviorRegistry(); |
294
|
|
|
$this->_behaviors->setTable($this); |
295
|
|
|
$this->_associations = $associations ?: new AssociationCollection(); |
296
|
|
|
|
297
|
|
|
$this->initialize($config); |
298
|
|
|
$this->_eventManager->on($this); |
299
|
|
|
$this->dispatchEvent('Model.initialize'); |
300
|
|
|
} |
301
|
|
|
|
302
|
|
|
/** |
303
|
|
|
* Get the default connection name. |
304
|
|
|
* |
305
|
|
|
* This method is used to get the fallback connection name if an |
306
|
|
|
* instance is created through the TableLocator without a connection. |
307
|
|
|
* |
308
|
|
|
* @return string |
309
|
|
|
* @see \Cake\ORM\Locator\TableLocator::get() |
310
|
|
|
*/ |
311
|
|
|
public static function defaultConnectionName() |
312
|
|
|
{ |
313
|
|
|
return 'default'; |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
/** |
317
|
|
|
* Initialize a table instance. Called after the constructor. |
318
|
|
|
* |
319
|
|
|
* You can use this method to define associations, attach behaviors |
320
|
|
|
* define validation and do any other initialization logic you need. |
321
|
|
|
* |
322
|
|
|
* ``` |
323
|
|
|
* public function initialize(array $config) |
324
|
|
|
* { |
325
|
|
|
* $this->belongsTo('Users'); |
326
|
|
|
* $this->belongsToMany('Tagging.Tags'); |
327
|
|
|
* $this->setPrimaryKey('something_else'); |
328
|
|
|
* } |
329
|
|
|
* ``` |
330
|
|
|
* |
331
|
|
|
* @param array $config Configuration options passed to the constructor |
332
|
|
|
* @return void |
333
|
|
|
*/ |
334
|
|
|
public function initialize(array $config) |
335
|
|
|
{ |
336
|
|
|
} |
337
|
|
|
|
338
|
|
|
/** |
339
|
|
|
* Sets the database table name. |
340
|
|
|
* |
341
|
|
|
* This can include the database schema name in the form 'schema.table'. |
342
|
|
|
* If the name must be quoted, enable automatic identifier quoting. |
343
|
|
|
* |
344
|
|
|
* @param string $table Table name. |
345
|
|
|
* @return $this |
346
|
|
|
*/ |
347
|
|
|
public function setTable($table) |
348
|
|
|
{ |
349
|
|
|
$this->_table = $table; |
350
|
|
|
|
351
|
|
|
return $this; |
352
|
|
|
} |
353
|
|
|
|
354
|
|
|
/** |
355
|
|
|
* Returns the database table name. |
356
|
|
|
* |
357
|
|
|
* This can include the database schema name if set using `setTable()`. |
358
|
|
|
* |
359
|
|
|
* @return string |
360
|
|
|
*/ |
361
|
|
|
public function getTable() |
362
|
|
|
{ |
363
|
|
|
if ($this->_table === null) { |
364
|
|
|
$table = namespaceSplit(get_class($this)); |
365
|
|
|
$table = substr(end($table), 0, -5); |
366
|
|
|
if (!$table) { |
367
|
|
|
$table = $this->getAlias(); |
368
|
|
|
} |
369
|
|
|
$this->_table = Inflector::underscore($table); |
370
|
|
|
} |
371
|
|
|
|
372
|
|
|
return $this->_table; |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
/** |
376
|
|
|
* Returns the database table name or sets a new one. |
377
|
|
|
* |
378
|
|
|
* @deprecated 3.4.0 Use setTable()/getTable() instead. |
379
|
|
|
* @param string|null $table the new table name |
380
|
|
|
* @return string |
381
|
|
|
*/ |
382
|
|
|
public function table($table = null) |
383
|
|
|
{ |
384
|
|
|
deprecationWarning( |
385
|
|
|
get_called_class() . '::table() is deprecated. ' . |
386
|
|
|
'Use setTable()/getTable() instead.' |
387
|
|
|
); |
388
|
|
|
if ($table !== null) { |
389
|
|
|
$this->setTable($table); |
390
|
|
|
} |
391
|
|
|
|
392
|
|
|
return $this->getTable(); |
393
|
|
|
} |
394
|
|
|
|
395
|
|
|
/** |
396
|
|
|
* Sets the table alias. |
397
|
|
|
* |
398
|
|
|
* @param string $alias Table alias |
399
|
|
|
* @return $this |
400
|
|
|
*/ |
401
|
|
|
public function setAlias($alias) |
402
|
|
|
{ |
403
|
|
|
$this->_alias = $alias; |
404
|
|
|
|
405
|
|
|
return $this; |
406
|
|
|
} |
407
|
|
|
|
408
|
|
|
/** |
409
|
|
|
* Returns the table alias. |
410
|
|
|
* |
411
|
|
|
* @return string |
412
|
|
|
*/ |
413
|
|
|
public function getAlias() |
414
|
|
|
{ |
415
|
|
|
if ($this->_alias === null) { |
416
|
|
|
$alias = namespaceSplit(get_class($this)); |
417
|
|
|
$alias = substr(end($alias), 0, -5) ?: $this->_table; |
418
|
|
|
$this->_alias = $alias; |
419
|
|
|
} |
420
|
|
|
|
421
|
|
|
return $this->_alias; |
422
|
|
|
} |
423
|
|
|
|
424
|
|
|
/** |
425
|
|
|
* {@inheritDoc} |
426
|
|
|
* @deprecated 3.4.0 Use setAlias()/getAlias() instead. |
427
|
|
|
*/ |
428
|
|
View Code Duplication |
public function alias($alias = null) |
429
|
|
|
{ |
430
|
|
|
deprecationWarning( |
431
|
|
|
get_called_class() . '::alias() is deprecated. ' . |
432
|
|
|
'Use setAlias()/getAlias() instead.' |
433
|
|
|
); |
434
|
|
|
if ($alias !== null) { |
435
|
|
|
$this->setAlias($alias); |
436
|
|
|
} |
437
|
|
|
|
438
|
|
|
return $this->getAlias(); |
439
|
|
|
} |
440
|
|
|
|
441
|
|
|
/** |
442
|
|
|
* Alias a field with the table's current alias. |
443
|
|
|
* |
444
|
|
|
* If field is already aliased it will result in no-op. |
445
|
|
|
* |
446
|
|
|
* @param string $field The field to alias. |
447
|
|
|
* @return string The field prefixed with the table alias. |
448
|
|
|
*/ |
449
|
|
|
public function aliasField($field) |
450
|
|
|
{ |
451
|
|
|
if (strpos($field, '.') !== false) { |
452
|
|
|
return $field; |
453
|
|
|
} |
454
|
|
|
|
455
|
|
|
return $this->getAlias() . '.' . $field; |
456
|
|
|
} |
457
|
|
|
|
458
|
|
|
/** |
459
|
|
|
* Sets the table registry key used to create this table instance. |
460
|
|
|
* |
461
|
|
|
* @param string $registryAlias The key used to access this object. |
462
|
|
|
* @return $this |
463
|
|
|
*/ |
464
|
|
|
public function setRegistryAlias($registryAlias) |
465
|
|
|
{ |
466
|
|
|
$this->_registryAlias = $registryAlias; |
467
|
|
|
|
468
|
|
|
return $this; |
469
|
|
|
} |
470
|
|
|
|
471
|
|
|
/** |
472
|
|
|
* Returns the table registry key used to create this table instance. |
473
|
|
|
* |
474
|
|
|
* @return string |
475
|
|
|
*/ |
476
|
|
|
public function getRegistryAlias() |
477
|
|
|
{ |
478
|
|
|
if ($this->_registryAlias === null) { |
479
|
|
|
$this->_registryAlias = $this->getAlias(); |
480
|
|
|
} |
481
|
|
|
|
482
|
|
|
return $this->_registryAlias; |
483
|
|
|
} |
484
|
|
|
|
485
|
|
|
/** |
486
|
|
|
* Returns the table registry key used to create this table instance or sets one. |
487
|
|
|
* |
488
|
|
|
* @deprecated 3.4.0 Use setRegistryAlias()/getRegistryAlias() instead. |
489
|
|
|
* @param string|null $registryAlias the key used to access this object |
490
|
|
|
* @return string |
491
|
|
|
*/ |
492
|
|
|
public function registryAlias($registryAlias = null) |
493
|
|
|
{ |
494
|
|
|
deprecationWarning( |
495
|
|
|
get_called_class() . '::registryAlias() is deprecated. ' . |
496
|
|
|
'Use setRegistryAlias()/getRegistryAlias() instead.' |
497
|
|
|
); |
498
|
|
|
if ($registryAlias !== null) { |
499
|
|
|
$this->setRegistryAlias($registryAlias); |
500
|
|
|
} |
501
|
|
|
|
502
|
|
|
return $this->getRegistryAlias(); |
503
|
|
|
} |
504
|
|
|
|
505
|
|
|
/** |
506
|
|
|
* Sets the connection instance. |
507
|
|
|
* |
508
|
|
|
* @param \Cake\Database\Connection $connection The connection instance |
509
|
|
|
* @return $this |
510
|
|
|
*/ |
511
|
|
|
public function setConnection(ConnectionInterface $connection) |
512
|
|
|
{ |
513
|
|
|
$this->_connection = $connection; |
|
|
|
|
514
|
|
|
|
515
|
|
|
return $this; |
516
|
|
|
} |
517
|
|
|
|
518
|
|
|
/** |
519
|
|
|
* Returns the connection instance. |
520
|
|
|
* |
521
|
|
|
* @return \Cake\Database\Connection |
522
|
|
|
*/ |
523
|
|
|
public function getConnection() |
524
|
|
|
{ |
525
|
|
|
return $this->_connection; |
526
|
|
|
} |
527
|
|
|
|
528
|
|
|
/** |
529
|
|
|
* Returns the connection instance or sets a new one |
530
|
|
|
* |
531
|
|
|
* @deprecated 3.4.0 Use setConnection()/getConnection() instead. |
532
|
|
|
* @param \Cake\Datasource\ConnectionInterface|null $connection The new connection instance |
533
|
|
|
* @return \Cake\Datasource\ConnectionInterface |
534
|
|
|
*/ |
535
|
|
|
public function connection(ConnectionInterface $connection = null) |
536
|
|
|
{ |
537
|
|
|
deprecationWarning( |
538
|
|
|
get_called_class() . '::connection() is deprecated. ' . |
539
|
|
|
'Use setConnection()/getConnection() instead.' |
540
|
|
|
); |
541
|
|
|
if ($connection !== null) { |
542
|
|
|
$this->setConnection($connection); |
543
|
|
|
} |
544
|
|
|
|
545
|
|
|
return $this->getConnection(); |
546
|
|
|
} |
547
|
|
|
|
548
|
|
|
/** |
549
|
|
|
* Returns the schema table object describing this table's properties. |
550
|
|
|
* |
551
|
|
|
* @return \Cake\Database\Schema\TableSchema |
552
|
|
|
*/ |
553
|
|
|
public function getSchema() |
554
|
|
|
{ |
555
|
|
|
if ($this->_schema === null) { |
556
|
|
|
$this->_schema = $this->_initializeSchema( |
557
|
|
|
$this->getConnection() |
558
|
|
|
->getSchemaCollection() |
559
|
|
|
->describe($this->getTable()) |
560
|
|
|
); |
561
|
|
|
} |
562
|
|
|
|
563
|
|
|
return $this->_schema; |
564
|
|
|
} |
565
|
|
|
|
566
|
|
|
/** |
567
|
|
|
* Sets the schema table object describing this table's properties. |
568
|
|
|
* |
569
|
|
|
* If an array is passed, a new TableSchema will be constructed |
570
|
|
|
* out of it and used as the schema for this table. |
571
|
|
|
* |
572
|
|
|
* @param array|\Cake\Database\Schema\TableSchema $schema Schema to be used for this table |
573
|
|
|
* @return $this |
574
|
|
|
*/ |
575
|
|
|
public function setSchema($schema) |
576
|
|
|
{ |
577
|
|
|
if (is_array($schema)) { |
578
|
|
|
$constraints = []; |
579
|
|
|
|
580
|
|
|
if (isset($schema['_constraints'])) { |
581
|
|
|
$constraints = $schema['_constraints']; |
582
|
|
|
unset($schema['_constraints']); |
583
|
|
|
} |
584
|
|
|
|
585
|
|
|
$schema = new TableSchema($this->getTable(), $schema); |
586
|
|
|
|
587
|
|
|
foreach ($constraints as $name => $value) { |
588
|
|
|
$schema->addConstraint($name, $value); |
589
|
|
|
} |
590
|
|
|
} |
591
|
|
|
|
592
|
|
|
$this->_schema = $schema; |
593
|
|
|
|
594
|
|
|
return $this; |
595
|
|
|
} |
596
|
|
|
|
597
|
|
|
/** |
598
|
|
|
* Returns the schema table object describing this table's properties. |
599
|
|
|
* |
600
|
|
|
* If a TableSchema is passed, it will be used for this table |
601
|
|
|
* instead of the default one. |
602
|
|
|
* |
603
|
|
|
* If an array is passed, a new TableSchema will be constructed |
604
|
|
|
* out of it and used as the schema for this table. |
605
|
|
|
* |
606
|
|
|
* @deprecated 3.4.0 Use setSchema()/getSchema() instead. |
607
|
|
|
* @param array|\Cake\Database\Schema\TableSchema|null $schema New schema to be used for this table |
608
|
|
|
* @return \Cake\Database\Schema\TableSchema |
609
|
|
|
*/ |
610
|
|
|
public function schema($schema = null) |
611
|
|
|
{ |
612
|
|
|
deprecationWarning( |
613
|
|
|
get_called_class() . '::schema() is deprecated. ' . |
614
|
|
|
'Use setSchema()/getSchema() instead.' |
615
|
|
|
); |
616
|
|
|
if ($schema !== null) { |
617
|
|
|
$this->setSchema($schema); |
618
|
|
|
} |
619
|
|
|
|
620
|
|
|
return $this->getSchema(); |
621
|
|
|
} |
622
|
|
|
|
623
|
|
|
/** |
624
|
|
|
* Override this function in order to alter the schema used by this table. |
625
|
|
|
* This function is only called after fetching the schema out of the database. |
626
|
|
|
* If you wish to provide your own schema to this table without touching the |
627
|
|
|
* database, you can override schema() or inject the definitions though that |
628
|
|
|
* method. |
629
|
|
|
* |
630
|
|
|
* ### Example: |
631
|
|
|
* |
632
|
|
|
* ``` |
633
|
|
|
* protected function _initializeSchema(\Cake\Database\Schema\TableSchema $schema) { |
634
|
|
|
* $schema->setColumnType('preferences', 'json'); |
635
|
|
|
* return $schema; |
636
|
|
|
* } |
637
|
|
|
* ``` |
638
|
|
|
* |
639
|
|
|
* @param \Cake\Database\Schema\TableSchema $schema The table definition fetched from database. |
640
|
|
|
* @return \Cake\Database\Schema\TableSchema the altered schema |
641
|
|
|
*/ |
642
|
|
|
protected function _initializeSchema(TableSchema $schema) |
643
|
|
|
{ |
644
|
|
|
return $schema; |
645
|
|
|
} |
646
|
|
|
|
647
|
|
|
/** |
648
|
|
|
* Test to see if a Table has a specific field/column. |
649
|
|
|
* |
650
|
|
|
* Delegates to the schema object and checks for column presence |
651
|
|
|
* using the Schema\Table instance. |
652
|
|
|
* |
653
|
|
|
* @param string $field The field to check for. |
654
|
|
|
* @return bool True if the field exists, false if it does not. |
655
|
|
|
*/ |
656
|
|
|
public function hasField($field) |
657
|
|
|
{ |
658
|
|
|
$schema = $this->getSchema(); |
659
|
|
|
|
660
|
|
|
return $schema->getColumn($field) !== null; |
661
|
|
|
} |
662
|
|
|
|
663
|
|
|
/** |
664
|
|
|
* Sets the primary key field name. |
665
|
|
|
* |
666
|
|
|
* @param string|string[] $key Sets a new name to be used as primary key |
667
|
|
|
* @return $this |
668
|
|
|
*/ |
669
|
|
|
public function setPrimaryKey($key) |
670
|
|
|
{ |
671
|
|
|
$this->_primaryKey = $key; |
672
|
|
|
|
673
|
|
|
return $this; |
674
|
|
|
} |
675
|
|
|
|
676
|
|
|
/** |
677
|
|
|
* Returns the primary key field name. |
678
|
|
|
* |
679
|
|
|
* @return string|string[] |
680
|
|
|
*/ |
681
|
|
|
public function getPrimaryKey() |
682
|
|
|
{ |
683
|
|
|
if ($this->_primaryKey === null) { |
684
|
|
|
$key = (array)$this->getSchema()->primaryKey(); |
685
|
|
|
if (count($key) === 1) { |
686
|
|
|
$key = $key[0]; |
687
|
|
|
} |
688
|
|
|
$this->_primaryKey = $key; |
689
|
|
|
} |
690
|
|
|
|
691
|
|
|
return $this->_primaryKey; |
692
|
|
|
} |
693
|
|
|
|
694
|
|
|
/** |
695
|
|
|
* Returns the primary key field name or sets a new one |
696
|
|
|
* |
697
|
|
|
* @deprecated 3.4.0 Use setPrimaryKey()/getPrimaryKey() instead. |
698
|
|
|
* @param string|string[]|null $key Sets a new name to be used as primary key |
699
|
|
|
* @return string|string[] |
700
|
|
|
*/ |
701
|
|
|
public function primaryKey($key = null) |
702
|
|
|
{ |
703
|
|
|
deprecationWarning( |
704
|
|
|
get_called_class() . '::primaryKey() is deprecated. ' . |
705
|
|
|
'Use setPrimaryKey()/getPrimaryKey() instead.' |
706
|
|
|
); |
707
|
|
|
if ($key !== null) { |
708
|
|
|
$this->setPrimaryKey($key); |
709
|
|
|
} |
710
|
|
|
|
711
|
|
|
return $this->getPrimaryKey(); |
712
|
|
|
} |
713
|
|
|
|
714
|
|
|
/** |
715
|
|
|
* Sets the display field. |
716
|
|
|
* |
717
|
|
|
* @param string $key Name to be used as display field. |
718
|
|
|
* @return $this |
719
|
|
|
*/ |
720
|
|
|
public function setDisplayField($key) |
721
|
|
|
{ |
722
|
|
|
$this->_displayField = $key; |
723
|
|
|
|
724
|
|
|
return $this; |
725
|
|
|
} |
726
|
|
|
|
727
|
|
|
/** |
728
|
|
|
* Returns the display field. |
729
|
|
|
* |
730
|
|
|
* @return string |
731
|
|
|
*/ |
732
|
|
|
public function getDisplayField() |
733
|
|
|
{ |
734
|
|
|
if ($this->_displayField === null) { |
735
|
|
|
$schema = $this->getSchema(); |
736
|
|
|
$primary = (array)$this->getPrimaryKey(); |
737
|
|
|
$this->_displayField = array_shift($primary); |
738
|
|
|
if ($schema->getColumn('title')) { |
739
|
|
|
$this->_displayField = 'title'; |
740
|
|
|
} |
741
|
|
|
if ($schema->getColumn('name')) { |
742
|
|
|
$this->_displayField = 'name'; |
743
|
|
|
} |
744
|
|
|
} |
745
|
|
|
|
746
|
|
|
return $this->_displayField; |
747
|
|
|
} |
748
|
|
|
|
749
|
|
|
/** |
750
|
|
|
* Returns the display field or sets a new one |
751
|
|
|
* |
752
|
|
|
* @deprecated 3.4.0 Use setDisplayField()/getDisplayField() instead. |
753
|
|
|
* @param string|null $key sets a new name to be used as display field |
754
|
|
|
* @return string |
755
|
|
|
*/ |
756
|
|
|
public function displayField($key = null) |
757
|
|
|
{ |
758
|
|
|
deprecationWarning( |
759
|
|
|
get_called_class() . '::displayField() is deprecated. ' . |
760
|
|
|
'Use setDisplayField()/getDisplayField() instead.' |
761
|
|
|
); |
762
|
|
|
if ($key !== null) { |
763
|
|
|
$this->setDisplayField($key); |
764
|
|
|
|
765
|
|
|
return $key; |
766
|
|
|
} |
767
|
|
|
|
768
|
|
|
return $this->getDisplayField(); |
769
|
|
|
} |
770
|
|
|
|
771
|
|
|
/** |
772
|
|
|
* Returns the class used to hydrate rows for this table. |
773
|
|
|
* |
774
|
|
|
* @return string |
775
|
|
|
*/ |
776
|
|
|
public function getEntityClass() |
777
|
|
|
{ |
778
|
|
|
if (!$this->_entityClass) { |
779
|
|
|
$default = Entity::class; |
780
|
|
|
$self = get_called_class(); |
781
|
|
|
$parts = explode('\\', $self); |
782
|
|
|
|
783
|
|
|
if ($self === __CLASS__ || count($parts) < 3) { |
784
|
|
|
return $this->_entityClass = $default; |
785
|
|
|
} |
786
|
|
|
|
787
|
|
|
$alias = Inflector::classify(Inflector::underscore(substr(array_pop($parts), 0, -5))); |
788
|
|
|
$name = implode('\\', array_slice($parts, 0, -1)) . '\\Entity\\' . $alias; |
789
|
|
|
if (!class_exists($name)) { |
790
|
|
|
return $this->_entityClass = $default; |
791
|
|
|
} |
792
|
|
|
|
793
|
|
|
$class = App::className($name, 'Model/Entity'); |
794
|
|
|
if (!$class) { |
795
|
|
|
throw new MissingEntityException([$name]); |
796
|
|
|
} |
797
|
|
|
|
798
|
|
|
$this->_entityClass = $class; |
799
|
|
|
} |
800
|
|
|
|
801
|
|
|
return $this->_entityClass; |
802
|
|
|
} |
803
|
|
|
|
804
|
|
|
/** |
805
|
|
|
* Sets the class used to hydrate rows for this table. |
806
|
|
|
* |
807
|
|
|
* @param string $name The name of the class to use |
808
|
|
|
* @throws \Cake\ORM\Exception\MissingEntityException when the entity class cannot be found |
809
|
|
|
* @return $this |
810
|
|
|
*/ |
811
|
|
|
public function setEntityClass($name) |
812
|
|
|
{ |
813
|
|
|
$class = App::className($name, 'Model/Entity'); |
814
|
|
|
if (!$class) { |
815
|
|
|
throw new MissingEntityException([$name]); |
816
|
|
|
} |
817
|
|
|
|
818
|
|
|
$this->_entityClass = $class; |
819
|
|
|
|
820
|
|
|
return $this; |
821
|
|
|
} |
822
|
|
|
|
823
|
|
|
/** |
824
|
|
|
* Returns the class used to hydrate rows for this table or sets |
825
|
|
|
* a new one |
826
|
|
|
* |
827
|
|
|
* @deprecated 3.4.0 Use setEntityClass()/getEntityClass() instead. |
828
|
|
|
* @param string|null $name The name of the class to use |
829
|
|
|
* @throws \Cake\ORM\Exception\MissingEntityException when the entity class cannot be found |
830
|
|
|
* @return string |
831
|
|
|
*/ |
832
|
|
View Code Duplication |
public function entityClass($name = null) |
833
|
|
|
{ |
834
|
|
|
deprecationWarning( |
835
|
|
|
get_called_class() . '::entityClass() is deprecated. ' . |
836
|
|
|
'Use setEntityClass()/getEntityClass() instead.' |
837
|
|
|
); |
838
|
|
|
if ($name !== null) { |
839
|
|
|
$this->setEntityClass($name); |
840
|
|
|
} |
841
|
|
|
|
842
|
|
|
return $this->getEntityClass(); |
843
|
|
|
} |
844
|
|
|
|
845
|
|
|
/** |
846
|
|
|
* Add a behavior. |
847
|
|
|
* |
848
|
|
|
* Adds a behavior to this table's behavior collection. Behaviors |
849
|
|
|
* provide an easy way to create horizontally re-usable features |
850
|
|
|
* that can provide trait like functionality, and allow for events |
851
|
|
|
* to be listened to. |
852
|
|
|
* |
853
|
|
|
* Example: |
854
|
|
|
* |
855
|
|
|
* Load a behavior, with some settings. |
856
|
|
|
* |
857
|
|
|
* ``` |
858
|
|
|
* $this->addBehavior('Tree', ['parent' => 'parentId']); |
859
|
|
|
* ``` |
860
|
|
|
* |
861
|
|
|
* Behaviors are generally loaded during Table::initialize(). |
862
|
|
|
* |
863
|
|
|
* @param string $name The name of the behavior. Can be a short class reference. |
864
|
|
|
* @param array $options The options for the behavior to use. |
865
|
|
|
* @return $this |
866
|
|
|
* @throws \RuntimeException If a behavior is being reloaded. |
867
|
|
|
* @see \Cake\ORM\Behavior |
868
|
|
|
*/ |
869
|
|
|
public function addBehavior($name, array $options = []) |
870
|
|
|
{ |
871
|
|
|
$this->_behaviors->load($name, $options); |
872
|
|
|
|
873
|
|
|
return $this; |
874
|
|
|
} |
875
|
|
|
|
876
|
|
|
/** |
877
|
|
|
* Adds an array of behaviors to the table's behavior collection. |
878
|
|
|
* |
879
|
|
|
* Example: |
880
|
|
|
* |
881
|
|
|
* ``` |
882
|
|
|
* $this->addBehaviors([ |
883
|
|
|
* 'Timestamp', |
884
|
|
|
* 'Tree' => ['level' => 'level'], |
885
|
|
|
* ]); |
886
|
|
|
* ``` |
887
|
|
|
* |
888
|
|
|
* @param array $behaviors All of the behaviors to load. |
889
|
|
|
* @return $this |
890
|
|
|
* @throws \RuntimeException If a behavior is being reloaded. |
891
|
|
|
*/ |
892
|
|
|
public function addBehaviors(array $behaviors) |
893
|
|
|
{ |
894
|
|
|
foreach ($behaviors as $name => $options) { |
895
|
|
|
if (is_int($name)) { |
896
|
|
|
$name = $options; |
897
|
|
|
$options = []; |
898
|
|
|
} |
899
|
|
|
|
900
|
|
|
$this->addBehavior($name, $options); |
901
|
|
|
} |
902
|
|
|
|
903
|
|
|
return $this; |
904
|
|
|
} |
905
|
|
|
|
906
|
|
|
/** |
907
|
|
|
* Removes a behavior from this table's behavior registry. |
908
|
|
|
* |
909
|
|
|
* Example: |
910
|
|
|
* |
911
|
|
|
* Remove a behavior from this table. |
912
|
|
|
* |
913
|
|
|
* ``` |
914
|
|
|
* $this->removeBehavior('Tree'); |
915
|
|
|
* ``` |
916
|
|
|
* |
917
|
|
|
* @param string $name The alias that the behavior was added with. |
918
|
|
|
* @return $this |
919
|
|
|
* @see \Cake\ORM\Behavior |
920
|
|
|
*/ |
921
|
|
|
public function removeBehavior($name) |
922
|
|
|
{ |
923
|
|
|
$this->_behaviors->unload($name); |
924
|
|
|
|
925
|
|
|
return $this; |
926
|
|
|
} |
927
|
|
|
|
928
|
|
|
/** |
929
|
|
|
* Returns the behavior registry for this table. |
930
|
|
|
* |
931
|
|
|
* @return \Cake\ORM\BehaviorRegistry The BehaviorRegistry instance. |
932
|
|
|
*/ |
933
|
|
|
public function behaviors() |
934
|
|
|
{ |
935
|
|
|
return $this->_behaviors; |
936
|
|
|
} |
937
|
|
|
|
938
|
|
|
/** |
939
|
|
|
* Get a behavior from the registry. |
940
|
|
|
* |
941
|
|
|
* @param string $name The behavior alias to get from the registry. |
942
|
|
|
* @return \Cake\ORM\Behavior |
943
|
|
|
* @throws \InvalidArgumentException If the behavior does not exist. |
944
|
|
|
*/ |
945
|
|
|
public function getBehavior($name) |
946
|
|
|
{ |
947
|
|
|
/** @var \Cake\ORM\Behavior $behavior */ |
948
|
|
|
$behavior = $this->_behaviors->get($name); |
949
|
|
|
if ($behavior === null) { |
950
|
|
|
throw new InvalidArgumentException(sprintf( |
951
|
|
|
'The %s behavior is not defined on %s.', |
952
|
|
|
$name, |
953
|
|
|
get_class($this) |
954
|
|
|
)); |
955
|
|
|
} |
956
|
|
|
|
957
|
|
|
return $behavior; |
958
|
|
|
} |
959
|
|
|
|
960
|
|
|
/** |
961
|
|
|
* Check if a behavior with the given alias has been loaded. |
962
|
|
|
* |
963
|
|
|
* @param string $name The behavior alias to check. |
964
|
|
|
* @return bool Whether or not the behavior exists. |
965
|
|
|
*/ |
966
|
|
|
public function hasBehavior($name) |
967
|
|
|
{ |
968
|
|
|
return $this->_behaviors->has($name); |
969
|
|
|
} |
970
|
|
|
|
971
|
|
|
/** |
972
|
|
|
* Returns an association object configured for the specified alias if any. |
973
|
|
|
* |
974
|
|
|
* @deprecated 3.6.0 Use getAssociation() and Table::hasAssociation() instead. |
975
|
|
|
* @param string $name the alias used for the association. |
976
|
|
|
* @return \Cake\ORM\Association|null Either the association or null. |
977
|
|
|
*/ |
978
|
|
|
public function association($name) |
979
|
|
|
{ |
980
|
|
|
deprecationWarning('Use Table::getAssociation() and Table::hasAssociation() instead.'); |
981
|
|
|
|
982
|
|
|
return $this->findAssociation($name); |
983
|
|
|
} |
984
|
|
|
|
985
|
|
|
/** |
986
|
|
|
* Returns an association object configured for the specified alias. |
987
|
|
|
* |
988
|
|
|
* The name argument also supports dot syntax to access deeper associations. |
989
|
|
|
* |
990
|
|
|
* ``` |
991
|
|
|
* $users = $this->getAssociation('Articles.Comments.Users'); |
992
|
|
|
* ``` |
993
|
|
|
* |
994
|
|
|
* Note that this method requires the association to be present or otherwise |
995
|
|
|
* throws an exception. |
996
|
|
|
* If you are not sure, use hasAssociation() before calling this method. |
997
|
|
|
* |
998
|
|
|
* @param string $name The alias used for the association. |
999
|
|
|
* @return \Cake\ORM\Association The association. |
1000
|
|
|
* @throws \InvalidArgumentException |
1001
|
|
|
*/ |
1002
|
|
|
public function getAssociation($name) |
1003
|
|
|
{ |
1004
|
|
|
$association = $this->findAssociation($name); |
1005
|
|
|
if (!$association) { |
1006
|
|
|
throw new InvalidArgumentException("The {$name} association is not defined on {$this->getAlias()}."); |
1007
|
|
|
} |
1008
|
|
|
|
1009
|
|
|
return $association; |
1010
|
|
|
} |
1011
|
|
|
|
1012
|
|
|
/** |
1013
|
|
|
* Checks whether a specific association exists on this Table instance. |
1014
|
|
|
* |
1015
|
|
|
* The name argument also supports dot syntax to access deeper associations. |
1016
|
|
|
* |
1017
|
|
|
* ``` |
1018
|
|
|
* $hasUsers = $this->hasAssociation('Articles.Comments.Users'); |
1019
|
|
|
* ``` |
1020
|
|
|
* |
1021
|
|
|
* @param string $name The alias used for the association. |
1022
|
|
|
* @return bool |
1023
|
|
|
*/ |
1024
|
|
|
public function hasAssociation($name) |
1025
|
|
|
{ |
1026
|
|
|
return $this->findAssociation($name) !== null; |
1027
|
|
|
} |
1028
|
|
|
|
1029
|
|
|
/** |
1030
|
|
|
* Returns an association object configured for the specified alias if any. |
1031
|
|
|
* |
1032
|
|
|
* The name argument also supports dot syntax to access deeper associations. |
1033
|
|
|
* |
1034
|
|
|
* ``` |
1035
|
|
|
* $users = $this->getAssociation('Articles.Comments.Users'); |
1036
|
|
|
* ``` |
1037
|
|
|
* |
1038
|
|
|
* @param string $name The alias used for the association. |
1039
|
|
|
* @return \Cake\ORM\Association|null Either the association or null. |
1040
|
|
|
*/ |
1041
|
|
|
protected function findAssociation($name) |
1042
|
|
|
{ |
1043
|
|
|
if (strpos($name, '.') === false) { |
1044
|
|
|
return $this->_associations->get($name); |
1045
|
|
|
} |
1046
|
|
|
|
1047
|
|
|
list($name, $next) = array_pad(explode('.', $name, 2), 2, null); |
1048
|
|
|
$result = $this->_associations->get($name); |
1049
|
|
|
|
1050
|
|
|
if ($result !== null && $next !== null) { |
1051
|
|
|
$result = $result->getTarget()->getAssociation($next); |
1052
|
|
|
} |
1053
|
|
|
|
1054
|
|
|
return $result; |
1055
|
|
|
} |
1056
|
|
|
|
1057
|
|
|
/** |
1058
|
|
|
* Get the associations collection for this table. |
1059
|
|
|
* |
1060
|
|
|
* @return \Cake\ORM\AssociationCollection|\Cake\ORM\Association[] The collection of association objects. |
1061
|
|
|
*/ |
1062
|
|
|
public function associations() |
1063
|
|
|
{ |
1064
|
|
|
return $this->_associations; |
1065
|
|
|
} |
1066
|
|
|
|
1067
|
|
|
/** |
1068
|
|
|
* Setup multiple associations. |
1069
|
|
|
* |
1070
|
|
|
* It takes an array containing set of table names indexed by association type |
1071
|
|
|
* as argument: |
1072
|
|
|
* |
1073
|
|
|
* ``` |
1074
|
|
|
* $this->Posts->addAssociations([ |
1075
|
|
|
* 'belongsTo' => [ |
1076
|
|
|
* 'Users' => ['className' => 'App\Model\Table\UsersTable'] |
1077
|
|
|
* ], |
1078
|
|
|
* 'hasMany' => ['Comments'], |
1079
|
|
|
* 'belongsToMany' => ['Tags'] |
1080
|
|
|
* ]); |
1081
|
|
|
* ``` |
1082
|
|
|
* |
1083
|
|
|
* Each association type accepts multiple associations where the keys |
1084
|
|
|
* are the aliases, and the values are association config data. If numeric |
1085
|
|
|
* keys are used the values will be treated as association aliases. |
1086
|
|
|
* |
1087
|
|
|
* @param array $params Set of associations to bind (indexed by association type) |
1088
|
|
|
* @return $this |
1089
|
|
|
* @see \Cake\ORM\Table::belongsTo() |
1090
|
|
|
* @see \Cake\ORM\Table::hasOne() |
1091
|
|
|
* @see \Cake\ORM\Table::hasMany() |
1092
|
|
|
* @see \Cake\ORM\Table::belongsToMany() |
1093
|
|
|
*/ |
1094
|
|
|
public function addAssociations(array $params) |
1095
|
|
|
{ |
1096
|
|
|
foreach ($params as $assocType => $tables) { |
1097
|
|
|
foreach ($tables as $associated => $options) { |
1098
|
|
|
if (is_numeric($associated)) { |
1099
|
|
|
$associated = $options; |
1100
|
|
|
$options = []; |
1101
|
|
|
} |
1102
|
|
|
$this->{$assocType}($associated, $options); |
1103
|
|
|
} |
1104
|
|
|
} |
1105
|
|
|
|
1106
|
|
|
return $this; |
1107
|
|
|
} |
1108
|
|
|
|
1109
|
|
|
/** |
1110
|
|
|
* Creates a new BelongsTo association between this table and a target |
1111
|
|
|
* table. A "belongs to" association is a N-1 relationship where this table |
1112
|
|
|
* is the N side, and where there is a single associated record in the target |
1113
|
|
|
* table for each one in this table. |
1114
|
|
|
* |
1115
|
|
|
* Target table can be inferred by its name, which is provided in the |
1116
|
|
|
* first argument, or you can either pass the to be instantiated or |
1117
|
|
|
* an instance of it directly. |
1118
|
|
|
* |
1119
|
|
|
* The options array accept the following keys: |
1120
|
|
|
* |
1121
|
|
|
* - className: The class name of the target table object |
1122
|
|
|
* - targetTable: An instance of a table object to be used as the target table |
1123
|
|
|
* - foreignKey: The name of the field to use as foreign key, if false none |
1124
|
|
|
* will be used |
1125
|
|
|
* - conditions: array with a list of conditions to filter the join with |
1126
|
|
|
* - joinType: The type of join to be used (e.g. INNER) |
1127
|
|
|
* - strategy: The loading strategy to use. 'join' and 'select' are supported. |
1128
|
|
|
* - finder: The finder method to use when loading records from this association. |
1129
|
|
|
* Defaults to 'all'. When the strategy is 'join', only the fields, containments, |
1130
|
|
|
* and where conditions will be used from the finder. |
1131
|
|
|
* |
1132
|
|
|
* This method will return the association object that was built. |
1133
|
|
|
* |
1134
|
|
|
* @param string $associated the alias for the target table. This is used to |
1135
|
|
|
* uniquely identify the association |
1136
|
|
|
* @param array $options list of options to configure the association definition |
1137
|
|
|
* @return \Cake\ORM\Association\BelongsTo |
1138
|
|
|
*/ |
1139
|
|
View Code Duplication |
public function belongsTo($associated, array $options = []) |
1140
|
|
|
{ |
1141
|
|
|
$options += ['sourceTable' => $this]; |
1142
|
|
|
|
1143
|
|
|
/** @var \Cake\ORM\Association\BelongsTo $association */ |
1144
|
|
|
$association = $this->_associations->load(BelongsTo::class, $associated, $options); |
1145
|
|
|
|
1146
|
|
|
return $association; |
1147
|
|
|
} |
1148
|
|
|
|
1149
|
|
|
/** |
1150
|
|
|
* Creates a new HasOne association between this table and a target |
1151
|
|
|
* table. A "has one" association is a 1-1 relationship. |
1152
|
|
|
* |
1153
|
|
|
* Target table can be inferred by its name, which is provided in the |
1154
|
|
|
* first argument, or you can either pass the class name to be instantiated or |
1155
|
|
|
* an instance of it directly. |
1156
|
|
|
* |
1157
|
|
|
* The options array accept the following keys: |
1158
|
|
|
* |
1159
|
|
|
* - className: The class name of the target table object |
1160
|
|
|
* - targetTable: An instance of a table object to be used as the target table |
1161
|
|
|
* - foreignKey: The name of the field to use as foreign key, if false none |
1162
|
|
|
* will be used |
1163
|
|
|
* - dependent: Set to true if you want CakePHP to cascade deletes to the |
1164
|
|
|
* associated table when an entity is removed on this table. The delete operation |
1165
|
|
|
* on the associated table will not cascade further. To get recursive cascades enable |
1166
|
|
|
* `cascadeCallbacks` as well. Set to false if you don't want CakePHP to remove |
1167
|
|
|
* associated data, or when you are using database constraints. |
1168
|
|
|
* - cascadeCallbacks: Set to true if you want CakePHP to fire callbacks on |
1169
|
|
|
* cascaded deletes. If false the ORM will use deleteAll() to remove data. |
1170
|
|
|
* When true records will be loaded and then deleted. |
1171
|
|
|
* - conditions: array with a list of conditions to filter the join with |
1172
|
|
|
* - joinType: The type of join to be used (e.g. LEFT) |
1173
|
|
|
* - strategy: The loading strategy to use. 'join' and 'select' are supported. |
1174
|
|
|
* - finder: The finder method to use when loading records from this association. |
1175
|
|
|
* Defaults to 'all'. When the strategy is 'join', only the fields, containments, |
1176
|
|
|
* and where conditions will be used from the finder. |
1177
|
|
|
* |
1178
|
|
|
* This method will return the association object that was built. |
1179
|
|
|
* |
1180
|
|
|
* @param string $associated the alias for the target table. This is used to |
1181
|
|
|
* uniquely identify the association |
1182
|
|
|
* @param array $options list of options to configure the association definition |
1183
|
|
|
* @return \Cake\ORM\Association\HasOne |
1184
|
|
|
*/ |
1185
|
|
View Code Duplication |
public function hasOne($associated, array $options = []) |
1186
|
|
|
{ |
1187
|
|
|
$options += ['sourceTable' => $this]; |
1188
|
|
|
|
1189
|
|
|
/** @var \Cake\ORM\Association\HasOne $association */ |
1190
|
|
|
$association = $this->_associations->load(HasOne::class, $associated, $options); |
1191
|
|
|
|
1192
|
|
|
return $association; |
1193
|
|
|
} |
1194
|
|
|
|
1195
|
|
|
/** |
1196
|
|
|
* Creates a new HasMany association between this table and a target |
1197
|
|
|
* table. A "has many" association is a 1-N relationship. |
1198
|
|
|
* |
1199
|
|
|
* Target table can be inferred by its name, which is provided in the |
1200
|
|
|
* first argument, or you can either pass the class name to be instantiated or |
1201
|
|
|
* an instance of it directly. |
1202
|
|
|
* |
1203
|
|
|
* The options array accept the following keys: |
1204
|
|
|
* |
1205
|
|
|
* - className: The class name of the target table object |
1206
|
|
|
* - targetTable: An instance of a table object to be used as the target table |
1207
|
|
|
* - foreignKey: The name of the field to use as foreign key, if false none |
1208
|
|
|
* will be used |
1209
|
|
|
* - dependent: Set to true if you want CakePHP to cascade deletes to the |
1210
|
|
|
* associated table when an entity is removed on this table. The delete operation |
1211
|
|
|
* on the associated table will not cascade further. To get recursive cascades enable |
1212
|
|
|
* `cascadeCallbacks` as well. Set to false if you don't want CakePHP to remove |
1213
|
|
|
* associated data, or when you are using database constraints. |
1214
|
|
|
* - cascadeCallbacks: Set to true if you want CakePHP to fire callbacks on |
1215
|
|
|
* cascaded deletes. If false the ORM will use deleteAll() to remove data. |
1216
|
|
|
* When true records will be loaded and then deleted. |
1217
|
|
|
* - conditions: array with a list of conditions to filter the join with |
1218
|
|
|
* - sort: The order in which results for this association should be returned |
1219
|
|
|
* - saveStrategy: Either 'append' or 'replace'. When 'append' the current records |
1220
|
|
|
* are appended to any records in the database. When 'replace' associated records |
1221
|
|
|
* not in the current set will be removed. If the foreign key is a null able column |
1222
|
|
|
* or if `dependent` is true records will be orphaned. |
1223
|
|
|
* - strategy: The strategy to be used for selecting results Either 'select' |
1224
|
|
|
* or 'subquery'. If subquery is selected the query used to return results |
1225
|
|
|
* in the source table will be used as conditions for getting rows in the |
1226
|
|
|
* target table. |
1227
|
|
|
* - finder: The finder method to use when loading records from this association. |
1228
|
|
|
* Defaults to 'all'. |
1229
|
|
|
* |
1230
|
|
|
* This method will return the association object that was built. |
1231
|
|
|
* |
1232
|
|
|
* @param string $associated the alias for the target table. This is used to |
1233
|
|
|
* uniquely identify the association |
1234
|
|
|
* @param array $options list of options to configure the association definition |
1235
|
|
|
* @return \Cake\ORM\Association\HasMany |
1236
|
|
|
*/ |
1237
|
|
View Code Duplication |
public function hasMany($associated, array $options = []) |
1238
|
|
|
{ |
1239
|
|
|
$options += ['sourceTable' => $this]; |
1240
|
|
|
|
1241
|
|
|
/** @var \Cake\ORM\Association\HasMany $association */ |
1242
|
|
|
$association = $this->_associations->load(HasMany::class, $associated, $options); |
1243
|
|
|
|
1244
|
|
|
return $association; |
1245
|
|
|
} |
1246
|
|
|
|
1247
|
|
|
/** |
1248
|
|
|
* Creates a new BelongsToMany association between this table and a target |
1249
|
|
|
* table. A "belongs to many" association is a M-N relationship. |
1250
|
|
|
* |
1251
|
|
|
* Target table can be inferred by its name, which is provided in the |
1252
|
|
|
* first argument, or you can either pass the class name to be instantiated or |
1253
|
|
|
* an instance of it directly. |
1254
|
|
|
* |
1255
|
|
|
* The options array accept the following keys: |
1256
|
|
|
* |
1257
|
|
|
* - className: The class name of the target table object. |
1258
|
|
|
* - targetTable: An instance of a table object to be used as the target table. |
1259
|
|
|
* - foreignKey: The name of the field to use as foreign key. |
1260
|
|
|
* - targetForeignKey: The name of the field to use as the target foreign key. |
1261
|
|
|
* - joinTable: The name of the table representing the link between the two |
1262
|
|
|
* - through: If you choose to use an already instantiated link table, set this |
1263
|
|
|
* key to a configured Table instance containing associations to both the source |
1264
|
|
|
* and target tables in this association. |
1265
|
|
|
* - dependent: Set to false, if you do not want junction table records removed |
1266
|
|
|
* when an owning record is removed. |
1267
|
|
|
* - cascadeCallbacks: Set to true if you want CakePHP to fire callbacks on |
1268
|
|
|
* cascaded deletes. If false the ORM will use deleteAll() to remove data. |
1269
|
|
|
* When true join/junction table records will be loaded and then deleted. |
1270
|
|
|
* - conditions: array with a list of conditions to filter the join with. |
1271
|
|
|
* - sort: The order in which results for this association should be returned. |
1272
|
|
|
* - strategy: The strategy to be used for selecting results Either 'select' |
1273
|
|
|
* or 'subquery'. If subquery is selected the query used to return results |
1274
|
|
|
* in the source table will be used as conditions for getting rows in the |
1275
|
|
|
* target table. |
1276
|
|
|
* - saveStrategy: Either 'append' or 'replace'. Indicates the mode to be used |
1277
|
|
|
* for saving associated entities. The former will only create new links |
1278
|
|
|
* between both side of the relation and the latter will do a wipe and |
1279
|
|
|
* replace to create the links between the passed entities when saving. |
1280
|
|
|
* - strategy: The loading strategy to use. 'select' and 'subquery' are supported. |
1281
|
|
|
* - finder: The finder method to use when loading records from this association. |
1282
|
|
|
* Defaults to 'all'. |
1283
|
|
|
* |
1284
|
|
|
* This method will return the association object that was built. |
1285
|
|
|
* |
1286
|
|
|
* @param string $associated the alias for the target table. This is used to |
1287
|
|
|
* uniquely identify the association |
1288
|
|
|
* @param array $options list of options to configure the association definition |
1289
|
|
|
* @return \Cake\ORM\Association\BelongsToMany |
1290
|
|
|
*/ |
1291
|
|
View Code Duplication |
public function belongsToMany($associated, array $options = []) |
1292
|
|
|
{ |
1293
|
|
|
$options += ['sourceTable' => $this]; |
1294
|
|
|
|
1295
|
|
|
/** @var \Cake\ORM\Association\BelongsToMany $association */ |
1296
|
|
|
$association = $this->_associations->load(BelongsToMany::class, $associated, $options); |
1297
|
|
|
|
1298
|
|
|
return $association; |
1299
|
|
|
} |
1300
|
|
|
|
1301
|
|
|
/** |
1302
|
|
|
* Creates a new Query for this repository and applies some defaults based on the |
1303
|
|
|
* type of search that was selected. |
1304
|
|
|
* |
1305
|
|
|
* ### Model.beforeFind event |
1306
|
|
|
* |
1307
|
|
|
* Each find() will trigger a `Model.beforeFind` event for all attached |
1308
|
|
|
* listeners. Any listener can set a valid result set using $query |
1309
|
|
|
* |
1310
|
|
|
* By default, `$options` will recognize the following keys: |
1311
|
|
|
* |
1312
|
|
|
* - fields |
1313
|
|
|
* - conditions |
1314
|
|
|
* - order |
1315
|
|
|
* - limit |
1316
|
|
|
* - offset |
1317
|
|
|
* - page |
1318
|
|
|
* - group |
1319
|
|
|
* - having |
1320
|
|
|
* - contain |
1321
|
|
|
* - join |
1322
|
|
|
* |
1323
|
|
|
* ### Usage |
1324
|
|
|
* |
1325
|
|
|
* Using the options array: |
1326
|
|
|
* |
1327
|
|
|
* ``` |
1328
|
|
|
* $query = $articles->find('all', [ |
1329
|
|
|
* 'conditions' => ['published' => 1], |
1330
|
|
|
* 'limit' => 10, |
1331
|
|
|
* 'contain' => ['Users', 'Comments'] |
1332
|
|
|
* ]); |
1333
|
|
|
* ``` |
1334
|
|
|
* |
1335
|
|
|
* Using the builder interface: |
1336
|
|
|
* |
1337
|
|
|
* ``` |
1338
|
|
|
* $query = $articles->find() |
1339
|
|
|
* ->where(['published' => 1]) |
1340
|
|
|
* ->limit(10) |
1341
|
|
|
* ->contain(['Users', 'Comments']); |
1342
|
|
|
* ``` |
1343
|
|
|
* |
1344
|
|
|
* ### Calling finders |
1345
|
|
|
* |
1346
|
|
|
* The find() method is the entry point for custom finder methods. |
1347
|
|
|
* You can invoke a finder by specifying the type: |
1348
|
|
|
* |
1349
|
|
|
* ``` |
1350
|
|
|
* $query = $articles->find('published'); |
1351
|
|
|
* ``` |
1352
|
|
|
* |
1353
|
|
|
* Would invoke the `findPublished` method. |
1354
|
|
|
* |
1355
|
|
|
* @param string $type the type of query to perform |
1356
|
|
|
* @param array|\ArrayAccess $options An array that will be passed to Query::applyOptions() |
1357
|
|
|
* @return \Cake\ORM\Query The query builder |
1358
|
|
|
*/ |
1359
|
|
|
public function find($type = 'all', $options = []) |
1360
|
|
|
{ |
1361
|
|
|
$query = $this->query(); |
1362
|
|
|
$query->select(); |
1363
|
|
|
|
1364
|
|
|
return $this->callFinder($type, $query, $options); |
|
|
|
|
1365
|
|
|
} |
1366
|
|
|
|
1367
|
|
|
/** |
1368
|
|
|
* Returns the query as passed. |
1369
|
|
|
* |
1370
|
|
|
* By default findAll() applies no conditions, you |
1371
|
|
|
* can override this method in subclasses to modify how `find('all')` works. |
1372
|
|
|
* |
1373
|
|
|
* @param \Cake\ORM\Query $query The query to find with |
1374
|
|
|
* @param array $options The options to use for the find |
1375
|
|
|
* @return \Cake\ORM\Query The query builder |
1376
|
|
|
*/ |
1377
|
|
|
public function findAll(Query $query, array $options) |
1378
|
|
|
{ |
1379
|
|
|
return $query; |
1380
|
|
|
} |
1381
|
|
|
|
1382
|
|
|
/** |
1383
|
|
|
* Sets up a query object so results appear as an indexed array, useful for any |
1384
|
|
|
* place where you would want a list such as for populating input select boxes. |
1385
|
|
|
* |
1386
|
|
|
* When calling this finder, the fields passed are used to determine what should |
1387
|
|
|
* be used as the array key, value and optionally what to group the results by. |
1388
|
|
|
* By default the primary key for the model is used for the key, and the display |
1389
|
|
|
* field as value. |
1390
|
|
|
* |
1391
|
|
|
* The results of this finder will be in the following form: |
1392
|
|
|
* |
1393
|
|
|
* ``` |
1394
|
|
|
* [ |
1395
|
|
|
* 1 => 'value for id 1', |
1396
|
|
|
* 2 => 'value for id 2', |
1397
|
|
|
* 4 => 'value for id 4' |
1398
|
|
|
* ] |
1399
|
|
|
* ``` |
1400
|
|
|
* |
1401
|
|
|
* You can specify which property will be used as the key and which as value |
1402
|
|
|
* by using the `$options` array, when not specified, it will use the results |
1403
|
|
|
* of calling `primaryKey` and `displayField` respectively in this table: |
1404
|
|
|
* |
1405
|
|
|
* ``` |
1406
|
|
|
* $table->find('list', [ |
1407
|
|
|
* 'keyField' => 'name', |
1408
|
|
|
* 'valueField' => 'age' |
1409
|
|
|
* ]); |
1410
|
|
|
* ``` |
1411
|
|
|
* |
1412
|
|
|
* Results can be put together in bigger groups when they share a property, you |
1413
|
|
|
* can customize the property to use for grouping by setting `groupField`: |
1414
|
|
|
* |
1415
|
|
|
* ``` |
1416
|
|
|
* $table->find('list', [ |
1417
|
|
|
* 'groupField' => 'category_id', |
1418
|
|
|
* ]); |
1419
|
|
|
* ``` |
1420
|
|
|
* |
1421
|
|
|
* When using a `groupField` results will be returned in this format: |
1422
|
|
|
* |
1423
|
|
|
* ``` |
1424
|
|
|
* [ |
1425
|
|
|
* 'group_1' => [ |
1426
|
|
|
* 1 => 'value for id 1', |
1427
|
|
|
* 2 => 'value for id 2', |
1428
|
|
|
* ] |
1429
|
|
|
* 'group_2' => [ |
1430
|
|
|
* 4 => 'value for id 4' |
1431
|
|
|
* ] |
1432
|
|
|
* ] |
1433
|
|
|
* ``` |
1434
|
|
|
* |
1435
|
|
|
* @param \Cake\ORM\Query $query The query to find with |
1436
|
|
|
* @param array $options The options for the find |
1437
|
|
|
* @return \Cake\ORM\Query The query builder |
1438
|
|
|
*/ |
1439
|
|
|
public function findList(Query $query, array $options) |
1440
|
|
|
{ |
1441
|
|
|
$options += [ |
1442
|
|
|
'keyField' => $this->getPrimaryKey(), |
1443
|
|
|
'valueField' => $this->getDisplayField(), |
1444
|
|
|
'groupField' => null, |
1445
|
|
|
]; |
1446
|
|
|
|
1447
|
|
View Code Duplication |
if (isset($options['idField'])) { |
1448
|
|
|
$options['keyField'] = $options['idField']; |
1449
|
|
|
unset($options['idField']); |
1450
|
|
|
deprecationWarning('Option "idField" is deprecated, use "keyField" instead.'); |
1451
|
|
|
} |
1452
|
|
|
|
1453
|
|
|
if ( |
1454
|
|
|
!$query->clause('select') && |
1455
|
|
|
!is_object($options['keyField']) && |
1456
|
|
|
!is_object($options['valueField']) && |
1457
|
|
|
!is_object($options['groupField']) |
1458
|
|
|
) { |
1459
|
|
|
$fields = array_merge( |
1460
|
|
|
(array)$options['keyField'], |
1461
|
|
|
(array)$options['valueField'], |
1462
|
|
|
(array)$options['groupField'] |
1463
|
|
|
); |
1464
|
|
|
$columns = $this->getSchema()->columns(); |
1465
|
|
|
if (count($fields) === count(array_intersect($fields, $columns))) { |
1466
|
|
|
$query->select($fields); |
1467
|
|
|
} |
1468
|
|
|
} |
1469
|
|
|
|
1470
|
|
|
$options = $this->_setFieldMatchers( |
1471
|
|
|
$options, |
1472
|
|
|
['keyField', 'valueField', 'groupField'] |
1473
|
|
|
); |
1474
|
|
|
|
1475
|
|
|
return $query->formatResults(function ($results) use ($options) { |
1476
|
|
|
/** @var \Cake\Collection\CollectionInterface $results */ |
1477
|
|
|
return $results->combine( |
1478
|
|
|
$options['keyField'], |
1479
|
|
|
$options['valueField'], |
1480
|
|
|
$options['groupField'] |
1481
|
|
|
); |
1482
|
|
|
}); |
1483
|
|
|
} |
1484
|
|
|
|
1485
|
|
|
/** |
1486
|
|
|
* Results for this finder will be a nested array, and is appropriate if you want |
1487
|
|
|
* to use the parent_id field of your model data to build nested results. |
1488
|
|
|
* |
1489
|
|
|
* Values belonging to a parent row based on their parent_id value will be |
1490
|
|
|
* recursively nested inside the parent row values using the `children` property |
1491
|
|
|
* |
1492
|
|
|
* You can customize what fields are used for nesting results, by default the |
1493
|
|
|
* primary key and the `parent_id` fields are used. If you wish to change |
1494
|
|
|
* these defaults you need to provide the keys `keyField`, `parentField` or `nestingKey` in |
1495
|
|
|
* `$options`: |
1496
|
|
|
* |
1497
|
|
|
* ``` |
1498
|
|
|
* $table->find('threaded', [ |
1499
|
|
|
* 'keyField' => 'id', |
1500
|
|
|
* 'parentField' => 'ancestor_id' |
1501
|
|
|
* 'nestingKey' => 'children' |
1502
|
|
|
* ]); |
1503
|
|
|
* ``` |
1504
|
|
|
* |
1505
|
|
|
* @param \Cake\ORM\Query $query The query to find with |
1506
|
|
|
* @param array $options The options to find with |
1507
|
|
|
* @return \Cake\ORM\Query The query builder |
1508
|
|
|
*/ |
1509
|
|
|
public function findThreaded(Query $query, array $options) |
1510
|
|
|
{ |
1511
|
|
|
$options += [ |
1512
|
|
|
'keyField' => $this->getPrimaryKey(), |
1513
|
|
|
'parentField' => 'parent_id', |
1514
|
|
|
'nestingKey' => 'children', |
1515
|
|
|
]; |
1516
|
|
|
|
1517
|
|
View Code Duplication |
if (isset($options['idField'])) { |
1518
|
|
|
$options['keyField'] = $options['idField']; |
1519
|
|
|
unset($options['idField']); |
1520
|
|
|
deprecationWarning('Option "idField" is deprecated, use "keyField" instead.'); |
1521
|
|
|
} |
1522
|
|
|
|
1523
|
|
|
$options = $this->_setFieldMatchers($options, ['keyField', 'parentField']); |
1524
|
|
|
|
1525
|
|
|
return $query->formatResults(function ($results) use ($options) { |
1526
|
|
|
/** @var \Cake\Collection\CollectionInterface $results */ |
1527
|
|
|
return $results->nest($options['keyField'], $options['parentField'], $options['nestingKey']); |
1528
|
|
|
}); |
1529
|
|
|
} |
1530
|
|
|
|
1531
|
|
|
/** |
1532
|
|
|
* Out of an options array, check if the keys described in `$keys` are arrays |
1533
|
|
|
* and change the values for closures that will concatenate the each of the |
1534
|
|
|
* properties in the value array when passed a row. |
1535
|
|
|
* |
1536
|
|
|
* This is an auxiliary function used for result formatters that can accept |
1537
|
|
|
* composite keys when comparing values. |
1538
|
|
|
* |
1539
|
|
|
* @param array $options the original options passed to a finder |
1540
|
|
|
* @param array $keys the keys to check in $options to build matchers from |
1541
|
|
|
* the associated value |
1542
|
|
|
* @return array |
1543
|
|
|
*/ |
1544
|
|
|
protected function _setFieldMatchers($options, $keys) |
1545
|
|
|
{ |
1546
|
|
|
foreach ($keys as $field) { |
1547
|
|
|
if (!is_array($options[$field])) { |
1548
|
|
|
continue; |
1549
|
|
|
} |
1550
|
|
|
|
1551
|
|
|
if (count($options[$field]) === 1) { |
1552
|
|
|
$options[$field] = current($options[$field]); |
1553
|
|
|
continue; |
1554
|
|
|
} |
1555
|
|
|
|
1556
|
|
|
$fields = $options[$field]; |
1557
|
|
|
$options[$field] = function ($row) use ($fields) { |
1558
|
|
|
$matches = []; |
1559
|
|
|
foreach ($fields as $field) { |
1560
|
|
|
$matches[] = $row[$field]; |
1561
|
|
|
} |
1562
|
|
|
|
1563
|
|
|
return implode(';', $matches); |
1564
|
|
|
}; |
1565
|
|
|
} |
1566
|
|
|
|
1567
|
|
|
return $options; |
1568
|
|
|
} |
1569
|
|
|
|
1570
|
|
|
/** |
1571
|
|
|
* {@inheritDoc} |
1572
|
|
|
* |
1573
|
|
|
* ### Usage |
1574
|
|
|
* |
1575
|
|
|
* Get an article and some relationships: |
1576
|
|
|
* |
1577
|
|
|
* ``` |
1578
|
|
|
* $article = $articles->get(1, ['contain' => ['Users', 'Comments']]); |
1579
|
|
|
* ``` |
1580
|
|
|
* |
1581
|
|
|
* @throws \Cake\Datasource\Exception\InvalidPrimaryKeyException When $primaryKey has an |
1582
|
|
|
* incorrect number of elements. |
1583
|
|
|
*/ |
1584
|
|
|
public function get($primaryKey, $options = []) |
1585
|
|
|
{ |
1586
|
|
|
$key = (array)$this->getPrimaryKey(); |
1587
|
|
|
$alias = $this->getAlias(); |
1588
|
|
|
foreach ($key as $index => $keyname) { |
1589
|
|
|
$key[$index] = $alias . '.' . $keyname; |
1590
|
|
|
} |
1591
|
|
|
$primaryKey = (array)$primaryKey; |
1592
|
|
|
if (count($key) !== count($primaryKey)) { |
1593
|
|
|
$primaryKey = $primaryKey ?: [null]; |
1594
|
|
|
$primaryKey = array_map(function ($key) { |
1595
|
|
|
return var_export($key, true); |
1596
|
|
|
}, $primaryKey); |
1597
|
|
|
|
1598
|
|
|
throw new InvalidPrimaryKeyException(sprintf( |
1599
|
|
|
'Record not found in table "%s" with primary key [%s]', |
1600
|
|
|
$this->getTable(), |
1601
|
|
|
implode(', ', $primaryKey) |
1602
|
|
|
)); |
1603
|
|
|
} |
1604
|
|
|
$conditions = array_combine($key, $primaryKey); |
1605
|
|
|
|
1606
|
|
|
$cacheConfig = isset($options['cache']) ? $options['cache'] : false; |
1607
|
|
|
$cacheKey = isset($options['key']) ? $options['key'] : false; |
1608
|
|
|
$finder = isset($options['finder']) ? $options['finder'] : 'all'; |
1609
|
|
|
unset($options['key'], $options['cache'], $options['finder']); |
1610
|
|
|
|
1611
|
|
|
$query = $this->find($finder, $options)->where($conditions); |
1612
|
|
|
|
1613
|
|
|
if ($cacheConfig) { |
1614
|
|
|
if (!$cacheKey) { |
1615
|
|
|
$cacheKey = sprintf( |
1616
|
|
|
'get:%s.%s%s', |
1617
|
|
|
$this->getConnection()->configName(), |
1618
|
|
|
$this->getTable(), |
1619
|
|
|
json_encode($primaryKey) |
1620
|
|
|
); |
1621
|
|
|
} |
1622
|
|
|
$query->cache($cacheKey, $cacheConfig); |
1623
|
|
|
} |
1624
|
|
|
|
1625
|
|
|
return $query->firstOrFail(); |
|
|
|
|
1626
|
|
|
} |
1627
|
|
|
|
1628
|
|
|
/** |
1629
|
|
|
* Handles the logic executing of a worker inside a transaction. |
1630
|
|
|
* |
1631
|
|
|
* @param callable $worker The worker that will run inside the transaction. |
1632
|
|
|
* @param bool $atomic Whether to execute the worker inside a database transaction. |
1633
|
|
|
* @return mixed |
1634
|
|
|
*/ |
1635
|
|
|
protected function _executeTransaction(callable $worker, $atomic = true) |
1636
|
|
|
{ |
1637
|
|
|
if ($atomic) { |
1638
|
|
|
return $this->getConnection()->transactional(function () use ($worker) { |
1639
|
|
|
return $worker(); |
1640
|
|
|
}); |
1641
|
|
|
} |
1642
|
|
|
|
1643
|
|
|
return $worker(); |
1644
|
|
|
} |
1645
|
|
|
|
1646
|
|
|
/** |
1647
|
|
|
* Checks if the caller would have executed a commit on a transaction. |
1648
|
|
|
* |
1649
|
|
|
* @param bool $atomic True if an atomic transaction was used. |
1650
|
|
|
* @param bool $primary True if a primary was used. |
1651
|
|
|
* @return bool Returns true if a transaction was committed. |
1652
|
|
|
*/ |
1653
|
|
|
protected function _transactionCommitted($atomic, $primary) |
1654
|
|
|
{ |
1655
|
|
|
return !$this->getConnection()->inTransaction() && ($atomic || (!$atomic && $primary)); |
1656
|
|
|
} |
1657
|
|
|
|
1658
|
|
|
/** |
1659
|
|
|
* Finds an existing record or creates a new one. |
1660
|
|
|
* |
1661
|
|
|
* A find() will be done to locate an existing record using the attributes |
1662
|
|
|
* defined in $search. If records matches the conditions, the first record |
1663
|
|
|
* will be returned. |
1664
|
|
|
* |
1665
|
|
|
* If no record can be found, a new entity will be created |
1666
|
|
|
* with the $search properties. If a callback is provided, it will be |
1667
|
|
|
* called allowing you to define additional default values. The new |
1668
|
|
|
* entity will be saved and returned. |
1669
|
|
|
* |
1670
|
|
|
* If your find conditions require custom order, associations or conditions, then the $search |
1671
|
|
|
* parameter can be a callable that takes the Query as the argument, or a \Cake\ORM\Query object passed |
1672
|
|
|
* as the $search parameter. Allowing you to customize the find results. |
1673
|
|
|
* |
1674
|
|
|
* ### Options |
1675
|
|
|
* |
1676
|
|
|
* The options array is passed to the save method with exception to the following keys: |
1677
|
|
|
* |
1678
|
|
|
* - atomic: Whether to execute the methods for find, save and callbacks inside a database |
1679
|
|
|
* transaction (default: true) |
1680
|
|
|
* - defaults: Whether to use the search criteria as default values for the new entity (default: true) |
1681
|
|
|
* |
1682
|
|
|
* @param array|callable|\Cake\ORM\Query $search The criteria to find existing |
1683
|
|
|
* records by. Note that when you pass a query object you'll have to use |
1684
|
|
|
* the 2nd arg of the method to modify the entity data before saving. |
1685
|
|
|
* @param callable|null $callback A callback that will be invoked for newly |
1686
|
|
|
* created entities. This callback will be called *before* the entity |
1687
|
|
|
* is persisted. |
1688
|
|
|
* @param array $options The options to use when saving. |
1689
|
|
|
* @return \Cake\Datasource\EntityInterface An entity. |
1690
|
|
|
* @throws \Cake\ORM\Exception\PersistenceFailedException When the entity couldn't be saved |
1691
|
|
|
*/ |
1692
|
|
|
public function findOrCreate($search, callable $callback = null, $options = []) |
1693
|
|
|
{ |
1694
|
|
|
$options = new ArrayObject($options + [ |
1695
|
|
|
'atomic' => true, |
1696
|
|
|
'defaults' => true, |
1697
|
|
|
]); |
1698
|
|
|
|
1699
|
|
|
$entity = $this->_executeTransaction(function () use ($search, $callback, $options) { |
1700
|
|
|
return $this->_processFindOrCreate($search, $callback, $options->getArrayCopy()); |
1701
|
|
|
}, $options['atomic']); |
1702
|
|
|
|
1703
|
|
View Code Duplication |
if ($entity && $this->_transactionCommitted($options['atomic'], true)) { |
1704
|
|
|
$this->dispatchEvent('Model.afterSaveCommit', compact('entity', 'options')); |
1705
|
|
|
} |
1706
|
|
|
|
1707
|
|
|
return $entity; |
1708
|
|
|
} |
1709
|
|
|
|
1710
|
|
|
/** |
1711
|
|
|
* Performs the actual find and/or create of an entity based on the passed options. |
1712
|
|
|
* |
1713
|
|
|
* @param array|callable|\Cake\ORM\Query $search The criteria to find an existing record by, or a callable tha will |
1714
|
|
|
* customize the find query. |
1715
|
|
|
* @param callable|null $callback A callback that will be invoked for newly |
1716
|
|
|
* created entities. This callback will be called *before* the entity |
1717
|
|
|
* is persisted. |
1718
|
|
|
* @param array $options The options to use when saving. |
1719
|
|
|
* @return \Cake\Datasource\EntityInterface An entity. |
1720
|
|
|
* @throws \Cake\ORM\Exception\PersistenceFailedException When the entity couldn't be saved |
1721
|
|
|
*/ |
1722
|
|
|
protected function _processFindOrCreate($search, callable $callback = null, $options = []) |
1723
|
|
|
{ |
1724
|
|
|
$query = $this->_getFindOrCreateQuery($search); |
1725
|
|
|
$row = $query->first(); |
|
|
|
|
1726
|
|
|
if ($row !== null) { |
1727
|
|
|
return $row; |
1728
|
|
|
} |
1729
|
|
|
|
1730
|
|
|
$entity = $this->newEntity(); |
1731
|
|
|
if ($options['defaults'] && is_array($search)) { |
1732
|
|
|
$accessibleFields = array_combine(array_keys($search), array_fill(0, count($search), true)); |
1733
|
|
|
$entity = $this->patchEntity($entity, $search, ['accessibleFields' => $accessibleFields]); |
1734
|
|
|
} |
1735
|
|
|
if ($callback !== null) { |
1736
|
|
|
$entity = $callback($entity) ?: $entity; |
1737
|
|
|
} |
1738
|
|
|
unset($options['defaults']); |
1739
|
|
|
|
1740
|
|
|
$result = $this->save($entity, $options); |
1741
|
|
|
|
1742
|
|
|
if ($result === false) { |
1743
|
|
|
throw new PersistenceFailedException($entity, ['findOrCreate']); |
1744
|
|
|
} |
1745
|
|
|
|
1746
|
|
|
return $entity; |
1747
|
|
|
} |
1748
|
|
|
|
1749
|
|
|
/** |
1750
|
|
|
* Gets the query object for findOrCreate(). |
1751
|
|
|
* |
1752
|
|
|
* @param array|callable|\Cake\ORM\Query $search The criteria to find existing records by. |
1753
|
|
|
* @return \Cake\ORM\Query |
1754
|
|
|
*/ |
1755
|
|
|
protected function _getFindOrCreateQuery($search) |
1756
|
|
|
{ |
1757
|
|
|
if (is_callable($search)) { |
1758
|
|
|
$query = $this->find(); |
1759
|
|
|
$search($query); |
1760
|
|
|
} elseif (is_array($search)) { |
1761
|
|
|
$query = $this->find()->where($search); |
1762
|
|
|
} elseif ($search instanceof Query) { |
1763
|
|
|
$query = $search; |
1764
|
|
|
} else { |
1765
|
|
|
throw new InvalidArgumentException('Search criteria must be an array, callable or Query'); |
1766
|
|
|
} |
1767
|
|
|
|
1768
|
|
|
return $query; |
1769
|
|
|
} |
1770
|
|
|
|
1771
|
|
|
/** |
1772
|
|
|
* Creates a new Query instance for a table. |
1773
|
|
|
* |
1774
|
|
|
* @return \Cake\ORM\Query |
1775
|
|
|
*/ |
1776
|
|
|
public function query() |
1777
|
|
|
{ |
1778
|
|
|
return new Query($this->getConnection(), $this); |
1779
|
|
|
} |
1780
|
|
|
|
1781
|
|
|
/** |
1782
|
|
|
* {@inheritDoc} |
1783
|
|
|
*/ |
1784
|
|
View Code Duplication |
public function updateAll($fields, $conditions) |
1785
|
|
|
{ |
1786
|
|
|
$query = $this->query(); |
1787
|
|
|
$query->update() |
1788
|
|
|
->set($fields) |
1789
|
|
|
->where($conditions); |
1790
|
|
|
$statement = $query->execute(); |
1791
|
|
|
$statement->closeCursor(); |
1792
|
|
|
|
1793
|
|
|
return $statement->rowCount(); |
1794
|
|
|
} |
1795
|
|
|
|
1796
|
|
|
/** |
1797
|
|
|
* {@inheritDoc} |
1798
|
|
|
*/ |
1799
|
|
View Code Duplication |
public function deleteAll($conditions) |
1800
|
|
|
{ |
1801
|
|
|
$query = $this->query() |
1802
|
|
|
->delete() |
1803
|
|
|
->where($conditions); |
1804
|
|
|
$statement = $query->execute(); |
1805
|
|
|
$statement->closeCursor(); |
1806
|
|
|
|
1807
|
|
|
return $statement->rowCount(); |
1808
|
|
|
} |
1809
|
|
|
|
1810
|
|
|
/** |
1811
|
|
|
* {@inheritDoc} |
1812
|
|
|
*/ |
1813
|
|
|
public function exists($conditions) |
1814
|
|
|
{ |
1815
|
|
|
return (bool)count( |
1816
|
|
|
$this->find('all') |
1817
|
|
|
->select(['existing' => 1]) |
1818
|
|
|
->where($conditions) |
|
|
|
|
1819
|
|
|
->limit(1) |
1820
|
|
|
->disableHydration() |
1821
|
|
|
->toArray() |
1822
|
|
|
); |
1823
|
|
|
} |
1824
|
|
|
|
1825
|
|
|
/** |
1826
|
|
|
* {@inheritDoc} |
1827
|
|
|
* |
1828
|
|
|
* ### Options |
1829
|
|
|
* |
1830
|
|
|
* The options array accepts the following keys: |
1831
|
|
|
* |
1832
|
|
|
* - atomic: Whether to execute the save and callbacks inside a database |
1833
|
|
|
* transaction (default: true) |
1834
|
|
|
* - checkRules: Whether or not to check the rules on entity before saving, if the checking |
1835
|
|
|
* fails, it will abort the save operation. (default:true) |
1836
|
|
|
* - associated: If `true` it will save 1st level associated entities as they are found |
1837
|
|
|
* in the passed `$entity` whenever the property defined for the association |
1838
|
|
|
* is marked as dirty. If an array, it will be interpreted as the list of associations |
1839
|
|
|
* to be saved. It is possible to provide different options for saving on associated |
1840
|
|
|
* table objects using this key by making the custom options the array value. |
1841
|
|
|
* If `false` no associated records will be saved. (default: `true`) |
1842
|
|
|
* - checkExisting: Whether or not to check if the entity already exists, assuming that the |
1843
|
|
|
* entity is marked as not new, and the primary key has been set. |
1844
|
|
|
* |
1845
|
|
|
* ### Events |
1846
|
|
|
* |
1847
|
|
|
* When saving, this method will trigger four events: |
1848
|
|
|
* |
1849
|
|
|
* - Model.beforeRules: Will be triggered right before any rule checking is done |
1850
|
|
|
* for the passed entity if the `checkRules` key in $options is not set to false. |
1851
|
|
|
* Listeners will receive as arguments the entity, options array and the operation type. |
1852
|
|
|
* If the event is stopped the rules check result will be set to the result of the event itself. |
1853
|
|
|
* - Model.afterRules: Will be triggered right after the `checkRules()` method is |
1854
|
|
|
* called for the entity. Listeners will receive as arguments the entity, |
1855
|
|
|
* options array, the result of checking the rules and the operation type. |
1856
|
|
|
* If the event is stopped the checking result will be set to the result of |
1857
|
|
|
* the event itself. |
1858
|
|
|
* - Model.beforeSave: Will be triggered just before the list of fields to be |
1859
|
|
|
* persisted is calculated. It receives both the entity and the options as |
1860
|
|
|
* arguments. The options array is passed as an ArrayObject, so any changes in |
1861
|
|
|
* it will be reflected in every listener and remembered at the end of the event |
1862
|
|
|
* so it can be used for the rest of the save operation. Returning false in any |
1863
|
|
|
* of the listeners will abort the saving process. If the event is stopped |
1864
|
|
|
* using the event API, the event object's `result` property will be returned. |
1865
|
|
|
* This can be useful when having your own saving strategy implemented inside a |
1866
|
|
|
* listener. |
1867
|
|
|
* - Model.afterSave: Will be triggered after a successful insert or save, |
1868
|
|
|
* listeners will receive the entity and the options array as arguments. The type |
1869
|
|
|
* of operation performed (insert or update) can be determined by checking the |
1870
|
|
|
* entity's method `isNew`, true meaning an insert and false an update. |
1871
|
|
|
* - Model.afterSaveCommit: Will be triggered after the transaction is committed |
1872
|
|
|
* for atomic save, listeners will receive the entity and the options array |
1873
|
|
|
* as arguments. |
1874
|
|
|
* |
1875
|
|
|
* This method will determine whether the passed entity needs to be |
1876
|
|
|
* inserted or updated in the database. It does that by checking the `isNew` |
1877
|
|
|
* method on the entity. If the entity to be saved returns a non-empty value from |
1878
|
|
|
* its `errors()` method, it will not be saved. |
1879
|
|
|
* |
1880
|
|
|
* ### Saving on associated tables |
1881
|
|
|
* |
1882
|
|
|
* This method will by default persist entities belonging to associated tables, |
1883
|
|
|
* whenever a dirty property matching the name of the property name set for an |
1884
|
|
|
* association in this table. It is possible to control what associations will |
1885
|
|
|
* be saved and to pass additional option for saving them. |
1886
|
|
|
* |
1887
|
|
|
* ``` |
1888
|
|
|
* // Only save the comments association |
1889
|
|
|
* $articles->save($entity, ['associated' => ['Comments']]); |
1890
|
|
|
* |
1891
|
|
|
* // Save the company, the employees and related addresses for each of them. |
1892
|
|
|
* // For employees do not check the entity rules |
1893
|
|
|
* $companies->save($entity, [ |
1894
|
|
|
* 'associated' => [ |
1895
|
|
|
* 'Employees' => [ |
1896
|
|
|
* 'associated' => ['Addresses'], |
1897
|
|
|
* 'checkRules' => false |
1898
|
|
|
* ] |
1899
|
|
|
* ] |
1900
|
|
|
* ]); |
1901
|
|
|
* |
1902
|
|
|
* // Save no associations |
1903
|
|
|
* $articles->save($entity, ['associated' => false]); |
1904
|
|
|
* ``` |
1905
|
|
|
* |
1906
|
|
|
* @param \Cake\Datasource\EntityInterface $entity |
1907
|
|
|
* @param array $options |
1908
|
|
|
* @return \Cake\Datasource\EntityInterface|false |
1909
|
|
|
* @throws \Cake\ORM\Exception\RolledbackTransactionException If the transaction is aborted in the afterSave event. |
1910
|
|
|
*/ |
1911
|
|
|
public function save(EntityInterface $entity, $options = []) |
1912
|
|
|
{ |
1913
|
|
|
if ($options instanceof SaveOptionsBuilder) { |
1914
|
|
|
$options = $options->toArray(); |
1915
|
|
|
} |
1916
|
|
|
|
1917
|
|
|
$options = new ArrayObject((array)$options + [ |
1918
|
|
|
'atomic' => true, |
1919
|
|
|
'associated' => true, |
1920
|
|
|
'checkRules' => true, |
1921
|
|
|
'checkExisting' => true, |
1922
|
|
|
'_primary' => true, |
1923
|
|
|
]); |
1924
|
|
|
|
1925
|
|
|
if ($entity->hasErrors($options['associated'])) { |
|
|
|
|
1926
|
|
|
return false; |
1927
|
|
|
} |
1928
|
|
|
|
1929
|
|
|
if ($entity->isNew() === false && !$entity->isDirty()) { |
1930
|
|
|
return $entity; |
1931
|
|
|
} |
1932
|
|
|
|
1933
|
|
|
$success = $this->_executeTransaction(function () use ($entity, $options) { |
1934
|
|
|
return $this->_processSave($entity, $options); |
1935
|
|
|
}, $options['atomic']); |
1936
|
|
|
|
1937
|
|
|
if ($success) { |
1938
|
|
View Code Duplication |
if ($this->_transactionCommitted($options['atomic'], $options['_primary'])) { |
1939
|
|
|
$this->dispatchEvent('Model.afterSaveCommit', compact('entity', 'options')); |
1940
|
|
|
} |
1941
|
|
View Code Duplication |
if ($options['atomic'] || $options['_primary']) { |
1942
|
|
|
$entity->clean(); |
1943
|
|
|
$entity->isNew(false); |
1944
|
|
|
$entity->setSource($this->getRegistryAlias()); |
1945
|
|
|
} |
1946
|
|
|
} |
1947
|
|
|
|
1948
|
|
|
return $success; |
1949
|
|
|
} |
1950
|
|
|
|
1951
|
|
|
/** |
1952
|
|
|
* Try to save an entity or throw a PersistenceFailedException if the application rules checks failed, |
1953
|
|
|
* the entity contains errors or the save was aborted by a callback. |
1954
|
|
|
* |
1955
|
|
|
* @param \Cake\Datasource\EntityInterface $entity the entity to be saved |
1956
|
|
|
* @param array|\ArrayAccess $options The options to use when saving. |
1957
|
|
|
* @return \Cake\Datasource\EntityInterface |
1958
|
|
|
* @throws \Cake\ORM\Exception\PersistenceFailedException When the entity couldn't be saved |
1959
|
|
|
* @see \Cake\ORM\Table::save() |
1960
|
|
|
*/ |
1961
|
|
View Code Duplication |
public function saveOrFail(EntityInterface $entity, $options = []) |
1962
|
|
|
{ |
1963
|
|
|
$saved = $this->save($entity, $options); |
|
|
|
|
1964
|
|
|
if ($saved === false) { |
1965
|
|
|
throw new PersistenceFailedException($entity, ['save']); |
1966
|
|
|
} |
1967
|
|
|
|
1968
|
|
|
return $saved; |
1969
|
|
|
} |
1970
|
|
|
|
1971
|
|
|
/** |
1972
|
|
|
* Performs the actual saving of an entity based on the passed options. |
1973
|
|
|
* |
1974
|
|
|
* @param \Cake\Datasource\EntityInterface $entity the entity to be saved |
1975
|
|
|
* @param \ArrayObject $options the options to use for the save operation |
1976
|
|
|
* @return \Cake\Datasource\EntityInterface|bool |
1977
|
|
|
* @throws \RuntimeException When an entity is missing some of the primary keys. |
1978
|
|
|
* @throws \Cake\ORM\Exception\RolledbackTransactionException If the transaction |
1979
|
|
|
* is aborted in the afterSave event. |
1980
|
|
|
*/ |
1981
|
|
|
protected function _processSave($entity, $options) |
1982
|
|
|
{ |
1983
|
|
|
$primaryColumns = (array)$this->getPrimaryKey(); |
1984
|
|
|
|
1985
|
|
|
if ($options['checkExisting'] && $primaryColumns && $entity->isNew() && $entity->has($primaryColumns)) { |
1986
|
|
|
$alias = $this->getAlias(); |
1987
|
|
|
$conditions = []; |
1988
|
|
|
foreach ($entity->extract($primaryColumns) as $k => $v) { |
1989
|
|
|
$conditions["$alias.$k"] = $v; |
1990
|
|
|
} |
1991
|
|
|
$entity->isNew(!$this->exists($conditions)); |
1992
|
|
|
} |
1993
|
|
|
|
1994
|
|
|
$mode = $entity->isNew() ? RulesChecker::CREATE : RulesChecker::UPDATE; |
1995
|
|
|
if ($options['checkRules'] && !$this->checkRules($entity, $mode, $options)) { |
1996
|
|
|
return false; |
1997
|
|
|
} |
1998
|
|
|
|
1999
|
|
|
$options['associated'] = $this->_associations->normalizeKeys($options['associated']); |
2000
|
|
|
$event = $this->dispatchEvent('Model.beforeSave', compact('entity', 'options')); |
2001
|
|
|
|
2002
|
|
|
if ($event->isStopped()) { |
2003
|
|
|
return $event->getResult(); |
2004
|
|
|
} |
2005
|
|
|
|
2006
|
|
|
$saved = $this->_associations->saveParents( |
2007
|
|
|
$this, |
2008
|
|
|
$entity, |
2009
|
|
|
$options['associated'], |
2010
|
|
|
['_primary' => false] + $options->getArrayCopy() |
2011
|
|
|
); |
2012
|
|
|
|
2013
|
|
|
if (!$saved && $options['atomic']) { |
2014
|
|
|
return false; |
2015
|
|
|
} |
2016
|
|
|
|
2017
|
|
|
$data = $entity->extract($this->getSchema()->columns(), true); |
2018
|
|
|
$isNew = $entity->isNew(); |
2019
|
|
|
|
2020
|
|
|
if ($isNew) { |
2021
|
|
|
$success = $this->_insert($entity, $data); |
2022
|
|
|
} else { |
2023
|
|
|
$success = $this->_update($entity, $data); |
2024
|
|
|
} |
2025
|
|
|
|
2026
|
|
|
if ($success) { |
2027
|
|
|
$success = $this->_onSaveSuccess($entity, $options); |
2028
|
|
|
} |
2029
|
|
|
|
2030
|
|
|
if (!$success && $isNew) { |
2031
|
|
|
$entity->unsetProperty($this->getPrimaryKey()); |
2032
|
|
|
$entity->isNew(true); |
2033
|
|
|
} |
2034
|
|
|
|
2035
|
|
|
return $success ? $entity : false; |
2036
|
|
|
} |
2037
|
|
|
|
2038
|
|
|
/** |
2039
|
|
|
* Handles the saving of children associations and executing the afterSave logic |
2040
|
|
|
* once the entity for this table has been saved successfully. |
2041
|
|
|
* |
2042
|
|
|
* @param \Cake\Datasource\EntityInterface $entity the entity to be saved |
2043
|
|
|
* @param \ArrayObject $options the options to use for the save operation |
2044
|
|
|
* @return bool True on success |
2045
|
|
|
* @throws \Cake\ORM\Exception\RolledbackTransactionException If the transaction |
2046
|
|
|
* is aborted in the afterSave event. |
2047
|
|
|
*/ |
2048
|
|
|
protected function _onSaveSuccess($entity, $options) |
2049
|
|
|
{ |
2050
|
|
|
$success = $this->_associations->saveChildren( |
2051
|
|
|
$this, |
2052
|
|
|
$entity, |
2053
|
|
|
$options['associated'], |
2054
|
|
|
['_primary' => false] + $options->getArrayCopy() |
2055
|
|
|
); |
2056
|
|
|
|
2057
|
|
|
if (!$success && $options['atomic']) { |
2058
|
|
|
return false; |
2059
|
|
|
} |
2060
|
|
|
|
2061
|
|
|
$this->dispatchEvent('Model.afterSave', compact('entity', 'options')); |
2062
|
|
|
|
2063
|
|
|
if ($options['atomic'] && !$this->getConnection()->inTransaction()) { |
2064
|
|
|
throw new RolledbackTransactionException(['table' => get_class($this)]); |
2065
|
|
|
} |
2066
|
|
|
|
2067
|
|
View Code Duplication |
if (!$options['atomic'] && !$options['_primary']) { |
2068
|
|
|
$entity->clean(); |
2069
|
|
|
$entity->isNew(false); |
2070
|
|
|
$entity->setSource($this->getRegistryAlias()); |
2071
|
|
|
} |
2072
|
|
|
|
2073
|
|
|
return true; |
2074
|
|
|
} |
2075
|
|
|
|
2076
|
|
|
/** |
2077
|
|
|
* Auxiliary function to handle the insert of an entity's data in the table |
2078
|
|
|
* |
2079
|
|
|
* @param \Cake\Datasource\EntityInterface $entity the subject entity from were $data was extracted |
2080
|
|
|
* @param array $data The actual data that needs to be saved |
2081
|
|
|
* @return \Cake\Datasource\EntityInterface|bool |
2082
|
|
|
* @throws \RuntimeException if not all the primary keys where supplied or could |
2083
|
|
|
* be generated when the table has composite primary keys. Or when the table has no primary key. |
2084
|
|
|
*/ |
2085
|
|
|
protected function _insert($entity, $data) |
2086
|
|
|
{ |
2087
|
|
|
$primary = (array)$this->getPrimaryKey(); |
2088
|
|
|
if (empty($primary)) { |
2089
|
|
|
$msg = sprintf( |
2090
|
|
|
'Cannot insert row in "%s" table, it has no primary key.', |
2091
|
|
|
$this->getTable() |
2092
|
|
|
); |
2093
|
|
|
throw new RuntimeException($msg); |
2094
|
|
|
} |
2095
|
|
|
$keys = array_fill(0, count($primary), null); |
2096
|
|
|
$id = (array)$this->_newId($primary) + $keys; |
2097
|
|
|
|
2098
|
|
|
// Generate primary keys preferring values in $data. |
2099
|
|
|
$primary = array_combine($primary, $id); |
2100
|
|
|
$primary = array_intersect_key($data, $primary) + $primary; |
2101
|
|
|
|
2102
|
|
|
$filteredKeys = array_filter($primary, function ($v) { |
2103
|
|
|
return $v !== null; |
2104
|
|
|
}); |
2105
|
|
|
$data += $filteredKeys; |
2106
|
|
|
|
2107
|
|
|
if (count($primary) > 1) { |
2108
|
|
|
$schema = $this->getSchema(); |
2109
|
|
|
foreach ($primary as $k => $v) { |
2110
|
|
|
if (!isset($data[$k]) && empty($schema->getColumn($k)['autoIncrement'])) { |
2111
|
|
|
$msg = 'Cannot insert row, some of the primary key values are missing. '; |
2112
|
|
|
$msg .= sprintf( |
2113
|
|
|
'Got (%s), expecting (%s)', |
2114
|
|
|
implode(', ', $filteredKeys + $entity->extract(array_keys($primary))), |
2115
|
|
|
implode(', ', array_keys($primary)) |
2116
|
|
|
); |
2117
|
|
|
throw new RuntimeException($msg); |
2118
|
|
|
} |
2119
|
|
|
} |
2120
|
|
|
} |
2121
|
|
|
|
2122
|
|
|
$success = false; |
2123
|
|
|
if (empty($data)) { |
2124
|
|
|
return $success; |
2125
|
|
|
} |
2126
|
|
|
|
2127
|
|
|
$statement = $this->query()->insert(array_keys($data)) |
2128
|
|
|
->values($data) |
2129
|
|
|
->execute(); |
2130
|
|
|
|
2131
|
|
|
if ($statement->rowCount() !== 0) { |
2132
|
|
|
$success = $entity; |
2133
|
|
|
$entity->set($filteredKeys, ['guard' => false]); |
2134
|
|
|
$schema = $this->getSchema(); |
2135
|
|
|
$driver = $this->getConnection()->getDriver(); |
2136
|
|
|
foreach ($primary as $key => $v) { |
2137
|
|
|
if (!isset($data[$key])) { |
2138
|
|
|
$id = $statement->lastInsertId($this->getTable(), $key); |
2139
|
|
|
$type = $schema->getColumnType($key); |
2140
|
|
|
$entity->set($key, Type::build($type)->toPHP($id, $driver)); |
2141
|
|
|
break; |
2142
|
|
|
} |
2143
|
|
|
} |
2144
|
|
|
} |
2145
|
|
|
$statement->closeCursor(); |
2146
|
|
|
|
2147
|
|
|
return $success; |
2148
|
|
|
} |
2149
|
|
|
|
2150
|
|
|
/** |
2151
|
|
|
* Generate a primary key value for a new record. |
2152
|
|
|
* |
2153
|
|
|
* By default, this uses the type system to generate a new primary key |
2154
|
|
|
* value if possible. You can override this method if you have specific requirements |
2155
|
|
|
* for id generation. |
2156
|
|
|
* |
2157
|
|
|
* Note: The ORM will not generate primary key values for composite primary keys. |
2158
|
|
|
* You can overwrite _newId() in your table class. |
2159
|
|
|
* |
2160
|
|
|
* @param string[] $primary The primary key columns to get a new ID for. |
2161
|
|
|
* @return string|null Either null or the primary key value or a list of primary key values. |
2162
|
|
|
*/ |
2163
|
|
|
protected function _newId($primary) |
2164
|
|
|
{ |
2165
|
|
|
if (!$primary || count((array)$primary) > 1) { |
|
|
|
|
2166
|
|
|
return null; |
2167
|
|
|
} |
2168
|
|
|
$typeName = $this->getSchema()->getColumnType($primary[0]); |
2169
|
|
|
$type = Type::build($typeName); |
2170
|
|
|
|
2171
|
|
|
return $type->newId(); |
2172
|
|
|
} |
2173
|
|
|
|
2174
|
|
|
/** |
2175
|
|
|
* Auxiliary function to handle the update of an entity's data in the table |
2176
|
|
|
* |
2177
|
|
|
* @param \Cake\Datasource\EntityInterface $entity the subject entity from were $data was extracted |
2178
|
|
|
* @param array $data The actual data that needs to be saved |
2179
|
|
|
* @return \Cake\Datasource\EntityInterface|bool |
2180
|
|
|
* @throws \InvalidArgumentException When primary key data is missing. |
2181
|
|
|
*/ |
2182
|
|
|
protected function _update($entity, $data) |
2183
|
|
|
{ |
2184
|
|
|
$primaryColumns = (array)$this->getPrimaryKey(); |
2185
|
|
|
$primaryKey = $entity->extract($primaryColumns); |
2186
|
|
|
|
2187
|
|
|
$data = array_diff_key($data, $primaryKey); |
2188
|
|
|
if (empty($data)) { |
2189
|
|
|
return $entity; |
2190
|
|
|
} |
2191
|
|
|
|
2192
|
|
|
if (count($primaryColumns) === 0) { |
2193
|
|
|
$entityClass = get_class($entity); |
2194
|
|
|
$table = $this->getTable(); |
2195
|
|
|
$message = "Cannot update `$entityClass`. The `$table` has no primary key."; |
2196
|
|
|
throw new InvalidArgumentException($message); |
2197
|
|
|
} |
2198
|
|
|
|
2199
|
|
|
if (!$entity->has($primaryColumns)) { |
2200
|
|
|
$message = 'All primary key value(s) are needed for updating, '; |
2201
|
|
|
$message .= get_class($entity) . ' is missing ' . implode(', ', $primaryColumns); |
2202
|
|
|
throw new InvalidArgumentException($message); |
2203
|
|
|
} |
2204
|
|
|
|
2205
|
|
|
$query = $this->query(); |
2206
|
|
|
$statement = $query->update() |
2207
|
|
|
->set($data) |
2208
|
|
|
->where($primaryKey) |
2209
|
|
|
->execute(); |
2210
|
|
|
|
2211
|
|
|
$success = false; |
2212
|
|
|
if ($statement->errorCode() === '00000') { |
2213
|
|
|
$success = $entity; |
2214
|
|
|
} |
2215
|
|
|
$statement->closeCursor(); |
2216
|
|
|
|
2217
|
|
|
return $success; |
2218
|
|
|
} |
2219
|
|
|
|
2220
|
|
|
/** |
2221
|
|
|
* Persists multiple entities of a table. |
2222
|
|
|
* |
2223
|
|
|
* The records will be saved in a transaction which will be rolled back if |
2224
|
|
|
* any one of the records fails to save due to failed validation or database |
2225
|
|
|
* error. |
2226
|
|
|
* |
2227
|
|
|
* @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to save. |
2228
|
|
|
* @param array|\ArrayAccess $options Options used when calling Table::save() for each entity. |
2229
|
|
|
* @return bool|\Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface False on failure, entities list on success. |
2230
|
|
|
* @throws \Exception |
2231
|
|
|
*/ |
2232
|
|
|
public function saveMany($entities, $options = []) |
2233
|
|
|
{ |
2234
|
|
|
$isNew = []; |
2235
|
|
|
$cleanup = function ($entities) use (&$isNew) { |
2236
|
|
|
foreach ($entities as $key => $entity) { |
2237
|
|
|
if (isset($isNew[$key]) && $isNew[$key]) { |
2238
|
|
|
$entity->unsetProperty($this->getPrimaryKey()); |
2239
|
|
|
$entity->isNew(true); |
2240
|
|
|
} |
2241
|
|
|
} |
2242
|
|
|
}; |
2243
|
|
|
|
2244
|
|
|
try { |
2245
|
|
|
$return = $this->getConnection() |
2246
|
|
|
->transactional(function () use ($entities, $options, &$isNew) { |
2247
|
|
|
foreach ($entities as $key => $entity) { |
2248
|
|
|
$isNew[$key] = $entity->isNew(); |
2249
|
|
|
if ($this->save($entity, $options) === false) { |
|
|
|
|
2250
|
|
|
return false; |
2251
|
|
|
} |
2252
|
|
|
} |
2253
|
|
|
}); |
2254
|
|
|
} catch (\Exception $e) { |
2255
|
|
|
$cleanup($entities); |
2256
|
|
|
|
2257
|
|
|
throw $e; |
2258
|
|
|
} |
2259
|
|
|
|
2260
|
|
|
if ($return === false) { |
2261
|
|
|
$cleanup($entities); |
2262
|
|
|
|
2263
|
|
|
return false; |
2264
|
|
|
} |
2265
|
|
|
|
2266
|
|
|
return $entities; |
2267
|
|
|
} |
2268
|
|
|
|
2269
|
|
|
/** |
2270
|
|
|
* {@inheritDoc} |
2271
|
|
|
* |
2272
|
|
|
* For HasMany and HasOne associations records will be removed based on |
2273
|
|
|
* the dependent option. Join table records in BelongsToMany associations |
2274
|
|
|
* will always be removed. You can use the `cascadeCallbacks` option |
2275
|
|
|
* when defining associations to change how associated data is deleted. |
2276
|
|
|
* |
2277
|
|
|
* ### Options |
2278
|
|
|
* |
2279
|
|
|
* - `atomic` Defaults to true. When true the deletion happens within a transaction. |
2280
|
|
|
* - `checkRules` Defaults to true. Check deletion rules before deleting the record. |
2281
|
|
|
* |
2282
|
|
|
* ### Events |
2283
|
|
|
* |
2284
|
|
|
* - `Model.beforeDelete` Fired before the delete occurs. If stopped the delete |
2285
|
|
|
* will be aborted. Receives the event, entity, and options. |
2286
|
|
|
* - `Model.afterDelete` Fired after the delete has been successful. Receives |
2287
|
|
|
* the event, entity, and options. |
2288
|
|
|
* - `Model.afterDeleteCommit` Fired after the transaction is committed for |
2289
|
|
|
* an atomic delete. Receives the event, entity, and options. |
2290
|
|
|
* |
2291
|
|
|
* The options argument will be converted into an \ArrayObject instance |
2292
|
|
|
* for the duration of the callbacks, this allows listeners to modify |
2293
|
|
|
* the options used in the delete operation. |
2294
|
|
|
* |
2295
|
|
|
*/ |
2296
|
|
|
public function delete(EntityInterface $entity, $options = []) |
2297
|
|
|
{ |
2298
|
|
|
$options = new ArrayObject((array)$options + [ |
2299
|
|
|
'atomic' => true, |
2300
|
|
|
'checkRules' => true, |
2301
|
|
|
'_primary' => true, |
2302
|
|
|
]); |
2303
|
|
|
|
2304
|
|
|
$success = $this->_executeTransaction(function () use ($entity, $options) { |
2305
|
|
|
return $this->_processDelete($entity, $options); |
2306
|
|
|
}, $options['atomic']); |
2307
|
|
|
|
2308
|
|
|
if ($success && $this->_transactionCommitted($options['atomic'], $options['_primary'])) { |
2309
|
|
|
$this->dispatchEvent('Model.afterDeleteCommit', [ |
2310
|
|
|
'entity' => $entity, |
2311
|
|
|
'options' => $options, |
2312
|
|
|
]); |
2313
|
|
|
} |
2314
|
|
|
|
2315
|
|
|
return $success; |
2316
|
|
|
} |
2317
|
|
|
|
2318
|
|
|
/** |
2319
|
|
|
* Try to delete an entity or throw a PersistenceFailedException if the entity is new, |
2320
|
|
|
* has no primary key value, application rules checks failed or the delete was aborted by a callback. |
2321
|
|
|
* |
2322
|
|
|
* @param \Cake\Datasource\EntityInterface $entity The entity to remove. |
2323
|
|
|
* @param array|\ArrayAccess $options The options for the delete. |
2324
|
|
|
* @return bool success |
2325
|
|
|
* @throws \Cake\ORM\Exception\PersistenceFailedException |
2326
|
|
|
* @see \Cake\ORM\Table::delete() |
2327
|
|
|
*/ |
2328
|
|
View Code Duplication |
public function deleteOrFail(EntityInterface $entity, $options = []) |
2329
|
|
|
{ |
2330
|
|
|
$deleted = $this->delete($entity, $options); |
|
|
|
|
2331
|
|
|
if ($deleted === false) { |
2332
|
|
|
throw new PersistenceFailedException($entity, ['delete']); |
2333
|
|
|
} |
2334
|
|
|
|
2335
|
|
|
return $deleted; |
2336
|
|
|
} |
2337
|
|
|
|
2338
|
|
|
/** |
2339
|
|
|
* Perform the delete operation. |
2340
|
|
|
* |
2341
|
|
|
* Will delete the entity provided. Will remove rows from any |
2342
|
|
|
* dependent associations, and clear out join tables for BelongsToMany associations. |
2343
|
|
|
* |
2344
|
|
|
* @param \Cake\Datasource\EntityInterface $entity The entity to delete. |
2345
|
|
|
* @param \ArrayObject $options The options for the delete. |
2346
|
|
|
* @throws \InvalidArgumentException if there are no primary key values of the |
2347
|
|
|
* passed entity |
2348
|
|
|
* @return bool success |
2349
|
|
|
*/ |
2350
|
|
|
protected function _processDelete($entity, $options) |
2351
|
|
|
{ |
2352
|
|
|
if ($entity->isNew()) { |
2353
|
|
|
return false; |
2354
|
|
|
} |
2355
|
|
|
|
2356
|
|
|
$primaryKey = (array)$this->getPrimaryKey(); |
2357
|
|
|
if (!$entity->has($primaryKey)) { |
2358
|
|
|
$msg = 'Deleting requires all primary key values.'; |
2359
|
|
|
throw new InvalidArgumentException($msg); |
2360
|
|
|
} |
2361
|
|
|
|
2362
|
|
|
if ($options['checkRules'] && !$this->checkRules($entity, RulesChecker::DELETE, $options)) { |
2363
|
|
|
return false; |
2364
|
|
|
} |
2365
|
|
|
|
2366
|
|
|
$event = $this->dispatchEvent('Model.beforeDelete', [ |
2367
|
|
|
'entity' => $entity, |
2368
|
|
|
'options' => $options, |
2369
|
|
|
]); |
2370
|
|
|
|
2371
|
|
|
if ($event->isStopped()) { |
2372
|
|
|
return $event->getResult(); |
2373
|
|
|
} |
2374
|
|
|
|
2375
|
|
|
$this->_associations->cascadeDelete( |
2376
|
|
|
$entity, |
2377
|
|
|
['_primary' => false] + $options->getArrayCopy() |
2378
|
|
|
); |
2379
|
|
|
|
2380
|
|
|
$query = $this->query(); |
2381
|
|
|
$conditions = (array)$entity->extract($primaryKey); |
2382
|
|
|
$statement = $query->delete() |
2383
|
|
|
->where($conditions) |
2384
|
|
|
->execute(); |
2385
|
|
|
|
2386
|
|
|
$success = $statement->rowCount() > 0; |
2387
|
|
|
if (!$success) { |
2388
|
|
|
return $success; |
2389
|
|
|
} |
2390
|
|
|
|
2391
|
|
|
$this->dispatchEvent('Model.afterDelete', [ |
2392
|
|
|
'entity' => $entity, |
2393
|
|
|
'options' => $options, |
2394
|
|
|
]); |
2395
|
|
|
|
2396
|
|
|
return $success; |
2397
|
|
|
} |
2398
|
|
|
|
2399
|
|
|
/** |
2400
|
|
|
* Returns true if the finder exists for the table |
2401
|
|
|
* |
2402
|
|
|
* @param string $type name of finder to check |
2403
|
|
|
* |
2404
|
|
|
* @return bool |
2405
|
|
|
*/ |
2406
|
|
|
public function hasFinder($type) |
2407
|
|
|
{ |
2408
|
|
|
$finder = 'find' . $type; |
2409
|
|
|
|
2410
|
|
|
return method_exists($this, $finder) || ($this->_behaviors && $this->_behaviors->hasFinder($type)); |
2411
|
|
|
} |
2412
|
|
|
|
2413
|
|
|
/** |
2414
|
|
|
* Calls a finder method directly and applies it to the passed query, |
2415
|
|
|
* if no query is passed a new one will be created and returned |
2416
|
|
|
* |
2417
|
|
|
* @param string $type name of the finder to be called |
2418
|
|
|
* @param \Cake\ORM\Query $query The query object to apply the finder options to |
2419
|
|
|
* @param array $options List of options to pass to the finder |
2420
|
|
|
* @return \Cake\ORM\Query |
2421
|
|
|
* @throws \BadMethodCallException |
2422
|
|
|
*/ |
2423
|
|
|
public function callFinder($type, Query $query, array $options = []) |
2424
|
|
|
{ |
2425
|
|
|
$query->applyOptions($options); |
2426
|
|
|
$options = $query->getOptions(); |
2427
|
|
|
$finder = 'find' . $type; |
2428
|
|
|
if (method_exists($this, $finder)) { |
2429
|
|
|
return $this->{$finder}($query, $options); |
2430
|
|
|
} |
2431
|
|
|
|
2432
|
|
|
if ($this->_behaviors && $this->_behaviors->hasFinder($type)) { |
2433
|
|
|
return $this->_behaviors->callFinder($type, [$query, $options]); |
2434
|
|
|
} |
2435
|
|
|
|
2436
|
|
|
throw new BadMethodCallException( |
2437
|
|
|
sprintf('Unknown finder method "%s"', $type) |
2438
|
|
|
); |
2439
|
|
|
} |
2440
|
|
|
|
2441
|
|
|
/** |
2442
|
|
|
* Provides the dynamic findBy and findByAll methods. |
2443
|
|
|
* |
2444
|
|
|
* @param string $method The method name that was fired. |
2445
|
|
|
* @param array $args List of arguments passed to the function. |
2446
|
|
|
* @return mixed |
2447
|
|
|
* @throws \BadMethodCallException when there are missing arguments, or when |
2448
|
|
|
* and & or are combined. |
2449
|
|
|
*/ |
2450
|
|
|
protected function _dynamicFinder($method, $args) |
2451
|
|
|
{ |
2452
|
|
|
$method = Inflector::underscore($method); |
2453
|
|
|
preg_match('/^find_([\w]+)_by_/', $method, $matches); |
2454
|
|
|
if (empty($matches)) { |
2455
|
|
|
// find_by_ is 8 characters. |
2456
|
|
|
$fields = substr($method, 8); |
2457
|
|
|
$findType = 'all'; |
2458
|
|
|
} else { |
2459
|
|
|
$fields = substr($method, strlen($matches[0])); |
2460
|
|
|
$findType = Inflector::variable($matches[1]); |
2461
|
|
|
} |
2462
|
|
|
$hasOr = strpos($fields, '_or_'); |
2463
|
|
|
$hasAnd = strpos($fields, '_and_'); |
2464
|
|
|
|
2465
|
|
|
$makeConditions = function ($fields, $args) { |
2466
|
|
|
$conditions = []; |
2467
|
|
|
if (count($args) < count($fields)) { |
2468
|
|
|
throw new BadMethodCallException(sprintf( |
2469
|
|
|
'Not enough arguments for magic finder. Got %s required %s', |
2470
|
|
|
count($args), |
2471
|
|
|
count($fields) |
2472
|
|
|
)); |
2473
|
|
|
} |
2474
|
|
|
foreach ($fields as $field) { |
2475
|
|
|
$conditions[$this->aliasField($field)] = array_shift($args); |
2476
|
|
|
} |
2477
|
|
|
|
2478
|
|
|
return $conditions; |
2479
|
|
|
}; |
2480
|
|
|
|
2481
|
|
|
if ($hasOr !== false && $hasAnd !== false) { |
2482
|
|
|
throw new BadMethodCallException( |
2483
|
|
|
'Cannot mix "and" & "or" in a magic finder. Use find() instead.' |
2484
|
|
|
); |
2485
|
|
|
} |
2486
|
|
|
|
2487
|
|
|
$conditions = []; |
2488
|
|
|
if ($hasOr === false && $hasAnd === false) { |
2489
|
|
|
$conditions = $makeConditions([$fields], $args); |
2490
|
|
|
} elseif ($hasOr !== false) { |
2491
|
|
|
$fields = explode('_or_', $fields); |
2492
|
|
|
$conditions = [ |
2493
|
|
|
'OR' => $makeConditions($fields, $args), |
2494
|
|
|
]; |
2495
|
|
|
} elseif ($hasAnd !== false) { |
2496
|
|
|
$fields = explode('_and_', $fields); |
2497
|
|
|
$conditions = $makeConditions($fields, $args); |
2498
|
|
|
} |
2499
|
|
|
|
2500
|
|
|
return $this->find($findType, [ |
2501
|
|
|
'conditions' => $conditions, |
2502
|
|
|
]); |
2503
|
|
|
} |
2504
|
|
|
|
2505
|
|
|
/** |
2506
|
|
|
* Handles behavior delegation + dynamic finders. |
2507
|
|
|
* |
2508
|
|
|
* If your Table uses any behaviors you can call them as if |
2509
|
|
|
* they were on the table object. |
2510
|
|
|
* |
2511
|
|
|
* @param string $method name of the method to be invoked |
2512
|
|
|
* @param array $args List of arguments passed to the function |
2513
|
|
|
* @return mixed |
2514
|
|
|
* @throws \BadMethodCallException |
2515
|
|
|
*/ |
2516
|
|
|
public function __call($method, $args) |
2517
|
|
|
{ |
2518
|
|
|
if ($this->_behaviors && $this->_behaviors->hasMethod($method)) { |
2519
|
|
|
return $this->_behaviors->call($method, $args); |
2520
|
|
|
} |
2521
|
|
|
if (preg_match('/^find(?:\w+)?By/', $method) > 0) { |
2522
|
|
|
return $this->_dynamicFinder($method, $args); |
2523
|
|
|
} |
2524
|
|
|
|
2525
|
|
|
throw new BadMethodCallException( |
2526
|
|
|
sprintf('Unknown method "%s"', $method) |
2527
|
|
|
); |
2528
|
|
|
} |
2529
|
|
|
|
2530
|
|
|
/** |
2531
|
|
|
* Returns the association named after the passed value if exists, otherwise |
2532
|
|
|
* throws an exception. |
2533
|
|
|
* |
2534
|
|
|
* @param string $property the association name |
2535
|
|
|
* @return \Cake\ORM\Association |
2536
|
|
|
* @throws \RuntimeException if no association with such name exists |
2537
|
|
|
*/ |
2538
|
|
|
public function __get($property) |
2539
|
|
|
{ |
2540
|
|
|
$association = $this->_associations->get($property); |
2541
|
|
|
if (!$association) { |
2542
|
|
|
throw new RuntimeException(sprintf( |
2543
|
|
|
'Undefined property `%s`. ' . |
2544
|
|
|
'You have not defined the `%s` association on `%s`.', |
2545
|
|
|
$property, |
2546
|
|
|
$property, |
2547
|
|
|
static::class |
2548
|
|
|
)); |
2549
|
|
|
} |
2550
|
|
|
|
2551
|
|
|
return $association; |
2552
|
|
|
} |
2553
|
|
|
|
2554
|
|
|
/** |
2555
|
|
|
* Returns whether an association named after the passed value |
2556
|
|
|
* exists for this table. |
2557
|
|
|
* |
2558
|
|
|
* @param string $property the association name |
2559
|
|
|
* @return bool |
2560
|
|
|
*/ |
2561
|
|
|
public function __isset($property) |
2562
|
|
|
{ |
2563
|
|
|
return $this->_associations->has($property); |
2564
|
|
|
} |
2565
|
|
|
|
2566
|
|
|
/** |
2567
|
|
|
* Get the object used to marshal/convert array data into objects. |
2568
|
|
|
* |
2569
|
|
|
* Override this method if you want a table object to use custom |
2570
|
|
|
* marshalling logic. |
2571
|
|
|
* |
2572
|
|
|
* @return \Cake\ORM\Marshaller |
2573
|
|
|
* @see \Cake\ORM\Marshaller |
2574
|
|
|
*/ |
2575
|
|
|
public function marshaller() |
2576
|
|
|
{ |
2577
|
|
|
return new Marshaller($this); |
2578
|
|
|
} |
2579
|
|
|
|
2580
|
|
|
/** |
2581
|
|
|
* {@inheritDoc} |
2582
|
|
|
* |
2583
|
|
|
* By default all the associations on this table will be hydrated. You can |
2584
|
|
|
* limit which associations are built, or include deeper associations |
2585
|
|
|
* using the options parameter: |
2586
|
|
|
* |
2587
|
|
|
* ``` |
2588
|
|
|
* $article = $this->Articles->newEntity( |
2589
|
|
|
* $this->request->getData(), |
2590
|
|
|
* ['associated' => ['Tags', 'Comments.Users']] |
2591
|
|
|
* ); |
2592
|
|
|
* ``` |
2593
|
|
|
* |
2594
|
|
|
* You can limit fields that will be present in the constructed entity by |
2595
|
|
|
* passing the `fields` option, which is also accepted for associations: |
2596
|
|
|
* |
2597
|
|
|
* ``` |
2598
|
|
|
* $article = $this->Articles->newEntity($this->request->getData(), [ |
2599
|
|
|
* 'fields' => ['title', 'body', 'tags', 'comments'], |
2600
|
|
|
* 'associated' => ['Tags', 'Comments.Users' => ['fields' => 'username']] |
2601
|
|
|
* ] |
2602
|
|
|
* ); |
2603
|
|
|
* ``` |
2604
|
|
|
* |
2605
|
|
|
* The `fields` option lets remove or restrict input data from ending up in |
2606
|
|
|
* the entity. If you'd like to relax the entity's default accessible fields, |
2607
|
|
|
* you can use the `accessibleFields` option: |
2608
|
|
|
* |
2609
|
|
|
* ``` |
2610
|
|
|
* $article = $this->Articles->newEntity( |
2611
|
|
|
* $this->request->getData(), |
2612
|
|
|
* ['accessibleFields' => ['protected_field' => true]] |
2613
|
|
|
* ); |
2614
|
|
|
* ``` |
2615
|
|
|
* |
2616
|
|
|
* By default, the data is validated before being passed to the new entity. In |
2617
|
|
|
* the case of invalid fields, those will not be present in the resulting object. |
2618
|
|
|
* The `validate` option can be used to disable validation on the passed data: |
2619
|
|
|
* |
2620
|
|
|
* ``` |
2621
|
|
|
* $article = $this->Articles->newEntity( |
2622
|
|
|
* $this->request->getData(), |
2623
|
|
|
* ['validate' => false] |
2624
|
|
|
* ); |
2625
|
|
|
* ``` |
2626
|
|
|
* |
2627
|
|
|
* You can also pass the name of the validator to use in the `validate` option. |
2628
|
|
|
* If `null` is passed to the first param of this function, no validation will |
2629
|
|
|
* be performed. |
2630
|
|
|
* |
2631
|
|
|
* You can use the `Model.beforeMarshal` event to modify request data |
2632
|
|
|
* before it is converted into entities. |
2633
|
|
|
*/ |
2634
|
|
|
public function newEntity($data = null, array $options = []) |
2635
|
|
|
{ |
2636
|
|
|
if ($data === null) { |
2637
|
|
|
$class = $this->getEntityClass(); |
2638
|
|
|
|
2639
|
|
|
return new $class([], ['source' => $this->getRegistryAlias()]); |
2640
|
|
|
} |
2641
|
|
|
if (!isset($options['associated'])) { |
2642
|
|
|
$options['associated'] = $this->_associations->keys(); |
2643
|
|
|
} |
2644
|
|
|
$marshaller = $this->marshaller(); |
2645
|
|
|
|
2646
|
|
|
return $marshaller->one($data, $options); |
2647
|
|
|
} |
2648
|
|
|
|
2649
|
|
|
/** |
2650
|
|
|
* {@inheritDoc} |
2651
|
|
|
* |
2652
|
|
|
* By default all the associations on this table will be hydrated. You can |
2653
|
|
|
* limit which associations are built, or include deeper associations |
2654
|
|
|
* using the options parameter: |
2655
|
|
|
* |
2656
|
|
|
* ``` |
2657
|
|
|
* $articles = $this->Articles->newEntities( |
2658
|
|
|
* $this->request->getData(), |
2659
|
|
|
* ['associated' => ['Tags', 'Comments.Users']] |
2660
|
|
|
* ); |
2661
|
|
|
* ``` |
2662
|
|
|
* |
2663
|
|
|
* You can limit fields that will be present in the constructed entities by |
2664
|
|
|
* passing the `fields` option, which is also accepted for associations: |
2665
|
|
|
* |
2666
|
|
|
* ``` |
2667
|
|
|
* $articles = $this->Articles->newEntities($this->request->getData(), [ |
2668
|
|
|
* 'fields' => ['title', 'body', 'tags', 'comments'], |
2669
|
|
|
* 'associated' => ['Tags', 'Comments.Users' => ['fields' => 'username']] |
2670
|
|
|
* ] |
2671
|
|
|
* ); |
2672
|
|
|
* ``` |
2673
|
|
|
* |
2674
|
|
|
* You can use the `Model.beforeMarshal` event to modify request data |
2675
|
|
|
* before it is converted into entities. |
2676
|
|
|
*/ |
2677
|
|
View Code Duplication |
public function newEntities(array $data, array $options = []) |
2678
|
|
|
{ |
2679
|
|
|
if (!isset($options['associated'])) { |
2680
|
|
|
$options['associated'] = $this->_associations->keys(); |
2681
|
|
|
} |
2682
|
|
|
$marshaller = $this->marshaller(); |
2683
|
|
|
|
2684
|
|
|
return $marshaller->many($data, $options); |
2685
|
|
|
} |
2686
|
|
|
|
2687
|
|
|
/** |
2688
|
|
|
* {@inheritDoc} |
2689
|
|
|
* |
2690
|
|
|
* When merging HasMany or BelongsToMany associations, all the entities in the |
2691
|
|
|
* `$data` array will appear, those that can be matched by primary key will get |
2692
|
|
|
* the data merged, but those that cannot, will be discarded. |
2693
|
|
|
* |
2694
|
|
|
* You can limit fields that will be present in the merged entity by |
2695
|
|
|
* passing the `fields` option, which is also accepted for associations: |
2696
|
|
|
* |
2697
|
|
|
* ``` |
2698
|
|
|
* $article = $this->Articles->patchEntity($article, $this->request->getData(), [ |
2699
|
|
|
* 'fields' => ['title', 'body', 'tags', 'comments'], |
2700
|
|
|
* 'associated' => ['Tags', 'Comments.Users' => ['fields' => 'username']] |
2701
|
|
|
* ] |
2702
|
|
|
* ); |
2703
|
|
|
* ``` |
2704
|
|
|
* |
2705
|
|
|
* By default, the data is validated before being passed to the entity. In |
2706
|
|
|
* the case of invalid fields, those will not be assigned to the entity. |
2707
|
|
|
* The `validate` option can be used to disable validation on the passed data: |
2708
|
|
|
* |
2709
|
|
|
* ``` |
2710
|
|
|
* $article = $this->patchEntity($article, $this->request->getData(),[ |
2711
|
|
|
* 'validate' => false |
2712
|
|
|
* ]); |
2713
|
|
|
* ``` |
2714
|
|
|
* |
2715
|
|
|
* You can use the `Model.beforeMarshal` event to modify request data |
2716
|
|
|
* before it is converted into entities. |
2717
|
|
|
* |
2718
|
|
|
* When patching scalar values (null/booleans/string/integer/float), if the property |
2719
|
|
|
* presently has an identical value, the setter will not be called, and the |
2720
|
|
|
* property will not be marked as dirty. This is an optimization to prevent unnecessary field |
2721
|
|
|
* updates when persisting entities. |
2722
|
|
|
*/ |
2723
|
|
View Code Duplication |
public function patchEntity(EntityInterface $entity, array $data, array $options = []) |
2724
|
|
|
{ |
2725
|
|
|
if (!isset($options['associated'])) { |
2726
|
|
|
$options['associated'] = $this->_associations->keys(); |
2727
|
|
|
} |
2728
|
|
|
$marshaller = $this->marshaller(); |
2729
|
|
|
|
2730
|
|
|
return $marshaller->merge($entity, $data, $options); |
2731
|
|
|
} |
2732
|
|
|
|
2733
|
|
|
/** |
2734
|
|
|
* {@inheritDoc} |
2735
|
|
|
* |
2736
|
|
|
* Those entries in `$entities` that cannot be matched to any record in |
2737
|
|
|
* `$data` will be discarded. Records in `$data` that could not be matched will |
2738
|
|
|
* be marshalled as a new entity. |
2739
|
|
|
* |
2740
|
|
|
* When merging HasMany or BelongsToMany associations, all the entities in the |
2741
|
|
|
* `$data` array will appear, those that can be matched by primary key will get |
2742
|
|
|
* the data merged, but those that cannot, will be discarded. |
2743
|
|
|
* |
2744
|
|
|
* You can limit fields that will be present in the merged entities by |
2745
|
|
|
* passing the `fields` option, which is also accepted for associations: |
2746
|
|
|
* |
2747
|
|
|
* ``` |
2748
|
|
|
* $articles = $this->Articles->patchEntities($articles, $this->request->getData(), [ |
2749
|
|
|
* 'fields' => ['title', 'body', 'tags', 'comments'], |
2750
|
|
|
* 'associated' => ['Tags', 'Comments.Users' => ['fields' => 'username']] |
2751
|
|
|
* ] |
2752
|
|
|
* ); |
2753
|
|
|
* ``` |
2754
|
|
|
* |
2755
|
|
|
* You can use the `Model.beforeMarshal` event to modify request data |
2756
|
|
|
* before it is converted into entities. |
2757
|
|
|
*/ |
2758
|
|
View Code Duplication |
public function patchEntities($entities, array $data, array $options = []) |
2759
|
|
|
{ |
2760
|
|
|
if (!isset($options['associated'])) { |
2761
|
|
|
$options['associated'] = $this->_associations->keys(); |
2762
|
|
|
} |
2763
|
|
|
$marshaller = $this->marshaller(); |
2764
|
|
|
|
2765
|
|
|
return $marshaller->mergeMany($entities, $data, $options); |
2766
|
|
|
} |
2767
|
|
|
|
2768
|
|
|
/** |
2769
|
|
|
* Validator method used to check the uniqueness of a value for a column. |
2770
|
|
|
* This is meant to be used with the validation API and not to be called |
2771
|
|
|
* directly. |
2772
|
|
|
* |
2773
|
|
|
* ### Example: |
2774
|
|
|
* |
2775
|
|
|
* ``` |
2776
|
|
|
* $validator->add('email', [ |
2777
|
|
|
* 'unique' => ['rule' => 'validateUnique', 'provider' => 'table'] |
2778
|
|
|
* ]) |
2779
|
|
|
* ``` |
2780
|
|
|
* |
2781
|
|
|
* Unique validation can be scoped to the value of another column: |
2782
|
|
|
* |
2783
|
|
|
* ``` |
2784
|
|
|
* $validator->add('email', [ |
2785
|
|
|
* 'unique' => [ |
2786
|
|
|
* 'rule' => ['validateUnique', ['scope' => 'site_id']], |
2787
|
|
|
* 'provider' => 'table' |
2788
|
|
|
* ] |
2789
|
|
|
* ]); |
2790
|
|
|
* ``` |
2791
|
|
|
* |
2792
|
|
|
* In the above example, the email uniqueness will be scoped to only rows having |
2793
|
|
|
* the same site_id. Scoping will only be used if the scoping field is present in |
2794
|
|
|
* the data to be validated. |
2795
|
|
|
* |
2796
|
|
|
* @param mixed $value The value of column to be checked for uniqueness. |
2797
|
|
|
* @param array $options The options array, optionally containing the 'scope' key. |
2798
|
|
|
* May also be the validation context, if there are no options. |
2799
|
|
|
* @param array|null $context Either the validation context or null. |
2800
|
|
|
* @return bool True if the value is unique, or false if a non-scalar, non-unique value was given. |
2801
|
|
|
*/ |
2802
|
|
|
public function validateUnique($value, array $options, array $context = null) |
2803
|
|
|
{ |
2804
|
|
|
if ($context === null) { |
2805
|
|
|
$context = $options; |
2806
|
|
|
} |
2807
|
|
|
$entity = new Entity( |
2808
|
|
|
$context['data'], |
2809
|
|
|
[ |
2810
|
|
|
'useSetters' => false, |
2811
|
|
|
'markNew' => $context['newRecord'], |
2812
|
|
|
'source' => $this->getRegistryAlias(), |
2813
|
|
|
] |
2814
|
|
|
); |
2815
|
|
|
$fields = array_merge( |
2816
|
|
|
[$context['field']], |
2817
|
|
|
isset($options['scope']) ? (array)$options['scope'] : [] |
2818
|
|
|
); |
2819
|
|
|
$values = $entity->extract($fields); |
2820
|
|
|
foreach ($values as $field) { |
2821
|
|
|
if ($field !== null && !is_scalar($field)) { |
2822
|
|
|
return false; |
2823
|
|
|
} |
2824
|
|
|
} |
2825
|
|
|
$class = static::IS_UNIQUE_CLASS; |
2826
|
|
|
$rule = new $class($fields, $options); |
2827
|
|
|
|
2828
|
|
|
return $rule($entity, ['repository' => $this]); |
2829
|
|
|
} |
2830
|
|
|
|
2831
|
|
|
/** |
2832
|
|
|
* Get the Model callbacks this table is interested in. |
2833
|
|
|
* |
2834
|
|
|
* By implementing the conventional methods a table class is assumed |
2835
|
|
|
* to be interested in the related event. |
2836
|
|
|
* |
2837
|
|
|
* Override this method if you need to add non-conventional event listeners. |
2838
|
|
|
* Or if you want you table to listen to non-standard events. |
2839
|
|
|
* |
2840
|
|
|
* The conventional method map is: |
2841
|
|
|
* |
2842
|
|
|
* - Model.beforeMarshal => beforeMarshal |
2843
|
|
|
* - Model.buildValidator => buildValidator |
2844
|
|
|
* - Model.beforeFind => beforeFind |
2845
|
|
|
* - Model.beforeSave => beforeSave |
2846
|
|
|
* - Model.afterSave => afterSave |
2847
|
|
|
* - Model.afterSaveCommit => afterSaveCommit |
2848
|
|
|
* - Model.beforeDelete => beforeDelete |
2849
|
|
|
* - Model.afterDelete => afterDelete |
2850
|
|
|
* - Model.afterDeleteCommit => afterDeleteCommit |
2851
|
|
|
* - Model.beforeRules => beforeRules |
2852
|
|
|
* - Model.afterRules => afterRules |
2853
|
|
|
* |
2854
|
|
|
* @return array |
2855
|
|
|
*/ |
2856
|
|
|
public function implementedEvents() |
2857
|
|
|
{ |
2858
|
|
|
$eventMap = [ |
2859
|
|
|
'Model.beforeMarshal' => 'beforeMarshal', |
2860
|
|
|
'Model.buildValidator' => 'buildValidator', |
2861
|
|
|
'Model.beforeFind' => 'beforeFind', |
2862
|
|
|
'Model.beforeSave' => 'beforeSave', |
2863
|
|
|
'Model.afterSave' => 'afterSave', |
2864
|
|
|
'Model.afterSaveCommit' => 'afterSaveCommit', |
2865
|
|
|
'Model.beforeDelete' => 'beforeDelete', |
2866
|
|
|
'Model.afterDelete' => 'afterDelete', |
2867
|
|
|
'Model.afterDeleteCommit' => 'afterDeleteCommit', |
2868
|
|
|
'Model.beforeRules' => 'beforeRules', |
2869
|
|
|
'Model.afterRules' => 'afterRules', |
2870
|
|
|
]; |
2871
|
|
|
$events = []; |
2872
|
|
|
|
2873
|
|
|
foreach ($eventMap as $event => $method) { |
2874
|
|
|
if (!method_exists($this, $method)) { |
2875
|
|
|
continue; |
2876
|
|
|
} |
2877
|
|
|
$events[$event] = $method; |
2878
|
|
|
} |
2879
|
|
|
|
2880
|
|
|
return $events; |
2881
|
|
|
} |
2882
|
|
|
|
2883
|
|
|
/** |
2884
|
|
|
* {@inheritDoc} |
2885
|
|
|
* |
2886
|
|
|
* @param \Cake\ORM\RulesChecker $rules The rules object to be modified. |
2887
|
|
|
* @return \Cake\ORM\RulesChecker |
2888
|
|
|
*/ |
2889
|
|
|
public function buildRules(RulesChecker $rules) |
2890
|
|
|
{ |
2891
|
|
|
return $rules; |
2892
|
|
|
} |
2893
|
|
|
|
2894
|
|
|
/** |
2895
|
|
|
* Gets a SaveOptionsBuilder instance. |
2896
|
|
|
* |
2897
|
|
|
* @param array $options Options to parse by the builder. |
2898
|
|
|
* @return \Cake\ORM\SaveOptionsBuilder |
2899
|
|
|
*/ |
2900
|
|
|
public function getSaveOptionsBuilder(array $options = []) |
2901
|
|
|
{ |
2902
|
|
|
return new SaveOptionsBuilder($this, $options); |
2903
|
|
|
} |
2904
|
|
|
|
2905
|
|
|
/** |
2906
|
|
|
* Loads the specified associations in the passed entity or list of entities |
2907
|
|
|
* by executing extra queries in the database and merging the results in the |
2908
|
|
|
* appropriate properties. |
2909
|
|
|
* |
2910
|
|
|
* ### Example: |
2911
|
|
|
* |
2912
|
|
|
* ``` |
2913
|
|
|
* $user = $usersTable->get(1); |
2914
|
|
|
* $user = $usersTable->loadInto($user, ['Articles.Tags', 'Articles.Comments']); |
2915
|
|
|
* echo $user->articles[0]->title; |
2916
|
|
|
* ``` |
2917
|
|
|
* |
2918
|
|
|
* You can also load associations for multiple entities at once |
2919
|
|
|
* |
2920
|
|
|
* ### Example: |
2921
|
|
|
* |
2922
|
|
|
* ``` |
2923
|
|
|
* $users = $usersTable->find()->where([...])->toList(); |
2924
|
|
|
* $users = $usersTable->loadInto($users, ['Articles.Tags', 'Articles.Comments']); |
2925
|
|
|
* echo $user[1]->articles[0]->title; |
2926
|
|
|
* ``` |
2927
|
|
|
* |
2928
|
|
|
* The properties for the associations to be loaded will be overwritten on each entity. |
2929
|
|
|
* |
2930
|
|
|
* @param \Cake\Datasource\EntityInterface|array $entities a single entity or list of entities |
2931
|
|
|
* @param array $contain A `contain()` compatible array. |
2932
|
|
|
* @see \Cake\ORM\Query::contain() |
2933
|
|
|
* @return \Cake\Datasource\EntityInterface|array |
2934
|
|
|
*/ |
2935
|
|
|
public function loadInto($entities, array $contain) |
2936
|
|
|
{ |
2937
|
|
|
return (new LazyEagerLoader())->loadInto($entities, $contain, $this); |
2938
|
|
|
} |
2939
|
|
|
|
2940
|
|
|
/** |
2941
|
|
|
* {@inheritDoc} |
2942
|
|
|
*/ |
2943
|
|
|
protected function validationMethodExists($method) |
2944
|
|
|
{ |
2945
|
|
|
return method_exists($this, $method) || $this->behaviors()->hasMethod($method); |
2946
|
|
|
} |
2947
|
|
|
|
2948
|
|
|
/** |
2949
|
|
|
* Returns an array that can be used to describe the internal state of this |
2950
|
|
|
* object. |
2951
|
|
|
* |
2952
|
|
|
* @return array |
2953
|
|
|
*/ |
2954
|
|
|
public function __debugInfo() |
2955
|
|
|
{ |
2956
|
|
|
$conn = $this->getConnection(); |
2957
|
|
|
$associations = $this->_associations; |
2958
|
|
|
$behaviors = $this->_behaviors; |
2959
|
|
|
|
2960
|
|
|
return [ |
2961
|
|
|
'registryAlias' => $this->getRegistryAlias(), |
2962
|
|
|
'table' => $this->getTable(), |
2963
|
|
|
'alias' => $this->getAlias(), |
2964
|
|
|
'entityClass' => $this->getEntityClass(), |
2965
|
|
|
'associations' => $associations ? $associations->keys() : false, |
2966
|
|
|
'behaviors' => $behaviors ? $behaviors->loaded() : false, |
2967
|
|
|
'defaultConnection' => static::defaultConnectionName(), |
2968
|
|
|
'connectionName' => $conn ? $conn->configName() : null, |
2969
|
|
|
]; |
2970
|
|
|
} |
2971
|
|
|
} |
2972
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.
Either this assignment is in error or an instanceof check should be added for that assignment.