CakePHP
  • Documentation
    • Book
    • API
    • Videos
    • Logos & Trademarks
  • Business Solutions
  • Swag
  • Road Trip
  • Team
  • Community
    • Community
    • Team
    • Issues (Github)
    • YouTube Channel
    • Get Involved
    • Bakery
    • Featured Resources
    • Newsletter
    • Certification
    • My CakePHP
    • CakeFest
    • Facebook
    • Twitter
    • Help & Support
    • Forum
    • Stack Overflow
    • IRC
    • Slack
    • Paid Support
CakePHP

C CakePHP 3.7 Red Velvet API

  • Overview
  • Tree
  • Deprecated
  • Version:
    • 3.7
      • 3.7
      • 3.6
      • 3.5
      • 3.4
      • 3.3
      • 3.2
      • 3.1
      • 3.0
      • 2.10
      • 2.9
      • 2.8
      • 2.7
      • 2.6
      • 2.5
      • 2.4
      • 2.3
      • 2.2
      • 2.1
      • 2.0
      • 1.3
      • 1.2

Namespaces

  • Cake
    • Auth
      • Storage
    • Cache
      • Engine
    • Collection
      • Iterator
    • Command
    • Console
      • Exception
    • Controller
      • Component
      • Exception
    • Core
      • Configure
        • Engine
      • Exception
      • Retry
    • Database
      • Driver
      • Exception
      • Expression
      • Schema
      • Statement
      • Type
    • Datasource
      • Exception
    • Error
      • Middleware
    • Event
      • Decorator
    • Filesystem
    • Form
    • Http
      • Client
        • Adapter
        • Auth
      • Cookie
      • Exception
      • Middleware
      • Session
    • I18n
      • Formatter
      • Middleware
      • Parser
    • Log
      • Engine
    • Mailer
      • Exception
      • Transport
    • Network
      • Exception
    • ORM
      • Association
      • Behavior
        • Translate
      • Exception
      • Locator
      • Rule
    • Routing
      • Exception
      • Filter
      • Middleware
      • Route
    • Shell
      • Helper
      • Task
    • TestSuite
      • Fixture
      • Stub
    • Utility
      • Exception
    • Validation
    • View
      • Exception
      • Form
      • Helper
      • Widget
  • None

Classes

  • Association
  • AssociationCollection
  • Behavior
  • BehaviorRegistry
  • EagerLoader
  • Entity
  • Marshaller
  • Query
  • ResultSet
  • RulesChecker
  • SaveOptionsBuilder
  • Table
  • TableRegistry

Interfaces

  • PropertyMarshalInterface

Traits

  • AssociationsNormalizerTrait
  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 Cake\Collection\Collection;
 19: use Cake\Database\Expression\TupleComparison;
 20: use Cake\Database\Type;
 21: use Cake\Datasource\EntityInterface;
 22: use Cake\Datasource\InvalidPropertyInterface;
 23: use Cake\ORM\Association\BelongsToMany;
 24: use RuntimeException;
 25: 
 26: /**
 27:  * Contains logic to convert array data into entities.
 28:  *
 29:  * Useful when converting request data into entities.
 30:  *
 31:  * @see \Cake\ORM\Table::newEntity()
 32:  * @see \Cake\ORM\Table::newEntities()
 33:  * @see \Cake\ORM\Table::patchEntity()
 34:  * @see \Cake\ORM\Table::patchEntities()
 35:  */
 36: class Marshaller
 37: {
 38: 
 39:     use AssociationsNormalizerTrait;
 40: 
 41:     /**
 42:      * The table instance this marshaller is for.
 43:      *
 44:      * @var \Cake\ORM\Table
 45:      */
 46:     protected $_table;
 47: 
 48:     /**
 49:      * Constructor.
 50:      *
 51:      * @param \Cake\ORM\Table $table The table this marshaller is for.
 52:      */
 53:     public function __construct(Table $table)
 54:     {
 55:         $this->_table = $table;
 56:     }
 57: 
 58:     /**
 59:      * Build the map of property => marshalling callable.
 60:      *
 61:      * @param array $data The data being marshalled.
 62:      * @param array $options List of options containing the 'associated' key.
 63:      * @throws \InvalidArgumentException When associations do not exist.
 64:      * @return array
 65:      */
 66:     protected function _buildPropertyMap($data, $options)
 67:     {
 68:         $map = [];
 69:         $schema = $this->_table->getSchema();
 70: 
 71:         // Is a concrete column?
 72:         foreach (array_keys($data) as $prop) {
 73:             $columnType = $schema->getColumnType($prop);
 74:             if ($columnType) {
 75:                 $map[$prop] = function ($value, $entity) use ($columnType) {
 76:                     return Type::build($columnType)->marshal($value);
 77:                 };
 78:             }
 79:         }
 80: 
 81:         // Map associations
 82:         if (!isset($options['associated'])) {
 83:             $options['associated'] = [];
 84:         }
 85:         $include = $this->_normalizeAssociations($options['associated']);
 86:         foreach ($include as $key => $nested) {
 87:             if (is_int($key) && is_scalar($nested)) {
 88:                 $key = $nested;
 89:                 $nested = [];
 90:             }
 91:             // If the key is not a special field like _ids or _joinData
 92:             // it is a missing association that we should error on.
 93:             if (!$this->_table->hasAssociation($key)) {
 94:                 if (substr($key, 0, 1) !== '_') {
 95:                     throw new \InvalidArgumentException(sprintf(
 96:                         'Cannot marshal data for "%s" association. It is not associated with "%s".',
 97:                         $key,
 98:                         $this->_table->getAlias()
 99:                     ));
100:                 }
101:                 continue;
102:             }
103:             $assoc = $this->_table->getAssociation($key);
104: 
105:             if (isset($options['forceNew'])) {
106:                 $nested['forceNew'] = $options['forceNew'];
107:             }
108:             if (isset($options['isMerge'])) {
109:                 $callback = function ($value, $entity) use ($assoc, $nested) {
110:                     /** @var \Cake\Datasource\EntityInterface $entity */
111:                     $options = $nested + ['associated' => [], 'association' => $assoc];
112: 
113:                     return $this->_mergeAssociation($entity->get($assoc->getProperty()), $assoc, $value, $options);
114:                 };
115:             } else {
116:                 $callback = function ($value, $entity) use ($assoc, $nested) {
117:                     $options = $nested + ['associated' => []];
118: 
119:                     return $this->_marshalAssociation($assoc, $value, $options);
120:                 };
121:             }
122:             $map[$assoc->getProperty()] = $callback;
123:         }
124: 
125:         $behaviors = $this->_table->behaviors();
126:         foreach ($behaviors->loaded() as $name) {
127:             $behavior = $behaviors->get($name);
128:             if ($behavior instanceof PropertyMarshalInterface) {
129:                 $map += $behavior->buildMarshalMap($this, $map, $options);
130:             }
131:         }
132: 
133:         return $map;
134:     }
135: 
136:     /**
137:      * Hydrate one entity and its associated data.
138:      *
139:      * ### Options:
140:      *
141:      * - validate: Set to false to disable validation. Can also be a string of the validator ruleset to be applied.
142:      *   Defaults to true/default.
143:      * - associated: Associations listed here will be marshalled as well. Defaults to null.
144:      * - fieldList: (deprecated) Since 3.4.0. Use fields instead.
145:      * - fields: A whitelist of fields to be assigned to the entity. If not present,
146:      *   the accessible fields list in the entity will be used. Defaults to null.
147:      * - accessibleFields: A list of fields to allow or deny in entity accessible fields. Defaults to null
148:      * - forceNew: When enabled, belongsToMany associations will have 'new' entities created
149:      *   when primary key values are set, and a record does not already exist. Normally primary key
150:      *   on missing entities would be ignored. Defaults to false.
151:      *
152:      * The above options can be used in each nested `associated` array. In addition to the above
153:      * options you can also use the `onlyIds` option for HasMany and BelongsToMany associations.
154:      * When true this option restricts the request data to only be read from `_ids`.
155:      *
156:      * ```
157:      * $result = $marshaller->one($data, [
158:      *   'associated' => ['Tags' => ['onlyIds' => true]]
159:      * ]);
160:      * ```
161:      *
162:      * @param array $data The data to hydrate.
163:      * @param array $options List of options
164:      * @return \Cake\Datasource\EntityInterface
165:      * @see \Cake\ORM\Table::newEntity()
166:      * @see \Cake\ORM\Entity::$_accessible
167:      */
168:     public function one(array $data, array $options = [])
169:     {
170:         list($data, $options) = $this->_prepareDataAndOptions($data, $options);
171: 
172:         $primaryKey = (array)$this->_table->getPrimaryKey();
173:         $entityClass = $this->_table->getEntityClass();
174:         /** @var \Cake\Datasource\EntityInterface $entity */
175:         $entity = new $entityClass();
176:         $entity->setSource($this->_table->getRegistryAlias());
177: 
178:         if (isset($options['accessibleFields'])) {
179:             foreach ((array)$options['accessibleFields'] as $key => $value) {
180:                 $entity->setAccess($key, $value);
181:             }
182:         }
183:         $errors = $this->_validate($data, $options, true);
184: 
185:         $options['isMerge'] = false;
186:         $propertyMap = $this->_buildPropertyMap($data, $options);
187:         $properties = [];
188:         foreach ($data as $key => $value) {
189:             if (!empty($errors[$key])) {
190:                 if ($entity instanceof InvalidPropertyInterface) {
191:                     $entity->setInvalidField($key, $value);
192:                 }
193:                 continue;
194:             }
195: 
196:             if ($value === '' && in_array($key, $primaryKey, true)) {
197:                 // Skip marshalling '' for pk fields.
198:                 continue;
199:             }
200:             if (isset($propertyMap[$key])) {
201:                 $properties[$key] = $propertyMap[$key]($value, $entity);
202:             } else {
203:                 $properties[$key] = $value;
204:             }
205:         }
206: 
207:         if (isset($options['fields'])) {
208:             foreach ((array)$options['fields'] as $field) {
209:                 if (array_key_exists($field, $properties)) {
210:                     $entity->set($field, $properties[$field]);
211:                 }
212:             }
213:         } else {
214:             $entity->set($properties);
215:         }
216: 
217:         // Don't flag clean association entities as
218:         // dirty so we don't persist empty records.
219:         foreach ($properties as $field => $value) {
220:             if ($value instanceof EntityInterface) {
221:                 $entity->setDirty($field, $value->isDirty());
222:             }
223:         }
224: 
225:         $entity->setErrors($errors);
226: 
227:         return $entity;
228:     }
229: 
230:     /**
231:      * Returns the validation errors for a data set based on the passed options
232:      *
233:      * @param array $data The data to validate.
234:      * @param array $options The options passed to this marshaller.
235:      * @param bool $isNew Whether it is a new entity or one to be updated.
236:      * @return array The list of validation errors.
237:      * @throws \RuntimeException If no validator can be created.
238:      */
239:     protected function _validate($data, $options, $isNew)
240:     {
241:         if (!$options['validate']) {
242:             return [];
243:         }
244: 
245:         $validator = null;
246:         if ($options['validate'] === true) {
247:             $validator = $this->_table->getValidator();
248:         } elseif (is_string($options['validate'])) {
249:             $validator = $this->_table->getValidator($options['validate']);
250:         } elseif (is_object($options['validate'])) {
251:             /** @var \Cake\Validation\Validator $validator */
252:             $validator = $options['validate'];
253:         }
254: 
255:         if ($validator === null) {
256:             throw new RuntimeException(
257:                 sprintf('validate must be a boolean, a string or an object. Got %s.', getTypeName($options['validate']))
258:             );
259:         }
260: 
261:         return $validator->errors($data, $isNew);
262:     }
263: 
264:     /**
265:      * Returns data and options prepared to validate and marshall.
266:      *
267:      * @param array $data The data to prepare.
268:      * @param array $options The options passed to this marshaller.
269:      * @return array An array containing prepared data and options.
270:      */
271:     protected function _prepareDataAndOptions($data, $options)
272:     {
273:         $options += ['validate' => true];
274: 
275:         if (!isset($options['fields']) && isset($options['fieldList'])) {
276:             deprecationWarning(
277:                 'The `fieldList` option for marshalling is deprecated. Use the `fields` option instead.'
278:             );
279:             $options['fields'] = $options['fieldList'];
280:             unset($options['fieldList']);
281:         }
282: 
283:         $tableName = $this->_table->getAlias();
284:         if (isset($data[$tableName])) {
285:             $data += $data[$tableName];
286:             unset($data[$tableName]);
287:         }
288: 
289:         $data = new ArrayObject($data);
290:         $options = new ArrayObject($options);
291:         $this->_table->dispatchEvent('Model.beforeMarshal', compact('data', 'options'));
292: 
293:         return [(array)$data, (array)$options];
294:     }
295: 
296:     /**
297:      * Create a new sub-marshaller and marshal the associated data.
298:      *
299:      * @param \Cake\ORM\Association $assoc The association to marshall
300:      * @param array $value The data to hydrate
301:      * @param array $options List of options.
302:      * @return \Cake\Datasource\EntityInterface|\Cake\Datasource\EntityInterface[]|null
303:      */
304:     protected function _marshalAssociation($assoc, $value, $options)
305:     {
306:         if (!is_array($value)) {
307:             return null;
308:         }
309:         $targetTable = $assoc->getTarget();
310:         $marshaller = $targetTable->marshaller();
311:         $types = [Association::ONE_TO_ONE, Association::MANY_TO_ONE];
312:         if (in_array($assoc->type(), $types)) {
313:             return $marshaller->one($value, (array)$options);
314:         }
315:         if ($assoc->type() === Association::ONE_TO_MANY || $assoc->type() === Association::MANY_TO_MANY) {
316:             $hasIds = array_key_exists('_ids', $value);
317:             $onlyIds = array_key_exists('onlyIds', $options) && $options['onlyIds'];
318: 
319:             if ($hasIds && is_array($value['_ids'])) {
320:                 return $this->_loadAssociatedByIds($assoc, $value['_ids']);
321:             }
322:             if ($hasIds || $onlyIds) {
323:                 return [];
324:             }
325:         }
326:         if ($assoc->type() === Association::MANY_TO_MANY) {
327:             return $marshaller->_belongsToMany($assoc, $value, (array)$options);
328:         }
329: 
330:         return $marshaller->many($value, (array)$options);
331:     }
332: 
333:     /**
334:      * Hydrate many entities and their associated data.
335:      *
336:      * ### Options:
337:      *
338:      * - validate: Set to false to disable validation. Can also be a string of the validator ruleset to be applied.
339:      *   Defaults to true/default.
340:      * - associated: Associations listed here will be marshalled as well. Defaults to null.
341:      * - fieldList: (deprecated) Since 3.4.0. Use fields instead
342:      * - fields: A whitelist of fields to be assigned to the entity. If not present,
343:      *   the accessible fields list in the entity will be used. Defaults to null.
344:      * - accessibleFields: A list of fields to allow or deny in entity accessible fields. Defaults to null
345:      * - forceNew: When enabled, belongsToMany associations will have 'new' entities created
346:      *   when primary key values are set, and a record does not already exist. Normally primary key
347:      *   on missing entities would be ignored. Defaults to false.
348:      *
349:      * @param array $data The data to hydrate.
350:      * @param array $options List of options
351:      * @return \Cake\Datasource\EntityInterface[] An array of hydrated records.
352:      * @see \Cake\ORM\Table::newEntities()
353:      * @see \Cake\ORM\Entity::$_accessible
354:      */
355:     public function many(array $data, array $options = [])
356:     {
357:         $output = [];
358:         foreach ($data as $record) {
359:             if (!is_array($record)) {
360:                 continue;
361:             }
362:             $output[] = $this->one($record, $options);
363:         }
364: 
365:         return $output;
366:     }
367: 
368:     /**
369:      * Marshals data for belongsToMany associations.
370:      *
371:      * Builds the related entities and handles the special casing
372:      * for junction table entities.
373:      *
374:      * @param \Cake\ORM\Association\BelongsToMany $assoc The association to marshal.
375:      * @param array $data The data to convert into entities.
376:      * @param array $options List of options.
377:      * @return \Cake\Datasource\EntityInterface[] An array of built entities.
378:      * @throws \BadMethodCallException
379:      * @throws \InvalidArgumentException
380:      * @throws \RuntimeException
381:      */
382:     protected function _belongsToMany(BelongsToMany $assoc, array $data, $options = [])
383:     {
384:         $associated = isset($options['associated']) ? $options['associated'] : [];
385:         $forceNew = isset($options['forceNew']) ? $options['forceNew'] : false;
386: 
387:         $data = array_values($data);
388: 
389:         $target = $assoc->getTarget();
390:         $primaryKey = array_flip((array)$target->getPrimaryKey());
391:         $records = $conditions = [];
392:         $primaryCount = count($primaryKey);
393:         $conditions = [];
394: 
395:         foreach ($data as $i => $row) {
396:             if (!is_array($row)) {
397:                 continue;
398:             }
399:             if (array_intersect_key($primaryKey, $row) === $primaryKey) {
400:                 $keys = array_intersect_key($row, $primaryKey);
401:                 if (count($keys) === $primaryCount) {
402:                     $rowConditions = [];
403:                     foreach ($keys as $key => $value) {
404:                         $rowConditions[][$target->aliasField($key)] = $value;
405:                     }
406: 
407:                     if ($forceNew && !$target->exists($rowConditions)) {
408:                         $records[$i] = $this->one($row, $options);
409:                     }
410: 
411:                     $conditions = array_merge($conditions, $rowConditions);
412:                 }
413:             } else {
414:                 $records[$i] = $this->one($row, $options);
415:             }
416:         }
417: 
418:         if (!empty($conditions)) {
419:             $query = $target->find();
420:             $query->andWhere(function ($exp) use ($conditions) {
421:                 /** @var \Cake\Database\Expression\QueryExpression $exp */
422:                 return $exp->or_($conditions);
423:             });
424: 
425:             $keyFields = array_keys($primaryKey);
426: 
427:             $existing = [];
428:             foreach ($query as $row) {
429:                 $k = implode(';', $row->extract($keyFields));
430:                 $existing[$k] = $row;
431:             }
432: 
433:             foreach ($data as $i => $row) {
434:                 $key = [];
435:                 foreach ($keyFields as $k) {
436:                     if (isset($row[$k])) {
437:                         $key[] = $row[$k];
438:                     }
439:                 }
440:                 $key = implode(';', $key);
441: 
442:                 // Update existing record and child associations
443:                 if (isset($existing[$key])) {
444:                     $records[$i] = $this->merge($existing[$key], $data[$i], $options);
445:                 }
446:             }
447:         }
448: 
449:         $jointMarshaller = $assoc->junction()->marshaller();
450: 
451:         $nested = [];
452:         if (isset($associated['_joinData'])) {
453:             $nested = (array)$associated['_joinData'];
454:         }
455: 
456:         foreach ($records as $i => $record) {
457:             // Update junction table data in _joinData.
458:             if (isset($data[$i]['_joinData'])) {
459:                 $joinData = $jointMarshaller->one($data[$i]['_joinData'], $nested);
460:                 $record->set('_joinData', $joinData);
461:             }
462:         }
463: 
464:         return $records;
465:     }
466: 
467:     /**
468:      * Loads a list of belongs to many from ids.
469:      *
470:      * @param \Cake\ORM\Association $assoc The association class for the belongsToMany association.
471:      * @param array $ids The list of ids to load.
472:      * @return \Cake\Datasource\EntityInterface[] An array of entities.
473:      */
474:     protected function _loadAssociatedByIds($assoc, $ids)
475:     {
476:         if (empty($ids)) {
477:             return [];
478:         }
479: 
480:         $target = $assoc->getTarget();
481:         $primaryKey = (array)$target->getPrimaryKey();
482:         $multi = count($primaryKey) > 1;
483:         $primaryKey = array_map([$target, 'aliasField'], $primaryKey);
484: 
485:         if ($multi) {
486:             $first = current($ids);
487:             if (!is_array($first) || count($first) !== count($primaryKey)) {
488:                 return [];
489:             }
490:             $filter = new TupleComparison($primaryKey, $ids, [], 'IN');
491:         } else {
492:             $filter = [$primaryKey[0] . ' IN' => $ids];
493:         }
494: 
495:         return $target->find()->where($filter)->toArray();
496:     }
497: 
498:     /**
499:      * Loads a list of belongs to many from ids.
500:      *
501:      * @param \Cake\ORM\Association $assoc The association class for the belongsToMany association.
502:      * @param array $ids The list of ids to load.
503:      * @return \Cake\Datasource\EntityInterface[] An array of entities.
504:      * @deprecated Use _loadAssociatedByIds()
505:      */
506:     protected function _loadBelongsToMany($assoc, $ids)
507:     {
508:         deprecationWarning(
509:             'Marshaller::_loadBelongsToMany() is deprecated. Use _loadAssociatedByIds() instead.'
510:         );
511: 
512:         return $this->_loadAssociatedByIds($assoc, $ids);
513:     }
514: 
515:     /**
516:      * Merges `$data` into `$entity` and recursively does the same for each one of
517:      * the association names passed in `$options`. When merging associations, if an
518:      * entity is not present in the parent entity for a given association, a new one
519:      * will be created.
520:      *
521:      * When merging HasMany or BelongsToMany associations, all the entities in the
522:      * `$data` array will appear, those that can be matched by primary key will get
523:      * the data merged, but those that cannot, will be discarded. `ids` option can be used
524:      * to determine whether the association must use the `_ids` format.
525:      *
526:      * ### Options:
527:      *
528:      * - associated: Associations listed here will be marshalled as well.
529:      * - validate: Whether or not to validate data before hydrating the entities. Can
530:      *   also be set to a string to use a specific validator. Defaults to true/default.
531:      * - fieldList: (deprecated) Since 3.4.0. Use fields instead
532:      * - fields: A whitelist of fields to be assigned to the entity. If not present
533:      *   the accessible fields list in the entity will be used.
534:      * - accessibleFields: A list of fields to allow or deny in entity accessible fields.
535:      *
536:      * The above options can be used in each nested `associated` array. In addition to the above
537:      * options you can also use the `onlyIds` option for HasMany and BelongsToMany associations.
538:      * When true this option restricts the request data to only be read from `_ids`.
539:      *
540:      * ```
541:      * $result = $marshaller->merge($entity, $data, [
542:      *   'associated' => ['Tags' => ['onlyIds' => true]]
543:      * ]);
544:      * ```
545:      *
546:      * @param \Cake\Datasource\EntityInterface $entity the entity that will get the
547:      * data merged in
548:      * @param array $data key value list of fields to be merged into the entity
549:      * @param array $options List of options.
550:      * @return \Cake\Datasource\EntityInterface
551:      * @see \Cake\ORM\Entity::$_accessible
552:      */
553:     public function merge(EntityInterface $entity, array $data, array $options = [])
554:     {
555:         list($data, $options) = $this->_prepareDataAndOptions($data, $options);
556: 
557:         $isNew = $entity->isNew();
558:         $keys = [];
559: 
560:         if (!$isNew) {
561:             $keys = $entity->extract((array)$this->_table->getPrimaryKey());
562:         }
563: 
564:         if (isset($options['accessibleFields'])) {
565:             foreach ((array)$options['accessibleFields'] as $key => $value) {
566:                 $entity->setAccess($key, $value);
567:             }
568:         }
569: 
570:         $errors = $this->_validate($data + $keys, $options, $isNew);
571:         $options['isMerge'] = true;
572:         $propertyMap = $this->_buildPropertyMap($data, $options);
573:         $properties = [];
574:         foreach ($data as $key => $value) {
575:             if (!empty($errors[$key])) {
576:                 if ($entity instanceof InvalidPropertyInterface) {
577:                     $entity->setInvalidField($key, $value);
578:                 }
579:                 continue;
580:             }
581:             $original = $entity->get($key);
582: 
583:             if (isset($propertyMap[$key])) {
584:                 $value = $propertyMap[$key]($value, $entity);
585: 
586:                 // Don't dirty scalar values and objects that didn't
587:                 // change. Arrays will always be marked as dirty because
588:                 // the original/updated list could contain references to the
589:                 // same objects, even though those objects may have changed internally.
590:                 if ((is_scalar($value) && $original === $value) ||
591:                     ($value === null && $original === $value) ||
592:                     (is_object($value) && !($value instanceof EntityInterface) && $original == $value)
593:                 ) {
594:                     continue;
595:                 }
596:             }
597:             $properties[$key] = $value;
598:         }
599: 
600:         $entity->setErrors($errors);
601:         if (!isset($options['fields'])) {
602:             $entity->set($properties);
603: 
604:             foreach ($properties as $field => $value) {
605:                 if ($value instanceof EntityInterface) {
606:                     $entity->setDirty($field, $value->isDirty());
607:                 }
608:             }
609: 
610:             return $entity;
611:         }
612: 
613:         foreach ((array)$options['fields'] as $field) {
614:             if (!array_key_exists($field, $properties)) {
615:                 continue;
616:             }
617:             $entity->set($field, $properties[$field]);
618:             if ($properties[$field] instanceof EntityInterface) {
619:                 $entity->setDirty($field, $properties[$field]->isDirty());
620:             }
621:         }
622: 
623:         return $entity;
624:     }
625: 
626:     /**
627:      * Merges each of the elements from `$data` into each of the entities in `$entities`
628:      * and recursively does the same for each of the association names passed in
629:      * `$options`. When merging associations, if an entity is not present in the parent
630:      * entity for a given association, a new one will be created.
631:      *
632:      * Records in `$data` are matched against the entities using the primary key
633:      * column. Entries in `$entities` that cannot be matched to any record in
634:      * `$data` will be discarded. Records in `$data` that could not be matched will
635:      * be marshalled as a new entity.
636:      *
637:      * When merging HasMany or BelongsToMany associations, all the entities in the
638:      * `$data` array will appear, those that can be matched by primary key will get
639:      * the data merged, but those that cannot, will be discarded.
640:      *
641:      * ### Options:
642:      *
643:      * - validate: Whether or not to validate data before hydrating the entities. Can
644:      *   also be set to a string to use a specific validator. Defaults to true/default.
645:      * - associated: Associations listed here will be marshalled as well.
646:      * - fieldList: (deprecated) Since 3.4.0. Use fields instead
647:      * - fields: A whitelist of fields to be assigned to the entity. If not present,
648:      *   the accessible fields list in the entity will be used.
649:      * - accessibleFields: A list of fields to allow or deny in entity accessible fields.
650:      *
651:      * @param \Cake\Datasource\EntityInterface[]|\Traversable $entities the entities that will get the
652:      *   data merged in
653:      * @param array $data list of arrays to be merged into the entities
654:      * @param array $options List of options.
655:      * @return \Cake\Datasource\EntityInterface[]
656:      * @see \Cake\ORM\Entity::$_accessible
657:      */
658:     public function mergeMany($entities, array $data, array $options = [])
659:     {
660:         $primary = (array)$this->_table->getPrimaryKey();
661: 
662:         $indexed = (new Collection($data))
663:             ->groupBy(function ($el) use ($primary) {
664:                 $keys = [];
665:                 foreach ($primary as $key) {
666:                     $keys[] = isset($el[$key]) ? $el[$key] : '';
667:                 }
668: 
669:                 return implode(';', $keys);
670:             })
671:             ->map(function ($element, $key) {
672:                 return $key === '' ? $element : $element[0];
673:             })
674:             ->toArray();
675: 
676:         $new = isset($indexed[null]) ? $indexed[null] : [];
677:         unset($indexed[null]);
678:         $output = [];
679: 
680:         foreach ($entities as $entity) {
681:             if (!($entity instanceof EntityInterface)) {
682:                 continue;
683:             }
684: 
685:             $key = implode(';', $entity->extract($primary));
686:             if ($key === null || !isset($indexed[$key])) {
687:                 continue;
688:             }
689: 
690:             $output[] = $this->merge($entity, $indexed[$key], $options);
691:             unset($indexed[$key]);
692:         }
693: 
694:         $conditions = (new Collection($indexed))
695:             ->map(function ($data, $key) {
696:                 return explode(';', $key);
697:             })
698:             ->filter(function ($keys) use ($primary) {
699:                 return count(array_filter($keys, 'strlen')) === count($primary);
700:             })
701:             ->reduce(function ($conditions, $keys) use ($primary) {
702:                 $fields = array_map([$this->_table, 'aliasField'], $primary);
703:                 $conditions['OR'][] = array_combine($fields, $keys);
704: 
705:                 return $conditions;
706:             }, ['OR' => []]);
707:         $maybeExistentQuery = $this->_table->find()->where($conditions);
708: 
709:         if (!empty($indexed) && count($maybeExistentQuery->clause('where'))) {
710:             foreach ($maybeExistentQuery as $entity) {
711:                 $key = implode(';', $entity->extract($primary));
712:                 if (isset($indexed[$key])) {
713:                     $output[] = $this->merge($entity, $indexed[$key], $options);
714:                     unset($indexed[$key]);
715:                 }
716:             }
717:         }
718: 
719:         foreach ((new Collection($indexed))->append($new) as $value) {
720:             if (!is_array($value)) {
721:                 continue;
722:             }
723:             $output[] = $this->one($value, $options);
724:         }
725: 
726:         return $output;
727:     }
728: 
729:     /**
730:      * Creates a new sub-marshaller and merges the associated data.
731:      *
732:      * @param \Cake\Datasource\EntityInterface|\Cake\Datasource\EntityInterface[] $original The original entity
733:      * @param \Cake\ORM\Association $assoc The association to merge
734:      * @param array $value The data to hydrate
735:      * @param array $options List of options.
736:      * @return \Cake\Datasource\EntityInterface|\Cake\Datasource\EntityInterface[]|null
737:      */
738:     protected function _mergeAssociation($original, $assoc, $value, $options)
739:     {
740:         if (!$original) {
741:             return $this->_marshalAssociation($assoc, $value, $options);
742:         }
743:         if (!is_array($value)) {
744:             return null;
745:         }
746: 
747:         $targetTable = $assoc->getTarget();
748:         $marshaller = $targetTable->marshaller();
749:         $types = [Association::ONE_TO_ONE, Association::MANY_TO_ONE];
750:         if (in_array($assoc->type(), $types)) {
751:             return $marshaller->merge($original, $value, (array)$options);
752:         }
753:         if ($assoc->type() === Association::MANY_TO_MANY) {
754:             return $marshaller->_mergeBelongsToMany($original, $assoc, $value, (array)$options);
755:         }
756: 
757:         if ($assoc->type() === Association::ONE_TO_MANY) {
758:             $hasIds = array_key_exists('_ids', $value);
759:             $onlyIds = array_key_exists('onlyIds', $options) && $options['onlyIds'];
760:             if ($hasIds && is_array($value['_ids'])) {
761:                 return $this->_loadAssociatedByIds($assoc, $value['_ids']);
762:             }
763:             if ($hasIds || $onlyIds) {
764:                 return [];
765:             }
766:         }
767: 
768:         return $marshaller->mergeMany($original, $value, (array)$options);
769:     }
770: 
771:     /**
772:      * Creates a new sub-marshaller and merges the associated data for a BelongstoMany
773:      * association.
774:      *
775:      * @param \Cake\Datasource\EntityInterface $original The original entity
776:      * @param \Cake\ORM\Association $assoc The association to marshall
777:      * @param array $value The data to hydrate
778:      * @param array $options List of options.
779:      * @return \Cake\Datasource\EntityInterface[]
780:      */
781:     protected function _mergeBelongsToMany($original, $assoc, $value, $options)
782:     {
783:         $associated = isset($options['associated']) ? $options['associated'] : [];
784: 
785:         $hasIds = array_key_exists('_ids', $value);
786:         $onlyIds = array_key_exists('onlyIds', $options) && $options['onlyIds'];
787: 
788:         if ($hasIds && is_array($value['_ids'])) {
789:             return $this->_loadAssociatedByIds($assoc, $value['_ids']);
790:         }
791:         if ($hasIds || $onlyIds) {
792:             return [];
793:         }
794: 
795:         if (!empty($associated) && !in_array('_joinData', $associated) && !isset($associated['_joinData'])) {
796:             return $this->mergeMany($original, $value, $options);
797:         }
798: 
799:         return $this->_mergeJoinData($original, $assoc, $value, $options);
800:     }
801: 
802:     /**
803:      * Merge the special _joinData property into the entity set.
804:      *
805:      * @param \Cake\Datasource\EntityInterface $original The original entity
806:      * @param \Cake\ORM\Association\BelongsToMany $assoc The association to marshall
807:      * @param array $value The data to hydrate
808:      * @param array $options List of options.
809:      * @return \Cake\Datasource\EntityInterface[] An array of entities
810:      */
811:     protected function _mergeJoinData($original, $assoc, $value, $options)
812:     {
813:         $associated = isset($options['associated']) ? $options['associated'] : [];
814:         $extra = [];
815:         foreach ($original as $entity) {
816:             // Mark joinData as accessible so we can marshal it properly.
817:             $entity->setAccess('_joinData', true);
818: 
819:             $joinData = $entity->get('_joinData');
820:             if ($joinData && $joinData instanceof EntityInterface) {
821:                 $extra[spl_object_hash($entity)] = $joinData;
822:             }
823:         }
824: 
825:         $joint = $assoc->junction();
826:         $marshaller = $joint->marshaller();
827: 
828:         $nested = [];
829:         if (isset($associated['_joinData'])) {
830:             $nested = (array)$associated['_joinData'];
831:         }
832: 
833:         $options['accessibleFields'] = ['_joinData' => true];
834: 
835:         $records = $this->mergeMany($original, $value, $options);
836:         foreach ($records as $record) {
837:             $hash = spl_object_hash($record);
838:             $value = $record->get('_joinData');
839: 
840:             // Already an entity, no further marshalling required.
841:             if ($value instanceof EntityInterface) {
842:                 continue;
843:             }
844: 
845:             // Scalar data can't be handled
846:             if (!is_array($value)) {
847:                 $record->unsetProperty('_joinData');
848:                 continue;
849:             }
850: 
851:             // Marshal data into the old object, or make a new joinData object.
852:             if (isset($extra[$hash])) {
853:                 $record->set('_joinData', $marshaller->merge($extra[$hash], $value, $nested));
854:             } elseif (is_array($value)) {
855:                 $joinData = $marshaller->one($value, $nested);
856:                 $record->set('_joinData', $joinData);
857:             }
858:         }
859: 
860:         return $records;
861:     }
862: }
863: 
Follow @CakePHP
#IRC
OpenHub
Rackspace
  • Business Solutions
  • Showcase
  • Documentation
  • Book
  • API
  • Videos
  • Logos & Trademarks
  • Community
  • Team
  • Issues (Github)
  • YouTube Channel
  • Get Involved
  • Bakery
  • Featured Resources
  • Newsletter
  • Certification
  • My CakePHP
  • CakeFest
  • Facebook
  • Twitter
  • Help & Support
  • Forum
  • Stack Overflow
  • IRC
  • Slack
  • Paid Support

Generated using CakePHP API Docs