Skip to content

Instantly share code, notes, and snippets.

@zdenekdrahos
Last active August 29, 2015 14:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zdenekdrahos/b65cc64ec7e2801ce92e to your computer and use it in GitHub Desktop.
Save zdenekdrahos/b65cc64ec7e2801ce92e to your computer and use it in GitHub Desktop.
<?php
namespace Diff\Patcher;
use Diff\Comparer\ValueComparer;
class MapAccumulator
{
private $base;
private $currentKey;
private $comparer;
private $errorHandler;
public function __construct(array $base, ValueComparer $c, callable $errorHandler)
{
$this->base = $base;
$this->comparer = $c;
$this->errorHandler = $errorHandler;
}
public function setCurrentKey($key)
{
$this->currentKey = $key;
}
public function addError($message)
{
$this->errorHandler->__invoke($message);
}
public function existsDiffOperation()
{
return array_key_exists($this->currentKey, $this->base);
}
public function isOperationChanged($diffOp)
{
return !$this->comparer->valuesAreEqual($this->base[$this->currentKey], $diffOp->getOldValue());
}
public function replaceOldOperation($diffOp)
{
$newValue = $diffOp->getNewValue();
$this->setOperation($newValue);
}
public function getCurrentOperation()
{
return $this->base[$this->currentKey];
}
public function setOperation($value)
{
$this->base[$this->currentKey] = $value;
}
public function removeCurrentOperation()
{
unset($this->base[$this->currentKey]);
}
public function getResult()
{
return $this->base;
}
}
<?php
namespace Diff\Patcher;
use Diff\DiffOp\Diff\Diff;
use Diff\Patcher\ListPatcher;
class MapOrListPatcher implements Patcher
{
private $listPatcher;
private $mapPatcher;
public function __construct(ListPatcher $l, Patcher $m)
{
$this->listPatcher = $l;
$this->mapPatcher = $m;
}
public function patch(array $base, Diff $diff)
{
if ($diff->looksAssociative()) {
return $this->mapPatcher->patch($base, $diff);
} else {
return $this->listPatcher->patch($base, $diff);
}
}
}
<?php
namespace Diff\Patcher;
use Diff\Comparer\StrictComparer;
use Diff\Comparer\ValueComparer;
use Diff\DiffOp\Diff\Diff;
/**
* Map patcher.
*
* @since 0.4
*
* @licence GNU GPL v2+
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
*/
class MapPatcher extends ThrowingPatcher {
/**
* @var Patcher
*/
private $mapListPatcher;
/**
* @var ValueComparer
*/
private $comparer;
/**
* @since 0.4
*
* @param bool $throwErrors
* @param Patcher|null $listPatcher The patcher that will be used for lists in the value
*/
public function __construct( $throwErrors = false, Patcher $listPatcher = null ) {
parent::__construct( $throwErrors );
if ( $listPatcher === null ) {
$listPatcher = new ListPatcher( $throwErrors );
}
$this->mapListPatcher = new MapOrListPatcher($listPatcher, $this);
$this->comparer = new StrictComparer();
}
/**
* @see Patcher::patch
*
* Applies the provided diff to the provided array and returns the result.
* The array is treated as a map, ie keys are held into account.
*
* It is possible to pass in non-associative diffs (those for which isAssociative)
* returns false, however the likely intended behavior can be obtained via
* a list patcher.
*
* @since 0.4
*
* @param array $base
* @param Diff $diff
*
* @return array
* @throws PatcherException
*/
public function patch( array $base, Diff $diff ) {
$errorHandler = function ($message) {
$this->handleError($message);
};
$accumulator = new MapAccumulator($base, $this->comparer, $errorHandler);
$strategies = new OperationStrategies($accumulator, $this->mapListPatcher);
static $factory = array(
'Diff\DiffOp\DiffOpAdd' => 'add',
'Diff\DiffOp\Diff\Diff' => 'diff',
'Diff\DiffOp\DiffOpRemove' => 'remove',
'Diff\DiffOp\DiffOpChange' => 'change',
);
foreach ($diff as $key => $diffOp) {
$type = get_class($diffOp);
$method = array_key_exists($type, $factory) ? $factory[$type] : 'unknownOperation';
$accumulator->setCurrentKey($key);
$strategies->$method($diffOp);
}
return $accumulator->getResult();
}
/**
* Sets the value comparer that should be used to determine if values are equal.
*
* @since 0.6
*
* @param ValueComparer $comparer
*/
public function setValueComparer( ValueComparer $comparer ) {
$this->comparer = $comparer;
}
}
<?php
namespace Diff\Patcher;
use Diff\DiffOp\DiffOpAdd;
use Diff\DiffOp\Diff\Diff;
use Diff\DiffOp\DiffOpRemove;
use Diff\DiffOp\DiffOpChange;
class OperationStrategies
{
private $accumulator;
private $mapListPatcher;
public function __construct(MapAccumulator $a, Patcher $p)
{
$this->accumulator = $a;
$this->mapListPatcher = $p;
}
public function add(DiffOpAdd $diffOp)
{
if ($this->accumulator->existsDiffOperation()) {
$this->accumulator->addError('Cannot add an element already present in a map');
return;
}
$this->accumulator->replaceOldOperation($diffOp);
}
public function diff(Diff $diffOp)
{
if (!$this->accumulator->existsDiffOperation()
&& ($diffOp->getChanges() !== array() || $diffOp->getRemovals() !== array())
) {
$this->accumulator->addError('Cannot apply a diff with non-add operations to an element not present in a map');
return;
}
if (!$this->accumulator->existsDiffOperation()) {
$this->accumulator->setOperation(array());
}
$diff = $this->mapListPatcher->patch($this->accumulator->getCurrentOperation(), $diffOp);
$this->accumulator->setOperation($diff);
}
public function remove(DiffOpRemove $diffOp)
{
if (!$this->accumulator->existsDiffOperation()) {
$this->accumulator->addError('Cannot do a non-add operation with an element not present in a map');
return;
}
if ($this->accumulator->isOperationChanged($diffOp)) {
$this->accumulator->addError('Tried removing a map value that mismatches the current value');
return;
}
$this->accumulator->removeCurrentOperation();
}
public function change(DiffOpChange $diffOp)
{
if (!$this->accumulator->existsDiffOperation()) {
$this->accumulator->addError('Cannot do a non-add operation with an element not present in a map');
return;
}
if ($this->accumulator->isOperationChanged($diffOp)) {
$this->accumulator->addError('Tried changing a map value from an invalid source value');
return;
}
$this->accumulator->replaceOldOperation($diffOp);
}
public function unknownOperation()
{
$this->accumulator->addError('Unknown diff operation cannot be applied to map element');
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment