Пример #1
0
 public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options)
 {
     if (empty($entity->display)) {
         $entity->display = Inflector::humanize($entity->name);
     }
     if ($entity->dirty('is_default') && $entity->is_default) {
         $this->updateAll(['is_default' => false], ['is_default' => true]);
     } elseif ($entity->dirty('is_default') && !$entity->is_default) {
         $entity->is_default = $entity->getOriginal('is_default');
     }
 }
 /**
  * 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);
     }
 }
Пример #3
0
 /**
  * Restores all (or given) trashed row(s).
  *
  * @param \Cake\Datasource\EntityInterface|null $entity to restore.
  * @return bool|\Cake\Datasource\EntityInterface|int|mixed
  */
 public function restoreTrash(EntityInterface $entity = null)
 {
     $data = [$this->getTrashField(false) => null];
     if ($entity instanceof EntityInterface) {
         if ($entity->dirty()) {
             throw new RuntimeException('Can not restore from a dirty entity.');
         }
         $entity->set($data);
         return $this->_table->save($entity);
     }
     return $this->_table->updateAll($data, $this->_getUnaryExpression());
 }
Пример #4
0
 /**
  * {@inheritDoc}
  *
  * ### Options
  *
  * The options array accepts the following keys:
  *
  * - atomic: Whether to execute the save and callbacks inside a database
  *   transaction (default: true)
  * - checkRules: Whether or not to check the rules on entity before saving, if the checking
  *   fails, it will abort the save operation. (default:true)
  * - associated: If true it will save all associated entities as they are found
  *   in the passed `$entity` whenever the property defined for the association
  *   is marked as dirty. Associated records are saved recursively unless told
  *   otherwise. If an array, it will be interpreted as the list of associations
  *   to be saved. It is possible to provide different options for saving on associated
  *   table objects using this key by making the custom options the array value.
  *   If false no associated records will be saved. (default: true)
  * - checkExisting: Whether or not to check if the entity already exists, assuming that the
  *   entity is marked as not new, and the primary key has been set.
  *
  * ### Events
  *
  * When saving, this method will trigger four events:
  *
  * - Model.beforeRules: Will be triggered right before any rule checking is done
  *   for the passed entity if the `checkRules` key in $options is not set to false.
  *   Listeners will receive as arguments the entity, options array and the operation type.
  *   If the event is stopped the rules check result will be set to the result of the event itself.
  * - Model.afterRules: Will be triggered right after the `checkRules()` method is
  *   called for the entity. Listeners will receive as arguments the entity,
  *   options array, the result of checking the rules and the operation type.
  *   If the event is stopped the checking result will be set to the result of
  *   the event itself.
  * - Model.beforeSave: Will be triggered just before the list of fields to be
  *   persisted is calculated. It receives both the entity and the options as
  *   arguments. The options array is passed as an ArrayObject, so any changes in
  *   it will be reflected in every listener and remembered at the end of the event
  *   so it can be used for the rest of the save operation. Returning false in any
  *   of the listeners will abort the saving process. If the event is stopped
  *   using the event API, the event object's `result` property will be returned.
  *   This can be useful when having your own saving strategy implemented inside a
  *   listener.
  * - Model.afterSave: Will be triggered after a successful insert or save,
  *   listeners will receive the entity and the options array as arguments. The type
  *   of operation performed (insert or update) can be determined by checking the
  *   entity's method `isNew`, true meaning an insert and false an update.
  * - Model.afterSaveCommit: Will be triggered after the transaction is commited
  *   for atomic save, listeners will receive the entity and the options array
  *   as arguments.
  *
  * This method will determine whether the passed entity needs to be
  * inserted or updated in the database. It does that by checking the `isNew`
  * method on the entity. If the entity to be saved returns a non-empty value from
  * its `errors()` method, it will not be saved.
  *
  * ### Saving on associated tables
  *
  * This method will by default persist entities belonging to associated tables,
  * whenever a dirty property matching the name of the property name set for an
  * association in this table. It is possible to control what associations will
  * be saved and to pass additional option for saving them.
  *
  * ```
  * // Only save the comments association
  * $articles->save($entity, ['associated' => ['Comments']);
  *
  * // Save the company, the employees and related addresses for each of them.
  * // For employees do not check the entity rules
  * $companies->save($entity, [
  *   'associated' => [
  *     'Employees' => [
  *       'associated' => ['Addresses'],
  *       'checkRules' => false
  *     ]
  *   ]
  * ]);
  *
  * // Save no associations
  * $articles->save($entity, ['associated' => false]);
  * ```
  *
  */
 public function save(EntityInterface $entity, $options = [])
 {
     $options = new ArrayObject($options + ['atomic' => true, 'associated' => true, 'checkRules' => true, 'checkExisting' => true, '_primary' => true]);
     if ($entity->errors()) {
         return false;
     }
     if ($entity->isNew() === false && !$entity->dirty()) {
         return $entity;
     }
     $connection = $this->connection();
     if ($options['atomic']) {
         $success = $connection->transactional(function () use($entity, $options) {
             return $this->_processSave($entity, $options);
         });
     } else {
         $success = $this->_processSave($entity, $options);
     }
     if ($success) {
         if (!$connection->inTransaction() && ($options['atomic'] || !$options['atomic'] && $options['_primary'])) {
             $this->dispatchEvent('Model.afterSaveCommit', compact('entity', 'options'));
         }
         if ($options['atomic'] || $options['_primary']) {
             $entity->isNew(false);
             $entity->source($this->registryAlias());
         }
     }
     return $success;
 }
Пример #5
0
 /**
  * Removes all links between the passed source entity and each of the provided
  * target entities. This method assumes that all passed objects are already persisted
  * in the database and that each of them contain a primary key value.
  *
  * ### Options
  *
  * Additionally to the default options accepted by `Table::delete()`, the following
  * keys are supported:
  *
  * - cleanProperty: Whether or not to remove all the objects in `$targetEntities` that
  * are stored in `$sourceEntity` (default: true)
  *
  * By default this method will unset each of the entity objects stored inside the
  * source entity.
  *
  * Changes are persisted in the database and also in the source entity.
  *
  * ### Example:
  *
  * ```
  * $user = $users->get(1);
  * $user->articles = [$article1, $article2, $article3, $article4];
  * $users->save($user, ['Associated' => ['Articles']]);
  * $allArticles = [$article1, $article2, $article3];
  * $users->Articles->unlink($user, $allArticles);
  * ```
  *
  * `$article->get('articles')` will contain only `[$article4]` after deleting in the database
  *
  * @param \Cake\Datasource\EntityInterface $sourceEntity an entity persisted in the source table for
  * this association
  * @param array $targetEntities list of entities persisted in the target table for
  * this association
  * @param array $options list of options to be passed to the internal `delete` call
  * @throws \InvalidArgumentException if non persisted entities are passed or if
  * any of them is lacking a primary key value
  * @return void
  */
 public function unlink(EntityInterface $sourceEntity, array $targetEntities, $options = [])
 {
     if (is_bool($options)) {
         $options = ['cleanProperty' => $options];
     } else {
         $options += ['cleanProperty' => true];
     }
     $foreignKey = (array) $this->foreignKey();
     $target = $this->target();
     $targetPrimaryKey = array_merge((array) $target->primaryKey(), $foreignKey);
     $property = $this->property();
     $conditions = ['OR' => (new Collection($targetEntities))->map(function ($entity) use($targetPrimaryKey) {
         return $entity->extract($targetPrimaryKey);
     })->toList()];
     $this->_unlink($foreignKey, $target, $conditions, $options);
     if ($options['cleanProperty']) {
         $sourceEntity->set($property, (new Collection($sourceEntity->get($property)))->reject(function ($assoc) use($targetEntities) {
             return in_array($assoc, $targetEntities);
         })->toList());
     }
     $sourceEntity->dirty($property, false);
 }
Пример #6
0
 /**
  * Helper method used to generated multiple translated field entities
  * out of the data found in the `_translations` property in the passed
  * entity. The result will be put into its `_i18n` property
  *
  * @param \Cake\Datasource\EntityInterface $entity Entity
  * @return void
  */
 protected function _bundleTranslatedFields($entity)
 {
     $translations = (array) $entity->get('_translations');
     if (empty($translations) && !$entity->dirty('_translations')) {
         return;
     }
     $fields = $this->_config['fields'];
     $primaryKey = (array) $this->_table->primaryKey();
     $key = $entity->get(current($primaryKey));
     $find = [];
     foreach ($translations as $lang => $translation) {
         foreach ($fields as $field) {
             if (!$translation->dirty($field)) {
                 continue;
             }
             $find[] = ['locale' => $lang, 'field' => $field, 'foreign_key' => $key];
             $contents[] = new Entity(['content' => $translation->get($field)], ['useSetters' => false]);
         }
     }
     if (empty($find)) {
         return;
     }
     $results = $this->_findExistingTranslations($find);
     foreach ($find as $i => $translation) {
         if (!empty($results[$i])) {
             $contents[$i]->set('id', $results[$i], ['setter' => false]);
             $contents[$i]->isNew(false);
         } else {
             $translation['model'] = $this->_config['referenceName'];
             $contents[$i]->set($translation, ['setter' => false, 'guard' => false]);
             $contents[$i]->isNew(true);
         }
     }
     $entity->set('_i18n', $contents);
 }
Пример #7
0
 /**
  * Helper method for saving an association's data.
  *
  * @param \Cake\ORM\Association $association The association object to save with.
  * @param \Cake\Datasource\EntityInterface $entity The entity to save
  * @param array $nested Options for deeper associations
  * @param array $options Original options
  * @return bool Success
  */
 protected function _save($association, $entity, $nested, $options)
 {
     if (!$entity->dirty($association->property())) {
         return true;
     }
     if (!empty($nested)) {
         $options = (array) $nested + $options;
     }
     return (bool) $association->saveAssociated($entity, $options);
 }
 /**
  * Helper method used to generated multiple translated field entities
  * out of the data found in the `_translations` property in the passed
  * entity. The result will be put into its `_i18n` property
  *
  * @param \Cake\Datasource\EntityInterface $entity Entity
  * @return void
  */
 protected function _bundleTranslatedFields($entity)
 {
     $translations = (array) $entity->get('_translations');
     if (empty($translations) && !$entity->dirty('_translations')) {
         return;
     }
     $primaryKey = (array) $this->_table->primaryKey();
     $key = $entity->get(current($primaryKey));
     foreach ($translations as $lang => $translation) {
         if (!$translation->id) {
             $update = ['id' => $key, 'locale' => $lang];
             $translation->set($update, ['guard' => false]);
         }
     }
     $entity->set('_i18n', $translations);
 }
Пример #9
0
 /**
  * Returns fields that have been marked as protected.
  *
  * @param \Cake\Datasource\EntityInterface $entity
  * @return array
  */
 public function fields(EntityInterface $entity)
 {
     $fields = [];
     foreach ((array) $this->config('fields') as $field) {
         if (!$entity->dirty($field)) {
             continue;
         }
         $fields[$field] = $entity->{$field};
         $entity->dirty($field, false);
     }
     return $fields;
 }
Пример #10
0
 /**
  * Update a field, if it hasn't been updated already
  *
  * @param \Cake\Datasource\EntityInterface $entity Entity instance.
  * @param string $field Field name
  * @param bool $refreshTimestamp Whether to refresh timestamp.
  * @return void
  */
 protected function _updateField($entity, $field, $refreshTimestamp)
 {
     if ($entity->dirty($field)) {
         return;
     }
     $entity->set($field, $this->timestamp(null, $refreshTimestamp));
 }
Пример #11
0
 /**
  * Callback to obfuscate the record(s)' primary key returned after a save operation.
  *
  * @param \Cake\ORM\Behavior\Event $event Event.
  * @param \Cake\ORM\Behavior\EntityInterface $entity Entity.
  * @param \ArrayObject $options Options.
  * @return void
  */
 public function afterSave(Event $event, EntityInterface $entity, ArrayObject $options)
 {
     $pk = $this->_table->primaryKey();
     $entity->set($pk, $this->obfuscate($entity->{$pk}));
     $entity->dirty($pk, false);
 }
 /**
  * Checks if required fields have been modified. Returns true
  * if any of the fields has been modified, otherwise false.
  *
  * @param \Cake\Datasource\EntityInterface $entity Entity instance
  * @param array $requiredFields Required fields list
  * @return bool
  */
 protected function _requiredFieldsModified(EntityInterface $entity, array $requiredFields)
 {
     $result = false;
     if (empty($requiredFields)) {
         return $result;
     }
     // check if any of the required fields was modified and set modified flag to true
     foreach ($requiredFields as $field) {
         if (!$entity->dirty($field)) {
             continue;
         }
         $result = true;
         break;
     }
     return $result;
 }
 /**
  * checkRules rule.
  *
  * @param \Cake\Datasource\EntityInterface $entity Entity.
  * @return bool
  */
 public function checkRules(EntityInterface $entity)
 {
     $field = $this->_config['discriminatorField'];
     if ($entity->dirty($field)) {
         return $this->_matches($entity->get($field), $this->acceptedDiscriminators());
     }
     return true;
 }
Пример #14
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;
 }
Пример #15
0
 /**
  * {@inheritDoc}
  *
  * ### Options
  *
  * The options array can receive the following keys:
  *
  * - atomic: Whether to execute the save and callbacks inside a database
  * transaction (default: true)
  * - validate: Whether or not validate the entity before saving, if validation
  * fails, it will abort the save operation. If this key is set to a string value,
  * the validator object registered in this table under the provided name will be
  * used instead of the default one. (default:true)
  * - associated: If true it will save all associated entities as they are found
  * in the passed `$entity` whenever the property defined for the association
  * is marked as dirty. Associated records are saved recursively unless told
  * otherwise. If an array, it will be interpreted as the list of associations
  * to be saved. It is possible to provide different options for saving on associated
  * table objects using this key by making the custom options the array value.
  * If false no associated records will be saved. (default: true)
  *
  * ### Events
  *
  * When saving, this method will trigger four events:
  *
  * - Model.beforeValidate: Will be triggered right before any validation is done
  * for the passed entity if the validate key in $options is not set to false.
  * Listeners will receive as arguments the entity, the options array and the
  * validation object to be used for validating the entity. If the event is
  * stopped the validation result will be set to the result of the event itself.
  * - Model.afterValidate: Will be triggered right after the `validate()` method is
  * called in the entity. Listeners will receive as arguments the entity, the
  * options array and the validation object to be used for validating the entity.
  * If the event is stopped the validation result will be set to the result of
  * the event itself.
  * - Model.beforeSave: Will be triggered just before the list of fields to be
  * persisted is calculated. It receives both the entity and the options as
  * arguments. The options array is passed as an ArrayObject, so any changes in
  * it will be reflected in every listener and remembered at the end of the event
  * so it can be used for the rest of the save operation. Returning false in any
  * of the listeners will abort the saving process. If the event is stopped
  * using the event API, the event object's `result` property will be returned.
  * This can be useful when having your own saving strategy implemented inside a
  * listener.
  * - Model.afterSave: Will be triggered after a successful insert or save,
  * listeners will receive the entity and the options array as arguments. The type
  * of operation performed (insert or update) can be determined by checking the
  * entity's method `isNew`, true meaning an insert and false an update.
  *
  * This method will determine whether the passed entity needs to be
  * inserted or updated in the database. It does that by checking the `isNew`
  * method on the entity, if no information can be found there, it will go
  * directly to the database to check the entity's status.
  *
  * ### Saving on associated tables
  *
  * This method will by default persist entities belonging to associated tables,
  * whenever a dirty property matching the name of the property name set for an
  * association in this table. It is possible to control what associations will
  * be saved and to pass additional option for saving them.
  *
  * {{{
  * // Only save the comments association
  * $articles->save($entity, ['associated' => ['Comments']);
  *
  * // Save the company, the employees and related addresses for each of them.
  * // For employees use the 'special' validation group
  * $companies->save($entity, [
  *   'associated' => [
  *     'Employees' => [
  *       'associated' => ['Addresses'],
  *       'validate' => 'special'
  *     ]
  *   ]
  * ]);
  *
  * // Save no associations
  * $articles->save($entity, ['associated' => false]);
  * }}}
  *
  */
 public function save(EntityInterface $entity, $options = [])
 {
     $options = new \ArrayObject($options + ['atomic' => true, 'validate' => true, 'associated' => true]);
     if ($entity->isNew() === false && !$entity->dirty()) {
         return $entity;
     }
     if ($options['atomic']) {
         $connection = $this->connection();
         $success = $connection->transactional(function () use($entity, $options) {
             return $this->_processSave($entity, $options);
         });
     } else {
         $success = $this->_processSave($entity, $options);
     }
     return $success;
 }
Пример #16
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);
     }
 }
Пример #17
0
 /**
  * Sets up hashid for model.
  *
  * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved
  * @return bool True if save should proceed, false otherwise
  */
 public function encode(EntityInterface $entity)
 {
     $idField = $this->_primaryKey;
     $id = $entity->get($idField);
     if (!$id) {
         return false;
     }
     $field = $this->_config['field'];
     if (!$field) {
         return false;
     }
     $hashid = $this->encodeId($id);
     $entity->set($field, $hashid);
     $entity->dirty($field, false);
     return true;
 }