Skip to content

Instantly share code, notes, and snippets.

@JeroenDeDauw
Created December 29, 2014 15:27
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 JeroenDeDauw/3d37919d0f87081eef6d to your computer and use it in GitHub Desktop.
Save JeroenDeDauw/3d37919d0f87081eef6d to your computer and use it in GitHub Desktop.
MapPatcher method split
<?php
namespace Diff\Patcher;
use Diff\Comparer\StrictComparer;
use Diff\Comparer\ValueComparer;
use Diff\DiffOp\Diff\Diff;
use Diff\DiffOp\DiffOpAdd;
use Diff\DiffOp\DiffOpChange;
use Diff\DiffOp\DiffOpRemove;
/**
* Map patcher.
*
* @since 0.4
*
* @licence GNU GPL v2+
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
*/
class MapPatcher extends ThrowingPatcher {
/**
* @var Patcher
*/
private $listPatcher;
/**
* @var ValueComparer|null
*/
private $comparer = null;
/**
* @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->listPatcher = $listPatcher;
}
/**
* @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 ) {
foreach ( $diff as $key => $diffOp ) {
$this->applyOperation( $base, $key, $diffOp );
}
return $base;
}
private function applyOperation( &$base, $key, $diffOp ) {
if ( $diffOp instanceof DiffOpAdd ) {
$this->applyDiffOpAdd( $base, $key, $diffOp );
}
else if ( $diffOp instanceof DiffOpChange ) {
$this->applyDiffOpChange( $base, $key, $diffOp );
}
else if ( $diffOp instanceof DiffOpRemove ) {
$this->applyDiffOpRemove( $base, $key, $diffOp );
}
else if ( $diffOp instanceof Diff ) {
$this->applyDiff( $base, $key, $diffOp );
}
else {
$this->handleError( 'Unknown diff operation cannot be applied to map element' );
}
}
private function applyDiffOpAdd( &$base, $key, DiffOpAdd $diffOp ) {
if ( array_key_exists( $key, $base ) ) {
$this->handleError( 'Cannot add an element already present in a map' );
return;
}
$base[$key] = $diffOp->getNewValue();
}
private function applyDiffOpRemove( &$base, $key, DiffOpRemove $diffOp ) {
if ( !array_key_exists( $key, $base ) ) {
$this->handleError( 'Cannot do a non-add operation with an element not present in a map' );
return;
}
if ( !$this->valuesAreEqual( $base[$key], $diffOp->getOldValue() ) ) {
$this->handleError( 'Tried removing a map value that mismatches the current value' );
return;
}
unset( $base[$key] );
}
private function applyDiffOpChange( &$base, $key, DiffOpChange $diffOp ) {
if ( !array_key_exists( $key, $base ) ) {
$this->handleError( 'Cannot do a non-add operation with an element not present in a map' );
return;
}
if ( !$this->valuesAreEqual( $base[$key], $diffOp->getOldValue() ) ) {
$this->handleError( 'Tried changing a map value from an invalid source value' );
return;
}
$base[$key] = $diffOp->getNewValue();
}
private function applyDiff( &$base, $key, Diff $diffOp ) {
if ( $this->isAttemptToModifyNotExistingElement( $base, $key, $diffOp ) ) {
$this->handleError( 'Cannot apply a diff with non-add operations to an element not present in a map' );
return;
}
if ( !array_key_exists( $key, $base ) ) {
$base[$key] = array();
}
$base[$key] = $this->patchMapOrList( $base[$key], $diffOp );
}
private function isAttemptToModifyNotExistingElement( $base, $key, Diff $diffOp ) {
return !array_key_exists( $key, $base )
&& ( $diffOp->getChanges() !== array() || $diffOp->getRemovals() !== array() );
}
private function patchMapOrList( array $base, Diff $diff ) {
if ( $diff->looksAssociative() ) {
$base = $this->patch( $base, $diff );
}
else {
$base = $this->listPatcher->patch( $base, $diff );
}
return $base;
}
private function valuesAreEqual( $firstValue, $secondValue ) {
if ( $this->comparer === null ) {
$this->comparer = new StrictComparer();
}
return $this->comparer->valuesAreEqual( $firstValue, $secondValue );
}
/**
* 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;
}
}
@zdenekdrahos
Copy link

Hi, it's similar solution to mine - https://gist.github.com/zdenekdrahos/b65cc64ec7e2801ce92e. I posted reply yesterday, but I am new in the group, so reply is under review and I have no idea when it will be published.

I don't remember exactly what I wrote, but my recommendation was to extract different strategies (add, diff, remove, change, unknown operation). As you noticed instanceof is the biggest smell. I used get_class, because one test failed when I try DiffOp::get_type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment