diff --git a/src/watoki/factory/Factory.php b/src/watoki/factory/Factory.php index 08711a0..fa6ade5 100755 --- a/src/watoki/factory/Factory.php +++ b/src/watoki/factory/Factory.php @@ -4,14 +4,28 @@ use watoki\factory\providers\DefaultProvider; use watoki\factory\providers\SingletonProvider; -class Factory { - - static $CLASS = __CLASS__; +/** + * Class Factory. + * The factory that build all of your object + * + * @author "Nikolas Martens" + * @license MIT + * @package watoki\factory + */ +class Factory +{ + /** @var string The Factory class name */ + public static $CLASS = __CLASS__; /** @var array|Provider[] */ private $providers = array(); - function __construct() { + /** + * Initialize the class. + * (Define the default provider, and self register as singleton) + */ + public function __construct() + { $this->setSingleton($this); $this->setProvider('stdClass', new DefaultProvider($this)); } @@ -21,34 +35,60 @@ function __construct() { * * If the class was registed as singleton, the previous instance is returned regardless of the arguments. * - * @param $class + * @param string $class The name (or alias) of the class to get * @param array $args Constructor arguments that cannot be provided by the factory (indexed by parameter name) + * * @return mixed An instance of the given class + * * @throws \Exception If the class or an injected class cannot be constructed */ - public function getInstance($class, $args = array()) { + public function getInstance($class, $args = array()) + { return $this->findMatchingProvider($class)->provide($class, $args); } /** - * @param object $instance + * Define a singleton + * + * @param object $instance The singleton instance * @param string|null $class If omitted, the class of the instance is used + * * @return object The $instance */ - public function setSingleton($instance, $class = null) { + public function setSingleton($instance, $class = null) + { $class = $class ?: get_class($instance); $this->setProvider($class, new SingletonProvider($instance)); return $instance; } - public function setProvider($class, Provider $provider) { + /** + * Define a provider of a class name + * + * @param string $class The name (or alias) of the class + * @param Provider $provider The class provider to use for this class name + */ + public function setProvider($class, Provider $provider) + { $this->providers[$this->normalizeClass($class)] = $provider; } - private function findMatchingProvider($class) { + /** + * Find the provider to use to get a instance for the class $class + * + * @param string $class The name (or alias) of the class + * + * @return Provider The provider to use + */ + private function findMatchingProvider($class) + { $reflection = new \ReflectionClass($class); + /* + * First check if a provider is defined for a concrete class + * (check the class, and all parents class) + */ while ($reflection) { $normalized = $this->normalizeClass($reflection->getName()); if (array_key_exists($normalized, $this->providers)) { @@ -58,6 +98,9 @@ private function findMatchingProvider($class) { $reflection = $reflection->getParentClass(); } + /* + * Check if one of the class interface have a provider defined + */ $reflection = new \ReflectionClass($class); foreach ($reflection->getInterfaces() as $interface) { $normalized = $this->normalizeClass($interface->getName()); @@ -66,10 +109,20 @@ private function findMatchingProvider($class) { } } + /* + * Finally, fallback to the default provider (the provider defined for StdClass) + */ return $this->providers['stdclass']; } - private function normalizeClass($class) { + /** + * Transform a class name into a string that can be used as a key (identifier) + * + * @param string $class The class name to transform + * @return string The "ready to use as key" class name + */ + private function normalizeClass($class) + { return trim(strtolower($class), '\\'); } } diff --git a/src/watoki/factory/Injector.php b/src/watoki/factory/Injector.php index 9a0ea6a..d713dac 100644 --- a/src/watoki/factory/Injector.php +++ b/src/watoki/factory/Injector.php @@ -5,7 +5,16 @@ use watoki\reflect\MethodAnalyzer; use watoki\reflect\ClassResolver; -class Injector { +/** + * Class Injector + * Inject objects, params into an instance + * + * @author "Nikolas Martens" + * @license MIT + * @package watoki\factory + */ +class Injector +{ /** @var bool */ private $throwException = true; @@ -13,24 +22,36 @@ class Injector { /** @var callable */ private $injector; - public function __construct(Factory $factory, callable $internalInjector = null) { + public function __construct(Factory $factory, callable $internalInjector = null) + { $this->injector = $internalInjector ?: function ($class) use ($factory) { return $factory->getInstance($class); }; } - public function setThrowWhenCantInjectProperty($throw) { + /** + * Indicate if an exception must be thrown if an error occur when injecting + * + * @param bool $throw Indicate if an exception must be thrown + */ + public function setThrowWhenCantInjectProperty($throw) + { $this->throwException = $throw; } /** - * @param string $class - * @param array $args - * @param callable $parameterFilter + * Instantiate a new object by its constructor + * + * @param string $class The class name to instantiate + * @param array $args List of arguments to use + * @param callable $parameterFilter Callback function for filtering parameters to inject + * * @return object An instance of $class + * * @throws InjectionException */ - public function injectConstructor($class, $args, $parameterFilter) { + public function injectConstructor($class, $args, $parameterFilter) + { $reflection = new \ReflectionClass($class); if ($reflection->isAbstract() || $reflection->isInterface()) { @@ -42,23 +63,31 @@ public function injectConstructor($class, $args, $parameterFilter) { } try { - return $reflection->newInstanceArgs($this->injectMethodArguments($reflection->getConstructor(), $args, $parameterFilter)); + return $reflection->newInstanceArgs($this->injectMethodArguments($reflection->getConstructor(), $args, + $parameterFilter)); } catch (InjectionException $e) { - throw new InjectionException('Error while injecting constructor of [' . $reflection->getName() . ']: ' . $e->getMessage(), 0, $e); + throw new InjectionException('Error while injecting constructor of [' . $reflection->getName() . ']: ' . $e->getMessage(), + 0, $e); } catch (\ReflectionException $re) { - throw new InjectionException('Error while injecting constructor of [' . $reflection->getName() . ']: ' . $re->getMessage(), 0, $re); + throw new InjectionException('Error while injecting constructor of [' . $reflection->getName() . ']: ' . $re->getMessage(), + 0, $re); } } /** + * Inject object/params in an object method + * * @param object $object Object to call method on * @param string $method Name of the method - * @param array $args + * @param array $args List of params to use * @param null|callable $parameterFilter If omitted, all missing arguments are injected + * * @return mixed The return value of the method + * * @throws InjectionException */ - public function injectMethod($object, $method, $args = array(), $parameterFilter = null) { + public function injectMethod($object, $method, $args = array(), $parameterFilter = null) + { $parameterFilter = $parameterFilter ?: function () { return true; }; @@ -70,13 +99,18 @@ public function injectMethod($object, $method, $args = array(), $parameterFilter } /** - * @param \ReflectionMethod $method - * @param array $args - * @param callable $parameterFilter + * Create the list of all arguments to inject in a method + * + * @param \ReflectionMethod $method The reflection method + * @param array $args List of params to inject + * @param callable $parameterFilter Callback function for filtering parameters to inject + * * @return array Of the injected arguments + * * @throws InjectionException */ - public function injectMethodArguments(\ReflectionMethod $method, array $args, $parameterFilter) { + public function injectMethodArguments(\ReflectionMethod $method, array $args, $parameterFilter) + { $analyzer = new MethodAnalyzer($method); try { return $analyzer->fillParameters($args, $this->injector, $parameterFilter); @@ -87,21 +121,27 @@ public function injectMethodArguments(\ReflectionMethod $method, array $args, $p } /** + * Inject value into object properties by reading "@property" (magic properties) annotation (include inherited). + * * @param object $object The object that the properties are injected into * @param callable $filter Function to determine if the passed property annotation should be included * @param \ReflectionClass $context The class to read the property annotations from (if not class of object) + * * @throws InjectionException */ - public function injectPropertyAnnotations($object, $filter, \ReflectionClass $context = null) { - $classReflection = $context ? : new \ReflectionClass($object); + public function injectPropertyAnnotations($object, $filter, \ReflectionClass $context = null) + { + $classReflection = $context ?: new \ReflectionClass($object); while ($classReflection) { $resolver = new ClassResolver($classReflection); $matches = array(); + // RegEx to find @property annotations preg_match_all('/@property\s+(\S+)\s+\$?(\S+).*/', $classReflection->getDocComment(), $matches); foreach ($matches[0] as $i => $match) { + // Filtering annotation with the filter class if (!call_user_func($filter, trim($match))) { continue; } @@ -111,21 +151,27 @@ public function injectPropertyAnnotations($object, $filter, \ReflectionClass $co $this->tryToInjectProperty($object, $propertyName, $resolver, $class); } + // Continue with the parent class (search for inherited properties) $classReflection = $classReflection->getParentClass(); } } /** + * Inject value into object properties. + * * @param object $object The object that the properties are injected into * @param callable $filter Function to determine if the passed \ReflectionProperty should be included * @param \ReflectionClass $context The class to read the property annotations from (if not class of object) + * * @throws InjectionException */ - public function injectProperties($object, $filter, \ReflectionClass $context = null) { - $classReflection = $context ? : new \ReflectionClass($object); + public function injectProperties($object, $filter, \ReflectionClass $context = null) + { + $classReflection = $context ?: new \ReflectionClass($object); foreach ($classReflection->getProperties() as $property) { $matches = array(); + // RegEx to find @var annotation preg_match('/@var\s+(\S+).*/', $property->getDocComment(), $matches); if (empty($matches) || !$filter($property)) { @@ -137,17 +183,40 @@ public function injectProperties($object, $filter, \ReflectionClass $context = n } } - private function tryToInjectProperty($targetObject, $propertyName, ClassResolver $resolver, $class) { + /** + * Inject a property into an object. + * Wrapper of self::injectProperty, to enhance error message. + * + * @param object $targetObject The object where the property must be injected + * @param string $propertyName The name of the property to inject + * @param ClassResolver $resolver The class resolver + * @param string $class The type (class name) of the property + * + * @throws InjectionException + */ + private function tryToInjectProperty($targetObject, $propertyName, ClassResolver $resolver, $class) + { try { $this->injectProperty($targetObject, $propertyName, $resolver, $class); } catch (InjectionException $e) { $targetClass = get_class($targetObject); throw new InjectionException("Error while injecting dependency [$propertyName] " . - "of [$targetClass]: " . $e->getMessage(), 0, $e); + "of [$targetClass]: " . $e->getMessage(), 0, $e); } } - private function injectProperty($targetObject, $propertyName, ClassResolver $resolver, $class) { + /** + * Inject a property into an object. + * + * @param object $targetObject The object where the property must be injected + * @param string $propertyName The name of the property to inject + * @param ClassResolver $resolver The class resolver + * @param string $class The type (class name) of the property + * + * @throws InjectionException + */ + private function injectProperty($targetObject, $propertyName, ClassResolver $resolver, $class) + { $type = $resolver->resolve($class); $classReflection = new \ReflectionClass($targetObject); diff --git a/src/watoki/factory/Provider.php b/src/watoki/factory/Provider.php index 2e38d98..2c20fd8 100644 --- a/src/watoki/factory/Provider.php +++ b/src/watoki/factory/Provider.php @@ -1,8 +1,25 @@ + * @license MIT + * @package watoki\factory + */ +interface Provider +{ + /** + * Get an instance of the class $class with the parameters $args + * + * @param string $class + * @param array $args + * + * @return object The $class instance + */ public function provide($class, array $args = array()); } \ No newline at end of file diff --git a/src/watoki/factory/exception/InjectionException.php b/src/watoki/factory/exception/InjectionException.php index d7f1882..a91602c 100644 --- a/src/watoki/factory/exception/InjectionException.php +++ b/src/watoki/factory/exception/InjectionException.php @@ -1,7 +1,16 @@ + * @license MIT + * @package watoki\factory\exception + */ +class InjectionException extends \Exception +{ + /** @var string The Factory class name */ public static $CLASS = __CLASS__; } \ No newline at end of file diff --git a/src/watoki/factory/providers/CallbackProvider.php b/src/watoki/factory/providers/CallbackProvider.php index e308df1..16f0656 100644 --- a/src/watoki/factory/providers/CallbackProvider.php +++ b/src/watoki/factory/providers/CallbackProvider.php @@ -3,16 +3,36 @@ use watoki\factory\Provider; -class CallbackProvider implements Provider { +/** + * Class CallbackProvider + * The provider use a user define callback function to have an instance of a class + * + * @author "Nikolas Martens" + * @license MIT + * @package watoki\factory\providers + */ +class CallbackProvider implements Provider +{ /** @var callable */ private $callback; - public function __construct($callback) { + /** + * Initialize the provider with the callback function. + * The callback function have this prototype: + * + * function(string $class, array $args) : object + * + * @param callable $callback + */ + public function __construct($callback) + { $this->callback = $callback; } - public function provide($class, array $args = array()) { + /** {@inheritdoc} */ + public function provide($class, array $args = array()) + { return call_user_func($this->callback, $class, $args); } } \ No newline at end of file diff --git a/src/watoki/factory/providers/DefaultProvider.php b/src/watoki/factory/providers/DefaultProvider.php index b44111d..5d57489 100644 --- a/src/watoki/factory/providers/DefaultProvider.php +++ b/src/watoki/factory/providers/DefaultProvider.php @@ -3,34 +3,59 @@ use watoki\factory\Factory; -class DefaultProvider extends MinimalProvider { - +/** + * Class DefaultProvider + * The default provider. Use annotations. + * + * @author "Nikolas Martens" + * @license MIT + * @package watoki\factory\providers + */ +class DefaultProvider extends MinimalProvider +{ + /** The injection token reads in annotation */ const INJECTION_TOKEN = '<-'; - + /** @var \Closure|callable The function to find if a property (magic variable) must be injected or not */ private $propertyFilter; - + /** @var \Closure|callable The function to find if the class variable must be injected on instantiation or not */ private $annotationFilter; - + /** @var string The name of the function to use (if exists) to inject properties(variables) into the new instance. */ private $injectionMethod = 'inject'; - function __construct(Factory $factory) { + /** + * Initialize the provider with the class factory. + * (initialize the injector, based on the factory, and "@var", "@param", "@property" filter functions) + * + * @param Factory $factory The classes factory + */ + function __construct(Factory $factory) + { parent::__construct($factory); + /* + * Change the default filter, to a new one that check if the param annotation contains the injection token + */ $this->setParameterFilter(function (\ReflectionParameter $parameter) { $pattern = '/@param.+\$' . $parameter->getName() . '.+' . DefaultProvider::INJECTION_TOKEN . '/'; return preg_match($pattern, $parameter->getDeclaringFunction()->getDocComment()); }); - + /* + * A callback function to check if the @property (magic variables) annotation contains teh injection token + */ $this->annotationFilter = function ($annotation) { return strpos($annotation, DefaultProvider::INJECTION_TOKEN) !== false; }; - + /* + * A callback function to check if the @var annotation contains teh injection token + */ $this->propertyFilter = function (\ReflectionProperty $property) { return strpos($property->getDocComment(), DefaultProvider::INJECTION_TOKEN) !== false; }; } - public function provide($class, array $args = array()) { + /** {@inheritdoc} */ + public function provide($class, array $args = array()) + { $instance = parent::provide($class, $args); if ($this->injectionMethod && method_exists($instance, $this->injectionMethod)) { @@ -45,37 +70,58 @@ public function provide($class, array $args = array()) { } /** + * Set the callback function to use to filter "@property" annotation. + * The filter function have this prototype: + * + * function(string $classname) : bool + * * @param callable $filter */ - public function setAnnotationFilter($filter) { + public function setAnnotationFilter($filter) + { $this->annotationFilter = $filter; } /** + * Get the function used to filter "@property" annotation + * * @return callable */ - public function getAnnotationFilter() { + public function getAnnotationFilter() + { return $this->annotationFilter; } /** + * Set the callback function to use to filter "@var" annotation. + * The filter function have this prototype: + * + * function(string $classname) : bool + * * @return callable */ - public function getPropertyFilter() { + public function getPropertyFilter() + { return $this->propertyFilter; } /** + * Get the function used to filter "@property" annotation + * * @param callable $propertyFilter */ - public function setPropertyFilter($propertyFilter) { + public function setPropertyFilter($propertyFilter) + { $this->propertyFilter = $propertyFilter; } /** + * Set the name of the instance function to used for injecting properties + * * @param string $injectionMethod */ - public function setInjectionMethod($injectionMethod) { + public function setInjectionMethod($injectionMethod) + { $this->injectionMethod = $injectionMethod; } } \ No newline at end of file diff --git a/src/watoki/factory/providers/MinimalProvider.php b/src/watoki/factory/providers/MinimalProvider.php index 3eaabcd..f27dcca 100644 --- a/src/watoki/factory/providers/MinimalProvider.php +++ b/src/watoki/factory/providers/MinimalProvider.php @@ -5,35 +5,62 @@ use watoki\factory\Injector; use watoki\factory\Provider; -class MinimalProvider implements Provider { - +/** + * Class MinimalProvider + * A base provider that use injector and a parameter injection filter. + * + * @author "Nikolas Martens" + * @license MIT + * @package watoki\factory\providers + */ +class MinimalProvider implements Provider +{ + /** @var Injector */ protected $injector; /** @var callable */ private $parameterFilter; - public function __construct(Factory $factory) { + /** + * Initialize the provider with the class factory. + * (initialize the injector, based on the factory, and the default filter function) + * + * @param Factory $factory The classes factory + */ + public function __construct(Factory $factory) + { $this->injector = new Injector($factory); $this->parameterFilter = function () { return true; }; } - public function provide($class, array $args = array()) { + /** {@inheritdoc} */ + public function provide($class, array $args = array()) + { return $this->injector->injectConstructor($class, $args, $this->parameterFilter); } /** + * Get the function used to filter which parameter can be injected or not + * * @return callable */ - public function getParameterFilter() { + public function getParameterFilter() + { return $this->parameterFilter; } /** + * Set the function that will be use to filter parameters. + * The filter function have this prototype: + * + * function(\ReflectionParameter $parameter) : bool + * * @param callable $parameterFilter Receives a \ReflectionParameter as argument */ - public function setParameterFilter($parameterFilter) { + public function setParameterFilter($parameterFilter) + { $this->parameterFilter = $parameterFilter; } } \ No newline at end of file diff --git a/src/watoki/factory/providers/SingletonProvider.php b/src/watoki/factory/providers/SingletonProvider.php index 87fe1c2..9e753ec 100644 --- a/src/watoki/factory/providers/SingletonProvider.php +++ b/src/watoki/factory/providers/SingletonProvider.php @@ -3,18 +3,32 @@ use watoki\factory\Provider; -class SingletonProvider implements Provider { +/** + * Class SingletonProvider + * A singleton provider. When use, the same instance of the class is return. + * + * @author "Nikolas Martens" + * @license MIT + * @package watoki\factory\providers + */ +class SingletonProvider implements Provider +{ private $instance; /** - * @param object $instance + * Initialize the provider with the instance to use a singleton + * + * @param object $instance The instance to use a singleton */ - function __construct($instance) { + public function __construct($instance) + { $this->instance = $instance; } - public function provide($class, array $args = array()) { + /** {@inheritdoc} */ + public function provide($class, array $args = array()) + { return $this->instance; } } \ No newline at end of file