From b0532759fa6c158cf5951650b522740f4d62480a Mon Sep 17 00:00:00 2001 From: Boris Date: Mon, 26 Mar 2018 11:13:22 +0200 Subject: [PATCH 1/3] Add parent Services Factory --- src/ServicesFactory.php | 123 ++++++++++++------ tests/ServicesFactory/ServicesFactoryTest.php | 85 ++++++++++++ 2 files changed, 170 insertions(+), 38 deletions(-) diff --git a/src/ServicesFactory.php b/src/ServicesFactory.php index 4ffcd3c..b7f48b5 100644 --- a/src/ServicesFactory.php +++ b/src/ServicesFactory.php @@ -59,11 +59,18 @@ class ServicesFactory implements ContainerInterface */ protected $registeredAliases = []; + /** + * @var array + */ + protected $parents = []; + /** * ServicesFactory constructor. */ public function __construct() { + $this->parents[] = $this; + // init collections $this->services = (new Collection())->restrictTo(ServiceSpecsInterface::class); $this->builders = (new Collection())->restrictTo(ServiceBuilderInterface::class); @@ -342,7 +349,7 @@ public function isServiceRegistered($service) return $has; } - + /** * @param $instance * @param $serviceSpecs @@ -358,44 +365,22 @@ public function injectDependencies($instance, $serviceSpecs = null) $injector($instance, $this, $serviceSpecs); }) ; - + if ($instance instanceof InjectionAnnotationProvider) { // automated injections $reflectedInstance = new \ReflectionObject($instance); $reflectedProperties = $reflectedInstance->getProperties(); - + foreach ($reflectedProperties as $reflectedProperty) { $injection = $this->getAnnotationsReader() ->getPropertyAnnotation($reflectedProperty, Annotation\Inject::class) ; if ($injection) { if ($injection->param) { - if ($this->has('config')) { - $config = $this->get('config'); - - if ($config instanceof Config) { - $params = $config->subset('ObjectivePHP\Application\Config\Param'); - - if ($params->has($injection->param)) { - $dependency = $params->get($injection->param); - } else { - if (isset($injection->default)) { - $dependency = $injection->default; - } else { - throw new Exception(sprintf('Config instance registered as "config" does not have a "%s" param, and no default value is provided', - $injection->param)); - } - } - - } else { - throw new Exception('Service registered as "config" in this factory is no a Config instance'); - } - } else { - throw new Exception('No Config is registered as "config" in this factory'); - } + $dependency = $this->getConfigParamToInject($injection); } else if ($injection->class || !$injection->service) { $className = $injection->getDependency(); - + if (!$className) { // use phpdocumentor to get var type $docblock = DocBlockFactory::createInstance()->create($reflectedProperty); @@ -406,18 +391,13 @@ public function injectDependencies($instance, $serviceSpecs = null) Exception::MISSING_DEPENDENCY_DEFINITION); } } - + $dependency = new $className; $this->injectDependencies($dependency); } else { - $serviceName = $injection->getDependency(); - if (!$this->has($serviceName)) { - throw new Exception(sprintf('Dependent service "%s" is not registered', $serviceName), - Exception::DEPENDENCY_NOT_FOUND); - } - $dependency = $this->get($serviceName); + $dependency = $this->getServiceToInject($injection->getDependency()); } - + if ($injection->setter) { $setter = $injection->setter; $instance->$setter($dependency); @@ -425,9 +405,9 @@ public function injectDependencies($instance, $serviceSpecs = null) if (!$reflectedProperty->isPublic()) { $reflectedProperty->setAccessible(true); } - + $reflectedProperty->setValue($instance, $dependency); - + if (!$reflectedProperty->isPublic()) { $reflectedProperty->setAccessible(false); } @@ -439,6 +419,60 @@ public function injectDependencies($instance, $serviceSpecs = null) return $this; } + + /** + * @param $serviceName + * @return mixed|null + * @throws Exception + */ + protected function getServiceToInject($serviceName) + { + foreach ($this->parents as $parent) { + if ($parent->has($serviceName)) { + return $parent->get($serviceName); + } + } + + throw new Exception(sprintf('Dependent service "%s" is not registered', $serviceName), + Exception::DEPENDENCY_NOT_FOUND); + } + + /** + * @throws Exception + */ + protected function getConfigParamToInject($injection) + { + $hasConfig = false; + foreach ($this->parents as $servicesFactory) { + if ($servicesFactory->has('config')) { + $hasConfig = true; + + $config = $servicesFactory->get('config'); + + if ($config instanceof Config) { + $params = $config->subset('ObjectivePHP\Application\Config\Param'); + + if ($params->has($injection->param)) { + return $dependency = $params->get($injection->param); + } else { + if (isset($injection->default)) { + return $dependency = $injection->default; + } + } + + } else { + throw new Exception('Service registered as "config" in this factory is no a Config instance'); + } + } + } + + if (false === $hasConfig) { + throw new Exception('No Config is registered as "config" neither in this factory nor in its parents'); + } + + throw new Exception(sprintf('Config instance registered as "config" does not have a "%s" param, and no default value is provided', + $injection->param)); + } /** * @return Collection @@ -518,5 +552,18 @@ protected function normalizeServiceId($service) // normalize service id return strtolower(($service instanceof ServiceReference) ? $service->getId() : $service); } - + + /** + * Register one or multiple parent containers. + * + * @param ContainerInterface[] ...$containers + * + * @return $this + */ + public function registerParentContainer(ContainerInterface ...$containers) + { + array_push($this->parents, ...$containers); + + return $this; + } } diff --git a/tests/ServicesFactory/ServicesFactoryTest.php b/tests/ServicesFactory/ServicesFactoryTest.php index 5055d7b..4b9ea83 100644 --- a/tests/ServicesFactory/ServicesFactoryTest.php +++ b/tests/ServicesFactory/ServicesFactoryTest.php @@ -14,9 +14,11 @@ use Fancy\Service\SimpleAnnotatedServiceReferringAnotherService; use Fancy\Service\SimpleAnnotatedServiceWitImplicitDependency; use Fancy\Service\TestService; + use Interop\Container\ContainerInterface; use ObjectivePHP\Config\Config; use ObjectivePHP\Invokable\InvokableInterface; use ObjectivePHP\PHPUnit\TestCase; + use ObjectivePHP\Primitives\Collection\Collection; use ObjectivePHP\ServicesFactory\Builder\ClassServiceBuilder; use ObjectivePHP\ServicesFactory\Builder\ServiceBuilderInterface; use ObjectivePHP\ServicesFactory\Exception\Exception; @@ -564,6 +566,89 @@ public function testAutoAliasing() $this->assertInstanceOf(TestService::class, $service); } + + public function testRegisterParentContainer() + { + $servicesFactory = new ServicesFactory(); + + $parents = []; + $parents[] = $this->getMockBuilder(ServicesFactory::class)->getMock(); + $parents[] = $this->getMockBuilder(ServicesFactory::class)->getMock(); + $parents[] = $this->getMockBuilder(ServicesFactory::class)->getMock(); + + $return = $servicesFactory->registerParentContainer(...$parents); + + $expected = [$servicesFactory]; + array_push($expected, ...$parents); + + $this->assertAttributeEquals($expected, 'parents', $servicesFactory); + $this->assertEquals($servicesFactory, $return); + } + + /** + * @throws \ObjectivePHP\Primitives\Exception + */ + public function testGetConfigParamToInjectWhenNoConfig() + { + $servicesFactory = new ServicesFactory(); + + $instance = new class implements InjectionAnnotationProvider{ + /** + * @Inject(param="APP_MODE") + */ + protected $mode; + }; + + $this->expectException(Exception::class); + $this->expectExceptionMessage('No Config is registered as "config" neither in this factory nor in its parents'); + $servicesFactory->injectDependencies($instance); + } + + /** + * @throws \ObjectivePHP\Primitives\Exception + * @throws Exception + */ + public function testGetConfigParamToInjectWhenNoConfigDoesNotExists() + { + $params = new Collection(); + $config = $this->getMockBuilder(Config::class)->setMethods(['subset'])->getMock(); + $config->expects($this->once())->method('subset')->with('ObjectivePHP\Application\Config\Param')->willReturn($params); + + $servicesFactory = new ServicesFactory(); + $servicesFactory->registerService([ + 'id' => 'config', + 'instance' => $config + ]); + + $instance = new class implements InjectionAnnotationProvider{ + /** + * @Inject(param="APP_MODE") + */ + protected $mode; + }; + + $this->expectException(Exception::class); + $this->expectExceptionMessage('Config instance registered as "config" does not have a "APP_MODE" param, and no default value is provided'); + $servicesFactory->injectDependencies($instance); + } + + /** + * Call protected/private method of a class. + * + * @param object &$object Instantiated object that we will run method on. + * @param string $methodName Method name to call + * @param array $parameters Array of parameters to pass into method. + * + * @return mixed Method return. + */ + public function invokeMethod(&$object, $methodName, array $parameters = array()) + { + $reflection = new \ReflectionClass(get_class($object)); + $method = $reflection->getMethod($methodName); + $method->setAccessible(true); + + return $method->invokeArgs($object, $parameters); + } } } From f531fa46aec622baf7f6502d7498f4456d90c765 Mon Sep 17 00:00:00 2001 From: Boris Date: Mon, 26 Mar 2018 16:39:21 +0200 Subject: [PATCH 2/3] Rename parent into sibling --- src/ServicesFactory.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/ServicesFactory.php b/src/ServicesFactory.php index b7f48b5..7ae1c17 100644 --- a/src/ServicesFactory.php +++ b/src/ServicesFactory.php @@ -62,14 +62,14 @@ class ServicesFactory implements ContainerInterface /** * @var array */ - protected $parents = []; + protected $siblings = []; /** * ServicesFactory constructor. */ public function __construct() { - $this->parents[] = $this; + $this->siblings[] = $this; // init collections $this->services = (new Collection())->restrictTo(ServiceSpecsInterface::class); @@ -427,9 +427,9 @@ public function injectDependencies($instance, $serviceSpecs = null) */ protected function getServiceToInject($serviceName) { - foreach ($this->parents as $parent) { - if ($parent->has($serviceName)) { - return $parent->get($serviceName); + foreach ($this->siblings as $sibling) { + if ($sibling->has($serviceName)) { + return $sibling->get($serviceName); } } @@ -443,7 +443,7 @@ protected function getServiceToInject($serviceName) protected function getConfigParamToInject($injection) { $hasConfig = false; - foreach ($this->parents as $servicesFactory) { + foreach ($this->siblings as $servicesFactory) { if ($servicesFactory->has('config')) { $hasConfig = true; @@ -467,7 +467,7 @@ protected function getConfigParamToInject($injection) } if (false === $hasConfig) { - throw new Exception('No Config is registered as "config" neither in this factory nor in its parents'); + throw new Exception('No Config is registered as "config" neither in this factory nor in its siblings'); } throw new Exception(sprintf('Config instance registered as "config" does not have a "%s" param, and no default value is provided', @@ -554,7 +554,7 @@ protected function normalizeServiceId($service) } /** - * Register one or multiple parent containers. + * Register one or multiple sibling containers. * * @param ContainerInterface[] ...$containers * @@ -562,7 +562,7 @@ protected function normalizeServiceId($service) */ public function registerParentContainer(ContainerInterface ...$containers) { - array_push($this->parents, ...$containers); + array_push($this->siblings, ...$containers); return $this; } From fe223894ad1b4529d74b63aadf13d69cebea226c Mon Sep 17 00:00:00 2001 From: Boris Date: Tue, 27 Mar 2018 10:20:29 +0200 Subject: [PATCH 3/3] Remove the current services factory from the siblings and fix PSR-2 --- src/Annotation/Inject.php | 15 +-- src/Builder/AbstractServiceBuilder.php | 85 +++++++------- src/Builder/ClassServiceBuilder.php | 29 +++-- src/Builder/DelegatedFactoryBuilder.php | 14 ++- src/Builder/PrefabServiceBuilder.php | 13 ++- src/Builder/ServiceBuilderInterface.php | 2 - src/Exception/Exception.php | 2 - src/Exception/ServiceNotFoundException.php | 10 +- src/ServiceReference.php | 2 - src/ServicesFactory.php | 106 +++++++++++------- src/ServicesFactoryAwareInterface.php | 11 +- src/ServicesFactoryAwareTrait.php | 22 ++-- src/Specs/AbstractServiceSpecs.php | 67 ++++++----- src/Specs/ClassServiceSpecs.php | 23 ++-- src/Specs/DelegatedFactorySpecs.php | 11 +- src/Specs/InjectionAnnotationProvider.php | 2 +- src/Specs/PrefabServiceSpecs.php | 12 +- src/Specs/ServiceSpecsInterface.php | 3 +- src/Specs/UndefinedServiceSpecs.php | 34 +++--- tests/ServicesFactory/ServicesFactoryTest.php | 36 ++---- 20 files changed, 250 insertions(+), 249 deletions(-) diff --git a/src/Annotation/Inject.php b/src/Annotation/Inject.php index 22d76f2..78eac9d 100644 --- a/src/Annotation/Inject.php +++ b/src/Annotation/Inject.php @@ -1,6 +1,7 @@ param) { + if ($this->param) { $type = 'param'; - } - else { + } else { $type = ($this->class || !$this->service) ? 'instance' : 'service'; } - switch($type) - { + switch ($type) { case 'instance': return $this->class; @@ -63,10 +62,6 @@ public function getDependency($config = null) case 'param': return 'param.value'; - } - } - - -} \ No newline at end of file +} diff --git a/src/Builder/AbstractServiceBuilder.php b/src/Builder/AbstractServiceBuilder.php index 75e719d..52b0567 100644 --- a/src/Builder/AbstractServiceBuilder.php +++ b/src/Builder/AbstractServiceBuilder.php @@ -2,7 +2,6 @@ namespace ObjectivePHP\ServicesFactory\Builder; - use ObjectivePHP\Config\ConfigReference; use ObjectivePHP\Primitives\Collection\Collection; use ObjectivePHP\ServicesFactory\ServicesFactory; @@ -16,77 +15,73 @@ * * @package ObjectivePHP\ServicesFactory\Builder */ - abstract class AbstractServiceBuilder implements ServiceBuilderInterface, ServicesFactoryAwareInterface - { +abstract class AbstractServiceBuilder implements ServiceBuilderInterface, ServicesFactoryAwareInterface +{ - use ServicesFactoryAwareTrait; + use ServicesFactoryAwareTrait; - /** + /** * This property should be initialized in extended classes * * @var Collection */ - protected $handledSpecs; + protected $handledSpecs; - /** + /** * AbstractServiceBuilder constructor. */ - public function __construct() - { - $this->handledSpecs = new Collection($this->handledSpecs); - } + public function __construct() + { + $this->handledSpecs = new Collection($this->handledSpecs); + } - /** + /** * @param ServiceSpecsInterface $serviceDefinition * * @return bool */ - public function doesHandle(ServiceSpecsInterface $serviceDefinition) - { - foreach ($this->getHandledSpecs() as $handledDefinition) - { - if ($serviceDefinition instanceof $handledDefinition) - { - return true; - } + public function doesHandle(ServiceSpecsInterface $serviceDefinition) + { + foreach ($this->getHandledSpecs() as $handledDefinition) { + if ($serviceDefinition instanceof $handledDefinition) { + return true; } - - return false; } - /** + return false; + } + + /** * @return Collection */ - public function getHandledSpecs() - { - return $this->handledSpecs; - } + public function getHandledSpecs() + { + return $this->handledSpecs; + } - /** + /** * Substitute all references to services in a param set * * @param Collection $params * * @return Collection */ - protected function substituteReferences(Collection $params) - { - $params->each(function (&$value) - { - if ($value instanceof ServiceReference) - { - $value = $this->getServicesFactory()->get($value->getId()); - } else if($value instanceof ConfigReference) { - $value = $this->getServicesFactory()->get('config')->get($value->getId()); - } - }); - } + protected function substituteReferences(Collection $params) + { + $params->each(function (&$value) { + if ($value instanceof ServiceReference) { + $value = $this->getServicesFactory()->get($value->getId()); + } elseif ($value instanceof ConfigReference) { + $value = $this->getServicesFactory()->get('config')->get($value->getId()); + } + }); + } - /** + /** * @return ServicesFactory */ - public function getServicesFactory() - { - return $this->servicesFactory; - } + public function getServicesFactory() + { + return $this->servicesFactory; } +} diff --git a/src/Builder/ClassServiceBuilder.php b/src/Builder/ClassServiceBuilder.php index a0c6d03..6fdb75f 100644 --- a/src/Builder/ClassServiceBuilder.php +++ b/src/Builder/ClassServiceBuilder.php @@ -28,18 +28,28 @@ public function build(ServiceSpecsInterface $serviceSpecs, $params = [], $servic { // check compatibility with the service definition - if (!$this->doesHandle($serviceSpecs)) - { - throw new Exception(sprintf('"%s" service definition is not handled by this builder.', get_class($serviceSpecs)), Exception::INCOMPATIBLE_SERVICE_DEFINITION); + if (!$this->doesHandle($serviceSpecs)) { + throw new Exception( + sprintf( + '"%s" service definition is not handled by this builder.', + get_class($serviceSpecs) + ), + Exception::INCOMPATIBLE_SERVICE_DEFINITION + ); } $serviceClassName = $serviceSpecs->getClass(); // check class existence - if(!class_exists($serviceClassName)) - { - throw new Exception(sprintf('Unable to build service: class "%s" is unknown', $serviceClassName), Exception::INVALID_SERVICE_SPECS); + if (!class_exists($serviceClassName)) { + throw new Exception( + sprintf( + 'Unable to build service: class "%s" is unknown', + $serviceClassName + ), + Exception::INVALID_SERVICE_SPECS + ); } // merge service defined and runtime params @@ -52,10 +62,8 @@ public function build(ServiceSpecsInterface $serviceSpecs, $params = [], $servic $service = new $serviceClassName(...$constructorParams->values()); // call setters if any - if($setters = $serviceSpecs->getSetters()) - { - foreach($setters as $setter => $setterParams) - { + if ($setters = $serviceSpecs->getSetters()) { + foreach ($setters as $setter => $setterParams) { $instanceSetterParams = clone Collection::cast($setterParams); $this->substituteReferences($instanceSetterParams); @@ -66,5 +74,4 @@ public function build(ServiceSpecsInterface $serviceSpecs, $params = [], $servic return $service; } - } diff --git a/src/Builder/DelegatedFactoryBuilder.php b/src/Builder/DelegatedFactoryBuilder.php index 67686db..727b8bf 100644 --- a/src/Builder/DelegatedFactoryBuilder.php +++ b/src/Builder/DelegatedFactoryBuilder.php @@ -32,8 +32,10 @@ public function build(ServiceSpecsInterface $serviceSpecs, $params = [], $actual // check compatibility with the service definition if (!$this->doesHandle($serviceSpecs)) { - throw new Exception(sprintf('"%s" service definition is not handled by this builder.', - get_class($serviceSpecs)), Exception::INCOMPATIBLE_SERVICE_DEFINITION); + throw new Exception(sprintf( + '"%s" service definition is not handled by this builder.', + get_class($serviceSpecs) + ), Exception::INCOMPATIBLE_SERVICE_DEFINITION); } @@ -46,8 +48,11 @@ public function build(ServiceSpecsInterface $serviceSpecs, $params = [], $actual } $factory = $factory->getCallable(); } catch (InvokableException $e) { - throw new Exception(sprintf('Unable to build service: provided factory is not callable'), - Exception::INVALID_SERVICE_SPECS, $e); + throw new Exception( + sprintf('Unable to build service: provided factory is not callable'), + Exception::INVALID_SERVICE_SPECS, + $e + ); } // merge service defined and runtime params @@ -62,5 +67,4 @@ public function build(ServiceSpecsInterface $serviceSpecs, $params = [], $actual return $service; } - } diff --git a/src/Builder/PrefabServiceBuilder.php b/src/Builder/PrefabServiceBuilder.php index 3b7b437..b0d0dac 100644 --- a/src/Builder/PrefabServiceBuilder.php +++ b/src/Builder/PrefabServiceBuilder.php @@ -2,7 +2,6 @@ namespace ObjectivePHP\ServicesFactory\Builder; - use ObjectivePHP\ServicesFactory\Exception\Exception; use ObjectivePHP\ServicesFactory\Specs\PrefabServiceSpecs; use ObjectivePHP\ServicesFactory\Specs\ServiceSpecsInterface; @@ -28,12 +27,16 @@ class PrefabServiceBuilder extends AbstractServiceBuilder public function build(ServiceSpecsInterface $serviceSpecs, $params = [], $serviceId = null) { // check compatibility with the service definition - if (!$this->doesHandle($serviceSpecs)) - { - throw new Exception(sprintf('"%s" service spec is not handled by this builder.', get_class($serviceSpecs)), Exception::INCOMPATIBLE_SERVICE_DEFINITION); + if (!$this->doesHandle($serviceSpecs)) { + throw new Exception( + sprintf( + '"%s" service spec is not handled by this builder.', + get_class($serviceSpecs) + ), + Exception::INCOMPATIBLE_SERVICE_DEFINITION + ); } return $serviceSpecs->getInstance(); } - } diff --git a/src/Builder/ServiceBuilderInterface.php b/src/Builder/ServiceBuilderInterface.php index b70506e..6eb2d67 100644 --- a/src/Builder/ServiceBuilderInterface.php +++ b/src/Builder/ServiceBuilderInterface.php @@ -2,7 +2,6 @@ namespace ObjectivePHP\ServicesFactory\Builder; - use ObjectivePHP\ServicesFactory\Specs\ServiceSpecsInterface; interface ServiceBuilderInterface @@ -26,5 +25,4 @@ public function doesHandle(ServiceSpecsInterface $serviceDefinition); * @return mixed */ public function build(ServiceSpecsInterface $serviceSpecs, $params = null); - } diff --git a/src/Exception/Exception.php b/src/Exception/Exception.php index 25b6309..a18fbc1 100644 --- a/src/Exception/Exception.php +++ b/src/Exception/Exception.php @@ -2,7 +2,6 @@ namespace ObjectivePHP\ServicesFactory\Exception; - use Interop\Container\Exception\ContainerException; class Exception extends \Exception implements ContainerException @@ -21,5 +20,4 @@ class Exception extends \Exception implements ContainerException // dependencies handling const DEPENDENCY_NOT_FOUND = 0x31; const MISSING_DEPENDENCY_DEFINITION = 0x32; - } diff --git a/src/Exception/ServiceNotFoundException.php b/src/Exception/ServiceNotFoundException.php index cadc7af..c6888c4 100644 --- a/src/Exception/ServiceNotFoundException.php +++ b/src/Exception/ServiceNotFoundException.php @@ -2,10 +2,8 @@ namespace ObjectivePHP\ServicesFactory\Exception; - - - class ServiceNotFoundException extends Exception - { - const UNREGISTERED_SERVICE_REFERENCE = 0x20; - } \ No newline at end of file +class ServiceNotFoundException extends Exception +{ + const UNREGISTERED_SERVICE_REFERENCE = 0x20; +} diff --git a/src/ServiceReference.php b/src/ServiceReference.php index aa2d409..4287e28 100644 --- a/src/ServiceReference.php +++ b/src/ServiceReference.php @@ -2,7 +2,6 @@ namespace ObjectivePHP\ServicesFactory; - class ServiceReference { @@ -28,5 +27,4 @@ public function __toString() { return (string) $this->id; } - } diff --git a/src/ServicesFactory.php b/src/ServicesFactory.php index 7ae1c17..f3c99c5 100644 --- a/src/ServicesFactory.php +++ b/src/ServicesFactory.php @@ -69,8 +69,6 @@ class ServicesFactory implements ContainerInterface */ public function __construct() { - $this->siblings[] = $this; - // init collections $this->services = (new Collection())->restrictTo(ServiceSpecsInterface::class); $this->builders = (new Collection())->restrictTo(ServiceBuilderInterface::class); @@ -109,8 +107,11 @@ public function registerService(...$servicesSpecs) try { $serviceSpecs = AbstractServiceSpecs::factory($serviceSpecs); } catch (\Exception $e) { - throw new Exception(AbstractServiceSpecs::class . '::factory() was unable to build service specifications', - Exception::INVALID_SERVICE_SPECS, $e); + throw new Exception( + AbstractServiceSpecs::class . '::factory() was unable to build service specifications', + Exception::INVALID_SERVICE_SPECS, + $e + ); } } @@ -121,8 +122,10 @@ public function registerService(...$servicesSpecs) // a service with same name already has been registered if ($previouslyRegistered->isFinal()) { // as it is marked as final, it cannot be overridden - throw new Exception(sprintf('Cannot override service "%s" as it has been registered as a final service', - $serviceId), Exception::FINAL_SERVICE_OVERRIDING_ATTEMPT); + throw new Exception(sprintf( + 'Cannot override service "%s" as it has been registered as a final service', + $serviceId + ), Exception::FINAL_SERVICE_OVERRIDING_ATTEMPT); } } @@ -130,14 +133,20 @@ public function registerService(...$servicesSpecs) $this->services[(string)$serviceId] = $serviceSpecs; $aliases = $serviceSpecs->getAliases() ?: []; - foreach($aliases as $alias) { - + foreach ($aliases as $alias) { if ($previouslyRegistered = $this->getServiceSpecs((string)$alias)) { // a service with same name already has been registered if ($previouslyRegistered->isFinal()) { // as it is marked as final, it cannot be overridden - throw new Exception(sprintf('Cannot override service "%s" using alias "%s" as it has been registered as a final service', - $serviceId, $alias), Exception::FINAL_SERVICE_OVERRIDING_ATTEMPT); + throw new Exception( + sprintf( + 'Cannot override service "%s" using alias + "%s" as it has been registered as a final service', + $serviceId, + $alias + ), + Exception::FINAL_SERVICE_OVERRIDING_ATTEMPT + ); } } @@ -155,7 +164,7 @@ public function registerService(...$servicesSpecs) */ public function getServiceSpecs($service) { - + $saved = $service; if ($service instanceof ServiceReference) { $service = $service->getId(); } @@ -169,6 +178,8 @@ public function getServiceSpecs($service) $service = $this->registeredAliases[$service]; } } + + $specs = $this->services[$service] ?? null; if (is_null($specs)) { @@ -180,7 +191,7 @@ public function getServiceSpecs($service) $specs = null; } } - + return $specs; } @@ -237,15 +248,19 @@ public function registerDelegateContainer(ContainerInterface $delegate) public function getConfig(): Config { if (!$this->has('config')) { - throw new Exception('No "config" service has been registered in this factory', - Exception::UNKNOWN_SERVICE_SPECS); + throw new Exception( + 'No "config" service has been registered in this factory', + Exception::UNKNOWN_SERVICE_SPECS + ); } $config = $this->get('config'); if (!$config instanceof Config) { - throw new Exception('Registered service "config" is not an instance of ' . Config::class, - Exception::INCOMPATIBLE_SERVICE_DEFINITION); + throw new Exception( + 'Registered service "config" is not an instance of ' . Config::class, + Exception::INCOMPATIBLE_SERVICE_DEFINITION + ); } return $config; @@ -274,11 +289,10 @@ public function has($service) */ public function get($service, $params = []) { - $service = $this->normalizeServiceId($service); - + $serviceSpecs = $this->getServiceSpecs($service); - + if (is_null($serviceSpecs)) { foreach ($this->delegateContainers as $delegate) { if ($instance = $delegate->get($service)) { @@ -288,12 +302,13 @@ public function get($service, $params = []) } } - throw new ServiceNotFoundException(sprintf('Service reference "%s" matches no registered service in this factory or its delegate containers', - $service), ServiceNotFoundException::UNREGISTERED_SERVICE_REFERENCE); + throw new ServiceNotFoundException(sprintf( + 'Service reference "%s" matches no registered service in this factory or its delegate containers', + $service + ), ServiceNotFoundException::UNREGISTERED_SERVICE_REFERENCE); } - if ( - !$serviceSpecs->isStatic() + if (!$serviceSpecs->isStatic() || $this->getInstances()->lacks($service) || $params ) { @@ -307,7 +322,8 @@ public function get($service, $params = []) $builder->setServicesFactory($this); } - $instance = $builder->build($serviceSpecs, $params, $service);; + $instance = $builder->build($serviceSpecs, $params, $service); + ; $this->injectDependencies($instance, $serviceSpecs); @@ -319,11 +335,9 @@ public function get($service, $params = []) } else { $this->instances[$service] = $instance; } - } return $this->instances[$service]; - } /** @@ -347,7 +361,6 @@ public function isServiceRegistered($service) } return $has; - } /** @@ -378,7 +391,7 @@ public function injectDependencies($instance, $serviceSpecs = null) if ($injection) { if ($injection->param) { $dependency = $this->getConfigParamToInject($injection); - } else if ($injection->class || !$injection->service) { + } elseif ($injection->class || !$injection->service) { $className = $injection->getDependency(); if (!$className) { @@ -387,8 +400,11 @@ public function injectDependencies($instance, $serviceSpecs = null) if ($docblock->hasTag('var')) { $className = (string)$docblock->getTagsByName('var')[0]->getType()->getFqsen(); } else { - throw new Exception('Undefined dependency. Use either dependency="|" or "@var $property ClassName"', - Exception::MISSING_DEPENDENCY_DEFINITION); + throw new Exception( + 'Undefined dependency. Use either dependency="|" + or "@var $property ClassName"', + Exception::MISSING_DEPENDENCY_DEFINITION + ); } } @@ -427,14 +443,21 @@ public function injectDependencies($instance, $serviceSpecs = null) */ protected function getServiceToInject($serviceName) { - foreach ($this->siblings as $sibling) { + $siblings = $this->siblings; + if (count($siblings) === 0) { + $siblings = [$this]; + } + + foreach ($siblings as $sibling) { if ($sibling->has($serviceName)) { return $sibling->get($serviceName); } } - throw new Exception(sprintf('Dependent service "%s" is not registered', $serviceName), - Exception::DEPENDENCY_NOT_FOUND); + throw new Exception( + sprintf('Dependent service "%s" is not registered', $serviceName), + Exception::DEPENDENCY_NOT_FOUND + ); } /** @@ -443,7 +466,13 @@ protected function getServiceToInject($serviceName) protected function getConfigParamToInject($injection) { $hasConfig = false; - foreach ($this->siblings as $servicesFactory) { + + $siblings = $this->siblings; + if (count($siblings) === 0) { + $siblings = [$this]; + } + + foreach ($siblings as $servicesFactory) { if ($servicesFactory->has('config')) { $hasConfig = true; @@ -459,7 +488,6 @@ protected function getConfigParamToInject($injection) return $dependency = $injection->default; } } - } else { throw new Exception('Service registered as "config" in this factory is no a Config instance'); } @@ -470,8 +498,10 @@ protected function getConfigParamToInject($injection) throw new Exception('No Config is registered as "config" neither in this factory nor in its siblings'); } - throw new Exception(sprintf('Config instance registered as "config" does not have a "%s" param, and no default value is provided', - $injection->param)); + throw new Exception(sprintf( + 'Config instance registered as "config" does not have a "%s" param, and no default value is provided', + $injection->param + )); } /** @@ -560,7 +590,7 @@ protected function normalizeServiceId($service) * * @return $this */ - public function registerParentContainer(ContainerInterface ...$containers) + public function registerSiblingContainer(ContainerInterface ...$containers) { array_push($this->siblings, ...$containers); diff --git a/src/ServicesFactoryAwareInterface.php b/src/ServicesFactoryAwareInterface.php index eaf78c4..beee6a3 100644 --- a/src/ServicesFactoryAwareInterface.php +++ b/src/ServicesFactoryAwareInterface.php @@ -9,18 +9,17 @@ namespace ObjectivePHP\ServicesFactory; - /** * Interface ServicesFactoryAwareInterface * * @package ObjectivePHP\ServicesFactory */ - interface ServicesFactoryAwareInterface - { - /** +interface ServicesFactoryAwareInterface +{ + /** * @param ServicesFactory $servicesFactory * * @return $this */ - public function setServicesFactory(ServicesFactory $servicesFactory); - } + public function setServicesFactory(ServicesFactory $servicesFactory); +} diff --git a/src/ServicesFactoryAwareTrait.php b/src/ServicesFactoryAwareTrait.php index a1b66a8..9cad20f 100644 --- a/src/ServicesFactoryAwareTrait.php +++ b/src/ServicesFactoryAwareTrait.php @@ -9,29 +9,27 @@ namespace ObjectivePHP\ServicesFactory; - /** * Class ServicesFactoryAwareTrait * * @package ObjectivePHP\ServicesFactory */ - trait ServicesFactoryAwareTrait - { - /** +trait ServicesFactoryAwareTrait +{ + /** * @var ServicesFactory */ - protected $servicesFactory; + protected $servicesFactory; - /** + /** * @param ServicesFactory $servicesFactory * * @return $this */ - public function setServicesFactory(ServicesFactory $servicesFactory) - { - $this->servicesFactory = $servicesFactory; - - return $this; - } + public function setServicesFactory(ServicesFactory $servicesFactory) + { + $this->servicesFactory = $servicesFactory; + return $this; } +} diff --git a/src/Specs/AbstractServiceSpecs.php b/src/Specs/AbstractServiceSpecs.php index f735bae..98a0cdb 100644 --- a/src/Specs/AbstractServiceSpecs.php +++ b/src/Specs/AbstractServiceSpecs.php @@ -46,10 +46,11 @@ public function __construct($serviceId, $params = []) // assign default values $this->setId($serviceId); - foreach($params as $param => $value) - { + foreach ($params as $param => $value) { $paramParts = explode('-', strtolower($param)); - array_walk($paramParts, function(&$part) { $part = ucfirst($part);}); + array_walk($paramParts, function (&$part) { + $part = ucfirst($part); + }); $setter = implode($paramParts); $this->$setter($value); } @@ -82,8 +83,7 @@ public function getAliases() { $aliases = $this->aliases; - if($autoAlias = $this->getAutoAlias()) - { + if ($autoAlias = $this->getAutoAlias()) { $aliases[] = $autoAlias; } @@ -144,37 +144,49 @@ public function setStatic($static) return $this; } - static function factory($rawDefinition) + public static function factory($rawDefinition) { $rawDefinition = Collection::cast($rawDefinition); // first check an id has been provided - if ($rawDefinition->lacks('id')) - { - throw new Exception('Missing mandatory \'id\' parameter in service definition', Exception::INCOMPLETE_SERVICE_SPECS); + if ($rawDefinition->lacks('id')) { + throw new Exception( + 'Missing mandatory \'id\' parameter in service definition', + Exception::INCOMPLETE_SERVICE_SPECS + ); } // try to guess service type if not provided - if($rawDefinition->lacks('type')) - { + if ($rawDefinition->lacks('type')) { $matchingTypes = []; - foreach(['instance' => PrefabServiceSpecs::class, 'class' => ClassServiceSpecs::class, 'factory' => DelegatedFactorySpecs::class] as $key => $type) - { - if($rawDefinition->has($key)) $matchingTypes[] = $type; + $types = [ + 'instance' => PrefabServiceSpecs::class, + 'class' => ClassServiceSpecs::class, + 'factory' => DelegatedFactorySpecs::class + ]; + + foreach ($types as $key => $type) { + if ($rawDefinition->has($key)) { + $matchingTypes[] = $type; + } } - if(!$matchingTypes) - { - // throw new Exception('The service specs factory has not been able to guess what type of service has been passed. Please check your syntax, or explicitly define the "type" key in your service specifications', Exception::INCOMPLETE_SERVICE_SPECS); + if (!$matchingTypes) { + // throw new Exception('The service specs factory has not been able to guess what type of service + // has been passed. Please check your syntax, or explicitly define the "type" key + // in your service specifications', Exception::INCOMPLETE_SERVICE_SPECS); // default to UndefinedService $matchingTypes[] = UndefinedServiceSpecs::class; } - if(count($matchingTypes) > 1) - { - throw new Exception('Service specifications are ambiguous: they contain both "instance" and "class" key. Please remove the unneeded oneor explicitly define the "type" key in your service specifications ', Exception::AMBIGUOUS_SERVICE_SPECS); + if (count($matchingTypes) > 1) { + throw new Exception( + 'Service specifications are ambiguous: they contain both "instance" and "class" key. + Please remove the unneeded oneor explicitly define the "type" key in your service specifications ', + Exception::AMBIGUOUS_SERVICE_SPECS + ); } // only one match @@ -184,17 +196,19 @@ static function factory($rawDefinition) $serviceDefinition = call_user_func([$rawDefinition['type'], 'factory'], $rawDefinition); // static - if ($rawDefinition->has('static')) - { + if ($rawDefinition->has('static')) { $serviceDefinition->setStatic($rawDefinition['static']); } // aliases - if ($rawDefinition->has('alias') || $rawDefinition->has('aliases')) - { + if ($rawDefinition->has('alias') || $rawDefinition->has('aliases')) { $aliases = new Collection(); - if ($rawDefinition->has('alias')) $aliases[] = $rawDefinition['alias']; - if ($rawDefinition->has('aliases')) $aliases->merge($rawDefinition['aliases']); + if ($rawDefinition->has('alias')) { + $aliases[] = $rawDefinition['alias']; + } + if ($rawDefinition->has('aliases')) { + $aliases->merge($rawDefinition['aliases']); + } $serviceDefinition->setAliases($aliases); } @@ -254,5 +268,4 @@ protected function getAutoAlias() { return null; } - } diff --git a/src/Specs/ClassServiceSpecs.php b/src/Specs/ClassServiceSpecs.php index 96eeaa6..693b7e0 100644 --- a/src/Specs/ClassServiceSpecs.php +++ b/src/Specs/ClassServiceSpecs.php @@ -2,7 +2,6 @@ namespace ObjectivePHP\ServicesFactory\Specs; - use ObjectivePHP\Primitives\Collection\Collection; use ObjectivePHP\Primitives\String\Str; use ObjectivePHP\ServicesFactory\Exception\Exception; @@ -46,33 +45,29 @@ public function __construct($id, $class, $params = []) * @param array|Collection $rawDefinition * @throws Exception */ - static public function factory($rawDefinition) + public static function factory($rawDefinition) { $rawDefinition = Collection::cast($rawDefinition); // then check check a class has been provided - if (!$rawDefinition->has('class')) - { + if (!$rawDefinition->has('class')) { throw new Exception('Missing \'class\' parameter', Exception::INCOMPLETE_SERVICE_SPECS); } - if (!is_string($class = $rawDefinition['class'])) - { + if (!is_string($class = $rawDefinition['class'])) { throw new Exception('\'class\' parameter has to be a string', Exception::INVALID_SERVICE_SPECS); } $serviceDefinition = new ClassServiceSpecs($rawDefinition['id'], $class); // constructor params - if ($rawDefinition->has('params')) - { + if ($rawDefinition->has('params')) { $serviceDefinition->setParams($rawDefinition['params']); } // setters - if ($rawDefinition->has('setters')) - { + if ($rawDefinition->has('setters')) { $serviceDefinition->setSetters($rawDefinition['setters']); } @@ -123,12 +118,10 @@ public function setClass($class) public function getAutoAlias() { - if ($this->isAutoAliasingEnabled()) - { + if ($this->isAutoAliasingEnabled()) { return strtolower(ltrim($this->class, '\\')); + } else { + return null; } - else return null; } - - } diff --git a/src/Specs/DelegatedFactorySpecs.php b/src/Specs/DelegatedFactorySpecs.php index 23e32de..6fd95e0 100644 --- a/src/Specs/DelegatedFactorySpecs.php +++ b/src/Specs/DelegatedFactorySpecs.php @@ -2,7 +2,6 @@ namespace ObjectivePHP\ServicesFactory\Specs; - use ObjectivePHP\Invokable\Invokable; use ObjectivePHP\Invokable\InvokableInterface; use ObjectivePHP\Primitives\Collection\Collection; @@ -41,22 +40,20 @@ public function __construct($id, $factory, $params = []) * @param array|Collection $rawDefinition * @throws Exception */ - static public function factory($rawDefinition) + public static function factory($rawDefinition) { $rawDefinition = Collection::cast($rawDefinition); // then check check a class has been provided - if (!$rawDefinition->has('factory')) - { + if (!$rawDefinition->has('factory')) { throw new Exception('Missing \'factory\' parameter', Exception::INCOMPLETE_SERVICE_SPECS); } $serviceDefinition = new DelegatedFactorySpecs($rawDefinition['id'], $rawDefinition['factory']); // constructor params - if ($rawDefinition->has('params')) - { + if ($rawDefinition->has('params')) { $serviceDefinition->setParams($rawDefinition['params']); } @@ -92,6 +89,4 @@ public function getAutoAlias() { return null; } - - } diff --git a/src/Specs/InjectionAnnotationProvider.php b/src/Specs/InjectionAnnotationProvider.php index ef60c9e..79130dd 100644 --- a/src/Specs/InjectionAnnotationProvider.php +++ b/src/Specs/InjectionAnnotationProvider.php @@ -9,4 +9,4 @@ interface InjectionAnnotationProvider { -} \ No newline at end of file +} diff --git a/src/Specs/PrefabServiceSpecs.php b/src/Specs/PrefabServiceSpecs.php index 271a479..1e03897 100644 --- a/src/Specs/PrefabServiceSpecs.php +++ b/src/Specs/PrefabServiceSpecs.php @@ -2,7 +2,6 @@ namespace ObjectivePHP\ServicesFactory\Specs; - use ObjectivePHP\Primitives\Collection\Collection; use ObjectivePHP\ServicesFactory\Exception\Exception; @@ -25,12 +24,11 @@ public function __construct($id, $instance) $this->setInstance($instance); } - static public function factory($rawDefinition) + public static function factory($rawDefinition) { $rawDefinition = Collection::cast($rawDefinition); - if (!$rawDefinition->has('instance')) - { + if (!$rawDefinition->has('instance')) { throw new Exception('Missing \'instance\' parameter', Exception::INCOMPLETE_SERVICE_SPECS); } @@ -60,10 +58,10 @@ public function setInstance($instance) protected function getAutoAlias() { - if ($this->isAutoAliasingEnabled() && is_object($this->instance)) - { + if ($this->isAutoAliasingEnabled() && is_object($this->instance)) { return strtolower(ltrim(get_class($this->instance), '\\')); + } else { + return null; } - else return null; } } diff --git a/src/Specs/ServiceSpecsInterface.php b/src/Specs/ServiceSpecsInterface.php index 40974f9..8fee730 100644 --- a/src/Specs/ServiceSpecsInterface.php +++ b/src/Specs/ServiceSpecsInterface.php @@ -2,7 +2,6 @@ namespace ObjectivePHP\ServicesFactory\Specs; - interface ServiceSpecsInterface { @@ -29,4 +28,4 @@ public function isStatic(); * @return bool */ public function isFinal(); -} \ No newline at end of file +} diff --git a/src/Specs/UndefinedServiceSpecs.php b/src/Specs/UndefinedServiceSpecs.php index d1c4459..794fe33 100644 --- a/src/Specs/UndefinedServiceSpecs.php +++ b/src/Specs/UndefinedServiceSpecs.php @@ -8,34 +8,32 @@ */ namespace ObjectivePHP\ServicesFactory\Specs; - - + use ObjectivePHP\ServicesFactory\Specs\AbstractServiceSpecs; - class UndefinedServiceSpecs extends AbstractServiceSpecs - { +class UndefinedServiceSpecs extends AbstractServiceSpecs +{ - /** + /** * @param $id * @param array $params * */ - public function __construct($id, $params = []) - { - parent::__construct($id); - - $this->setParams($params); - } + public function __construct($id, $params = []) + { + parent::__construct($id); - static function factory($rawDefinition) - { - $id = $rawDefinition['id']; - unset($rawDefinition['id']); - $params = $rawDefinition; + $this->setParams($params); + } + public static function factory($rawDefinition) + { + $id = $rawDefinition['id']; + unset($rawDefinition['id']); + $params = $rawDefinition; - return new static($id, $params); - } + return new static($id, $params); } +} diff --git a/tests/ServicesFactory/ServicesFactoryTest.php b/tests/ServicesFactory/ServicesFactoryTest.php index 4b9ea83..b00cfcf 100644 --- a/tests/ServicesFactory/ServicesFactoryTest.php +++ b/tests/ServicesFactory/ServicesFactoryTest.php @@ -571,17 +571,17 @@ public function testRegisterParentContainer() { $servicesFactory = new ServicesFactory(); - $parents = []; - $parents[] = $this->getMockBuilder(ServicesFactory::class)->getMock(); - $parents[] = $this->getMockBuilder(ServicesFactory::class)->getMock(); - $parents[] = $this->getMockBuilder(ServicesFactory::class)->getMock(); + $siblings = []; + $siblings[] = $this->getMockBuilder(ServicesFactory::class)->getMock(); + $siblings[] = $this->getMockBuilder(ServicesFactory::class)->getMock(); + $siblings[] = $this->getMockBuilder(ServicesFactory::class)->getMock(); - $return = $servicesFactory->registerParentContainer(...$parents); + $return = $servicesFactory->registerSiblingContainer(...$siblings); - $expected = [$servicesFactory]; - array_push($expected, ...$parents); + $expected = []; + array_push($expected, ...$siblings); - $this->assertAttributeEquals($expected, 'parents', $servicesFactory); + $this->assertAttributeEquals($expected, 'siblings', $servicesFactory); $this->assertEquals($servicesFactory, $return); } @@ -600,7 +600,7 @@ public function testGetConfigParamToInjectWhenNoConfig() }; $this->expectException(Exception::class); - $this->expectExceptionMessage('No Config is registered as "config" neither in this factory nor in its parents'); + $this->expectExceptionMessage('No Config is registered as "config" neither in this factory nor in its siblings'); $servicesFactory->injectDependencies($instance); } @@ -631,24 +631,6 @@ public function testGetConfigParamToInjectWhenNoConfigDoesNotExists() $this->expectExceptionMessage('Config instance registered as "config" does not have a "APP_MODE" param, and no default value is provided'); $servicesFactory->injectDependencies($instance); } - - /** - * Call protected/private method of a class. - * - * @param object &$object Instantiated object that we will run method on. - * @param string $methodName Method name to call - * @param array $parameters Array of parameters to pass into method. - * - * @return mixed Method return. - */ - public function invokeMethod(&$object, $methodName, array $parameters = array()) - { - $reflection = new \ReflectionClass(get_class($object)); - $method = $reflection->getMethod($methodName); - $method->setAccessible(true); - - return $method->invokeArgs($object, $parameters); - } } }