Skip to content

Commit

Permalink
Refactored ArrayDefinition extracted ArrayBuilder
Browse files Browse the repository at this point in the history
  • Loading branch information
hiqsol committed Jun 8, 2019
1 parent 8ab030a commit ee21ce6
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 116 deletions.
2 changes: 1 addition & 1 deletion src/Container.php
Expand Up @@ -157,7 +157,7 @@ private function buildInternal(string $id, array $params = [])
private function buildPrimitive(string $class, array $params = [])
{
if (class_exists($class)) {
$definition = ArrayDefinition::fromClassName($class);
$definition = new ArrayDefinition($class);

return $definition->resolve($this, $params);
}
Expand Down
122 changes: 122 additions & 0 deletions src/definitions/ArrayBuilder.php
@@ -0,0 +1,122 @@
<?php

namespace yii\di\definitions;

use Psr\Container\ContainerInterface;
use yii\di\contracts\Definition;
use yii\di\exceptions\NotInstantiableException;
use yii\di\resolvers\ClassNameResolver;

/**
* Builds object by ArrayDefinition.
*/
class ArrayBuilder
{
private static $dependencies = [];

public function build(ContainerInterface $container, ArrayDefinition $def)
{
$class = $def->getClass();
$dependencies = $this->getDependencies($class);

if (!empty($def->getParams())) {
foreach (array_values($def->getParams()) as $index => $param) {
if ($param instanceof Definition) {
$dependencies[$index] = $param;
} else {
$dependencies[$index] = new ValueDefinition($param);
}
}
}

$resolved = $this->resolveDependencies($container, $dependencies);
$object = new $class(...$resolved);
return $this->configure($container, $object, $def->getConfig());
}

/**
* Resolves dependencies by replacing them with the actual object instances.
* @param ContainerInterface $container
* @param Definition[] $dependencies the dependencies
* @return array the resolved dependencies
*/
private function resolveDependencies(ContainerInterface $container, array $dependencies): array
{
$container = $container->parentContainer ?? $container;
$result = [];
/** @var Definition $dependency */
foreach ($dependencies as $dependency) {
$result[] = $this->resolveDependency($container, $dependency);
}

return $result;
}

/**
* This function resolves a dependency recursively, checking for loops.
* TODO add checking for loops
* @param ContainerInterface $container
* @param Definition $dependency
* @return mixed
*/
private function resolveDependency(ContainerInterface $container, Definition $dependency)
{
while ($dependency instanceof Definition) {
$dependency = $dependency->resolve($container);
}
return $dependency;
}

/**
* Returns the dependencies of the specified class.
* @param string $class class name, interface name or alias name
* @return Definition[] the dependencies of the specified class.
* @throws NotInstantiableException
* @internal
*/
private function getDependencies(string $class): array
{
if (!isset(self::$dependencies[$class])) {
self::$dependencies[$class] = $this->getResolver()->resolveConstructor($class);
}

return self::$dependencies[$class];
}

private static $resolver;

private function getResolver()
{
if (static::$resolver === null) {
// For now use hard coded resolver.
static::$resolver = new ClassNameResolver();
}

return static::$resolver;
}

/**
* Configures an object with the given configuration.
* @param ContainerInterface $container
* @param object $object the object to be configured
* @param iterable $config property values and methods to call
* @return object the object itself
*/
private function configure(ContainerInterface $container, $object, iterable $config)
{
foreach ($config as $action => $arguments) {
if (substr($action, -2) === '()') {
// method call
\call_user_func_array([$object, substr($action, 0, -2)], $arguments);
} else {
// property
if ($arguments instanceof Definition) {
$arguments = $arguments->resolve($container);
}
$object->$action = $arguments;
}
}

return $object;
}
}
159 changes: 48 additions & 111 deletions src/definitions/ArrayDefinition.php
Expand Up @@ -3,161 +3,98 @@

use Psr\Container\ContainerInterface;
use yii\di\contracts\Definition;
use yii\di\exceptions\InvalidConfigException;
use yii\di\exceptions\NotInstantiableException;
use yii\di\resolvers\ClassNameResolver;

/**
* Builds object by array config.
* @package yii\di
*/
class ArrayDefinition implements Definition
{
private $class;
private $params;
private $config;

private static $dependencies = [];

private const CLASS_KEY = '__class';
private const CONSTRUCT_KEY = '__construct()';

/**
* Injector constructor.
* @param string $class class name, must not be empty
* @param array $params
* @param array $config
*/
public function __construct(array $config)
public function __construct(string $class, array $params = [], array $config = [])
{
if (empty($class)) {
throw Exception('class name not given');
}
$this->class = $class;
$this->params = $params;
$this->config = $config;
}

public function getArray(): array
/**
* @return string
*/
public function getClass(): string
{
return $this->config;
return $this->class;
}

/**
* @param string $class
* @return self
* @return array
*/
public static function fromClassName(string $class): self
public function getParams(): array
{
return new static([self::CLASS_KEY => $class]);
return $this->params;
}

public function resolve(ContainerInterface $container, array $params = [])
/**
* @return array
*/
public function getConfig(): array
{
$config = $this->config;

if (empty($config[self::CLASS_KEY])) {
throw new NotInstantiableException(var_export($config, true));
}

if (!empty($params)) {
$config[self::CONSTRUCT_KEY] = array_merge($config[self::CONSTRUCT_KEY] ?? [], $params);
}

return $this->buildFromArray($container, $config);
return $this->config;
}

private function buildFromArray(ContainerInterface $container, array $config)
{
if (empty($config[self::CLASS_KEY])) {
throw new NotInstantiableException(var_export($config, true));
}
$class = $config[self::CLASS_KEY];
unset($config[self::CLASS_KEY]);

$dependencies = $this->getDependencies($class);

if (isset($config[self::CONSTRUCT_KEY])) {
foreach (array_values($config[self::CONSTRUCT_KEY]) as $index => $param) {
if ($param instanceof Definition) {
$dependencies[$index] = $param;
} else {
$dependencies[$index] = new ValueDefinition($param);
}
}
unset($config[self::CONSTRUCT_KEY]);
}

$resolved = $this->resolveDependencies($container, $dependencies);
$object = new $class(...$resolved);
return $this->configure($container, $object, $config);
}
private const CLASS_KEY = '__class';
private const PARAMS_KEY = '__construct()';

/**
* Resolves dependencies by replacing them with the actual object instances.
* @param ContainerInterface $container
* @param Definition[] $dependencies the dependencies
* @return array the resolved dependencies
* @param string $class class name
* @param array $params
* @param array $config
* @return self
*/
private function resolveDependencies(ContainerInterface $container, array $dependencies): array
public static function fromArray(string $class = null, array $params = [], array $config = []): self
{
$container = $container->parentContainer ?? $container;
$result = [];
/** @var Definition $dependency */
foreach ($dependencies as $dependency) {
$result[] = $this->resolveDependency($container, $dependency);
}
$class = $config[self::CLASS_KEY] ?? $class;
$params = $config[self::PARAMS_KEY] ?? $params;

return $result;
}
unset($config[self::CLASS_KEY]);
unset($config[self::PARAMS_KEY]);

/**
* This function resolves a dependency recursively, checking for loops.
* TODO add checking for loops
* @param ContainerInterface $container
* @param Definition $dependency
* @return mixed
*/
private function resolveDependency(ContainerInterface $container, Definition $dependency)
{
while ($dependency instanceof Definition) {
$dependency = $dependency->resolve($container);
if (empty($class)) {
throw new NotInstantiableException(var_export($config, true));
}
return $dependency;

return new static($class, $params, $config);
}

/**
* Returns the dependencies of the specified class.
* @param string $class class name, interface name or alias name
* @return Definition[] the dependencies of the specified class.
* @throws NotInstantiableException
* @internal
*/
private function getDependencies(string $class): array
public function resolve(ContainerInterface $container, array $params = [])
{
if (!isset($this->dependencies[$class])) {
// For now use hard coded resolver.
$resolver = new ClassNameResolver();

self::$dependencies[$class] = $resolver->resolveConstructor($class);
if (!empty($params)) {
$this->params = array_merge($this->params, $params);
}

return self::$dependencies[$class];
return $this->getBuilder()->build($container, $this);
}

/**
* Configures an object with the given configuration.
* @param ContainerInterface $container
* @param object $object the object to be configured
* @param iterable $config property values and methods to call
* @return object the object itself
*/
private function configure(ContainerInterface $container, $object, iterable $config)
private static $builder;

private function getBuilder()
{
foreach ($config as $action => $arguments) {
if (substr($action, -2) === '()') {
// method call
\call_user_func_array([$object, substr($action, 0, -2)], $arguments);
} else {
// property
if ($arguments instanceof Definition) {
$arguments = $arguments->resolve($container);
}
$object->$action = $arguments;
}
if (static::$builder === null) {
static::$builder = new ArrayBuilder();
}

return $object;
return static::$builder;
}
}
5 changes: 1 addition & 4 deletions src/definitions/Normalizer.php
Expand Up @@ -60,10 +60,7 @@ public static function normalize($config, string $id = null): Definition
if (\is_array($config)
&& !(isset($config[0], $config[1]) && count($config) === 2)
) {
if ($id && empty($config['__class'])) {
$config['__class'] = $id;
}
return new ArrayDefinition($config);
return ArrayDefinition::fromArray($id, [], $config);
}

if (\is_callable($config)) {
Expand Down

0 comments on commit ee21ce6

Please sign in to comment.