Пример #1
0
 /**
  * Performs the existence check
  *
  * @param \Cake\Datasource\EntityInterface $entity The entity from where to extract the fields
  * @param array $options Options passed to the check,
  * where the `repository` key is required.
  * @return bool
  */
 public function __invoke(EntityInterface $entity, array $options)
 {
     if (is_string($this->_repository)) {
         $this->_repository = $options['repository']->association($this->_repository);
     }
     $source = !empty($options['repository']) ? $options['repository'] : $this->_repository;
     $source = $source instanceof Association ? $source->source() : $source;
     $target = $this->_repository instanceof Association ? $this->_repository->target() : $this->_repository;
     if (!empty($options['_sourceTable']) && $target === $options['_sourceTable']) {
         return true;
     }
     if (!$entity->extract($this->_fields, true)) {
         return true;
     }
     $nulls = 0;
     $schema = $source->schema();
     foreach ($this->_fields as $field) {
         if ($schema->column($field) && $schema->isNullable($field) && $entity->get($field) === null) {
             $nulls++;
         }
     }
     if ($nulls === count($this->_fields)) {
         return true;
     }
     $primary = array_map([$this->_repository, 'aliasField'], (array) $this->_repository->primaryKey());
     $conditions = array_combine($primary, $entity->extract($this->_fields));
     return $this->_repository->exists($conditions);
 }
Пример #2
0
 /**
  * Performs the uniqueness check
  *
  * @param \Cake\Datasource\EntityInterface $entity The entity from where to extract the fields
  * @param array $options Options passed to the check,
  * where the `repository` key is required.
  * @return bool
  */
 public function __invoke(EntityInterface $entity, array $options)
 {
     if (!$entity->extract($this->_fields, true)) {
         return true;
     }
     $conditions = $entity->extract($this->_fields);
     if ($entity->isNew() === false) {
         $keys = (array) $options['repository']->primaryKey();
         $keys = $entity->extract($keys);
         if (array_filter($keys, 'strlen')) {
             $conditions['NOT'] = $keys;
         }
     }
     return !$options['repository']->exists($conditions);
 }
Пример #3
0
 /**
  * Performs the existence check
  *
  * @param \Cake\Datasource\EntityInterface $entity The entity from where to extract the fields
  * @param array $options Options passed to the check,
  * where the `repository` key is required.
  * @throws \RuntimeException When the rule refers to an undefined association.
  * @return bool
  */
 public function __invoke(EntityInterface $entity, array $options)
 {
     if (is_string($this->_repository)) {
         $repository = $options['repository']->association($this->_repository);
         if (!$repository) {
             throw new RuntimeException(sprintf("ExistsIn rule for '%s' is invalid. The '%s' association is not defined.", implode(', ', $this->_fields), $this->_repository));
         }
         $this->_repository = $repository;
     }
     $source = $target = $this->_repository;
     if (!empty($options['repository'])) {
         $source = $options['repository'];
     }
     if ($source instanceof Association) {
         $source = $source->source();
     }
     if ($target instanceof Association) {
         $bindingKey = (array) $target->bindingKey();
         $target = $target->target();
     } else {
         $bindingKey = (array) $target->primaryKey();
     }
     if (!empty($options['_sourceTable']) && $target === $options['_sourceTable']) {
         return true;
     }
     if (!$entity->extract($this->_fields, true)) {
         return true;
     }
     if ($this->_fieldsAreNull($entity, $source)) {
         return true;
     }
     $primary = array_map([$target, 'aliasField'], $bindingKey);
     $conditions = array_combine($primary, $entity->extract($this->_fields));
     return $target->exists($conditions);
 }
Пример #4
0
 /**
  * Performs the existence check
  *
  * @param \Cake\Datasource\EntityInterface $entity The entity from where to extract the fields
  * @param array $options Options passed to the check,
  * where the `repository` key is required.
  * @return bool
  */
 public function __invoke(EntityInterface $entity, array $options)
 {
     if (is_string($this->_repository)) {
         $this->_repository = $options['repository']->association($this->_repository);
     }
     if (!empty($options['_sourceTable'])) {
         $source = $this->_repository instanceof Association ? $this->_repository->target() : $this->_repository;
         if ($source === $options['_sourceTable']) {
             return true;
         }
     }
     if (!$entity->extract($this->_fields, true)) {
         return true;
     }
     $conditions = array_combine((array) $this->_repository->primaryKey(), $entity->extract($this->_fields));
     return $this->_repository->exists($conditions);
 }
 /**
  * beforeSave callback
  *
  * @param Event $event CakePHP Event
  * @param Entity $entity Entity to be saved
  * @param ArrayObject $options Additional options
  * @return void
  */
 public function beforeSave(Event $event, EntityInterface $entity, \ArrayObject $options)
 {
     if (!$entity->isNew() && $entity->dirty()) {
         $fields = array_keys($entity->toArray());
         $dirtyFields = $entity->extract($fields, true);
         unset($dirtyFields['modified']);
         $this->_dirtyFields[$entity->id] = array_keys($dirtyFields);
     }
 }
 /**
  * Save also related model data
  *
  * @param \Cake\Event\Event
  * @param \Cake\ORM\Entity;
  * @return void
  */
 public function beforeSave(Event $event, EntityInterface $entity, \ArrayObject $options)
 {
     $relatedEntities = [];
     foreach ($this->config('fields') as $field => $mapped) {
         list($mappedTable, $mappedField) = explode('.', $mapped);
         if (!isset($this->_table->{$mappedTable}) || $this->_table->{$mappedTable}->isOwningSide($this->_table)) {
             throw new Exception(sprintf('Incorrect definition of related data to persist for %s', $mapped));
         }
         $foreignKeys = $entity->extract((array) $this->_table->{$mappedTable}->foreignKey());
         $dirtyForeignKeys = $entity->extract((array) $this->_table->{$mappedTable}->foreignKey(), true);
         if (!empty($dirtyForeignKeys)) {
             // get related entity
             if (empty($relatedEntities[$mappedTable])) {
                 $relatedEntities[$mappedTable] = $this->_table->{$mappedTable}->get($foreignKeys);
             }
             // set field value
             $entity->set($field, $relatedEntities[$mappedTable]->get($mappedField));
         }
     }
 }
Пример #7
0
 /**
  * Cascade a delete to remove dependent records.
  *
  * This method does nothing if the association is not dependent.
  *
  * @param \Cake\Datasource\EntityInterface $entity The entity that started the cascaded delete.
  * @param array $options The options for the original delete.
  * @return bool Success.
  */
 public function cascadeDelete(EntityInterface $entity, array $options = [])
 {
     if (!$this->dependent()) {
         return true;
     }
     $table = $this->target();
     $foreignKey = (array) $this->foreignKey();
     $bindingKey = (array) $this->bindingKey();
     $conditions = array_combine($foreignKey, $entity->extract($bindingKey));
     if ($this->_cascadeCallbacks) {
         foreach ($this->find()->where($conditions)->toList() as $related) {
             $table->delete($related, $options);
         }
         return true;
     }
     $conditions = array_merge($conditions, $this->conditions());
     return $table->deleteAll($conditions);
 }
Пример #8
0
 /**
  * Takes an entity from the source table and looks if there is a field
  * matching the property name for this association. The found entity will be
  * saved on the target table for this association by passing supplied
  * `$options`
  *
  * @param \Cake\Datasource\EntityInterface $entity an entity from the source table
  * @param array|\ArrayObject $options options to be passed to the save method in
  * the target table
  * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns
  * the saved entity
  * @see Table::save()
  * @throws \InvalidArgumentException when the association data cannot be traversed.
  */
 public function saveAssociated(EntityInterface $entity, array $options = [])
 {
     $targetEntities = $entity->get($this->property());
     if (empty($targetEntities)) {
         return $entity;
     }
     if (!is_array($targetEntities) && !$targetEntities instanceof \Traversable) {
         $name = $this->property();
         $message = sprintf('Could not save %s, it cannot be traversed', $name);
         throw new \InvalidArgumentException($message);
     }
     $properties = array_combine((array) $this->foreignKey(), $entity->extract((array) $this->source()->primaryKey()));
     $target = $this->target();
     $original = $targetEntities;
     $options['_sourceTable'] = $this->source();
     foreach ($targetEntities as $k => $targetEntity) {
         if (!$targetEntity instanceof EntityInterface) {
             break;
         }
         if (!empty($options['atomic'])) {
             $targetEntity = clone $targetEntity;
         }
         $targetEntity->set($properties, ['guard' => false]);
         if ($target->save($targetEntity, $options)) {
             $targetEntities[$k] = $targetEntity;
             continue;
         }
         if (!empty($options['atomic'])) {
             $original[$k]->errors($targetEntity->errors());
             $entity->set($this->property(), $original);
             return false;
         }
     }
     $entity->set($this->property(), $targetEntities);
     return $entity;
 }
Пример #9
0
 /**
  * Updates counter cache for a single association
  *
  * @param \Cake\Event\Event $event Event instance.
  * @param \Cake\Datasource\EntityInterface $entity Entity
  * @param Association $assoc The association object
  * @param array $settings The settings for for counter cache for this association
  * @return void
  */
 protected function _processAssociation(Event $event, EntityInterface $entity, Association $assoc, array $settings)
 {
     $foreignKeys = (array) $assoc->foreignKey();
     $primaryKeys = (array) $assoc->target()->primaryKey();
     $countConditions = $entity->extract($foreignKeys);
     $updateConditions = array_combine($primaryKeys, $countConditions);
     $countOriginalConditions = $entity->extractOriginalChanged($foreignKeys);
     if ($countOriginalConditions !== []) {
         $updateOriginalConditions = array_combine($primaryKeys, $countOriginalConditions);
     }
     foreach ($settings as $field => $config) {
         if (is_int($field)) {
             $field = $config;
             $config = [];
         }
         if (!is_string($config) && is_callable($config)) {
             $count = $config($event, $entity, $this->_table, false);
         } else {
             $count = $this->_getCount($config, $countConditions);
         }
         $assoc->target()->updateAll([$field => $count], $updateConditions);
         if (isset($updateOriginalConditions)) {
             if (!is_string($config) && is_callable($config)) {
                 $count = $config($event, $entity, $this->_table, true);
             } else {
                 $count = $this->_getCount($config, $countOriginalConditions);
             }
             $assoc->target()->updateAll([$field => $count], $updateOriginalConditions);
         }
     }
 }
Пример #10
0
 /**
  * Perform the delete operation.
  *
  * Will delete the entity provided. Will remove rows from any
  * dependent associations, and clear out join tables for BelongsToMany associations.
  *
  * @param \Cake\DataSource\EntityInterface $entity The entity to delete.
  * @param \ArrayObject $options The options for the delete.
  * @throws \InvalidArgumentException if there are no primary key values of the
  * passed entity
  * @return bool success
  */
 protected function _processDelete($entity, $options)
 {
     $eventManager = $this->getEventManager();
     $event = new Event('Model.beforeDelete', $this, ['entity' => $entity, 'options' => $options]);
     $eventManager->dispatch($event);
     if ($event->isStopped()) {
         return $event->result;
     }
     if ($entity->isNew()) {
         return false;
     }
     $primaryKey = (array) $this->primaryKey();
     $conditions = (array) $entity->extract($primaryKey);
     if (!array_filter($conditions, 'strlen')) {
         $msg = 'Deleting requires a primary key value';
         throw new \InvalidArgumentException($msg);
     }
     $this->_associations->cascadeDelete($entity, $options->getArrayCopy());
     $query = $this->query();
     $statement = $query->delete()->where($conditions)->execute();
     $success = $statement->rowCount() > 0;
     if (!$success) {
         return $success;
     }
     $event = new Event('Model.afterDelete', $this, ['entity' => $entity, 'options' => $options]);
     $eventManager->dispatch($event);
     return $success;
 }
Пример #11
0
 /**
  * Returns the list of joint entities that exist between the source entity
  * and each of the passed target entities
  *
  * @param \Cake\Datasource\EntityInterface $sourceEntity The row belonging to the source side
  *   of this association.
  * @param array $targetEntities The rows belonging to the target side of this
  *   association.
  * @throws \InvalidArgumentException if any of the entities is lacking a primary
  *   key value
  * @return array
  */
 protected function _collectJointEntities($sourceEntity, $targetEntities)
 {
     $target = $this->target();
     $source = $this->source();
     $junction = $this->junction();
     $jointProperty = $this->_junctionProperty;
     $primary = (array) $target->primaryKey();
     $result = [];
     $missing = [];
     foreach ($targetEntities as $entity) {
         if (!$entity instanceof EntityInterface) {
             continue;
         }
         $joint = $entity->get($jointProperty);
         if (!$joint || !$joint instanceof EntityInterface) {
             $missing[] = $entity->extract($primary);
             continue;
         }
         $result[] = $joint;
     }
     if (empty($missing)) {
         return $result;
     }
     $belongsTo = $junction->association($target->alias());
     $hasMany = $source->association($junction->alias());
     $foreignKey = (array) $this->foreignKey();
     $assocForeignKey = (array) $belongsTo->foreignKey();
     $sourceKey = $sourceEntity->extract((array) $source->primaryKey());
     foreach ($missing as $key) {
         $unions[] = $hasMany->find('all')->where(array_combine($foreignKey, $sourceKey))->andWhere(array_combine($assocForeignKey, $key));
     }
     $query = array_shift($unions);
     foreach ($unions as $q) {
         $query->union($q);
     }
     return array_merge($result, $query->toArray());
 }
Пример #12
0
 /**
  * Takes an entity from the source table and looks if there is a field
  * matching the property name for this association. The found entity will be
  * saved on the target table for this association by passing supplied
  * `$options`
  *
  * @param \Cake\Datasource\EntityInterface $entity an entity from the source table
  * @param array|\ArrayObject $options options to be passed to the save method in
  * the target table
  * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns
  * the saved entity
  * @see Table::save()
  */
 public function saveAssociated(EntityInterface $entity, array $options = [])
 {
     $targetEntity = $entity->get($this->property());
     if (empty($targetEntity) || !$targetEntity instanceof EntityInterface) {
         return $entity;
     }
     $properties = array_combine((array) $this->foreignKey(), $entity->extract((array) $this->bindingKey()));
     $targetEntity->set($properties, ['guard' => false]);
     if (!$this->target()->save($targetEntity, $options)) {
         $targetEntity->unsetProperty(array_keys($properties));
         return false;
     }
     return $entity;
 }
Пример #13
0
 /**
  * Merges `$data` into `$entity`.
  *
  * ### Options:
  *
  * * validate: Whether or not to validate data before hydrating the entities. Can
  *   also be set to a string to use a specific validator. Defaults to true/default.
  * * fieldList: A whitelist of fields to be assigned to the entity. If not present
  *   the accessible fields list in the entity will be used.
  * * accessibleFields: A list of fields to allow or deny in entity accessible fields.
  *
  * @param \Cake\Datasource\EntityInterface $entity the entity that will get the
  * data merged in
  * @param array $data key value list of fields to be merged into the entity
  * @param array $options List of options.
  * @return \Cake\Datasource\EntityInterface
  */
 public function merge(EntityInterface $entity, array $data, array $options = [])
 {
     list($data, $options) = $this->_prepareDataAndOptions($data, $options);
     $isNew = $entity->isNew();
     $keys = [];
     if (!$isNew) {
         $keys = $entity->extract((array) $this->_endpoint->primaryKey());
     }
     if (isset($options['accessibleFields'])) {
         foreach ((array) $options['accessibleFields'] as $key => $value) {
             $entity->accessible($key, $value);
         }
     }
     $errors = $this->_validate($data + $keys, $options, $isNew);
     $properties = [];
     foreach ($data as $key => $value) {
         if (!empty($errors[$key])) {
             continue;
         }
         $properties[$key] = $value;
     }
     if (!isset($options['fieldList'])) {
         $entity->set($properties);
         $entity->errors($errors);
         return $entity;
     }
     foreach ((array) $options['fieldList'] as $field) {
         if (array_key_exists($field, $properties)) {
             $entity->set($field, $properties[$field]);
         }
     }
     $entity->errors($errors);
     return $entity;
 }
Пример #14
0
 /**
  * Persists all audit log events stored in the `_eventQueue` key inside $options.
  *
  * @param Cake\Event\Event The Model event that is enclosed inside a transaction
  * @param Cake\Datasource\EntityInterface $entity The entity that is to be saved or deleted
  * @param ArrayObject $options Options array containing the `_auditQueue` key
  * @return void
  */
 public function afterDelete(Event $event, EntityInterface $entity, $options)
 {
     if (!isset($options['_auditQueue'])) {
         return;
     }
     $transaction = $options['_auditTransaction'];
     $parent = isset($options['_sourceTable']) ? $options['_sourceTable']->table() : null;
     $primary = $entity->extract((array) $this->_table->primaryKey());
     $auditEvent = new AuditDeleteEvent($transaction, $primary, $this->_table->table(), $parent);
     $options['_auditQueue']->attach($entity, $auditEvent);
 }
 /**
  * Returns an array with foreignKey value.
  *
  * @param \Cake\Datasource\EntityInterface $entity Entity.
  * @return array
  */
 protected function _extractForeignKey($entity)
 {
     $foreignKey = (array) $this->_config['foreignKey'];
     $primaryKey = (array) $this->_table->primaryKey();
     $pkValue = $entity->extract($primaryKey);
     return array_combine($foreignKey, $pkValue);
 }
Пример #16
0
 /**
  * Takes an entity from the source table and looks if there is a field
  * matching the property name for this association. The found entity will be
  * saved on the target table for this association by passing supplied
  * `$options`
  *
  * @param \Cake\Datasource\EntityInterface $entity an entity from the source table
  * @param array|\ArrayObject $options options to be passed to the save method in
  * the target table
  * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns
  * the saved entity
  * @see Table::save()
  * @throws \InvalidArgumentException when the association data cannot be traversed.
  */
 public function saveAssociated(EntityInterface $entity, array $options = [])
 {
     $targetEntities = $entity->get($this->property());
     if (empty($targetEntities) && $this->_saveStrategy !== self::SAVE_REPLACE) {
         return $entity;
     }
     if (!is_array($targetEntities) && !$targetEntities instanceof Traversable) {
         $name = $this->property();
         $message = sprintf('Could not save %s, it cannot be traversed', $name);
         throw new InvalidArgumentException($message);
     }
     $foreignKey = (array) $this->foreignKey();
     $properties = array_combine($foreignKey, $entity->extract((array) $this->bindingKey()));
     $target = $this->target();
     $original = $targetEntities;
     $options['_sourceTable'] = $this->source();
     $unlinkSuccessful = null;
     if ($this->_saveStrategy === self::SAVE_REPLACE) {
         $unlinkSuccessful = $this->_unlinkAssociated($properties, $entity, $target, $targetEntities, $options);
     }
     if ($unlinkSuccessful === false) {
         return false;
     }
     foreach ($targetEntities as $k => $targetEntity) {
         if (!$targetEntity instanceof EntityInterface) {
             break;
         }
         if (!empty($options['atomic'])) {
             $targetEntity = clone $targetEntity;
         }
         if ($properties !== $targetEntity->extract($foreignKey)) {
             $targetEntity->set($properties, ['guard' => false]);
         }
         if ($target->save($targetEntity, $options)) {
             $targetEntities[$k] = $targetEntity;
             continue;
         }
         if (!empty($options['atomic'])) {
             $original[$k]->errors($targetEntity->errors());
             $entity->set($this->property(), $original);
             return false;
         }
     }
     $entity->set($this->property(), $targetEntities);
     return $entity;
 }
Пример #17
0
 /**
  * Auxiliary function to handle the update of an entity's data in the table
  *
  * @param \Cake\Datasource\EntityInterface $entity the subject entity from were $data was extracted
  * @param array $data The actual data that needs to be saved
  * @return \Cake\Datasource\EntityInterface|bool
  * @throws \InvalidArgumentException When primary key data is missing.
  */
 protected function _update($entity, $data)
 {
     $primaryColumns = (array) $this->primaryKey();
     $primaryKey = $entity->extract($primaryColumns);
     $data = array_diff_key($data, $primaryKey);
     if (empty($data)) {
         return $entity;
     }
     if (!$entity->has($primaryColumns)) {
         $message = 'All primary key value(s) are needed for updating';
         throw new InvalidArgumentException($message);
     }
     $query = $this->query();
     $statement = $query->update()->set($data)->where($primaryKey)->execute();
     $success = false;
     if ($statement->errorCode() === '00000') {
         $success = $entity;
     }
     $statement->closeCursor();
     return $success;
 }
 public function __invoke(EntityInterface $row)
 {
     $primaryKey = array_values($row->extract((array) $this->table->primaryKey()));
     $row->_links = ['self' => ['href' => Router::url(['controller' => $row->source(), 'action' => 'view'] + $primaryKey)]];
     return $this->enrich($row);
 }
Пример #19
0
 /**
  * Merges `$data` into `$entity` and recursively does the same for each one of
  * the association names passed in `$options`. When merging associations, if an
  * entity is not present in the parent entity for a given association, a new one
  * will be created.
  *
  * When merging HasMany or BelongsToMany associations, all the entities in the
  * `$data` array will appear, those that can be matched by primary key will get
  * the data merged, but those that cannot, will be discarded. `ids` option can be used
  * to determine whether the association must use the `_ids` format.
  *
  * ### Options:
  *
  * - associated: Associations listed here will be marshalled as well.
  * - validate: Whether or not to validate data before hydrating the entities. Can
  *   also be set to a string to use a specific validator. Defaults to true/default.
  * - fieldList: A whitelist of fields to be assigned to the entity. If not present
  *   the accessible fields list in the entity will be used.
  * - accessibleFields: A list of fields to allow or deny in entity accessible fields.
  *
  * The above options can be used in each nested `associated` array. In addition to the above
  * options you can also use the `onlyIds` option for HasMany and BelongsToMany associations.
  * When true this option restricts the request data to only be read from `_ids`.
  *
  * ```
  * $result = $marshaller->merge($entity, $data, [
  *   'associated' => ['Tags' => ['onlyIds' => true]]
  * ]);
  * ```
  *
  * @param \Cake\Datasource\EntityInterface $entity the entity that will get the
  * data merged in
  * @param array $data key value list of fields to be merged into the entity
  * @param array $options List of options.
  * @return \Cake\Datasource\EntityInterface
  */
 public function merge(EntityInterface $entity, array $data, array $options = [])
 {
     list($data, $options) = $this->_prepareDataAndOptions($data, $options);
     $propertyMap = $this->_buildPropertyMap($options);
     $isNew = $entity->isNew();
     $keys = [];
     if (!$isNew) {
         $keys = $entity->extract((array) $this->_table->primaryKey());
     }
     if (isset($options['accessibleFields'])) {
         foreach ((array) $options['accessibleFields'] as $key => $value) {
             $entity->accessible($key, $value);
         }
     }
     $errors = $this->_validate($data + $keys, $options, $isNew);
     $schema = $this->_table->schema();
     $properties = $marshalledAssocs = [];
     foreach ($data as $key => $value) {
         if (!empty($errors[$key])) {
             if ($entity instanceof InvalidPropertyInterface) {
                 $entity->invalid($key, $value);
             }
             continue;
         }
         $columnType = $schema->columnType($key);
         $original = $entity->get($key);
         if (isset($propertyMap[$key])) {
             $assoc = $propertyMap[$key]['association'];
             $value = $this->_mergeAssociation($original, $assoc, $value, $propertyMap[$key]);
             $marshalledAssocs[$key] = true;
         } elseif ($columnType) {
             $converter = Type::build($columnType);
             $value = $converter->marshal($value);
             $isObject = is_object($value);
             if (!$isObject && $original === $value || $isObject && $original == $value) {
                 continue;
             }
         }
         $properties[$key] = $value;
     }
     if (!isset($options['fieldList'])) {
         $entity->set($properties);
         $entity->errors($errors);
         foreach (array_keys($marshalledAssocs) as $field) {
             if ($properties[$field] instanceof EntityInterface) {
                 $entity->dirty($field, $properties[$field]->dirty());
             }
         }
         return $entity;
     }
     foreach ((array) $options['fieldList'] as $field) {
         if (array_key_exists($field, $properties)) {
             $entity->set($field, $properties[$field]);
             if ($properties[$field] instanceof EntityInterface && isset($marshalledAssocs[$field])) {
                 $entity->dirty($field, $properties[$field]->dirty());
             }
         }
     }
     $entity->errors($errors);
     return $entity;
 }
Пример #20
0
 /**
  * Performs the existence check
  *
  * @param \Cake\Datasource\EntityInterface $entity The entity from where to extract the fields
  * @param array $options Options passed to the check,
  * where the `repository` key is required.
  * @throws \RuntimeException When the rule refers to an undefined association.
  * @return bool
  */
 public function __invoke(EntityInterface $entity, array $options)
 {
     if (is_string($this->_repository)) {
         $repository = $options['repository']->association($this->_repository);
         if (!$repository) {
             throw new RuntimeException(sprintf("ExistsIn rule for '%s' is invalid. '%s' is not associated with '%s'.", implode(', ', $this->_fields), $this->_repository, get_class($options['repository'])));
         }
         $this->_repository = $repository;
     }
     $source = $target = $this->_repository;
     $isAssociation = $target instanceof Association;
     $bindingKey = $isAssociation ? (array) $target->bindingKey() : (array) $target->primaryKey();
     $realTarget = $isAssociation ? $target->target() : $target;
     if (!empty($options['_sourceTable']) && $realTarget === $options['_sourceTable']) {
         return true;
     }
     if (!empty($options['repository'])) {
         $source = $options['repository'];
     }
     if ($source instanceof Association) {
         $source = $source->source();
     }
     if (!$entity->extract($this->_fields, true)) {
         return true;
     }
     if ($this->_fieldsAreNull($entity, $source)) {
         return true;
     }
     if ($this->_options['allowNullableNulls']) {
         $schema = $source->schema();
         foreach ($this->_fields as $i => $field) {
             if ($schema->column($field) && $schema->isNullable($field) && $entity->get($field) === null) {
                 unset($bindingKey[$i]);
                 unset($this->_fields[$i]);
             }
         }
     }
     $primary = array_map([$target, 'aliasField'], $bindingKey);
     $conditions = array_combine($primary, $entity->extract($this->_fields));
     return $target->exists($conditions);
 }
Пример #21
0
 /**
  * Ensures that the provided entity contains non-empty values for the left and
  * right fields
  *
  * @param \Cake\Datasource\EntityInterface $entity The entity to ensure fields for
  * @return void
  */
 protected function _ensureFields($entity)
 {
     $config = $this->config();
     $fields = [$config['left'], $config['right']];
     $values = array_filter($entity->extract($fields));
     if (count($values) === count($fields)) {
         return;
     }
     $fresh = $this->_table->get($entity->get($this->_getPrimaryKey()), $fields);
     $entity->set($fresh->extract($fields), ['guard' => false]);
     foreach ($fields as $field) {
         $entity->dirty($field, false);
     }
 }
Пример #22
0
 /**
  * Merges `$data` into `$entity` and recursively does the same for each one of
  * the association names passed in `$options`. When merging associations, if an
  * entity is not present in the parent entity for a given association, a new one
  * will be created.
  *
  * When merging HasMany or BelongsToMany associations, all the entities in the
  * `$data` array will appear, those that can be matched by primary key will get
  * the data merged, but those that cannot, will be discarded. `ids` option can be used
  * to determine whether the association must use the `_ids` format.
  *
  * ### Options:
  *
  * - associated: Associations listed here will be marshalled as well.
  * - validate: Whether or not to validate data before hydrating the entities. Can
  *   also be set to a string to use a specific validator. Defaults to true/default.
  * - fieldList: A whitelist of fields to be assigned to the entity. If not present
  *   the accessible fields list in the entity will be used.
  * - accessibleFields: A list of fields to allow or deny in entity accessible fields.
  *
  * The above options can be used in each nested `associated` array. In addition to the above
  * options you can also use the `onlyIds` option for HasMany and BelongsToMany associations.
  * When true this option restricts the request data to only be read from `_ids`.
  *
  * ```
  * $result = $marshaller->merge($entity, $data, [
  *   'associated' => ['Tags' => ['onlyIds' => true]]
  * ]);
  * ```
  *
  * @param \Cake\Datasource\EntityInterface $entity the entity that will get the
  * data merged in
  * @param array $data key value list of fields to be merged into the entity
  * @param array $options List of options.
  * @return \Cake\Datasource\EntityInterface
  */
 public function merge(EntityInterface $entity, array $data, array $options = [])
 {
     list($data, $options) = $this->_prepareDataAndOptions($data, $options);
     $isNew = $entity->isNew();
     $keys = [];
     if (!$isNew) {
         $keys = $entity->extract((array) $this->_table->primaryKey());
     }
     if (isset($options['accessibleFields'])) {
         foreach ((array) $options['accessibleFields'] as $key => $value) {
             $entity->accessible($key, $value);
         }
     }
     $errors = $this->_validate($data + $keys, $options, $isNew);
     $schema = $this->_table->schema();
     $options['isMerge'] = true;
     $propertyMap = $this->_buildPropertyMap($data, $options);
     $properties = $marshalledAssocs = [];
     foreach ($data as $key => $value) {
         if (!empty($errors[$key])) {
             if ($entity instanceof InvalidPropertyInterface) {
                 $entity->invalid($key, $value);
             }
             continue;
         }
         $original = $entity->get($key);
         if (isset($propertyMap[$key])) {
             $value = $propertyMap[$key]($value, $entity);
             // Don't dirty scalar values and objects that didn't
             // change. Arrays will always be marked as dirty because
             // the original/updated list could contain references to the
             // same objects, even though those objects may have changed internally.
             if (is_scalar($value) && $original === $value || $value === null && $original === $value || is_object($value) && !$value instanceof EntityInterface && $original == $value) {
                 continue;
             }
         }
         $properties[$key] = $value;
     }
     $entity->errors($errors);
     if (!isset($options['fieldList'])) {
         $entity->set($properties);
         foreach ($properties as $field => $value) {
             if ($value instanceof EntityInterface) {
                 $entity->dirty($field, $value->dirty());
             }
         }
         return $entity;
     }
     foreach ((array) $options['fieldList'] as $field) {
         if (array_key_exists($field, $properties)) {
             $entity->set($field, $properties[$field]);
             if ($properties[$field] instanceof EntityInterface) {
                 $entity->dirty($field, $properties[$field]->dirty());
             }
         }
     }
     return $entity;
 }