diff --git a/CHANGELOG.md b/CHANGELOG.md index 081ee974..095267e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## Added - Support for many tags (filter dictionaries) - The registry can now be autowired +- Implement CombinedDictionary. Allow to merge different dictionaries into one ## Changed - Category is now called tag #34 diff --git a/spec/Knp/DictionaryBundle/Dictionary/CombinedSpec.php b/spec/Knp/DictionaryBundle/Dictionary/CombinedSpec.php new file mode 100644 index 00000000..204e967a --- /dev/null +++ b/spec/Knp/DictionaryBundle/Dictionary/CombinedSpec.php @@ -0,0 +1,126 @@ +beConstructedWith('combined_dictionary', [ + $dictionary1, + $dictionary2, + $dictionary3, + ]); + } + + function it_is_initializable() + { + $this->shouldHaveType(Dictionary\Combined::class); + } + + function it_is_a_dictionary() + { + $this->shouldImplement(Dictionary::class); + } + + function it_access_to_value_like_an_array($dictionary1, $dictionary2, $dictionary3) + { + $dictionary1->getIterator()->willReturn(new ArrayIterator(['foo1' => 'foo10'])); + + $dictionary2->getIterator()->willReturn(new ArrayIterator(['bar1' => 'bar10'])); + + $dictionary3->getIterator()->willReturn(new ArrayIterator(['baz1' => 'baz10'])); + + $this['foo1']->shouldBe('foo10'); + $this['bar1']->shouldBe('bar10'); + $this['baz1']->shouldBe('baz10'); + } + + function it_getvalues_should_return_dictionaries_values($dictionary1, $dictionary2, $dictionary3) + { + $dictionary1->getIterator()->willReturn(new ArrayIterator([ + 'foo1' => 'foo10', + 'foo2' => 'foo20', + ])); + + $dictionary2->getIterator()->willReturn(new ArrayIterator([ + 'bar1' => 'bar10', + 'bar2' => 'bar20', + ])); + + $dictionary3->getIterator()->willReturn(new ArrayIterator([ + 'foo1' => 'baz10', + 'bar2' => 'baz20', + ])); + + $this->getKeys()->shouldReturn([ + 'foo1', + 'foo2', + 'bar1', + 'bar2', + ]); + $this->getValues()->shouldReturn([ + 'foo1' => 'baz10', + 'foo2' => 'foo20', + 'bar1' => 'bar10', + 'bar2' => 'baz20', + ]); + } + + function it_can_iterate_over_dictionaries($dictionary1, $dictionary2, $dictionary3) + { + $dictionary1->getIterator()->willReturn(new ArrayIterator([ + 'foo1' => 'foo10', + 'foo2' => 'foo20', + ])); + + $dictionary2->getIterator()->willReturn(new ArrayIterator([ + 'bar1' => 'bar10', + 'bar2' => 'bar20', + ])); + + $dictionary3->getIterator()->willReturn(new ArrayIterator([ + 'foo2' => 'baz20', + 'bar2' => 'baz20', + ])); + + $this->shouldIterateLike([ + 'foo1' => 'foo10', + 'foo2' => 'baz20', + 'bar1' => 'bar10', + 'bar2' => 'baz20', + ]); + } + + function it_sums_the_count_of_elements($dictionary1, $dictionary2, $dictionary3) + { + $dictionary1->getIterator()->willReturn(new ArrayIterator([ + 'foo1' => 'foo10', + ])); + + $dictionary2->getIterator()->willReturn(new ArrayIterator([ + 'bar1' => 'bar10', + 'bar2' => 'bar20', + ])); + + $dictionary3->getIterator()->willReturn(new ArrayIterator([ + 'baz1' => 'baz10', + 'baz2' => 'baz20', + 'baz3' => 'baz30', + 'baz4' => 'baz40', + ])); + + $this->count()->shouldReturn(7); + } + + function its_getname_should_return_dictionary_name() + { + $this->getName()->shouldReturn('combined_dictionary'); + } +} diff --git a/spec/Knp/DictionaryBundle/Dictionary/Factory/CombinedSpec.php b/spec/Knp/DictionaryBundle/Dictionary/Factory/CombinedSpec.php new file mode 100644 index 00000000..aa6ac18a --- /dev/null +++ b/spec/Knp/DictionaryBundle/Dictionary/Factory/CombinedSpec.php @@ -0,0 +1,68 @@ +beConstructedWith(new Dictionary\DictionaryRegistry()); + } + + function it_is_initializable() + { + $this->shouldHaveType(Dictionary\Factory\Combined::class); + } + + function it_is_a_factory() + { + $this->shouldHaveType(Dictionary\Factory::class); + } + + function it_supports_specific_config() + { + $this->supports(['type' => 'combined'])->shouldReturn(true); + } + + function it_creates_a_dictionary(Dictionary $dictionary1, Dictionary $dictionary2, Dictionary $dictionary3) + { + $dictionary1->getIterator()->willReturn(new ArrayIterator(['foo1' => 'foo10', 'foo2' => 'foo20'])); + $dictionary1->getName()->willReturn('dictionary1'); + + $dictionary2->getIterator()->willReturn(new ArrayIterator(['bar1' => 'bar10', 'bar2' => 'bar20'])); + $dictionary2->getName()->willReturn('dictionary2'); + + $dictionary3->getIterator()->willReturn(new ArrayIterator(['foo2' => 'baz20', 'bar2' => 'baz20'])); + $dictionary3->getName()->willReturn('dictionary3'); + + $registry = new Dictionary\DictionaryRegistry(); + $registry->add($dictionary1->getWrappedObject()); + $registry->add($dictionary2->getWrappedObject()); + $registry->add($dictionary3->getWrappedObject()); + $this->beConstructedWith($registry); + + $config = [ + 'type' => 'combined', + 'dictionaries' => [ + 'dictionary1', + 'dictionary2', + 'dictionary3', + ], + ]; + + $dictionary = $this->create('combined_dictionary', $config); + + $dictionary->getValues()->shouldReturn([ + 'foo1' => 'foo10', + 'foo2' => 'baz20', + 'bar1' => 'bar10', + 'bar2' => 'baz20', + ]); + } +} diff --git a/src/Knp/DictionaryBundle/Dictionary/Combined.php b/src/Knp/DictionaryBundle/Dictionary/Combined.php new file mode 100644 index 00000000..a3556acd --- /dev/null +++ b/src/Knp/DictionaryBundle/Dictionary/Combined.php @@ -0,0 +1,90 @@ +dictionary = new CallableDictionary($name, function () use ($dictionaries) { + $data = []; + + foreach ($dictionaries as $dictionary) { + $data = $this->merge($data, iterator_to_array($dictionary)); + } + + return $data; + }); + } + + public function getName(): string + { + return $this->dictionary->getName(); + } + + public function getValues(): array + { + return $this->dictionary->getValues(); + } + + public function getKeys(): array + { + return $this->dictionary->getKeys(); + } + + public function offsetExists($offset) + { + return $this->dictionary->offsetExists($offset); + } + + public function offsetGet($offset) + { + return $this->dictionary->offsetGet($offset); + } + + public function offsetSet($offset, $value): void + { + $this->dictionary->offsetSet($offset, $value); + } + + public function offsetUnset($offset): void + { + $this->dictionary->offsetUnset($offset); + } + + public function count(): int + { + return \count($this->dictionary->getValues()); + } + + public function getIterator() + { + return $this->dictionary->getIterator(); + } + + private function merge(array $array1, array $array2): array + { + if ($array1 === array_values($array1) && $array2 === array_values($array2)) { + return array_merge($array1, $array2); + } + + $data = []; + + foreach ([$array1, $array2] as $array) { + foreach ($array as $key => $value) { + $data[$key] = $value; + } + } + + return $data; + } +} diff --git a/src/Knp/DictionaryBundle/Dictionary/Factory/Combined.php b/src/Knp/DictionaryBundle/Dictionary/Factory/Combined.php new file mode 100644 index 00000000..4b49e016 --- /dev/null +++ b/src/Knp/DictionaryBundle/Dictionary/Factory/Combined.php @@ -0,0 +1,51 @@ +dictionaries = $dictionaries; + } + + /** + * {@inheritdoc} + */ + public function create($name, array $config): Dictionary + { + if (!isset($config['dictionaries'])) { + throw new InvalidArgumentException(sprintf( + 'Dictionary of type %s must contains a key "dictionaries".', + self::TYPE + )); + } + + $dictionaries = array_map(function ($name) { + return $this->dictionaries[$name]; + }, $config['dictionaries']); + + return new Dictionary\Combined($name, $dictionaries); + } + + /** + * {@inheritdoc} + */ + public function supports(array $config): bool + { + return isset($config['type']) ? self::TYPE === $config['type'] : false; + } +}