Created
December 29, 2014 15:27
-
-
Save JeroenDeDauw/3d37919d0f87081eef6d to your computer and use it in GitHub Desktop.
MapPatcher method split
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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 usedget_class
, because one test failed when I tryDiffOp::get_type
.