From 3bb41ed1cb037b54bccc7c336704d9139ff2a82e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20FIDRY?= Date: Tue, 9 May 2017 22:56:56 +0100 Subject: [PATCH] POC: add support for immutable functions --- .../Object/ImmutableByCloneObject.php | 8 ++ .../Definition/Object/ImmutableObject.php | 8 ++ fixtures/Definition/Value/FakeObject.php | 8 ++ .../Entity/DummyWithImmutableFunction.php | 34 +++++++ src/Definition/Flag/ConfiguratorFlag.php | 27 ++++++ .../MethodCall/ConfiguratorMethodCall.php | 81 ++++++++++++++++ src/Definition/Object/CompleteObject.php | 8 ++ src/Definition/Object/SimpleObject.php | 4 +- .../ConfiguratorFlagHandler.php | 40 ++++++++ .../Chainable/ConfiguratorFlagParser.php | 41 ++++++++ .../ConfiguratorMethodCallProcessor.php | 94 +++++++++++++++++++ .../MethodCallWithReferenceProcessor.php | 10 +- .../Chainable/OptionalMethodCallProcessor.php | 4 +- .../Chainable/SimpleMethodCallProcessor.php | 6 +- src/Generator/GenerationContext.php | 20 ++++ src/Loader/NativeLoader.php | 5 + src/ObjectInterface.php | 7 ++ tests/Loader/LoaderIntegrationTest.php | 24 +++++ 18 files changed, 420 insertions(+), 9 deletions(-) create mode 100644 fixtures/Entity/DummyWithImmutableFunction.php create mode 100644 src/Definition/Flag/ConfiguratorFlag.php create mode 100644 src/Definition/MethodCall/ConfiguratorMethodCall.php create mode 100644 src/FixtureBuilder/Denormalizer/Fixture/SpecificationBagDenormalizer/Calls/MethodFlagHandler/ConfiguratorFlagHandler.php create mode 100644 src/FixtureBuilder/Denormalizer/FlagParser/Chainable/ConfiguratorFlagParser.php create mode 100644 src/Generator/Caller/Chainable/ConfiguratorMethodCallProcessor.php diff --git a/fixtures/Definition/Object/ImmutableByCloneObject.php b/fixtures/Definition/Object/ImmutableByCloneObject.php index a0ac5f04c..19c53c876 100644 --- a/fixtures/Definition/Object/ImmutableByCloneObject.php +++ b/fixtures/Definition/Object/ImmutableByCloneObject.php @@ -53,6 +53,14 @@ public function getInstance() return $this->instance; } + /** + * @inheritdoc + */ + public function withInstance($newInstance) + { + return new self($this->id, $newInstance); + } + public function __clone() { $this->instance = clone $this->instance; diff --git a/fixtures/Definition/Object/ImmutableObject.php b/fixtures/Definition/Object/ImmutableObject.php index 13c666b6b..13f9e5851 100644 --- a/fixtures/Definition/Object/ImmutableObject.php +++ b/fixtures/Definition/Object/ImmutableObject.php @@ -53,4 +53,12 @@ public function getInstance() { return deep_clone($this->instance); } + + /** + * @inheritdoc + */ + public function withInstance($newInstance) + { + return new self($this->id, $newInstance); + } } diff --git a/fixtures/Definition/Value/FakeObject.php b/fixtures/Definition/Value/FakeObject.php index 66e0ff0f4..abce1d887 100644 --- a/fixtures/Definition/Value/FakeObject.php +++ b/fixtures/Definition/Value/FakeObject.php @@ -35,4 +35,12 @@ public function getInstance() { $this->__call(__METHOD__, func_get_args()); } + + /** + * @inheritdoc + */ + public function withInstance($newInstance) + { + $this->__call(__METHOD__, func_get_args()); + } } diff --git a/fixtures/Entity/DummyWithImmutableFunction.php b/fixtures/Entity/DummyWithImmutableFunction.php new file mode 100644 index 000000000..86917d23f --- /dev/null +++ b/fixtures/Entity/DummyWithImmutableFunction.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Nelmio\Alice\Entity; + +class DummyWithImmutableFunction +{ + private $val; + + public function __construct(string $val) + { + $this->val = $val; + } + + public function withVal(string $val): self + { + return new self($val); + } + + public function getVal(): string + { + return $this->val; + } +} diff --git a/src/Definition/Flag/ConfiguratorFlag.php b/src/Definition/Flag/ConfiguratorFlag.php new file mode 100644 index 000000000..25c752b9b --- /dev/null +++ b/src/Definition/Flag/ConfiguratorFlag.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Nelmio\Alice\Definition\Flag; + +use Nelmio\Alice\Definition\FlagInterface; + +final class ConfiguratorFlag implements FlagInterface +{ + /** + * @inheritdoc + */ + public function __toString(): string + { + return 'configurator'; + } +} diff --git a/src/Definition/MethodCall/ConfiguratorMethodCall.php b/src/Definition/MethodCall/ConfiguratorMethodCall.php new file mode 100644 index 000000000..273b9a1f5 --- /dev/null +++ b/src/Definition/MethodCall/ConfiguratorMethodCall.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Nelmio\Alice\Definition\MethodCall; + +use Nelmio\Alice\Definition\Flag\OptionalFlag; +use Nelmio\Alice\Definition\MethodCallInterface; + +/** + * Represents a method call that is a configurator, i.e. for which the results should be kept. + */ +final class ConfiguratorMethodCall implements MethodCallInterface +{ + /** + * @var MethodCallInterface + */ + private $methodCall; + + public function __construct(MethodCallInterface $methodCall) + { + $this->methodCall = $methodCall; + } + + /** + * @inheritdoc + */ + public function withArguments(array $arguments = null): self + { + $clone = clone $this; + $clone->methodCall = $clone->methodCall->withArguments($arguments); + + return $clone; + } + + /** + * @inheritdoc + */ + public function getCaller() + { + return $this->methodCall->getCaller(); + } + + /** + * @inheritdoc + */ + public function getMethod(): string + { + return $this->methodCall->getMethod(); + } + + /** + * @inheritdoc + */ + public function getArguments() + { + return $this->methodCall->getArguments(); + } + + public function getOriginalMethodCall(): MethodCallInterface + { + return $this->methodCall; + } + + /** + * @inheritdoc + */ + public function __toString(): string + { + return $this->methodCall->__toString(); + } +} diff --git a/src/Definition/Object/CompleteObject.php b/src/Definition/Object/CompleteObject.php index 8c383b045..7236d6b9c 100644 --- a/src/Definition/Object/CompleteObject.php +++ b/src/Definition/Object/CompleteObject.php @@ -46,6 +46,14 @@ public function getInstance() return $this->object->getInstance(); } + /** + * @inheritdoc + */ + public function withInstance($newInstance) + { + throw new \LogicException('TODO'); + } + public function __clone() { $this->object = clone $this->object; diff --git a/src/Definition/Object/SimpleObject.php b/src/Definition/Object/SimpleObject.php index be4ebe462..2a5ba8ba1 100644 --- a/src/Definition/Object/SimpleObject.php +++ b/src/Definition/Object/SimpleObject.php @@ -46,9 +46,7 @@ public function __construct(string $id, $instance) } /** - * @param object $newInstance - * - * @return self + * @inheritdoc */ public function withInstance($newInstance): self { diff --git a/src/FixtureBuilder/Denormalizer/Fixture/SpecificationBagDenormalizer/Calls/MethodFlagHandler/ConfiguratorFlagHandler.php b/src/FixtureBuilder/Denormalizer/Fixture/SpecificationBagDenormalizer/Calls/MethodFlagHandler/ConfiguratorFlagHandler.php new file mode 100644 index 000000000..5370e349a --- /dev/null +++ b/src/FixtureBuilder/Denormalizer/Fixture/SpecificationBagDenormalizer/Calls/MethodFlagHandler/ConfiguratorFlagHandler.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Nelmio\Alice\FixtureBuilder\Denormalizer\Fixture\SpecificationBagDenormalizer\Calls\MethodFlagHandler; + +use Nelmio\Alice\Definition\Flag\ConfiguratorFlag; +use Nelmio\Alice\Definition\Flag\OptionalFlag; +use Nelmio\Alice\Definition\FlagInterface; +use Nelmio\Alice\Definition\MethodCall\ConfiguratorMethodCall; +use Nelmio\Alice\Definition\MethodCall\OptionalMethodCall; +use Nelmio\Alice\Definition\MethodCallInterface; +use Nelmio\Alice\FixtureBuilder\Denormalizer\Fixture\SpecificationBagDenormalizer\Calls\MethodFlagHandler; +use Nelmio\Alice\IsAServiceTrait; + +final class ConfiguratorFlagHandler implements MethodFlagHandler +{ + use IsAServiceTrait; + + /** + * @inheritdoc + */ + public function handleMethodFlags(MethodCallInterface $methodCall, FlagInterface $flag): MethodCallInterface + { + if ($flag instanceof ConfiguratorFlag) { + return new ConfiguratorMethodCall($methodCall); + } + + return $methodCall; + } +} \ No newline at end of file diff --git a/src/FixtureBuilder/Denormalizer/FlagParser/Chainable/ConfiguratorFlagParser.php b/src/FixtureBuilder/Denormalizer/FlagParser/Chainable/ConfiguratorFlagParser.php new file mode 100644 index 000000000..af04dc5ca --- /dev/null +++ b/src/FixtureBuilder/Denormalizer/FlagParser/Chainable/ConfiguratorFlagParser.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Nelmio\Alice\FixtureBuilder\Denormalizer\FlagParser\Chainable; + +use Nelmio\Alice\Definition\Flag\ConfiguratorFlag; +use Nelmio\Alice\Definition\Flag\UniqueFlag; +use Nelmio\Alice\Definition\FlagBag; +use Nelmio\Alice\FixtureBuilder\Denormalizer\FlagParser\ChainableFlagParserInterface; +use Nelmio\Alice\IsAServiceTrait; + +final class ConfiguratorFlagParser implements ChainableFlagParserInterface +{ + use IsAServiceTrait; + + /** + * @inheritdoc + */ + public function canParse(string $element): bool + { + return 'configurator' === $element; + } + + /** + * @inheritdoc + */ + public function parse(string $element): FlagBag + { + return (new FlagBag(''))->withFlag(new ConfiguratorFlag()); + } +} diff --git a/src/Generator/Caller/Chainable/ConfiguratorMethodCallProcessor.php b/src/Generator/Caller/Chainable/ConfiguratorMethodCallProcessor.php new file mode 100644 index 000000000..137ba7f41 --- /dev/null +++ b/src/Generator/Caller/Chainable/ConfiguratorMethodCallProcessor.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Nelmio\Alice\Generator\Caller\Chainable; + +use Nelmio\Alice\Definition\MethodCall\ConfiguratorMethodCall; +use Nelmio\Alice\Definition\MethodCall\SimpleMethodCall; +use Nelmio\Alice\Definition\MethodCallInterface; +use Nelmio\Alice\Definition\Object\SimpleObject; +use Nelmio\Alice\Definition\ValueInterface; +use Nelmio\Alice\FixtureInterface; +use Nelmio\Alice\Generator\Caller\CallProcessorAwareInterface; +use Nelmio\Alice\Generator\Caller\CallProcessorInterface; +use Nelmio\Alice\Generator\Caller\ChainableCallProcessorInterface; +use Nelmio\Alice\Generator\CallerInterface; +use Nelmio\Alice\Generator\GenerationContext; +use Nelmio\Alice\Generator\ResolvedFixtureSet; +use Nelmio\Alice\Generator\ValueResolverAwareInterface; +use Nelmio\Alice\Generator\ValueResolverInterface; +use Nelmio\Alice\IsAServiceTrait; +use Nelmio\Alice\ObjectInterface; +use Nelmio\Alice\Throwable\Exception\Generator\Resolver\ResolverNotFoundExceptionFactory; +use Nelmio\Alice\Throwable\Exception\Generator\Resolver\UnresolvableValueDuringGenerationExceptionFactory; +use Nelmio\Alice\Throwable\InstantiationThrowable; +use Nelmio\Alice\Throwable\ResolutionThrowable; + +final class ConfiguratorMethodCallProcessor implements ChainableCallProcessorInterface, CallProcessorAwareInterface +{ + use IsAServiceTrait; + + /** + * @var CallProcessorInterface|null + */ + private $processor; + + public function __construct(CallProcessorInterface $processor = null) + { + $this->processor = $processor; + } + + /** + * @inheritdoc + */ + public function withProcessor(CallProcessorInterface $processor): self + { + return new self($processor); + } + + /** + * @inheritdoc + */ + public function canProcess(MethodCallInterface $methodCall): bool + { + return $methodCall instanceof ConfiguratorMethodCall; + } + + /** + * @inheritdoc + */ + public function process( + ObjectInterface $object, + ResolvedFixtureSet $fixtureSet, + GenerationContext $context, + MethodCallInterface $methodCall + ): ResolvedFixtureSet + { + if (null === $this->processor) { + throw new \LogicException('TODO'); + } + + $context->markRetrieveCallResult(); + + $fixtureSet = $this->processor->process( + $object, + $fixtureSet, + $context, + $methodCall->getOriginalMethodCall() + ); + + $context->unmarkRetrieveCallResult(); + + return $fixtureSet; + } +} diff --git a/src/Generator/Caller/Chainable/MethodCallWithReferenceProcessor.php b/src/Generator/Caller/Chainable/MethodCallWithReferenceProcessor.php index f36eb34dc..05484d7e9 100644 --- a/src/Generator/Caller/Chainable/MethodCallWithReferenceProcessor.php +++ b/src/Generator/Caller/Chainable/MethodCallWithReferenceProcessor.php @@ -64,13 +64,17 @@ public function process( $reference = $methodCall->getCaller(); if (false === ($reference instanceof StaticReference)) { - //TODO: throw error + throw new \LogicException('TODO'); } - $reference->getId()::{$methodCall->getMethod()}(...$methodCall->getArguments()); + $result = $reference->getId()::{$methodCall->getMethod()}(...$methodCall->getArguments()); + + if ($context->needsCallResult()) { + $object = $object->withInstance($result); + } return $fixtureSet->withObjects( - $fixtureSet->getObjects()->with($object) + $fixtureSet->getObjects()->with($result) ); } } diff --git a/src/Generator/Caller/Chainable/OptionalMethodCallProcessor.php b/src/Generator/Caller/Chainable/OptionalMethodCallProcessor.php index a99ad50e3..a33f19cc2 100644 --- a/src/Generator/Caller/Chainable/OptionalMethodCallProcessor.php +++ b/src/Generator/Caller/Chainable/OptionalMethodCallProcessor.php @@ -76,11 +76,11 @@ public function process( ): ResolvedFixtureSet { if (false === ($methodCall instanceof OptionalMethodCall)) { - //TODO: throw error + throw new \LogicException('TODO'); } if (null === $this->processor) { - //TODO: throw error + throw new \LogicException('TODO'); } if (mt_rand(0, 99) >= $methodCall->getPercentage()) { diff --git a/src/Generator/Caller/Chainable/SimpleMethodCallProcessor.php b/src/Generator/Caller/Chainable/SimpleMethodCallProcessor.php index 4f7c2bb13..cadc85fe0 100644 --- a/src/Generator/Caller/Chainable/SimpleMethodCallProcessor.php +++ b/src/Generator/Caller/Chainable/SimpleMethodCallProcessor.php @@ -52,7 +52,11 @@ public function process( MethodCallInterface $methodCall ): ResolvedFixtureSet { - $object->getInstance()->{$methodCall->getMethod()}(...$methodCall->getArguments()); + $result = $object->getInstance()->{$methodCall->getMethod()}(...$methodCall->getArguments()); + + if ($context->needsCallResult()) { + $object = $object->withInstance($result); + } return $fixtureSet->withObjects( $fixtureSet->getObjects()->with($object) diff --git a/src/Generator/GenerationContext.php b/src/Generator/GenerationContext.php index 5596ce804..e3822b4d1 100644 --- a/src/Generator/GenerationContext.php +++ b/src/Generator/GenerationContext.php @@ -39,6 +39,11 @@ final class GenerationContext */ private $cache = []; + /** + * @var bool + */ + private $retrieveCallResult = false; + public function __construct() { $this->isFirstPass = true; @@ -86,6 +91,21 @@ public function cacheValue(string $key, $value) $this->cache[$key] = $value; } + public function markRetrieveCallResult(): void + { + $this->retrieveCallResult = true; + } + + public function unmarkRetrieveCallResult(): void + { + $this->retrieveCallResult = false; + } + + public function needsCallResult(): bool + { + return $this->retrieveCallResult; + } + /** * @param string $key * diff --git a/src/Loader/NativeLoader.php b/src/Loader/NativeLoader.php index d9e776ed5..c2a4682e0 100644 --- a/src/Loader/NativeLoader.php +++ b/src/Loader/NativeLoader.php @@ -25,8 +25,10 @@ use Nelmio\Alice\FixtureBuilder\Denormalizer\Fixture\SpecificationBagDenormalizer\Calls\MethodFlagHandler\ConfiguratorFlagHandler; use Nelmio\Alice\FixtureBuilder\Denormalizer\Fixture\SpecificationBagDenormalizer\Calls\MethodFlagHandler\OptionalFlagHandler; use Nelmio\Alice\FixtureBuilder\Denormalizer\Fixture\SpecificationBagDenormalizer\Constructor\FactoryDenormalizer; +use Nelmio\Alice\FixtureBuilder\Denormalizer\FlagParser\Chainable\ConfiguratorFlagParser; use Nelmio\Alice\Generator\Caller\CallProcessorInterface; use Nelmio\Alice\Generator\Caller\CallProcessorRegistry; +use Nelmio\Alice\Generator\Caller\Chainable\ConfiguratorMethodCallProcessor; use Nelmio\Alice\Generator\Caller\Chainable\MethodCallWithReferenceProcessor; use Nelmio\Alice\Generator\Caller\Chainable\OptionalMethodCallProcessor; use Nelmio\Alice\Generator\Caller\Chainable\SimpleMethodCallProcessor; @@ -342,6 +344,7 @@ protected function createFixtureDenormalizer(): FixtureDenormalizerInterface protected function createFlagParser(): FlagParserInterface { $registry = new FlagParserRegistry([ + new ConfiguratorFlagParser(), new ExtendFlagParser(), new OptionalFlagParser(), new TemplateFlagParser(), @@ -378,6 +381,7 @@ protected function createCallsDenormalizer(): CallsDenormalizerInterface $this->getArgumentsDenormalizer() ), [ + new ConfiguratorFlagHandler(), new OptionalFlagHandler(), ] ); @@ -586,6 +590,7 @@ protected function createCaller(): CallerInterface protected function createCallProcessor(): CallProcessorInterface { return new CallProcessorRegistry([ + new ConfiguratorMethodCallProcessor(), new MethodCallWithReferenceProcessor(), new OptionalMethodCallProcessor(), new SimpleMethodCallProcessor(), diff --git a/src/ObjectInterface.php b/src/ObjectInterface.php index a2fe694ef..0f92f9f74 100644 --- a/src/ObjectInterface.php +++ b/src/ObjectInterface.php @@ -30,4 +30,11 @@ public function getId(): string; * @return object */ public function getInstance(); + + /** + * @param object $newInstance + * + * @return static + */ + public function withInstance($newInstance); } diff --git a/tests/Loader/LoaderIntegrationTest.php b/tests/Loader/LoaderIntegrationTest.php index 173c9744e..bbab014a4 100644 --- a/tests/Loader/LoaderIntegrationTest.php +++ b/tests/Loader/LoaderIntegrationTest.php @@ -19,6 +19,7 @@ use Nelmio\Alice\DataLoaderInterface; use Nelmio\Alice\Entity\Caller\Dummy; use Nelmio\Alice\Entity\DummyWithConstructorParam; +use Nelmio\Alice\Entity\DummyWithImmutableFunction; use Nelmio\Alice\Entity\DummyWithPublicProperty; use Nelmio\Alice\Entity\DummyWithPrivateProperty; use Nelmio\Alice\Entity\DummyWithVariadicConstructorParam; @@ -3314,5 +3315,28 @@ public function provideFixturesToGenerate() ], ], ]; + + yield '[configurator] named factory' => [ + [ + DummyWithImmutableFunction::class => [ + 'dummy' => [ + '__construct' => false, + '__calls' => [ + [ + 'withVal (configurator)' => [ + 'foo' + ], + ], + ], + ], + ], + ], + [ + 'parameters' => [], + 'objects' => [ + 'dummy' => new DummyWithImmutableFunction('foo'), + ], + ], + ]; } }