diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a85c7311e4..55172f6fe01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - Added Factory Adapter loaders [#11001](https://github.com/phalcon/cphalcon/issues/11001) - Added ability to sanitize URL to `Phalcon\Filter` - Add argument to interface `Phalcon\Mvc\Model\Query\BuilderInterface::join()` - `type` to specify type join +- Added `Phalcon\Mvc\Model::hasUpdated` and `Phalcon\Mvc\Model:getUpdatedFields`, way to check if fields were updated after create/save/update # [3.1.2](https://github.com/phalcon/cphalcon/releases/tag/v3.1.2) (2017-XX-XX) - Fixed PHP 7.1 issues [#12055](https://github.com/phalcon/cphalcon/issues/12055) diff --git a/phalcon/mvc/model.zep b/phalcon/mvc/model.zep index f7bcde283dc..b137b3318c7 100644 --- a/phalcon/mvc/model.zep +++ b/phalcon/mvc/model.zep @@ -113,6 +113,8 @@ abstract class Model implements EntityInterface, ModelInterface, ResultInterface protected _snapshot; + protected _oldSnapshot = []; + const OP_NONE = 0; const OP_CREATE = 1; @@ -2456,8 +2458,9 @@ abstract class Model implements EntityInterface, ModelInterface, ResultInterface */ let useDynamicUpdate = (boolean) manager->isUsingDynamicUpdate(this); + let snapshot = this->_snapshot; + if useDynamicUpdate { - let snapshot = this->_snapshot; if typeof snapshot != "array" { let useDynamicUpdate = false; } @@ -2650,9 +2653,11 @@ abstract class Model implements EntityInterface, ModelInterface, ResultInterface ], bindTypes); if success && manager->isKeepingSnapshots(this) { - if typeof this->_snapshot == "array" { - let this->_snapshot = array_merge(this->_snapshot, newSnapshot); + if typeof snapshot == "array" { + let this->_oldSnapshot = snapshot; + let this->_snapshot = array_merge(snapshot, newSnapshot); } else { + let this->_oldSnapshot = []; let this->_snapshot = newSnapshot; } } @@ -3829,6 +3834,7 @@ abstract class Model implements EntityInterface, ModelInterface, ResultInterface let snapshot = data; } + let this->_oldSnapshot = snapshot; let this->_snapshot = snapshot; } @@ -3873,6 +3879,34 @@ abstract class Model implements EntityInterface, ModelInterface, ResultInterface return count(changedFields) > 0; } + /** + * Check if a specific attribute was updated + * This only works if the model is keeping data snapshots + * + * @param string|array fieldName + */ + public function hasUpdated(var fieldName = null, boolean allFields = false) -> boolean + { + var updatedFields; + + let updatedFields = this->getUpdatedFields(); + + /** + * If a field was specified we only check it + */ + if typeof fieldName == "string" { + return in_array(fieldName, updatedFields); + } elseif typeof fieldName == "array" { + if allFields { + return array_intersect(fieldName, updatedFields) == fieldName; + } + + return count(array_intersect(fieldName, updatedFields)) > 0; + } + + return count(updatedFields) > 0; + } + /** * Returns a list of changed values. * @@ -3896,13 +3930,6 @@ abstract class Model implements EntityInterface, ModelInterface, ResultInterface throw new Exception("The record doesn't have a valid data snapshot"); } - /** - * Dirty state must be DIRTY_PERSISTENT to make the checking - */ - if this->_dirtyState != self::DIRTY_STATE_PERSISTENT { - throw new Exception("Change checking cannot be performed because the object has not been persisted or is deleted"); - } - /** * Return the models meta-data */ @@ -3956,6 +3983,61 @@ abstract class Model implements EntityInterface, ModelInterface, ResultInterface return changed; } + /** + * Returns a list of updated values. + * + * + * $robots = Robots::findFirst(); + * print_r($robots->getChangedFields()); // [] + * + * $robots->deleted = 'Y'; + * + * $robots->getChangedFields(); + * print_r($robots->getChangedFields()); // ["deleted"] + * $robots->save(); + * print_r($robots->getChangedFields()); // [] + * print_r($robots->getUpdatedFields()); // ["deleted"] + * + */ + public function getUpdatedFields() + { + var updated, name, snapshot, + oldSnapshot, value; + + let snapshot = this->_snapshot; + let oldSnapshot = this->_oldSnapshot; + + if typeof snapshot != "array" { + throw new Exception("The record doesn't have a valid data snapshot"); + } + + /** + * Dirty state must be DIRTY_PERSISTENT to make the checking + */ + if this->_dirtyState != self::DIRTY_STATE_PERSISTENT { + throw new Exception("Change checking cannot be performed because the object has not been persisted or is deleted"); + } + + let updated = []; + + for name, value in snapshot { + /** + * If some attribute is not present in the oldSnapshot, we assume the record as changed + */ + if !isset oldSnapshot[name] { + let updated[] = name; + continue; + } + + if value !== oldSnapshot[name] { + let updated[] = name; + continue; + } + } + + return updated; + } + /** * Sets if a model must use dynamic update instead of the all-field update * diff --git a/tests/unit/Mvc/Model/SnapshotTest.php b/tests/unit/Mvc/Model/SnapshotTest.php index ff8ebb3fb58..de6c989d4f1 100644 --- a/tests/unit/Mvc/Model/SnapshotTest.php +++ b/tests/unit/Mvc/Model/SnapshotTest.php @@ -316,4 +316,90 @@ function () { } ); } + + /** + * Tests get updated fields new instance exception + * + * @author Wojciech Ślawski + * @since 2017-03-28 + */ + public function testUpdatedFieldsNewException() + { + $this->specify( + 'When getting updated fields from not persistent instance there should be exception', + function () { + $robots = new Robots( + [ + 'name' => 'test', + 'year' => 2017, + 'datetime' => (new \DateTime())->format('Y-m-d'), + 'text' => 'asd', + ] + ); + + $robots->getUpdatedFields(); + }, + [ + 'throws' => ['Phalcon\Mvc\Model\Exception', 'The record doesn\'t have a valid data snapshot'], + ] + ); + } + + /** + * Tests get updated fields deleted instance exception + * + * @author Wojciech Ślawski + * @since 2017-03-28 + */ + public function testUpdatedFieldsDeleteException() + { + $this->specify( + 'When getting updated fields from deleted instance there should be exception', + function () { + $robots = new Robots( + [ + 'name' => 'test', + 'year' => 2017, + 'datetime' => (new \DateTime())->format('Y-m-d'), + 'text' => 'asd', + ] + ); + + $robots->create(); + $robots->delete(); + + $robots->getUpdatedFields(); + }, + [ + 'throws' => [ + 'Phalcon\Mvc\Model\Exception', + 'Change checking cannot be performed because the object has not been persisted or is deleted', + ], + ] + ); + } + + /** + * Tests get updated fields + * + * @author Wojciech Ślawski + * @since 2017-03-28 + */ + public function testUpdatedFields() + { + $this->specify( + 'Getting updated fields is not working correctly', + function () { + $robots = Robots::findFirst(); + $robots->name = 'changedName'; + expect($robots->getSnapshotData())->notEmpty(); + expect($robots->hasChanged('name'))->true(); + expect($robots->hasUpdated('name'))->false(); + $robots->save(); + expect($robots->getSnapshotData())->notEmpty(); + expect($robots->hasChanged('name'))->false(); + expect($robots->hasUpdated('name'))->true(); + } + ); + } }