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();
+ }
+ );
+ }
}