Skip to content
Merged
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
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "2.1-dev"
"dev-master": "3.0-dev"
}
}
}
55 changes: 55 additions & 0 deletions src/Autowire.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

declare(strict_types=1);

namespace Respect\Config;

use Psr\Container\ContainerInterface;
use ReflectionNamedType;

use function array_key_exists;
use function end;
use function key;

class Autowire extends Instantiator
{
protected ContainerInterface|null $container = null;

public function setContainer(ContainerInterface $container): void
{
$this->container = $container;
}

/** @inheritDoc */
protected function cleanupParams(array $params): array
{
$constructor = $this->reflection()->getConstructor();
if ($constructor && $this->container) {
foreach ($constructor->getParameters() as $param) {
$name = $param->getName();
if (array_key_exists($name, $this->params)) {
$params[$name] = $this->lazyLoad($params[$name] ?? null);
continue;
}

$type = $param->getType();
if (
!($type instanceof ReflectionNamedType) || $type->isBuiltin()
|| !$this->container->has($type->getName())
) {
continue;
}

$params[$name] = $this->container->get($type->getName());
}

while (end($params) === null && ($key = key($params)) !== null) {
unset($params[$key]);
}

return $params;
}

return parent::cleanupParams($params);
}
}
67 changes: 62 additions & 5 deletions src/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@
use InvalidArgumentException;
use Psr\Container\ContainerInterface;
use ReflectionClass;
use ReflectionException;
use ReflectionFunction;
use ReflectionNamedType;

use function array_filter;
use function array_map;
use function assert;
use function call_user_func;
use function class_exists;
use function constant;
use function count;
use function current;
Expand Down Expand Up @@ -47,7 +50,16 @@ public function has(string $id): bool
$this->configure();
}

return parent::offsetExists($id);
if (!parent::offsetExists($id)) {
return false;
}

$entry = $this[$id];
if ($entry instanceof Instantiator) {
return class_exists($entry->getClassName());
}

return true;
}

public function getItem(string $name, bool $raw = false): mixed
Expand All @@ -64,7 +76,11 @@ public function getItem(string $name, bool $raw = false): mixed
return $this[$name];
}

return $this->lazyLoad($name);
try {
return $this->lazyLoad($name);
} catch (ReflectionException $e) {
throw new NotFoundException('Item ' . $name . ' not found: ' . $e->getMessage(), 0, $e);
}
}

public function get(string $id): mixed
Expand Down Expand Up @@ -97,13 +113,33 @@ public function loadArray(array $configurator): void
{
foreach ($this->state() + $configurator as $key => $value) {
if ($value instanceof Closure) {
$this->offsetSet((string) $key, $value);
continue;
}

if ($value instanceof Instantiator) {
$this->offsetSet((string) $key, $value);
continue;
}

$this->parseItem($key, $value);
}
}

public function offsetSet(mixed $key, mixed $value): void
{
if ($value instanceof Autowire) {
$value->setContainer($this);
}

parent::offsetSet($key, $value);
}

public function set(string $name, mixed $value): void
{
$this[$name] = $value;
}

protected function configure(): void
{
$configurator = $this->configurator;
Expand Down Expand Up @@ -206,7 +242,7 @@ protected function parseInstantiator(string $key, mixed $value): void
}

/** @var class-string $keyClass */
$instantiator = new Instantiator($keyClass);
$instantiator = $this->createInstantiator($keyClass);

if (is_array($value)) {
foreach ($value as $property => $pValue) {
Expand All @@ -219,6 +255,23 @@ protected function parseInstantiator(string $key, mixed $value): void
$this->offsetSet($keyName, $instantiator);
}

/** @param class-string $keyClass */
protected function createInstantiator(string $keyClass): Instantiator
{
if (!str_contains($keyClass, ' ')) {
return new Instantiator($keyClass);
}

[$modifier, $className] = explode(' ', $keyClass, 2);

/** @var class-string $className */
return match ($modifier) {
'new' => new Factory($className),
'autowire' => new Autowire($className),
default => new Instantiator($keyClass),
};
}

protected function parseValue(mixed $value): mixed
{
if ($value instanceof Instantiator) {
Expand Down Expand Up @@ -319,11 +372,15 @@ protected function parseArgumentList(string $value): array
protected function lazyLoad(string $name): mixed
{
$callback = $this[$name];
if ($callback instanceof Instantiator && $callback->getMode() !== Instantiator::MODE_FACTORY) {
if ($callback instanceof Instantiator && !$callback instanceof Factory) {
return $this[$name] = $callback();
}

return $callback();
if ($callback instanceof Closure) {
return $this[$name] = $callback($this);
}

return call_user_func($callback);
}

public function __isset(string $name): bool
Expand Down
15 changes: 15 additions & 0 deletions src/Factory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Respect\Config;

class Factory extends Instantiator
{
public function getInstance(bool $forceNew = false): mixed
{
$this->instance = null;

return parent::getInstance(true);
}
}
103 changes: 35 additions & 68 deletions src/Instantiator.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,22 @@
use function call_user_func_array;
use function count;
use function end;
use function explode;
use function func_get_args;
use function is_array;
use function is_callable;
use function is_object;
use function key;
use function str_contains;
use function stripos;
use function strtolower;

class Instantiator
{
public const false MODE_DEPENDENCY = false;
public const string MODE_FACTORY = 'new';

protected mixed $instance = null;

/** @var ReflectionClass<object> */
protected ReflectionClass $reflection;
/** @var ReflectionClass<object>|null */
protected ReflectionClass|null $reflection = null;

/** @var array<string, mixed> */
protected array $constructor = [];
/** @var array<string, mixed>|null */
protected array|null $constructor = null;

/** @var array<string, mixed> */
protected array $params = [];
Expand All @@ -45,25 +39,15 @@ class Instantiator
/** @var array<string, mixed> */
protected array $propertySetters = [];

protected string|false $mode = self::MODE_DEPENDENCY;

/** @param class-string $className */
public function __construct(protected string $className)
/**
* @param class-string $className
* @param array<string, mixed> $params Initial parameters (constructor, method, or property)
*/
public function __construct(protected string $className, array $params = [])
{
if (str_contains(strtolower($className), ' ')) {
[$mode, $className] = explode(' ', $className, 2);
$this->mode = $mode;
/** @var class-string $className */
$this->className = $className;
foreach ($params as $name => $value) {
$this->setParam($name, $value);
}

$this->reflection = new ReflectionClass($className);
$this->constructor = $this->findConstructorParams($this->reflection);
}

public function getMode(): string|false
{
return $this->mode;
}

public function getClassName(): string
Expand All @@ -73,10 +57,6 @@ public function getClassName(): string

public function getInstance(bool $forceNew = false): mixed
{
if ($this->mode === self::MODE_FACTORY) {
$this->instance = null;
}

if ($this->instance && !$forceNew) {
return $this->instance;
}
Expand All @@ -98,15 +78,12 @@ static function (mixed $result) use ($className, &$instance, $staticMethods): vo
);
}

$constructor = $this->reflection->getConstructor();
$hasConstructor = $constructor ? $constructor->isPublic() : false;
if (empty($instance)) {
if (empty($this->constructor) || !$hasConstructor) {
$instance = new $className();
$constructorParams = $this->cleanupParams($this->constructor ?? []);
if (empty($constructorParams)) {
$instance = $this->reflection()->newInstance();
} else {
$instance = $this->reflection->newInstanceArgs(
$this->cleanupParams($this->constructor),
);
$instance = $this->reflection()->newInstanceArgs($constructorParams);
}
}

Expand Down Expand Up @@ -156,19 +133,20 @@ public function getParams(): array
return $this->params;
}

/** @return ReflectionClass<object> */
protected function reflection(): ReflectionClass
{
return $this->reflection ??= new ReflectionClass($this->className);
}

/**
* @param array<mixed> $params
*
* @return array<mixed>
*/
protected function cleanupParams(array $params): array
{
while (end($params) === null) {
$key = key($params);
if ($key === null) {
break;
}

while (end($params) === null && ($key = key($params)) !== null) {
unset($params[$key]);
}

Expand All @@ -184,27 +162,6 @@ protected function lazyLoad(mixed $value): mixed
return $value instanceof self ? $value->getInstance() : $value;
}

/**
* @param ReflectionClass<object> $class
*
* @return array<string, mixed>
*/
protected function findConstructorParams(ReflectionClass $class): array
{
$params = [];
$constructor = $class->getConstructor();

if (!$constructor) {
return [];
}

foreach ($constructor->getParameters() as $param) {
$params[$param->getName()] = $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null;
}

return $params;
}

protected function processValue(mixed $value): mixed
{
if (is_array($value)) {
Expand All @@ -218,6 +175,16 @@ protected function processValue(mixed $value): mixed

protected function matchConstructorParam(string $name): bool
{
if ($this->constructor === null) {
$this->constructor = [];
$ctor = $this->reflection()->getConstructor();
if ($ctor) {
foreach ($ctor->getParameters() as $param) {
$this->constructor[$param->getName()] = null;
}
}
}

return array_key_exists($name, $this->constructor);
}

Expand All @@ -229,13 +196,13 @@ protected function matchFullConstructor(string $name): bool

protected function matchMethod(string $name): bool
{
return $this->reflection->hasMethod($name);
return $this->reflection()->hasMethod($name);
}

protected function matchStaticMethod(string $name): bool
{
return $this->reflection->hasMethod($name)
&& $this->reflection->getMethod($name)->isStatic();
return $this->reflection()->hasMethod($name)
&& $this->reflection()->getMethod($name)->isStatic();
}

/** @param array{string, mixed} $methodCalls */
Expand Down
Loading
Loading