diff --git a/src/bitExpert/Disco/AnnotationBeanFactory.php b/src/bitExpert/Disco/AnnotationBeanFactory.php index ac2a708..3b39b6b 100644 --- a/src/bitExpert/Disco/AnnotationBeanFactory.php +++ b/src/bitExpert/Disco/AnnotationBeanFactory.php @@ -8,12 +8,12 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -declare(strict_types=1); +declare(strict_types = 1); namespace bitExpert\Disco; +use bitExpert\Disco\Proxy\Configuration\AliasContainerInterface; use bitExpert\Disco\Proxy\Configuration\ConfigurationFactory; -use Exception; /** * {@link \bitExpert\Disco\BeanFactory} implementation. @@ -35,7 +35,7 @@ class AnnotationBeanFactory implements BeanFactory */ protected $config; /** - * @var object + * @var AliasContainerInterface */ protected $beanStore; @@ -64,19 +64,22 @@ public function get($id) { $instance = null; - $id = $this->normalizeBeanId($id); - if ($this->beanIdExists($id)) { - try { - $instance = call_user_func([$this->beanStore, $id]); - } catch (\Throwable $e) { - $message = sprintf( - 'Exception occured while instanciating "%s": %s', - $id, - $e->getMessage() - ); - - throw new BeanException($message, 0, $e); + try { + if (is_callable([$this->beanStore, $id])) { + $instance = $this->beanStore->$id(); } + + if ($this->beanStore->hasAlias($id)) { + $instance = $this->beanStore->getAlias($id); + } + } catch (\Throwable $e) { + $message = sprintf( + 'Exception occured while instanciating "%s": %s', + $id, + $e->getMessage() + ); + + throw new BeanException($message, 0, $e); } if (null === $instance) { @@ -91,8 +94,7 @@ public function get($id) */ public function has($id) { - $id = $this->normalizeBeanId($id); - return $this->beanIdExists($id); + return is_callable([$this->beanStore, $id]) || $this->beanStore->hasAlias($id); } /** @@ -124,40 +126,4 @@ protected function initBeanStore($configClassName, array $parameters, BeanFactor $configFactory = new ConfigurationFactory($config); return $configFactory->createInstance($configClassName, $parameters); } - - /** - * Helper method to "normalize" bean identifiers. Since the bean identifier is basically a method name - * of the config class we can only support a subset of characters, namely alphabetic characters, digits - * and underscore. - * - * @param string $id - * @return string - */ - protected function normalizeBeanId($id) - { - // filter out all invalid characters - $id = preg_replace('#[^a-zA-Z0-9_]#', '', $id); - // prepend underscore when first character is neither an alphabetic character nor a underscore - if (!preg_match('#^[a-zA-Z_]#', $id)) { - $id = '_' . $id; - } - - return $id; - } - - /** - * Returns true if the container can return an entry for the given identifier. - * Returns false otherwise. Expects $id to be normalized! - * - * @param string $id Identifier of the entry to look for. - * @return boolean - */ - protected function beanIdExists($id) - { - if (empty($id) or !is_string($id)) { - return false; - } - - return is_callable([$this->beanStore, $id]); - } } diff --git a/src/bitExpert/Disco/Annotations/Bean.php b/src/bitExpert/Disco/Annotations/Bean.php index 7cc87a6..11fb5b4 100644 --- a/src/bitExpert/Disco/Annotations/Bean.php +++ b/src/bitExpert/Disco/Annotations/Bean.php @@ -12,6 +12,8 @@ namespace bitExpert\Disco\Annotations; +use Doctrine\Common\Annotations\Annotation\Attribute; +use Doctrine\Common\Annotations\Annotation\Attributes; use Doctrine\Common\Annotations\AnnotationException; /** @@ -21,6 +23,7 @@ * @Attribute("scope", type = "string"), * @Attribute("singleton", type = "bool"), * @Attribute("lazy", type = "bool"), + * @Attribute("alias", type = "string"), * }) */ class Bean @@ -52,6 +55,7 @@ public function __construct(array $attributes = []) $this->scope = self::SCOPE_REQUEST; $this->singleton = true; $this->lazy = false; + $this->alias = ''; if (isset($attributes['value'])) { if (isset($attributes['value']['scope']) && (strtolower($attributes['value']['scope']) === 'session')) { @@ -65,6 +69,10 @@ public function __construct(array $attributes = []) if (isset($attributes['value']['lazy'])) { $this->lazy = $this->parseBooleanValue($attributes['value']['lazy']); } + + if (isset($attributes['value']['alias'])) { + $this->alias = $attributes['value']['alias']; + } } } @@ -108,6 +116,15 @@ public function isLazy() : bool return $this->lazy; } + /** + * Returns the alias for the bean instance. Returns an empty string when no alias was set. + * @return string + */ + public function getAlias(): string + { + return $this->alias; + } + /** * Helper function to cast a string value to a boolean representation. * diff --git a/src/bitExpert/Disco/Proxy/Configuration/AliasContainerInterface.php b/src/bitExpert/Disco/Proxy/Configuration/AliasContainerInterface.php new file mode 100644 index 0000000..39f3987 --- /dev/null +++ b/src/bitExpert/Disco/Proxy/Configuration/AliasContainerInterface.php @@ -0,0 +1,42 @@ +setVisibility(self::VISIBILITY_PRIVATE); + $this->setDocBlock('@var array contains a list of aliases and their bean references'); + } +} diff --git a/src/bitExpert/Disco/Proxy/Configuration/ConfigurationGenerator.php b/src/bitExpert/Disco/Proxy/Configuration/ConfigurationGenerator.php index 4579554..e34d7e9 100644 --- a/src/bitExpert/Disco/Proxy/Configuration/ConfigurationGenerator.php +++ b/src/bitExpert/Disco/Proxy/Configuration/ConfigurationGenerator.php @@ -18,7 +18,9 @@ use bitExpert\Disco\Annotations\Parameters; use bitExpert\Disco\Proxy\Configuration\MethodGenerator\BeanMethod; use bitExpert\Disco\Proxy\Configuration\MethodGenerator\Constructor; +use bitExpert\Disco\Proxy\Configuration\MethodGenerator\GetAlias; use bitExpert\Disco\Proxy\Configuration\MethodGenerator\GetParameter; +use bitExpert\Disco\Proxy\Configuration\MethodGenerator\HasAlias; use bitExpert\Disco\Proxy\Configuration\MethodGenerator\MagicSleep; use Doctrine\Common\Annotations\AnnotationReader; use Doctrine\Common\Annotations\AnnotationRegistry; @@ -78,6 +80,7 @@ public function generate(ReflectionClass $originalClass, ClassGenerator $classGe $sessionBeansProperty = new SessionBeansProperty(); $postProcessorsProperty = new BeanPostProcessorsProperty(); $parameterValuesProperty = new ParameterValuesProperty(); + $aliasesProperty = new AliasesProperty(); $getParameterMethod = new GetParameter($originalClass, $parameterValuesProperty); try { @@ -96,12 +99,15 @@ public function generate(ReflectionClass $originalClass, ClassGenerator $classGe } $classGenerator->setExtendedClass($originalClass->getName()); + $classGenerator->setImplementedInterfaces([AliasContainerInterface::class]); $classGenerator->addPropertyFromGenerator($forceLazyInitProperty); $classGenerator->addPropertyFromGenerator($sessionBeansProperty); $classGenerator->addPropertyFromGenerator($postProcessorsProperty); $classGenerator->addPropertyFromGenerator($parameterValuesProperty); + $classGenerator->addPropertyFromGenerator($aliasesProperty); $postProcessorMethods = []; + $aliases = []; $methods = $originalClass->getMethods(ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED); foreach ($methods as $method) { if (null !== $this->reader->getMethodAnnotation($method, BeanPostProcessor::class)) { @@ -121,6 +127,11 @@ public function generate(ReflectionClass $originalClass, ClassGenerator $classGe ); } + // if alias is defined append it to the aliases list + if ($beanAnnotation->getAlias() !== '' && !isset($aliases[$beanAnnotation->getAlias()])) { + $aliases[$beanAnnotation->getAlias()] = $method->getName(); + } + /* @var \bitExpert\Disco\Annotations\Parameters $parametersAnnotation */ $parametersAnnotation = $this->reader->getMethodAnnotation($method, Parameters::class); if (null === $parametersAnnotation) { @@ -168,6 +179,8 @@ public function generate(ReflectionClass $originalClass, ClassGenerator $classGe $classGenerator->addMethodFromGenerator($proxyMethod); } + $aliasesProperty->setDefaultValue($aliases); + $classGenerator->addMethodFromGenerator( new Constructor( $originalClass, @@ -183,5 +196,17 @@ public function generate(ReflectionClass $originalClass, ClassGenerator $classGe $sessionBeansProperty ) ); + $classGenerator->addMethodFromGenerator( + new GetAlias( + $originalClass, + $aliasesProperty + ) + ); + $classGenerator->addMethodFromGenerator( + new HasAlias( + $originalClass, + $aliasesProperty + ) + ); } } diff --git a/src/bitExpert/Disco/Proxy/Configuration/MethodGenerator/GetAlias.php b/src/bitExpert/Disco/Proxy/Configuration/MethodGenerator/GetAlias.php new file mode 100644 index 0000000..f0c6fa3 --- /dev/null +++ b/src/bitExpert/Disco/Proxy/Configuration/MethodGenerator/GetAlias.php @@ -0,0 +1,51 @@ +setType('string'); + + $body = 'if ($this->hasAlias($'.$aliasParameter->getName().')) {' . PHP_EOL; + $body .= ' $methodname = $this->' . $aliasesProperty->getName() . '[$'.$aliasParameter->getName().'];' . + PHP_EOL; + $body .= ' return $this->$methodname();' . PHP_EOL; + $body .= '}' . PHP_EOL . PHP_EOL; + $body .= 'throw new '.BeanNotFoundException::class.'(sprintf(\'Alias "%s" is not defined!\', $'. + $aliasParameter->getName().'));' . PHP_EOL; + + $this->setParameter($aliasParameter); + $this->setVisibility(self::VISIBILITY_PUBLIC); + $this->setBody($body); + } +} diff --git a/src/bitExpert/Disco/Proxy/Configuration/MethodGenerator/HasAlias.php b/src/bitExpert/Disco/Proxy/Configuration/MethodGenerator/HasAlias.php new file mode 100644 index 0000000..29716c8 --- /dev/null +++ b/src/bitExpert/Disco/Proxy/Configuration/MethodGenerator/HasAlias.php @@ -0,0 +1,46 @@ +setType('string'); + + $this->setParameter($aliasParameter); + $this->setVisibility(self::VISIBILITY_PUBLIC); + $this->setReturnType('bool'); + $this->setBody( + 'return !empty($'.$aliasParameter->getName().') && '. + 'isset($this->' . $aliasesProperty->getName() . '[$'.$aliasParameter->getName().']);' + ); + } +} diff --git a/src/bitExpert/Disco/Proxy/Configuration/MethodGenerator/MagicSleep.php b/src/bitExpert/Disco/Proxy/Configuration/MethodGenerator/MagicSleep.php index 6480f23..3dd15b5 100644 --- a/src/bitExpert/Disco/Proxy/Configuration/MethodGenerator/MagicSleep.php +++ b/src/bitExpert/Disco/Proxy/Configuration/MethodGenerator/MagicSleep.php @@ -25,14 +25,14 @@ class MagicSleep extends MagicMethodGenerator * Creates a new {@link \bitExpert\Disco\Proxy\Configuration\MethodGenerator\MagicSleep}. * * @param ReflectionClass $originalClass - * @param SessionBeansProperty $sessionBeansProperty + * @param SessionBeansProperty $aliasesProperty */ - public function __construct(ReflectionClass $originalClass, SessionBeansProperty $sessionBeansProperty) + public function __construct(ReflectionClass $originalClass, SessionBeansProperty $aliasesProperty) { parent::__construct($originalClass, '__sleep'); $this->setBody( - 'return ["' . $sessionBeansProperty->getName() . '"];' + 'return ["' . $aliasesProperty->getName() . '"];' ); } } diff --git a/tests/bitExpert/Disco/AnnotationBeanFactoryUnitTest.php b/tests/bitExpert/Disco/AnnotationBeanFactoryUnitTest.php index eca197d..f8c5b86 100644 --- a/tests/bitExpert/Disco/AnnotationBeanFactoryUnitTest.php +++ b/tests/bitExpert/Disco/AnnotationBeanFactoryUnitTest.php @@ -15,7 +15,7 @@ use bitExpert\Disco\Config\BeanConfiguration; use bitExpert\Disco\Config\BeanConfigurationSubclass; use bitExpert\Disco\Config\BeanConfigurationTrait; -use bitExpert\Disco\Config\BeanConfigurationWithNormalizedIds; +use bitExpert\Disco\Config\BeanConfigurationWithAliases; use bitExpert\Disco\Config\BeanConfigurationWithParameterizedPostProcessor; use bitExpert\Disco\Config\BeanConfigurationWithParameters; use bitExpert\Disco\Config\BeanConfigurationWithPostProcessor; @@ -540,25 +540,37 @@ public function retrievingBeanWithStringInjected() /** * @test - * @dataProvider beannameProvider + * @dataProvider beanAliasProvider */ - public function retrievingBeanWithNormalizedBeanId($beanId, $beanType) + public function retrievingBeanByAlias($beanId, $beanType) { - $this->beanFactory = new AnnotationBeanFactory(BeanConfigurationWithNormalizedIds::class); + $this->beanFactory = new AnnotationBeanFactory(BeanConfigurationWithAliases::class); BeanFactoryRegistry::register($this->beanFactory); $bean = $this->beanFactory->get($beanId); $this->assertInstanceOf($beanType, $bean); } - public function beannameProvider() + /** + * @test + */ + public function retrievingProtectedBeanByAlias() + { + $this->beanFactory = new AnnotationBeanFactory(BeanConfigurationWithAliases::class); + BeanFactoryRegistry::register($this->beanFactory); + + $this->assertFalse($this->beanFactory->has('internalServiceWithAlias')); + $this->assertTrue($this->beanFactory->has('aliasIsPublicForInternalService')); + $this->assertInstanceOf(SampleService::class, $this->beanFactory->get('aliasIsPublicForInternalService')); + } + + public function beanAliasProvider() { return [ ['\my\Custom\Namespace', SampleService::class], ['my::Custom::Namespace', SampleService::class], - ['*myCustomNamespace*', SampleService::class], - ['Bean_With_Underscores', SampleService::class], - ['1Bean', SampleService::class], + ['Alias_With_Underscore', SampleService::class], + ['123456', SampleService::class], ]; } } diff --git a/tests/bitExpert/Disco/Annotations/BeanUnitTest.php b/tests/bitExpert/Disco/Annotations/BeanUnitTest.php index 2189f11..4ce8617 100644 --- a/tests/bitExpert/Disco/Annotations/BeanUnitTest.php +++ b/tests/bitExpert/Disco/Annotations/BeanUnitTest.php @@ -28,6 +28,7 @@ public function emptyAttributesArraySetsDefaultValues() $this->assertFalse($bean->isSession()); $this->assertTrue($bean->isSingleton()); $this->assertFalse($bean->isLazy()); + $this->assertEmpty($bean->getAlias()); } /** @@ -171,4 +172,14 @@ public function markingBeanAsNonLazyWithInt() $this->assertFalse($bean->isLazy()); } + + /** + * @test + */ + public function aliasingBean() + { + $bean = new Bean(['value' => ['alias' => 'someAlias']]); + + $this->assertSame($bean->getAlias(), 'someAlias'); + } } diff --git a/tests/bitExpert/Disco/Config/BeanConfigurationWithAliases.php b/tests/bitExpert/Disco/Config/BeanConfigurationWithAliases.php new file mode 100644 index 0000000..6c31ce3 --- /dev/null +++ b/tests/bitExpert/Disco/Config/BeanConfigurationWithAliases.php @@ -0,0 +1,66 @@ +