| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756 |
- <?php
- /**
- * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
- * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
- *
- * Licensed under The MIT License
- * For full copyright and license information, please see the LICENSE.txt
- * Redistributions of files must retain the above copyright notice.
- *
- * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
- * @link http://cakephp.org CakePHP(tm) Project
- * @since CakePHP(tm) v 3.0.0
- * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
- */
- namespace Cake\Model\Behavior;
- use Cake\Event\Event;
- use Cake\ORM\Behavior;
- use Cake\ORM\Entity;
- use Cake\ORM\Table;
- /**
- * Makes the table to which this is attached to behave like a nested set and
- * provides methods for managing and retrieving information out of the derived
- * hierarchical structure.
- *
- * Tables attaching this behavior are required to have a column referencing the
- * parent row, and two other numeric columns (lft and rght) where the implicit
- * order will be cached.
- *
- * For more information on what is a nested set and a how it works refer to
- * http://www.sitepoint.com/hierarchical-data-database-2/
- */
- class TreeBehavior extends Behavior {
- /**
- * Table instance
- *
- * @var \Cake\ORM\Table
- */
- protected $_table;
- /**
- * Default config
- *
- * These are merged with user-provided configuration when the behavior is used.
- *
- * @var array
- */
- protected $_defaultConfig = [
- 'implementedFinders' => [
- 'path' => 'findPath',
- 'children' => 'findChildren',
- 'treeList' => 'findTreeList'
- ],
- 'implementedMethods' => [
- 'childCount' => 'childCount',
- 'moveUp' => 'moveUp',
- 'moveDown' => 'moveDown',
- 'recover' => 'recover',
- 'removeFromTree' => 'removeFromTree'
- ],
- 'parent' => 'parent_id',
- 'left' => 'lft',
- 'right' => 'rght',
- 'scope' => null
- ];
- /**
- * Constructor
- *
- * @param Table $table The table this behavior is attached to.
- * @param array $config The config for this behavior.
- */
- public function __construct(Table $table, array $config = []) {
- parent::__construct($table, $config);
- $this->_table = $table;
- }
- /**
- * Before save listener.
- * Transparently manages setting the lft and rght fields if the parent field is
- * included in the parameters to be saved.
- *
- * @param \Cake\Event\Event the beforeSave event that was fired
- * @param \Cake\ORM\Entity the entity that is going to be saved
- * @return void
- * @throws \RuntimeException if the parent to set for the node is invalid
- */
- public function beforeSave(Event $event, Entity $entity) {
- $isNew = $entity->isNew();
- $config = $this->config();
- $parent = $entity->get($config['parent']);
- $primaryKey = (array)$this->_table->primaryKey();
- $dirty = $entity->dirty($config['parent']);
- if ($isNew && $parent) {
- if ($entity->get($primaryKey[0]) == $parent) {
- throw new \RuntimeException("Cannot set a node's parent as itself");
- }
- $parentNode = $this->_getNode($parent);
- $edge = $parentNode->get($config['right']);
- $entity->set($config['left'], $edge);
- $entity->set($config['right'], $edge + 1);
- $this->_sync(2, '+', ">= {$edge}");
- }
- if ($isNew && !$parent) {
- $edge = $this->_getMax();
- $entity->set($config['left'], $edge + 1);
- $entity->set($config['right'], $edge + 2);
- }
- if (!$isNew && $dirty && $parent) {
- $this->_setParent($entity, $parent);
- }
- if (!$isNew && $dirty && !$parent) {
- $this->_setAsRoot($entity);
- }
- }
- /**
- * Also deletes the nodes in the subtree of the entity to be delete
- *
- * @param \Cake\Event\Event the beforeDelete event that was fired
- * @param \Cake\ORM\Entity the entity that is going to be saved
- * @return void
- */
- public function beforeDelete(Event $event, Entity $entity) {
- $config = $this->config();
- $this->_ensureFields($entity);
- $left = $entity->get($config['left']);
- $right = $entity->get($config['right']);
- $diff = $right - $left + 1;
- if ($diff > 2) {
- $this->_table->deleteAll([
- "{$config['left']} >=" => $left + 1,
- "{$config['left']} <=" => $right - 1
- ]);
- }
- $this->_sync($diff, '-', "> {$right}");
- }
- /**
- * Sets the correct left and right values for the passed entity so it can be
- * updated to a new parent. It also makes the hole in the tree so the node
- * move can be done without corrupting the structure.
- *
- * @param \Cake\ORM\Entity $entity The entity to re-parent
- * @param mixed $parent the id of the parent to set
- * @return void
- * @throws \RuntimeException if the parent to set to the entity is not valid
- */
- protected function _setParent($entity, $parent) {
- $config = $this->config();
- $parentNode = $this->_getNode($parent);
- $this->_ensureFields($entity);
- $parentLeft = $parentNode->get($config['left']);
- $parentRight = $parentNode->get($config['right']);
- $right = $entity->get($config['right']);
- $left = $entity->get($config['left']);
- if ($parentLeft > $left && $parentLeft < $right) {
- throw new \RuntimeException(sprintf(
- 'Cannot use node "%s" as parent for entity "%s"',
- $parent,
- $entity->get($this->_table->primaryKey())
- ));
- }
- // Values for moving to the left
- $diff = $right - $left + 1;
- $targetLeft = $parentRight;
- $targetRight = $diff + $parentRight - 1;
- $min = $parentRight;
- $max = $left - 1;
- if ($left < $targetLeft) {
- //Moving to the right
- $targetLeft = $parentRight - $diff;
- $targetRight = $parentRight - 1;
- $min = $right + 1;
- $max = $parentRight - 1;
- $diff *= -1;
- }
- if ($right - $left > 1) {
- //Correcting internal subtree
- $internalLeft = $left + 1;
- $internalRight = $right - 1;
- $this->_sync($targetLeft - $left, '+', "BETWEEN {$internalLeft} AND {$internalRight}", true);
- }
- $this->_sync($diff, '+', "BETWEEN {$min} AND {$max}");
- if ($right - $left > 1) {
- $this->_unmarkInternalTree();
- }
- //Allocating new position
- $entity->set($config['left'], $targetLeft);
- $entity->set($config['right'], $targetRight);
- }
- /**
- * Updates the left and right column for the passed entity so it can be set as
- * a new root in the tree. It also modifies the ordering in the rest of the tree
- * so the structure remains valid
- *
- * @param \Cake\ORM\Entity $entity The entity to set as a new root
- * @return void
- */
- protected function _setAsRoot($entity) {
- $config = $this->config();
- $edge = $this->_getMax();
- $this->_ensureFields($entity);
- $right = $entity->get($config['right']);
- $left = $entity->get($config['left']);
- $diff = $right - $left;
- if ($right - $left > 1) {
- //Correcting internal subtree
- $internalLeft = $left + 1;
- $internalRight = $right - 1;
- $this->_sync($edge - $diff - $left, '+', "BETWEEN {$internalLeft} AND {$internalRight}", true);
- }
- $this->_sync($diff + 1, '-', "BETWEEN {$right} AND {$edge}");
- if ($right - $left > 1) {
- $this->_unmarkInternalTree();
- }
- $entity->set($config['left'], $edge - $diff);
- $entity->set($config['right'], $edge);
- }
- /**
- * Helper method used to invert the sign of the left and right columns that are
- * less than 0. They were set to negative values before so their absolute value
- * wouldn't change while performing other tree transformations.
- *
- * @return void
- */
- protected function _unmarkInternalTree() {
- $config = $this->config();
- $query = $this->_table->query();
- $this->_table->updateAll([
- $query->newExpr()->add("{$config['left']} = {$config['left']} * -1"),
- $query->newExpr()->add("{$config['right']} = {$config['right']} * -1"),
- ], [$config['left'] . ' <' => 0]);
- }
- /**
- * Custom finder method which can be used to return the list of nodes from the root
- * to a specific node in the tree. This custom finder requires that the key 'for'
- * is passed in the options containing the id of the node to get its path for.
- *
- * @param \Cake\ORM\Query $query The constructed query to modify
- * @param array $options the list of options for the query
- * @return \Cake\ORM\Query
- * @throws \InvalidArgumentException If the 'for' key is missing in options
- */
- public function findPath($query, $options) {
- if (empty($options['for'])) {
- throw new \InvalidArgumentException("The 'for' key is required for find('path')");
- }
- $config = $this->config();
- list($left, $right) = [$config['left'], $config['right']];
- $node = $this->_table->get($options['for'], ['fields' => [$left, $right]]);
- return $this->_scope($query)
- ->where([
- "$left <=" => $node->get($left),
- "$right >=" => $node->get($right)
- ]);
- }
- /**
- * Get the number of children nodes.
- *
- * @param \Cake\ORM\Entity $node The entity to count children for
- * @param boolean $direct whether to count all nodes in the subtree or just
- * direct children
- * @return integer Number of children nodes.
- */
- public function childCount(Entity $node, $direct = false) {
- $config = $this->config();
- list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']];
- if ($direct) {
- return $this->_scope($this->_table->find())
- ->where([$parent => $node->get($this->_table->primaryKey())])
- ->count();
- }
- $this->_ensureFields($node);
- return ($node->{$right} - $node->{$left} - 1) / 2;
- }
- /**
- * Get the children nodes of the current model
- *
- * Available options are:
- *
- * - for: The id of the record to read.
- * - direct: Boolean, whether to return only the direct (true), or all (false) children,
- * defaults to false (all children).
- *
- * If the direct option is set to true, only the direct children are returned (based upon the parent_id field)
- *
- * @param \Cake\ORM\Query $query
- * @param array $options Array of options as described above
- * @return \Cake\ORM\Query
- * @throws \InvalidArgumentException When the 'for' key is not passed in $options
- */
- public function findChildren($query, $options) {
- $config = $this->config();
- $options += ['for' => null, 'direct' => false];
- list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']];
- list($for, $direct) = [$options['for'], $options['direct']];
- if (empty($for)) {
- throw new \InvalidArgumentException("The 'for' key is required for find('children')");
- }
- if ($query->clause('order') === null) {
- $query->order([$left => 'ASC']);
- }
- if ($direct) {
- return $this->_scope($query)->where([$parent => $for]);
- }
- $node = $this->_getNode($for);
- return $this->_scope($query)
- ->where([
- "{$right} <" => $node->{$right},
- "{$left} >" => $node->{$left}
- ]);
- }
- /**
- * Gets a representation of the elements in the tree as a flat list where the keys are
- * the primary key for the table and the values are the display field for the table.
- * Values are prefixed to visually indicate relative depth in the tree.
- *
- * Avaliable options are:
- *
- * - keyPath: A dot separated path to fetch the field to use for the array key, or a closure to
- * return the key out of the provided row.
- * - valuePath: A dot separated path to fetch the field to use for the array value, or a closure to
- * return the value out of the provided row.
- * - spacer: A string to be used as prefix for denoting the depth in the tree for each item
- *
- * @param \Cake\ORM\Query $query
- * @param array $options Array of options as described above
- * @return \Cake\ORM\Query
- */
- public function findTreeList($query, $options) {
- return $this->_scope($query)
- ->find('threaded', ['parentField' => $this->config()['parent']])
- ->formatResults(function($results) use ($options) {
- $options += [
- 'keyPath' => $this->_table->primaryKey(),
- 'valuePath' => $this->_table->displayField(),
- 'spacer' => '_'
- ];
- return $results
- ->listNested()
- ->printer($options['valuePath'], $options['keyPath'], $options['spacer']);
- });
- }
- /**
- * Removes the current node from the tree, by positioning it as a new root
- * and re-parents all children up one level.
- *
- * Note that the node will not be deleted just moved away from its current position
- * without moving its children with it.
- *
- * @param \Cake\ORM\Entity $node The node to remove from the tree
- * @return \Cake\ORM\Entity|false the node after being removed from the tree or
- * false on error
- */
- public function removeFromTree(Entity $node) {
- return $this->_table->connection()->transactional(function() use ($node) {
- $this->_ensureFields($node);
- return $this->_removeFromTree($node);
- });
- }
- /**
- * Helper function containing the actual code for removeFromTree
- *
- * @param \Cake\ORM\Entity $node The node to remove from the tree
- * @return \Cake\ORM\Entity|false the node after being removed from the tree or
- * false on error
- */
- protected function _removeFromTree($node) {
- $config = $this->config();
- $left = $node->get($config['left']);
- $right = $node->get($config['right']);
- $parent = $node->get($config['parent']);
- $node->set($config['parent'], null);
- if ($right - $left == 1) {
- return $this->_table->save($node);
- }
- $primary = $this->_table->primaryKey();
- $this->_table->updateAll(
- [$config['parent'] => $parent],
- [$config['parent'] => $node->get($primary)]
- );
- $this->_sync(1, '-', 'BETWEEN ' . ($left + 1) . ' AND ' . ($right - 1));
- $this->_sync(2, '-', "> {$right}");
- $edge = $this->_getMax();
- $node->set($config['left'], $edge + 1);
- $node->set($config['right'], $edge + 2);
- $fields = [$config['parent'], $config['left'], $config['right']];
- $this->_table->updateAll($node->extract($fields), [$primary => $node->get($primary)]);
- foreach ($fields as $field) {
- $node->dirty($field, false);
- }
- return $node;
- }
- /**
- * Reorders the node without changing its parent.
- *
- * If the node is the first child, or is a top level node with no previous node
- * this method will return false
- *
- * @param \Cake\ORM\Entity $node The node to move
- * @param integer|boolean $number How many places to move the node, or true to move to first position
- * @throws \Cake\ORM\Error\RecordNotFoundException When node was not found
- * @return \Cake\ORM\Entity|boolean $node The node after being moved or false on failure
- */
- public function moveUp(Entity $node, $number = 1) {
- return $this->_table->connection()->transactional(function() use ($node, $number) {
- $this->_ensureFields($node);
- return $this->_moveUp($node, $number);
- });
- }
- /**
- * Helper function used with the actual code for moveUp
- *
- * @param \Cake\ORM\Entity $node The node to move
- * @param integer|boolean $number How many places to move the node, or true to move to first position
- * @throws \Cake\ORM\Error\RecordNotFoundException When node was not found
- * @return \Cake\ORM\Entity|boolean $node The node after being moved or false on failure
- */
- protected function _moveUp($node, $number) {
- $config = $this->config();
- list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']];
- if (!$number) {
- return false;
- }
- $parentLeft = 0;
- if ($node->get($parent)) {
- $parentLeft = $this->_getNode($node->get($parent))->get($left);
- }
- $edge = $this->_getMax();
- while ($number-- > 0) {
- list($nodeLeft, $nodeRight) = array_values($node->extract([$left, $right]));
- if ($parentLeft && ($nodeLeft - 1 == $parentLeft)) {
- break;
- }
- $nextNode = $this->_scope($this->_table->find())
- ->select([$left, $right])
- ->where([$right => ($nodeLeft - 1)])
- ->first();
- if (!$nextNode) {
- break;
- }
- $this->_sync($edge - $nextNode->{$left} + 1, '+', "BETWEEN {$nextNode->{$left}} AND {$nextNode->{$right}}");
- $this->_sync($nodeLeft - $nextNode->{$left}, '-', "BETWEEN {$nodeLeft} AND {$nodeRight}");
- $this->_sync($edge - $nextNode->{$left} - ($nodeRight - $nodeLeft), '-', "> {$edge}");
- $newLeft = $nodeLeft;
- if ($nodeLeft >= $nextNode->{$left} || $nodeLeft <= $nextNode->{$right}) {
- $newLeft -= $edge - $nextNode->{$left} + 1;
- }
- $newLeft = $nodeLeft - ($nodeLeft - $nextNode->{$left});
- $node->set($left, $newLeft);
- $node->set($right, $newLeft + ($nodeRight - $nodeLeft));
- }
- $node->dirty($left, false);
- $node->dirty($right, false);
- return $node;
- }
- /**
- * Reorders the node without changing the parent.
- *
- * If the node is the last child, or is a top level node with no subsequent node
- * this method will return false
- *
- * @param \Cake\ORM\Entity $node The node to move
- * @param integer|boolean $number How many places to move the node or true to move to last position
- * @throws \Cake\ORM\Error\RecordNotFoundException When node was not found
- * @return \Cake\ORM\Entity|boolean the entity after being moved or false on failure
- */
- public function moveDown(Entity $node, $number = 1) {
- return $this->_table->connection()->transactional(function() use ($node, $number) {
- $this->_ensureFields($node);
- return $this->_moveDown($node, $number);
- });
- }
- /**
- * Helper function used with the actual code for moveDown
- *
- * @param \Cake\ORM\Entity $node The node to move
- * @param integer|boolean $number How many places to move the node, or true to move to last position
- * @throws \Cake\ORM\Error\RecordNotFoundException When node was not found
- * @return \Cake\ORM\Entity|boolean $node The node after being moved or false on failure
- */
- protected function _moveDown($node, $number) {
- $config = $this->config();
- list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']];
- if (!$number) {
- return false;
- }
- $parentRight = 0;
- if ($node->get($parent)) {
- $parentRight = $this->_getNode($node->get($parent))->get($right);
- }
- if ($number === true) {
- $number = PHP_INT_MAX;
- }
- $edge = $this->_getMax();
- while ($number-- > 0) {
- list($nodeLeft, $nodeRight) = array_values($node->extract([$left, $right]));
- if ($parentRight && ($nodeRight + 1 == $parentRight)) {
- break;
- }
- $nextNode = $this->_scope($this->_table->find())
- ->select([$left, $right])
- ->where([$left => $nodeRight + 1])
- ->first();
- if (!$nextNode) {
- break;
- }
- $this->_sync($edge - $nodeLeft + 1, '+', "BETWEEN {$nodeLeft} AND {$nodeRight}");
- $this->_sync($nextNode->{$left} - $nodeLeft, '-', "BETWEEN {$nextNode->{$left}} AND {$nextNode->{$right}}");
- $this->_sync($edge - $nodeLeft - ($nextNode->{$right} - $nextNode->{$left}), '-', "> {$edge}");
- $newLeft = $edge + 1;
- if ($newLeft >= $nextNode->{$left} || $newLeft <= $nextNode->{$right}) {
- $newLeft -= $nextNode->{$left} - $nodeLeft;
- }
- $newLeft -= $nextNode->{$right} - $nextNode->{$left} - 1;
- $node->set($left, $newLeft);
- $node->set($right, $newLeft + ($nodeRight - $nodeLeft));
- }
- $node->dirty($left, false);
- $node->dirty($right, false);
- return $node;
- }
- /**
- * Returns a single node from the tree from its primary key
- *
- * @param mixed $id
- * @return Cake\ORM\Entity
- * @throws \Cake\ORM\Error\RecordNotFoundException When node was not found
- */
- protected function _getNode($id) {
- $config = $this->config();
- list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']];
- $primaryKey = $this->_table->primaryKey();
- $node = $this->_scope($this->_table->find())
- ->select([$parent, $left, $right])
- ->where([$primaryKey => $id])
- ->first();
- if (!$node) {
- throw new \Cake\ORM\Error\RecordNotFoundException("Node \"{$id}\" was not found in the tree.");
- }
- return $node;
- }
- /**
- * Recovers the lft and right column values out of the hirearchy defined by the
- * parent column.
- *
- * @return void
- */
- public function recover() {
- $this->_table->connection()->transactional(function() {
- $this->_recoverTree();
- });
- }
- /**
- * Recursive method used to recover a single level of the tree
- *
- * @param integer $counter The Last left column value that was assigned
- * @param mixed $parentId the parent id of the level to be recovered
- * @return integer Ne next value to use for the left column
- */
- protected function _recoverTree($counter = 0, $parentId = null) {
- $config = $this->config();
- list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']];
- $pk = (array)$this->_table->primaryKey();
- $query = $this->_scope($this->_table->query())
- ->select($pk)
- ->where(function($exp) use ($parentId, $parent) {
- return $parentId === null ? $exp->isNull($parent) : $exp->eq($parent, $parentId);
- })
- ->order($pk)
- ->hydrate(false)
- ->bufferResults(false);
- $leftCounter = $counter;
- foreach ($query as $row) {
- $counter++;
- $counter = $this->_recoverTree($counter, $row[$pk[0]]);
- }
- if ($parentId === null) {
- return $counter;
- }
- $this->_table->updateAll(
- [$left => $leftCounter, $right => $counter + 1],
- [$pk[0] => $parentId]
- );
- return $counter + 1;
- }
- /**
- * Returns the maximum index value in the table.
- *
- * @return integer
- */
- protected function _getMax() {
- $config = $this->config();
- $field = $config['right'];
- $edge = $this->_scope($this->_table->find())
- ->select([$field])
- ->order([$field => 'DESC'])
- ->first();
- if (empty($edge->{$field})) {
- return 0;
- }
- return $edge->{$field};
- }
- /**
- * Auxiliary function used to automatically alter the value of both the left and
- * right columns by a certain amount that match the passed conditions
- *
- * @param integer $shift the value to use for operating the left and right columns
- * @param string $dir The operator to use for shifting the value (+/-)
- * @param string $conditions a SQL snipped to be used for comparing left or right
- * against it.
- * @param boolean $mark whether to mark the updated values so that they can not be
- * modified by future calls to this function.
- * @return void
- */
- protected function _sync($shift, $dir, $conditions, $mark = false) {
- $config = $this->config();
- foreach ([$config['left'], $config['right']] as $field) {
- $query = $this->_scope($this->_table->query());
- $mark = $mark ? '*-1' : '';
- $template = sprintf('%s = (%s %s %s)%s', $field, $field, $dir, $shift, $mark);
- $query->update()->set($query->newExpr()->add($template));
- $query->where("{$field} {$conditions}");
- $query->execute();
- }
- }
- /**
- * Alters the passed query so that it only returns scoped records as defined
- * in the tree configuration.
- *
- * @param \Cake\ORM\Query $query the Query to modify
- * @return \Cake\ORM\Query
- */
- protected function _scope($query) {
- $config = $this->config();
- if (is_array($config['scope'])) {
- return $query->where($config['scope']);
- } elseif (is_callable($config['scope'])) {
- return $config['scope']($query);
- }
- return $query;
- }
- /**
- * Ensures that the provided entity contains non-empty values for the left and
- * right fields
- *
- * @param \Cake\ORM\Entity $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->_table->primaryKey()), $fields);
- $entity->set($fresh->extract($fields), ['guard' => false]);
- foreach ($fields as $field) {
- $entity->dirty($field, false);
- }
- }
- }
|