/var/www/vhosts/ihelp.ro/httpdocs/vendor/cakephp/cakephp/src/ORM/Association
NameSizeModeActions
Loader/-0755rm
BelongsTo.php66540644editdlrm
BelongsToMany.php538850644editdlrm
DependentDeleteHelper.php23650644editdlrm
HasMany.php232510644editdlrm
HasOne.php47110644editdlrm
Edit: /var/www/vhosts/ihelp.ro/httpdocs/vendor/cakephp/cakephp/src/ORM/Association/HasMany.php (23251B)
*/ protected $_validStrategies = [ self::STRATEGY_SELECT, self::STRATEGY_SUBQUERY, ]; /** * Saving strategy that will only append to the links set * * @var string */ public const SAVE_APPEND = 'append'; /** * Saving strategy that will replace the links with the provided set * * @var string */ public const SAVE_REPLACE = 'replace'; /** * Saving strategy to be used by this association * * @var string */ protected $_saveStrategy = self::SAVE_APPEND; /** * Returns whether the passed table is the owning side for this * association. This means that rows in the 'target' table would miss important * or required information if the row in 'source' did not exist. * * @param \Cake\ORM\Table $side The potential Table with ownership * @return bool */ public function isOwningSide(Table $side): bool { return $side === $this->getSource(); } /** * Sets the strategy that should be used for saving. * * @param string $strategy the strategy name to be used * @throws \InvalidArgumentException if an invalid strategy name is passed * @return $this */ public function setSaveStrategy(string $strategy) { if (!in_array($strategy, [self::SAVE_APPEND, self::SAVE_REPLACE], true)) { $msg = sprintf('Invalid save strategy "%s"', $strategy); throw new InvalidArgumentException($msg); } $this->_saveStrategy = $strategy; return $this; } /** * Gets the strategy that should be used for saving. * * @return string the strategy to be used for saving */ public function getSaveStrategy(): string { return $this->_saveStrategy; } /** * 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 $options options to be passed to the save method in the target table * @return \Cake\Datasource\EntityInterface|false false if $entity could not be saved, otherwise it returns * the saved entity * @see \Cake\ORM\Table::save() * @throws \InvalidArgumentException when the association data cannot be traversed. */ public function saveAssociated(EntityInterface $entity, array $options = []) { $targetEntities = $entity->get($this->getProperty()); $isEmpty = in_array($targetEntities, [null, [], '', false], true); if ($isEmpty) { if ( $entity->isNew() || $this->getSaveStrategy() !== self::SAVE_REPLACE ) { return $entity; } $targetEntities = []; } if (!is_iterable($targetEntities)) { $name = $this->getProperty(); $message = sprintf('Could not save %s, it cannot be traversed', $name); throw new InvalidArgumentException($message); } $foreignKeyReference = array_combine( (array)$this->getForeignKey(), $entity->extract((array)$this->getBindingKey()) ); $options['_sourceTable'] = $this->getSource(); if ( $this->_saveStrategy === self::SAVE_REPLACE && !$this->_unlinkAssociated($foreignKeyReference, $entity, $this->getTarget(), $targetEntities, $options) ) { return false; } if (!is_array($targetEntities)) { $targetEntities = iterator_to_array($targetEntities); } if (!$this->_saveTarget($foreignKeyReference, $entity, $targetEntities, $options)) { return false; } return $entity; } /** * Persists each of the entities into the target table and creates links between * the parent entity and each one of the saved target entities. * * @param array $foreignKeyReference The foreign key reference defining the link between the * target entity, and the parent entity. * @param \Cake\Datasource\EntityInterface $parentEntity The source entity containing the target * entities to be saved. * @param array $entities list of entities * to persist in target table and to link to the parent entity * @param array $options list of options accepted by `Table::save()`. * @return bool `true` on success, `false` otherwise. */ protected function _saveTarget( array $foreignKeyReference, EntityInterface $parentEntity, array $entities, array $options ): bool { $foreignKey = array_keys($foreignKeyReference); $table = $this->getTarget(); $original = $entities; foreach ($entities as $k => $entity) { if (!($entity instanceof EntityInterface)) { break; } if (!empty($options['atomic'])) { $entity = clone $entity; } if ($foreignKeyReference !== $entity->extract($foreignKey)) { $entity->set($foreignKeyReference, ['guard' => false]); } if ($table->save($entity, $options)) { $entities[$k] = $entity; continue; } if (!empty($options['atomic'])) { $original[$k]->setErrors($entity->getErrors()); if ($entity instanceof InvalidPropertyInterface) { $original[$k]->setInvalid($entity->getInvalid()); } return false; } } $parentEntity->set($this->getProperty(), $entities); return true; } /** * Associates the source entity to each of the target entities provided. * When using this method, all entities in `$targetEntities` will be appended to * the source entity's property corresponding to this association object. * * This method does not check link uniqueness. * Changes are persisted in the database and also in the source entity. * * ### Example: * * ``` * $user = $users->get(1); * $allArticles = $articles->find('all')->toArray(); * $users->Articles->link($user, $allArticles); * ``` * * `$user->get('articles')` will contain all articles in `$allArticles` after linking * * @param \Cake\Datasource\EntityInterface $sourceEntity the row belonging to the `source` side * of this association * @param array $targetEntities list of entities belonging to the `target` side * of this association * @param array $options list of options to be passed to the internal `save` call * @return bool true on success, false otherwise */ public function link(EntityInterface $sourceEntity, array $targetEntities, array $options = []): bool { $saveStrategy = $this->getSaveStrategy(); $this->setSaveStrategy(self::SAVE_APPEND); $property = $this->getProperty(); $currentEntities = array_unique( array_merge( (array)$sourceEntity->get($property), $targetEntities ) ); $sourceEntity->set($property, $currentEntities); $savedEntity = $this->getConnection()->transactional(function () use ($sourceEntity, $options) { return $this->saveAssociated($sourceEntity, $options); }); $ok = ($savedEntity instanceof EntityInterface); $this->setSaveStrategy($saveStrategy); if ($ok) { $sourceEntity->set($property, $savedEntity->get($property)); $sourceEntity->setDirty($property, false); } return $ok; } /** * Removes all links between the passed source entity and each of the provided * target entities. This method assumes that all passed objects are already persisted * in the database and that each of them contain a primary key value. * * ### Options * * Additionally to the default options accepted by `Table::delete()`, the following * keys are supported: * * - cleanProperty: Whether to remove all the objects in `$targetEntities` that * are stored in `$sourceEntity` (default: true) * * By default this method will unset each of the entity objects stored inside the * 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|bool $options list of options to be passed to the internal `delete` call. * If boolean it will be used a value for "cleanProperty" option. * @throws \InvalidArgumentException if non persisted entities are passed or if * any of them is lacking a primary key value * @return void */ public function unlink(EntityInterface $sourceEntity, array $targetEntities, $options = []): void { if (is_bool($options)) { $options = [ 'cleanProperty' => $options, ]; } else { $options += ['cleanProperty' => true]; } if (count($targetEntities) === 0) { return; } $foreignKey = (array)$this->getForeignKey(); $target = $this->getTarget(); $targetPrimaryKey = array_merge((array)$target->getPrimaryKey(), $foreignKey); $property = $this->getProperty(); $conditions = [ 'OR' => (new Collection($targetEntities)) ->map(function ($entity) use ($targetPrimaryKey) { /** @var \Cake\Datasource\EntityInterface $entity */ return $entity->extract($targetPrimaryKey); }) ->toList(), ]; $this->_unlink($foreignKey, $target, $conditions, $options); $result = $sourceEntity->get($property); if ($options['cleanProperty'] && $result !== null) { $sourceEntity->set( $property, (new Collection($sourceEntity->get($property))) ->reject( function ($assoc) use ($targetEntities) { return in_array($assoc, $targetEntities); } ) ->toList() ); } $sourceEntity->setDirty($property, false); } /** * Replaces existing association links between the source entity and the target * with the ones passed. This method does a smart cleanup, links that are already * persisted and present in `$targetEntities` will not be deleted, new links will * be created for the passed target entities that are not already in the database * and the rest will be removed. * * For example, if an author has many articles, such as 'article1','article 2' and 'article 3' and you pass * to this method an array containing the entities for articles 'article 1' and 'article 4', * only the link for 'article 1' will be kept in database, the links for 'article 2' and 'article 3' will be * deleted and the link for 'article 4' will be created. * * Existing links are not deleted and created again, they are either left untouched * or updated. * * This method does not check link uniqueness. * * On success, the passed `$sourceEntity` will contain `$targetEntities` as value * in the corresponding property for this association. * * Additional options for new links to be saved can be passed in the third argument, * check `Table::save()` for information on the accepted options. * * ### Example: * * ``` * $author->articles = [$article1, $article2, $article3, $article4]; * $authors->save($author); * $articles = [$article1, $article3]; * $authors->getAssociation('articles')->replace($author, $articles); * ``` * * `$author->get('articles')` will contain only `[$article1, $article3]` at the end * * @param \Cake\Datasource\EntityInterface $sourceEntity an entity persisted in the source table for * this association * @param array $targetEntities list of entities from the target table to be linked * @param array $options list of options to be passed to the internal `save`/`delete` calls * when persisting/updating new links, or deleting existing ones * @throws \InvalidArgumentException if non persisted entities are passed or if * any of them is lacking a primary key value * @return bool success */ public function replace(EntityInterface $sourceEntity, array $targetEntities, array $options = []): bool { $property = $this->getProperty(); $sourceEntity->set($property, $targetEntities); $saveStrategy = $this->getSaveStrategy(); $this->setSaveStrategy(self::SAVE_REPLACE); $result = $this->saveAssociated($sourceEntity, $options); $ok = ($result instanceof EntityInterface); if ($ok) { $sourceEntity = $result; } $this->setSaveStrategy($saveStrategy); return $ok; } /** * Deletes/sets null the related objects according to the dependency between source and targets * and foreign key nullability. Skips deleting records present in $remainingEntities * * @param array $foreignKeyReference The foreign key reference defining the link between the * target entity, and the parent entity. * @param \Cake\Datasource\EntityInterface $entity the entity which should have its associated entities unassigned * @param \Cake\ORM\Table $target The associated table * @param iterable $remainingEntities Entities that should not be deleted * @param array $options list of options accepted by `Table::delete()` * @return bool success */ protected function _unlinkAssociated( array $foreignKeyReference, EntityInterface $entity, Table $target, iterable $remainingEntities = [], array $options = [] ): bool { $primaryKey = (array)$target->getPrimaryKey(); $exclusions = new Collection($remainingEntities); $exclusions = $exclusions->map( function ($ent) use ($primaryKey) { /** @var \Cake\Datasource\EntityInterface $ent */ return $ent->extract($primaryKey); } ) ->filter( function ($v) { return !in_array(null, $v, true); } ) ->toList(); $conditions = $foreignKeyReference; if (count($exclusions) > 0) { $conditions = [ 'NOT' => [ 'OR' => $exclusions, ], $foreignKeyReference, ]; } return $this->_unlink(array_keys($foreignKeyReference), $target, $conditions, $options); } /** * Deletes/sets null the related objects matching $conditions. * * The action which is taken depends on the dependency between source and * targets and also on foreign key nullability. * * @param array $foreignKey array of foreign key properties * @param \Cake\ORM\Table $target The associated table * @param array $conditions The conditions that specifies what are the objects to be unlinked * @param array $options list of options accepted by `Table::delete()` * @return bool success */ protected function _unlink(array $foreignKey, Table $target, array $conditions = [], array $options = []): bool { $mustBeDependent = (!$this->_foreignKeyAcceptsNull($target, $foreignKey) || $this->getDependent()); if ($mustBeDependent) { if ($this->_cascadeCallbacks) { $conditions = new QueryExpression($conditions); $conditions->traverse(function ($entry) use ($target): void { if ($entry instanceof FieldInterface) { $field = $entry->getField(); if (is_string($field)) { $entry->setField($target->aliasField($field)); } } }); $query = $this->find()->where($conditions); $ok = true; foreach ($query as $assoc) { $ok = $ok && $target->delete($assoc, $options); } return $ok; } $this->deleteAll($conditions); return true; } $updateFields = array_fill_keys($foreignKey, null); $this->updateAll($updateFields, $conditions); return true; } /** * Checks the nullable flag of the foreign key * * @param \Cake\ORM\Table $table the table containing the foreign key * @param array $properties the list of fields that compose the foreign key * @return bool */ protected function _foreignKeyAcceptsNull(Table $table, array $properties): bool { return !in_array( false, array_map( function ($prop) use ($table) { return $table->getSchema()->isNullable($prop); }, $properties ) ); } /** * Get the relationship type. * * @return string */ public function type(): string { return self::ONE_TO_MANY; } /** * Whether this association can be expressed directly in a query join * * @param array $options custom options key that could alter the return value * @return bool if the 'matching' key in $option is true then this function * will return true, false otherwise */ public function canBeJoined(array $options = []): bool { return !empty($options['matching']); } /** * Gets the name of the field representing the foreign key to the source table. * * @return array|string */ public function getForeignKey() { if ($this->_foreignKey === null) { $this->_foreignKey = $this->_modelKey($this->getSource()->getTable()); } return $this->_foreignKey; } /** * Sets the sort order in which target records should be returned. * * @param mixed $sort A find() compatible order clause * @return $this */ public function setSort($sort) { $this->_sort = $sort; return $this; } /** * Gets the sort order in which target records should be returned. * * @return mixed */ public function getSort() { return $this->_sort; } /** * @inheritDoc */ public function defaultRowValue(array $row, bool $joined): array { $sourceAlias = $this->getSource()->getAlias(); if (isset($row[$sourceAlias])) { $row[$sourceAlias][$this->getProperty()] = $joined ? null : []; } return $row; } /** * Parse extra options passed in the constructor. * * @param array $options original list of options passed in constructor * @return void */ protected function _options(array $options): void { if (!empty($options['saveStrategy'])) { $this->setSaveStrategy($options['saveStrategy']); } if (isset($options['sort'])) { $this->setSort($options['sort']); } } /** * @inheritDoc */ public function eagerLoader(array $options): Closure { $loader = new SelectLoader([ 'alias' => $this->getAlias(), 'sourceAlias' => $this->getSource()->getAlias(), 'targetAlias' => $this->getTarget()->getAlias(), 'foreignKey' => $this->getForeignKey(), 'bindingKey' => $this->getBindingKey(), 'strategy' => $this->getStrategy(), 'associationType' => $this->type(), 'sort' => $this->getSort(), 'finder' => [$this, 'find'], ]); return $loader->buildEagerLoader($options); } /** * @inheritDoc */ public function cascadeDelete(EntityInterface $entity, array $options = []): bool { $helper = new DependentDeleteHelper(); return $helper->cascadeDelete($this, $entity, $options); } }