Skip to content

Commit

Permalink
Merge pull request #22 from Taluu/collection-snapshot
Browse files Browse the repository at this point in the history
Collection snapshot
  • Loading branch information
Taluu committed Jul 19, 2014
2 parents 5b6994f + ea2952d commit 05eb4dc
Show file tree
Hide file tree
Showing 16 changed files with 293 additions and 36 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Totem
|||||| Snapshots currently natively supported :
(o)(o) - Object
| /\ | - Array
(====)
(====) - Collection
_(_,__)
(___\___)
```
Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
},

"require": {
"php": ">=5.4"
"php": ">=5.4",
"symfony/property-access": "~2.5"
},

"require-dev": {
Expand All @@ -45,4 +46,3 @@
"minimum-stability": "dev",
"prefer-stable": true
}

28 changes: 19 additions & 9 deletions docs/basic-usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ on the root type of your data. Currently, here are the built-in types :

- ``Totem\Snapshot\ArraySnapshot``, if your root data is an array
- ``Totem\Snapshot\ObjectSnapshot``, if your root data is an object.
- ``Totem\Snapshot\CollectionSnapshot``, if your root data is a collection.

.. warning::
The Collection Snapshot is not a recursive snapshot. It will snapshots its
arrays and objects, but not its collections. It will consider them as
arrays, as there is no easy way to determine what is a collection (except on
userland, like the root of the data), and what is the primary key of each
elements in said collection.

Second Step : Calculate the diff between your two snapshots
-----------------------------------------------------------
Expand All @@ -40,15 +48,17 @@ one after that modification), you may calculte the diff ::
You have then a ``Totem\Set`` object, which is already computed. It is in fact a
sort of a container, in which will be stored all the modifications that happened
since the "before" snapshot until the "after" snapshot. Each items of this
snapshot is either a ``Totem\Set`` object if the key was an object or an array
that was modified between the "before" changeset and the "after" changeset, or a
``Totem\AbstractChange`` object if your data was completely changed (if it is a
whole different array, object, or whatever else -- string, integer, boolean, you
name it). This change can be represented in 3 states :

- a ``Totem\Change\Addition`` if the key was **added** in the new state ;
- a ``Totem\Change\Modification`` if the key was **modified** ;
- a ``Totem\Change\Removal`` if the key was removed from the data
set may have two forms :

- a ``Totem\Set`` if the item was a snapshot material in the "before" snapshot
and in the "after" snapshot ;
- a ``Totem\AbstractChange`` object if your data was completely changed (if it
is a whole different array, object, or whatever else -- string, integer,
boolean, you name it). This change can be represented in 3 states :

- a ``Totem\Change\Addition`` if the key was **added** in the new state ;
- a ``Totem\Change\Modification`` if the key was **modified** ;
- a ``Totem\Change\Removal`` if the key was **removed** from the data

Third and final step : manipulate your diff
-------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
version = '1.3'

# The full version, including alpha/beta/rc tags.
release = '1.3.0'
release = '1.3.3'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
Expand Down
5 changes: 5 additions & 0 deletions src/AbstractSnapshot.php
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,11 @@ protected function normalize()
}

foreach ($this->data as &$value) {
// If the value is already an instance of a snapshot, we do not need to check if we have to snapshot it
if ($value instanceof self) {
continue;
}

switch (gettype($value)) {
case 'object':
$value = new Snapshot\ObjectSnapshot($value);
Expand Down
4 changes: 4 additions & 0 deletions src/Set.php
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,11 @@ private function computeEntry(AbstractSnapshot $old, AbstractSnapshot $new, $key
// unknown type : compare raw data
case $values['old'] !== $values['new']:
return new Modification($values['old'], $values['new']);
// PHPUnit coverage wtf start
// @codeCoverageIgnoreStart
}
// @codeCoverageIgnoreEnd
// PHPUnit coverage wtf end
}

/**
Expand Down
127 changes: 127 additions & 0 deletions src/Snapshot/CollectionSnapshot.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<?php
/**
* This file is part of the Totem package
*
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*
* @copyright Baptiste Clavié <[email protected]>
* @license http://www.opensource.org/licenses/MIT-License MIT License
*/

namespace Totem\Snapshot;

use Traversable,
ArrayAccess,
ReflectionClass,
InvalidArgumentException;

use Symfony\Component\PropertyAccess\PropertyAccess;

use Totem\AbstractSnapshot;

/**
* Represents a snapshot of a collection
*
* A collection is an array of numerical indexes of array elements
*
* BE CAREFUL, as this collection is _not_ recursive. Its elements will be
* translated as either an ArraySnapshot, or an ObjectSnapshot if it fits, but
* none of its child will be translated as a new CollectionSnapshot.
*
* @author Baptiste Clavié <[email protected]>
*/
class CollectionSnapshot extends AbstractSnapshot
{
/**
* Construct the snapshot
*
* The following options are taken into account :
* - snapshotClass : snapshot class to use for each elements. If none are
* given, the normalize() method will transform this into
* either an ArraySnapshot or an ObjectSnapshot, depending
* on the situation.
*
* @param mixed $data Either an array or a traversable, data to take a snapshot of
* @param mixed $pkey Key to use as a primary key
* @param array $options Array of options
*
* @throws InvalidArgumentException the $data is not an array or a Traversable
* @throws InvalidArgumentException the $data is not an at least 2 dimensional array
* @throws InvalidArgumentException the snapshotClass in the options is not loadable
* @throws InvalidArgumentException the snapshotClass in the options is not a valid snapshot class
* @throws InvalidArgumentException one of the elements of the collection does not have a $pkey key
*/
public function __construct($data, $pkey, array $options = [])
{
$this->data = [];
$this->raw = $data;

$snapshot = null;
$accessor = PropertyAccess::createPropertyAccessorBuilder()->enableExceptionOnInvalidIndex()->getPropertyAccessor();

if (isset($options['snapshotClass'])) {
if (!class_exists($options['snapshotClass'])) {
throw new InvalidArgumentException(sprintf('The snapshot class "%s" does not seem to be loadable', $options['snapshotClass']));
}

$refl = new ReflectionClass($options['snapshotClass']);

if (!$refl->isInstantiable() || !$refl->isSubclassOf('Totem\\AbstractSnapshot')) {
throw new InvalidArgumentException('A Snapshot Class should be instantiable and extends abstract class Totem\\AbstractSnapshot');
}

$snapshot = $options['snapshotClass'];
}

if ($data instanceof Traversable) {
$data = iterator_to_array($data);
}

if (!is_array($data)) {
throw new InvalidArgumentException(sprintf('An array or a Traversable was expected to take a snapshot of a collection, "%s" given', is_object($data) ? get_class($data) : gettype($data)));
}

foreach ($data as $key => $value)
{
$primary = $pkey;

if (!is_object($value)) {
$primary = '[' . $primary . ']';
}

if (!is_int($key)) {
throw new InvalidArgumentException('The given array / Traversable is not a collection as it contains non numeric keys');
}

if (!$accessor->isReadable($value, $primary)) {
throw new InvalidArgumentException(sprintf('The key "%s" is not defined or readable in one of the elements of the collection', $pkey));
}

$this->data[$accessor->getValue($value, $primary)] = $this->snapshot($value, $snapshot);
}

parent::normalize();
}

/**
* Snapshots a value
*
* If the value is already a snapshot, it won't be snapshotted ; otherwise,
* if the class is null, then the value will be left as is.
*
* @param mixed $value Value to snapshot
* @param string $class Class to use to snapshot the value
*
* @return mixed A snapshot if the value was snapshotted, the original value otherwise
*/
private function snapshot($value, $class = null)
{
if (null === $class || $value instanceof AbstractSnapshot) {
return $value;
}

return new $class($value);
}
}

30 changes: 27 additions & 3 deletions test/AbstractSnapshotTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@

namespace Totem;

use \ReflectionMethod;
use ReflectionMethod,
ReflectionProperty;

use \PHPUnit_Framework_TestCase;
use PHPUnit_Framework_TestCase;

class AbstractSnapshotTest extends PHPUnit_Framework_TestCase
{
Expand Down Expand Up @@ -76,7 +77,7 @@ public function testOffsetSet()
* @expectedException InvalidArgumentException
* @expectedExceptionMessage The computed data is not an array, "string" given
*/
public function testNormalizer()
public function testInvalidDataNormalizer()
{
$snapshot = new Snapshot(['data' => 'foo']);

Expand All @@ -85,6 +86,29 @@ public function testNormalizer()
$refl->invoke($snapshot);
}

/** @dataProvider normalizerProvider */
public function testNormalizer($data, $snapshotClass)
{
$snapshot = new Snapshot;

$property = new ReflectionProperty('Totem\\AbstractSnapshot', 'data');
$property->setAccessible(true);
$property->setValue($snapshot, [$data]);

$method = new ReflectionMethod('Totem\\AbstractSnapshot', 'normalize');
$method->setAccessible(true);
$method->invoke($snapshot);

$this->assertInstanceOf($snapshotClass, $property->getValue($snapshot)[0]);
}

public function normalizerProvider()
{
return [[new Snapshot, 'Totem\\Snapshot'],
[['foo' => 'bar'], 'Totem\\Snapshot\\ArraySnapshot'],
[(object) ['foo' => 'bar'], 'Totem\\Snapshot\\ObjectSnapshot']];
}

public function testDiff()
{
$snapshot = new Snapshot(['data' => []]);
Expand Down
4 changes: 1 addition & 3 deletions test/Change/AdditionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@
* @license http://www.opensource.org/licenses/MIT-License MIT License
*/

namespace Totem\ChangeSet;

use Totem\Change\Addition;
namespace Totem\Change;

class AdditionTest extends \PHPUnit_Framework_TestCase
{
Expand Down
4 changes: 1 addition & 3 deletions test/Change/ModificationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@
* @license http://www.opensource.org/licenses/MIT-License MIT License
*/

namespace Totem\ChangeSet;

use Totem\Change\Modification;
namespace Totem\Change;

class ModificationTest extends \PHPUnit_Framework_TestCase
{
Expand Down
4 changes: 1 addition & 3 deletions test/Change/RemovalTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@
* @license http://www.opensource.org/licenses/MIT-License MIT License
*/

namespace Totem\ChangeSet;

use Totem\Change\Removal;
namespace Totem\Change;

class RemovalTest extends \PHPUnit_Framework_TestCase
{
Expand Down
3 changes: 1 addition & 2 deletions test/SetTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@

use \PHPUnit_Framework_TestCase;

use Totem\Set,
Totem\Snapshot\ArraySnapshot,
use Totem\Snapshot\ArraySnapshot,
Totem\Snapshot\ObjectSnapshot;

class SetTest extends \PHPUnit_Framework_TestCase
Expand Down
2 changes: 0 additions & 2 deletions test/Snapshot.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@

namespace Totem;

use Totem\AbstractSnapshot;

class Snapshot extends AbstractSnapshot
{
public function __construct(array $args = [])
Expand Down
8 changes: 3 additions & 5 deletions test/Snapshot/ArraySnapshotTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,10 @@

namespace Totem\Snapshot;

use \stdClass,
\ReflectionMethod;
use stdClass,
ReflectionMethod;

use \PHPUnit_Framework_TestCase;

use Totem\Snapshot\ArraySnapshot;
use PHPUnit_Framework_TestCase;

class ArraySnapshotTest extends PHPUnit_Framework_TestCase
{
Expand Down
Loading

0 comments on commit 05eb4dc

Please sign in to comment.