Last active
April 27, 2025 21:44
-
-
Save skrajewski/2801cd20e8f41d4e3dbde74b8a6c9fa8 to your computer and use it in GitHub Desktop.
Revisions
-
skrajewski revised this gist
Nov 12, 2020 . 1 changed file with 7 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,7 @@ <?php declare(strict_types=1); class AmbiguousElementException extends \InvalidArgumentException { } -
skrajewski revised this gist
Nov 12, 2020 . No changes.There are no files selected for viewing
-
skrajewski revised this gist
Nov 12, 2020 . 1 changed file with 1 addition and 2 deletions.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -18,5 +18,4 @@ $cartCollection = new SynchronizedCollection($cart, $policy); $newCartCollection = $cartCollection->sync($newCart, fn($newProduct, $product) => $newProduct["id"] === $product->getId()); ``` Ideas, comments, and improvements are welcome. -
skrajewski revised this gist
Nov 12, 2020 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -1,6 +1,6 @@ # SynchronizedCollection This is the code of the component called _SynchronizedCollection_ described in the article https://szymonkrajewski.pl/synchronized-collection/. ## Example usage @@ -18,5 +18,5 @@ $cartCollection = new SynchronizedCollection($cart, $policy); $newCartCollection = $cartCollection->sync($newCart, fn($newProduct, $product) => $newProduct["id"] === $product->getId()); ``` Ideas, comments, and improvements are welcome. -
skrajewski revised this gist
Nov 12, 2020 . 1 changed file with 18 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -2,4 +2,21 @@ This is the code of component called _SynchronizedCollection_ described in the article https://szymonkrajewski.pl/synchronized-collection/. ## Example usage ```php $cart = $this->getCart(); $newCart = $request->request->get("cart"); $policy = new DynamicSynchronizationPolicy( fn($data) => $cart->addProduct(new ProductRef($data["id"], $data["quantity"])), fn($origin, $data) => $origin->setQuantity($data["quantity"]), fn($origin) => $cart->removeProduct($origin) }; $cartCollection = new SynchronizedCollection($cart, $policy); $newCartCollection = $cartCollection->sync($newCart, fn($newProduct, $product) => $newProduct["id"] === $product->getId()); ``` Ideas, comments and improvements are welcome. -
skrajewski created this gist
Nov 12, 2020 .There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,32 @@ <?php declare(strict_types=1); final class DynamicSynchronizationPolicy implements SynchronizationPolicy { private $addCallback; private $updateCallback; private $removeCallback; public function __construct(Callable $addCallback, Callable $updateCallback, Callable $removeCallback) { $this->addCallback = $addCallback; $this->updateCallback = $updateCallback; $this->removeCallback = $removeCallback; } public function handleAdd($newData) { return call_user_func($this->addCallback, $newData); } public function handleUpdate($origin, $updatedData) { return call_user_func($this->updateCallback, $origin, $updatedData); } public function handleRemove($origin) { call_user_func($this->removeCallback, $origin); } } This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,5 @@ # SynchronizedCollection This is the code of component called _SynchronizedCollection_ described in the article https://szymonkrajewski.pl/synchronized-collection/. Ideas, comments and improvements are welcome. This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,18 @@ <?php declare(strict_types=1); interface SynchronizationPolicy { /** * @return mixed added element */ public function handleAdd($data); /** * @return mixed updated element */ public function handleUpdate($origin, $data); public function handleRemove($origin); } This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,62 @@ <?php declare(strict_types=1); class SynchronizedCollection { private array $collection; private SynchronizationPolicy $policy; public function __construct(array $collection, SynchronizationPolicy $policy) { $this->collection = $collection; $this->policy = $policy; } /** * @param array $elements Array of elements to synchronize * @param callable $matcher Function that match element from collection to coresponding element * @return $this */ public function sync(array $elements, Callable $matcher): self { $copiedCollection = $this->collection; foreach ($this->collection as $key => $origin) { // find updated version of origin in provided array $updatedVersion = array_filter($elements, fn($element) => $matcher($element, $origin)); // if origin is not found, then handle removal if (count($updatedVersion) === 0) { $this->policy->handleRemove($origin); unset($copiedCollection[$key]); continue; } // if origin is found then handle update if (count($updatedVersion) === 1) { $index = array_key_first($updatedVersion); $copiedCollection[$key] = $this->policy->handleUpdate($origin, reset($updatedVersion)); unset($elements[$index]); continue; } // if origin is matched against more than one element, then throw exception throw new AmbiguousElementException("Provided array contains ambiguous element."); } array_walk($elements, function ($addData) use (&$copiedCollection) $copiedCollection[] = $this->policy->handleAdd($addData); }); return new static(array_values($copiedCollection), $this->policy); } public function toArray(): array { return $this->collection; } } This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,194 @@ <?php use Codeception\Stub\Expected; use Codeception\Test\Unit; class SynchronizedCollectionTest extends TestUnit { /** * @var UnitTester */ protected $tester; /** * @test */ public function itShouldReturnEntryCollectionIfAnotherCollectionIsTheSame() { $entry = [ ['id' => 1, 'name' => 'John'], ['id' => 2, 'name' => 'Wane'] ]; $matcher = fn($prop, $origin) => $prop['id'] === $origin['id']; $mockedPolicy = $this->makeEmpty(SynchronizationPolicy::class, [ 'handleAdd' => Expected::never(), 'handleUpdate' => Expected::exactly(2, fn ($origin, $updateData) => array_merge($origin, $updateData)), 'handleRemove' => Expected::never(), ]); $collection = new SynchronizedCollection($entry, $mockedPolicy); $synchronized = $collection->sync([ ['id' => 1, 'name' => 'John'], ['id' => 2, 'name' => 'Wane'] ], $matcher); $this->assertEquals([ ['id' => 1, 'name' => 'John'], ['id' => 2, 'name' => 'Wane'] ], $synchronized->toArray()); } /** * @test */ public function itShouldAddNewElementToSynchronizedCollection() { $entry = [ ['id' => 1, 'name' => 'John'], ['id' => 2, 'name' => 'Wane'] ]; $matcher = fn($prop, $origin) => isset($prop['id']) && $prop['id'] === $origin['id']; $mockedPolicy = $this->makeEmpty(SynchronizationPolicy::class, [ 'handleAdd' => Expected::once(fn ($newData) => array_merge(['id' => 3], $newData)), 'handleUpdate' => Expected::exactly(2, fn ($origin, $updateData) => array_merge($origin, $updateData)), 'handleRemove' => Expected::never(), ]); $collection = new SynchronizedCollection($entry, $mockedPolicy); $synchronized = $collection->sync([ ['id' => 1, 'name' => 'John'], ['id' => 2, 'name' => 'Wane'], ['name' => 'Marry'] ], $matcher); $this->assertEquals([ ['id' => 1, 'name' => 'John'], ['id' => 2, 'name' => 'Wane'], ['id' => 3, 'name' => 'Marry'], ], $synchronized->toArray()); } /** * @test */ public function itShouldRemoveExistingElementFromSynchronizedCollection() { $entry = [ ['id' => 1, 'name' => 'John'], ['id' => 2, 'name' => 'Wane'] ]; $matcher = fn($prop, $origin) => isset($prop['id']) && $prop['id'] === $origin['id']; $mockedPolicy = $this->makeEmpty(SynchronizationPolicy::class, [ 'handleAdd' => Expected::never(), 'handleUpdate' => Expected::exactly(1, fn ($origin, $updateData) => array_merge($origin, $updateData)), 'handleRemove' => Expected::once(), ]); $collection = new SynchronizedCollection($entry, $mockedPolicy); $synchronized = $collection->sync([ ['id' => 2, 'name' => 'Wane'] ], $matcher); $this->assertEquals([ ['id' => 2, 'name' => 'Wane'] ], $synchronized->toArray()); } /** * @test */ public function itShouldAddNewElementAndRemoveExistingElementFromSynchronizedCollection() { $entry = [ ['id' => 1, 'name' => 'John'], ['id' => 2, 'name' => 'Wane'] ]; $matcher = fn($prop, $origin) => isset($prop['id']) && $prop['id'] === $origin['id']; $mockedPolicy = $this->makeEmpty(SynchronizationPolicy::class, [ 'handleAdd' => Expected::once(fn ($newData) => array_merge(['id' => 3], $newData)), 'handleUpdate' => Expected::exactly(1, fn ($origin, $updateData) => array_merge($origin, $updateData)), 'handleRemove' => Expected::once(), ]); $collection = new SynchronizedCollection($entry, $mockedPolicy); $synchronized = $collection->sync([ ['name' => 'Tim'], ['id' => 2, 'name' => 'Wane'] ], $matcher); $this->assertEquals([ ['id' => 2, 'name' => 'Wane'], ['id' => 3, 'name' => 'Tim'] ], $synchronized->toArray()); } /** * @test */ public function itShouldUpdateElementAndAddNewElementAndRemoveExistingElementFromSynchronizedCollection() { $entry = [ ['id' => 1, 'name' => 'John'], ['id' => 2, 'name' => 'Wane'] ]; $matcher = fn($prop, $origin) => isset($prop['id']) && $prop['id'] === $origin['id']; $mockedPolicy = $this->makeEmpty(SynchronizationPolicy::class, [ 'handleAdd' => Expected::once(fn ($newData) => array_merge(['id' => 3], $newData)), 'handleUpdate' => Expected::exactly(1, fn ($origin, $updateData) => array_merge($origin, $updateData)), 'handleRemove' => Expected::once(), ]); $collection = new SynchronizedCollection($entry, $mockedPolicy); $synchronized = $collection->sync([ ['name' => 'Tim'], ['id' => 2, 'name' => 'Wane_UPDATED'] ], $matcher); $this->assertEquals([ ['id' => 2, 'name' => 'Wane_UPDATED'], ['id' => 3, 'name' => 'Tim'] ], $synchronized->toArray()); } /** * @test */ public function itShouldThrowExceptionIfProvidedArrayContainsAmbiguousElement() { $entry = [ ['id' => 2, 'name' => 'Wane'] ]; $matcher = fn($prop, $origin) => isset($prop['id']) && $prop['id'] === $origin['id']; $mockedPolicy = $this->makeEmpty(SynchronizationPolicy::class, [ 'handleAdd' => Expected::never(), 'handleUpdate' => Expected::never(), 'handleRemove' => Expected::never() ]); $collection = new SynchronizedCollection($entry, $mockedPolicy); $this->tester->expectThrowable(AmbiguousElementException::class, function () use ($collection, $matcher) { $collection->sync([ ['id' => 2, 'name' => 'Wane'], ['id' => 2, 'name' => 'Tim'] ], $matcher); }); } }