NotionCommotion Posted June 10, 2018 Share Posted June 10, 2018 The below PHP object and sub-objects all have methods. After it is created, it is later modified by changing its property values and/or its sub-object property values as well as adding/deleting from the collections. How can one best determine what was changed? Or more accurately, how can one best make a snapshot of it so changes can be identified? One option is for the collection constructor's to clone the content and store it as $this->initial and for the other objects to create an array with individual properties and store it in $this->initial. Or maybe for the parent object to create a JSON representative of all of it? I then can easily use the various array functions to determine the difference. Thank you { "property1": 1, "property2": 1, "property3": 1, "collection1": { "content": [{ "property4": "foo", "property5": "blabla" }, { "property4": "foo", "property5": "blabla" }, { "property4": "foo", "property5": "blabla" } ], "property6": 123 }, "collection2": { "content": [{ "property4": "foo", "property5": "blabla" }, { "property4": "foo", "property5": "blabla" }, { "property4": "foo", "property5": "blabla" } ], "property6": 123, "subCollection1": { "content": [{ "property7": "foo", "property8": "blabla" }, { "property7": "foo", "property8": "blabla" }, { "property7": "foo", "property8": "blabla" } ], "property9": 123 } } } Quote Link to comment https://forums.phpfreaks.com/topic/307366-determine-changes-to-an-object/ Share on other sites More sharing options...
requinix Posted June 10, 2018 Share Posted June 10, 2018 Do you actually need to know the differences, or is it enough to know that it changed? What for? Do changes always happen through methods run on the parent object (eg, setters) or can someone make changes without the object knowing (eg, modifying a property directly)? Quote Link to comment https://forums.phpfreaks.com/topic/307366-determine-changes-to-an-object/#findComment-1558897 Share on other sites More sharing options...
NotionCommotion Posted June 10, 2018 Author Share Posted June 10, 2018 32 minutes ago, requinix said: What for? Probably because I am doing something that I shouldn't be doing. For better or worse, I wasn't going to start using Doctrine or similar right now, and this was my attempt of building my own reverse mapper. SubObjectMapper::read() queries the database using ORDER BY position, wraps each result in a class, and returns the array of objects to the parent mapper. ParentObjectMapper::read() wraps these objects as some other values in the main class, and returns the object to the service. The service then invokes some method on this object (or the sub-objects by using a getter) which will update a property value, add or remove a collection item, swap positions of two collection items, etc. Originally, I was going to just have individual methods in the mappers for each of these activities, but thought it might be cleaner letting the service do whatever to the object, and then having the mapper ask the object what has been changed, and update the database as applicable. To determine what has been changed, the constructors for ParentEntity, SeriesCollection, and CategoriesCollection (and similar objects in the the two collection classes) would store the initial state, and use this to return the changes. Going about it wrong? public function read($id) { $sql='SELECT a,b,c FROM t WHERE id=?'; $stmt=$this->pdo->prepare($sql); $stmt->execute([$this->properties['themesId']]); $return new ParentEntity( $stmt->fetch(), new Entities\SeriesCollection(...$this->seriesMapper->read()), new Entities\CategoriesCollection(...$this->categoryMapper->read()) ); } Quote Link to comment https://forums.phpfreaks.com/topic/307366-determine-changes-to-an-object/#findComment-1558898 Share on other sites More sharing options...
requinix Posted June 10, 2018 Share Posted June 10, 2018 I wouldn't say it's wrong. There are multiple ways to solve this problem and that's one. You might as well store a copy of the original data and compare with the current. Quote Link to comment https://forums.phpfreaks.com/topic/307366-determine-changes-to-an-object/#findComment-1558899 Share on other sites More sharing options...
NotionCommotion Posted June 10, 2018 Author Share Posted June 10, 2018 Well, that is reassuring. I am assuming you mean storing the original data received by each entity constructor and that each object and sub-object should be responsible to store their own data. Doing so will be easy enough for ParentEntity as well as for each of the individual Entities\CategoryNode objects in the arrays returned by categoryMapper->read() (shown below, and similar for series). Where I am struggling is how to deal with CategoriesCollection. It is extended from Collection shown at the bottom and receives an array of Nodes and doesn't receive "original data" the same way as the other classes. As an array, it is not passed by reference so I can copy it using $this->blueprintbut but it will be a copy of the same referenced node objects. Much appreciated. public function read() { //initiatate query... $collection=[]; foreach($stmt as $rs) { $collection[]=new Entities\CategoryNode($rs); } return $collection; } abstract class Collection implements \JsonSerializable, \ArrayAccess, \IteratorAggregate , CollectionInterface { protected $container=[]; protected $blueprint; public function __construct(Node ...$nodes) { //$nodes is an array of Node objects $this->container=$nodes; $this->blueprint=$nodes; } public function jsonSerialize() { $arr=[]; foreach($this->container as $node) $arr[]=$node; return $arr; } public function getIterator() { return new \ArrayIterator($this->container); } public function hasContent() { return !empty($this->container); } public function offsetSet($offset, $value) { if (is_null($offset)) { $this->container[] = $value; } elseif(is_int($offset) || ctype_digit($offset)) { $this->container[$offset] = $value; } else throw new CollectionException('Offset must be an integer'); } public function offsetExists($offset) { if(!is_int($offset) && !ctype_digit($offset)) throw new CollectionException('Offset must be an integer'); return isset($this->container[$offset]); //return array_key_exists($offset, $this->container); } public function offsetUnset($offset) { if(!is_int($offset) && !ctype_digit($offset)) throw new CollectionException('Offset must be an integer'); if(!isset($this->container[$offset])) throw new CollectionException('Offset does not exist'); unset($this->container[$offset]); $this->container=array_values($this->container); } public function offsetGet($offset) { return isset($this->container[$offset]) ? $this->container[$offset] : null; //return $this->_data[$offset]; } public function update($newValues) { // Common charts need $newValues->categories=[] if(count($this->container)!==count($newValues)) { throw new CollectionException('Collection counts do not match.'); } foreach($this->container as $key=>$item) { $item->update($newValues[$key]); } return $this; } public function getChanges(){ $add=[]; $diff=[]; foreach($this->container as $node) { $changes=$node->getChanges(); $add[]=$changes['add']; $diff[]=$changes['diff']; } return ['add'=>$add, 'diff'=>$diff]; } /** * Returns an array who's index is the position array given unique * * @param string $prop * @return array */ public function getPositionChanges($prop){ return array_udiff_assoc($this->container, $this->blueprint, function ($stack, $originalStack) { return is_numeric($stack->$prop) && is_numeric($originalStack->$prop) ?$stack->$prop - $originalStack->$prop :strcmp($stack->$prop, $originalStack->$prop); } ); } public function getAll(){ $this->container; } public function getIdByPosition($position){ return $this->container[$position]->id; } public function move(int $initialPosition, int $finalPosition){ if(!isset($this->container[$initialPosition]) || !isset($this->container[$finalPosition])) throw new CollectionException('Index does not exist'); $node=$this->container[$initialPosition]; unset($this->container[$initialPosition]); array_splice($this->container, $finalPosition, 0, $node); $this->container=array_values($this->container); //?? return $this->container; } } Quote Link to comment https://forums.phpfreaks.com/topic/307366-determine-changes-to-an-object/#findComment-1558900 Share on other sites More sharing options...
requinix Posted June 10, 2018 Share Posted June 10, 2018 The data it receives is the objects in the collections. If there are initial members of the collection then you'll need to track that, but otherwise the changes are what objects are added and removed. Remember the whole process is recursive, so even if an object originally in the collection is not removed, it may still have been modified. Applies to any class that can contain child objects. Quote Link to comment https://forums.phpfreaks.com/topic/307366-determine-changes-to-an-object/#findComment-1558901 Share on other sites More sharing options...
NotionCommotion Posted June 10, 2018 Author Share Posted June 10, 2018 (edited) Thanks requnix, How would you determine whether an object has been added or removed? Please see Collection::getChanges(). class ChartMapper extends Mappers { public function update($obj) { $changes=$obj->getChanges(false); //Update the database as applicable $this->seriesMapper->update($obj->getSeriesCollection()); $this->categoriesMapper->update($obj->getCategoriesCollection()); } } class CategoryMapper extends Mappers { public function update(Collection $collection) { $changes=$collection->getChanges(false);// [ 'added'=>[Node], 'deleted'=>[Node], 'changed'=>[Node] ] //Update the database as applicable } } class CategoriesCollection extends Collection {} abstract class Collection { protected $container; protected $blueprint; public function __construct(Node ...$nodes) { $this->container=$nodes; $this->blueprint=$nodes; } public function getChanges($getChildChanges=false){ //Obviously the following will not work $added=array_diff($this->container, $this->blueprint); $removed=array_diff($this->blueprint, $this->container); $common=array_intersect($this->blueprint, $this->container); $changed=[]; foreach($common as $node) { if($changes=$node->getChanges($getChildChanges)) { $changed[]=$changes; } } return ['added'=>$added, 'removed'=>$removed, 'changed'=>$changed]; } } class ChartEntity extends Entities{ public function __construct(array $properties, Entities\SeriesCollection $seriesCollection, Entities\CategoriesCollection $categoriesCollection) { $this->setValues($properties); $this->seriesCollection=$seriesCollection; $this->categoriesCollection=$categoriesCollection; $this->blueprint=$properties; } protected function getClassPropertyNames(){ return ['id', 'idPublic', 'name', 'type', 'themesId', 'theme', 'config', 'config_default', 'typeName', 'masterType', 'title', 'subtitle', 'xAxis', 'yAxis']; } public function getChanges($getChildChanges=false){ $changes=parent::getChanges(); if($getChildChanges) { $changes['series']=$this->seriesCollection->getChanges($getChildChanges); $changes['categories']=$this->categoriesCollection->getChanges($getChildChanges); } return $changes; } } class CategoryNode extends Entity { protected function getClassPropertyNames(){ return ['id', 'name', 'position']; } } abstract class Entity { protected $blueprint; public function __construct(array $properties) { $this->setValues($properties); $this->blueprint=$properties; } abstract protected function getClassPropertyNames(); protected function setValues(array $properties) { $propertyNames=array_keys($properties); $expectedProperties=$this->getClassPropertyNames(); if($error=array_diff($expectedProperties,$propertyNames)) { throw new \Exception("Values for ".implode(', ',$error)." must be provided"); } if($error=array_diff($propertyNames, $expectedProperties)) { throw new \Exception("Unexpected properties ".implode(', ',$error)." were received"); } foreach($properties as $name=>$value) { $this->name=$value; } } public function getChanges($getChildChanges=false){ $current=[]; foreach($this->getClassPropertyNames() as $name) { $current[$name]=$this->$name; } return array_diff_assoc($current, $this->blueprint); } //__get and __set... } Edited June 10, 2018 by NotionCommotion Quote Link to comment https://forums.phpfreaks.com/topic/307366-determine-changes-to-an-object/#findComment-1558903 Share on other sites More sharing options...
NotionCommotion Posted June 10, 2018 Author Share Posted June 10, 2018 Ah ha! I was caught up on moving the objects in the collection. Instead I will leave them in place and just update the position property and all is easy. Quote Link to comment https://forums.phpfreaks.com/topic/307366-determine-changes-to-an-object/#findComment-1558905 Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.