Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 64 additions & 11 deletions src/watoki/factory/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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" <Nikolas.M@rtens.org>
* @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));
}
Expand All @@ -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)) {
Expand All @@ -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());
Expand All @@ -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), '\\');
}
}
115 changes: 92 additions & 23 deletions src/watoki/factory/Injector.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,53 @@
use watoki\reflect\MethodAnalyzer;
use watoki\reflect\ClassResolver;

class Injector {
/**
* Class Injector
* Inject objects, params into an instance
*
* @author "Nikolas Martens" <Nikolas.M@rtens.org>
* @license MIT
* @package watoki\factory
*/
class Injector
{

/** @var bool */
private $throwException = true;

/** @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()) {
Expand All @@ -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;
};
Expand All @@ -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);
Expand All @@ -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;
}
Expand All @@ -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)) {
Expand All @@ -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);

Expand Down
19 changes: 18 additions & 1 deletion src/watoki/factory/Provider.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,25 @@
<?php
namespace watoki\factory;

interface Provider {
/**
* Interface Provider
* Describe how to get an instance from a class name and params
*
* @author "Nikolas Martens" <Nikolas.M@rtens.org>
* @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());

}
Loading