diff --git a/src/Container/ContainerResolver.php b/src/Container/ContainerResolver.php index 78e3966..fb89816 100644 --- a/src/Container/ContainerResolver.php +++ b/src/Container/ContainerResolver.php @@ -3,7 +3,6 @@ namespace ArrayAccess\TrayDigita\Container; -use ArrayAccess\TrayDigita\Container\Exceptions\ContainerFrozenException; use ArrayAccess\TrayDigita\Container\Exceptions\ContainerNotFoundException; use ArrayAccess\TrayDigita\Container\Interfaces\ContainerAllocatorInterface; use ArrayAccess\TrayDigita\Container\Interfaces\ContainerIndicateInterface; @@ -11,14 +10,29 @@ use ArrayAccess\TrayDigita\Event\Interfaces\ManagerAllocatorInterface; use ArrayAccess\TrayDigita\Event\Interfaces\ManagerInterface; use ArrayAccess\TrayDigita\Exceptions\Logical\UnResolveAbleException; -use ArrayAccess\TrayDigita\Util\Filter\Consolidation; +use ArrayAccess\TrayDigita\Http\Factory\RequestFactory; +use ArrayAccess\TrayDigita\Http\Factory\ResponseFactory; +use ArrayAccess\TrayDigita\Http\Request; +use ArrayAccess\TrayDigita\Http\Response; +use ArrayAccess\TrayDigita\Http\ServerRequest; +use ArrayAccess\TrayDigita\Util\Filter\ContainerHelper; use Closure; use Psr\Container\ContainerInterface; +use Psr\Http\Message\RequestFactoryInterface; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseFactoryInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestFactoryInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\StreamFactoryInterface; use ReflectionClass; +use ReflectionException; use ReflectionFunction; use ReflectionFunctionAbstract; use ReflectionMethod; use ReflectionNamedType; +use ReflectionParameter; +use ReflectionType; use ReflectionUnionType; use Throwable; use function array_key_exists; @@ -26,6 +40,7 @@ use function class_exists; use function count; use function end; +use function gettype; use function is_a; use function is_array; use function is_callable; @@ -37,8 +52,14 @@ class ContainerResolver implements ContainerIndicateInterface { + private bool $hasAliasedMethod; + private bool $hasParameterMethod; + public function __construct(protected ContainerInterface $container) { + $isContainer = $this->container instanceof ContainerInterface; + $this->hasAliasedMethod = $isContainer|| method_exists($this->container, 'getAliases'); + $this->hasParameterMethod = $isContainer || method_exists($this->container, 'getParameters'); } public function getContainer(): ContainerInterface @@ -50,20 +71,21 @@ public function getContainer(): ContainerInterface * @template T of object * @param callable|array|class-string|mixed $callable * @param array $arguments + * @param array|null $fallback * @return array|T|mixed * @throws Throwable */ - public function resolveCallable(mixed $callable, array $arguments = []): mixed - { + public function resolveCallable( + mixed $callable, + array $arguments = [], + ?array $fallback = null + ): mixed { $container = $this->getContainer(); $value = $callable; - if (is_string($callable) - && Consolidation::isValidClassName($callable) - && class_exists($callable) - ) { + if (is_string($callable) && class_exists($callable)) { $ref = new ReflectionClass($callable); if ($ref->isInstantiable()) { - $arguments = $this->resolveArguments($ref, $arguments); + $arguments = $this->resolveArguments($ref, $arguments, $fallback); // resolver empty arguments when auto resolve enabled $value = new $callable( ...$arguments @@ -164,123 +186,255 @@ public function resolve(string $id): mixed } /** - * @throws Throwable - * @throws ContainerNotFoundException - * @throws ContainerFrozenException - * @throws UnResolveAbleException + * Resolve argument for builtin & object + * + * @param ReflectionNamedType $type + * @param mixed $argumentValue + * @param $found + * @return mixed */ - public function resolveArguments( - ReflectionClass|ReflectionFunctionAbstract $reflection, - $arguments - ): array { - $reflectionName = $reflection->getName(); - $reflection = $reflection instanceof ReflectionClass - ? $reflection->getConstructor() - : $reflection; - - // resolver empty arguments when auto resolve enabled - /*if (!empty($arguments) && count($arguments) === $reflection->getNumberOfRequiredParameters()) { - return $arguments; - }*/ + private function resolveTheArgumentObjectBuiltin( + ReflectionNamedType $type, + mixed $argumentValue, + &$found = null + ) : mixed { + $found = false; + if ($argumentValue === null && $type->allowsNull()) { + $found = true; + return null; + } + if ($type->isBuiltin()) { + $argType = gettype($argumentValue); + $found = $argType === $type->getName(); + return $found ? $argumentValue : null; + } + if (is_object($argumentValue) && is_a($argumentValue, $type->getName())) { + $found = true; + return $argumentValue; + } + return null; + } - $parameters = $reflection?->getParameters()??[]; + private function resolveFactoryObject(ReflectionNamedType $type, &$found = null) + { + $found = false; + if ($type->isBuiltin()) { + return null; + } + $name = $type->getName(); + $factory = [ + ServerRequest::class => ServerRequestInterface::class, + Request::class => RequestInterface::class, + Response::class => ResponseInterface::class, + ServerRequestInterface::class => ServerRequestInterface::class, + RequestInterface::class => RequestInterface::class, + ResponseInterface::class => ResponseInterface::class, + ]; + if (!isset($factory[$name])) { + return null; + } $container = $this->getContainer(); - $containerParameters = method_exists($container, 'getParameters') - ? $container->getParameters() - : []; - $containerParameters = (array) $containerParameters; - $containerAliases = method_exists($container, 'getAliases') - ? $container->getAliases() - : []; - $paramArguments = []; - foreach ($parameters as $parameter) { - $argumentName = $parameter->getName(); - $type = $parameter->getType(); - if ($type instanceof ReflectionUnionType) { - foreach ($type->getTypes() as $unionType) { - if (!$unionType instanceof ReflectionNamedType - || $unionType->isBuiltin() - ) { - continue; - } + $name = $factory[$name]; + switch ($name) { + case RequestInterface::class: + case ServerRequestInterface::class: + $found = true; + $serverRequest = ServerRequest::fromGlobals( + ContainerHelper::use(ServerRequestFactoryInterface::class, $container), + ContainerHelper::use(StreamFactoryInterface::class, $container) + ); + if ($name === ServerRequestInterface::class) { + return $serverRequest; + } + return (ContainerHelper::getNull( + RequestFactoryInterface::class, + $this->getContainer() + )??new RequestFactory())->createRequest($serverRequest->getMethod(), $serverRequest->getUri()); + case ResponseInterface::class: + $found = true; + return (ContainerHelper::getNull( + ResponseFactoryInterface::class, + $this->getContainer() + )??new ResponseFactory())->createResponse(); + } + return null; + } - $name = $unionType->getName(); - if ($name === ContainerInterface::class - || is_a($name, __CLASS__) - || $container->has($name) - ) { - $type = $unionType; - break; + private function resolveInternalArgument( + ReflectionParameter $parameter, + ReflectionType $refType, + int $offset, + array $arguments, + array $containerParameters, + array $containerAliases, + array &$paramArguments, + &$paramFound = null + ): void { + $paramFound = false; + if ($refType instanceof ReflectionUnionType) { + foreach ($refType->getTypes() as $type) { + if ($type instanceof ReflectionNamedType) { + $this->resolveInternalArgument( + $parameter, + $type, + $offset, + $arguments, + $containerParameters, + $containerAliases, + $paramArguments, + $paramFound + ); + if ($paramFound) { + return; } } } - if (!$type instanceof ReflectionNamedType - || $type->isBuiltin() - ) { - if (isset($arguments[$parameter->getName()])) { - $paramArguments[$argumentName] = $containerParameters[$parameter->getName()]; - continue; - } - if (array_key_exists($parameter->getName(), $containerParameters)) { - $paramArguments[$argumentName] = $containerParameters[$parameter->getName()]; - continue; - } + return; + } + + if (!$refType instanceof ReflectionNamedType) { + return; + } - if ($parameter->isDefaultValueAvailable()) { - $paramArguments[$argumentName] = $parameter->getDefaultValue(); + $parameterName = $parameter->getName(); + $refName = $refType->getName(); + if ($arguments !== []) { + foreach ([$refName, $parameterName, $offset] as $val) { + if (!array_key_exists($val, $arguments)) { continue; } - if ($parameter->allowsNull()) { - $paramArguments[$argumentName] = null; - continue; + $value = $this->resolveTheArgumentObjectBuiltin($refType, $arguments[$val], $paramFound); + if ($paramFound) { + $paramArguments[$parameter->getName()] = $value; + return; } - $paramArguments = []; - break; } + } - if (isset($arguments[$parameter->getName()])) { - $paramArguments[$argumentName] = $arguments[$parameter->getName()]; - continue; + if (array_key_exists($parameterName, $containerParameters)) { + $value = $this->resolveTheArgumentObjectBuiltin( + $refType, + $containerParameters[$parameterName], + $paramFound + ); + if ($paramFound) { + $paramArguments[$parameterName] = $value; + return; } + } - $name = $type->getName(); - if ($name === ContainerInterface::class - || is_a($name, __CLASS__) - ) { - $paramArguments[$argumentName] = $container->has($name) - ? $container->get($name) - : $container; - continue; - } - if (!$container->has($name) - && isset($containerAliases[$name]) - && $container->has($containerAliases[$name]) - ) { - $paramArguments[$argumentName] = $container->get($containerAliases[$name]); - continue; + if ($refName === ContainerInterface::class || is_a($refName, $this->container::class)) { + $paramFound = true; + if ($this->container->has($refName)) { + try { + $param = $this->container->get($refName); + if (is_object($param) && is_a($param, ContainerInterface::class)) { + $paramArguments[$parameterName] = $param; + return; + } + } catch (Throwable) { + } } + $paramArguments[$parameterName] = $this->container; + return; + } - if (!$container->has($name)) { - if (array_key_exists($name, $containerParameters)) { - $param = $containerParameters[$name]; - if (is_string($param) && $container->has($param)) { - $param = $container->get($param); + if (!$refType->isBuiltin()) { + if ($this->container->has($refName)) { + try { + $value = $this->resolveTheArgumentObjectBuiltin( + $refType, + $this->container->get($refName), + $paramFound + ); + if ($paramFound) { + $paramArguments[$parameterName] = $value; + return; } - $paramArguments[$argumentName] = $param; - continue; - } - if ($parameter->isDefaultValueAvailable()) { - $paramArguments[$argumentName] = $parameter->getDefaultValue(); - continue; + } catch (Throwable) { } - if ($parameter->allowsNull()) { - $paramArguments[$argumentName] = null; - continue; + } + if (isset($containerAliases[$parameterName]) + && $this->container->has($containerAliases[$parameterName]) + ) { + try { + $value = $this->resolveTheArgumentObjectBuiltin( + $refType, + $this->container->get($containerAliases[$parameterName]), + $paramFound + ); + if ($paramFound) { + $paramArguments[$parameterName] = $value; + return; + } + } catch (Throwable) { } - $paramArguments = []; - break; } - $paramArguments[$argumentName] = $container->get($name); + } + if (($isDefault = $parameter->isDefaultValueAvailable()) + || $parameter->allowsNull() + ) { + $paramFound = true; + try { + $paramArguments[$parameterName] = $isDefault ? $parameter->getDefaultValue() : null; + return; + } catch (ReflectionException) { + } + } + if ($paramFound) { + return; + } + $res = $this->resolveFactoryObject($refType, $paramFound); + if ($paramFound) { + $paramArguments[$parameterName] = $res; + } + } + + /** + * @throws Throwable + */ + public function resolveArguments( + ReflectionClass|ReflectionFunctionAbstract $reflection, + $arguments, + ?array $fallback = null + ): array { + $reflectionName = $reflection->getName(); + $reflection = $reflection instanceof ReflectionClass + ? $reflection->getConstructor() + : $reflection; + + // resolver empty arguments when auto resolve enabled + /*if (!empty($arguments) && count($arguments) === $reflection->getNumberOfRequiredParameters()) { + return $arguments; + }*/ + $parameters = $reflection?->getParameters()??[]; + $container = $this->getContainer(); + /** @noinspection PhpPossiblePolymorphicInvocationInspection */ + $containerParameters = (array) ($this->hasParameterMethod ? $container->getParameters() : []); + /** @noinspection PhpPossiblePolymorphicInvocationInspection */ + $containerAliases = (array) ($this->hasAliasedMethod ? $container->getAliases() : []); + $paramArguments = []; + foreach ($parameters as $offset => $parameter) { + $type = $parameter->getType(); + $this->resolveInternalArgument( + $parameter, + $type, + $offset, + $arguments, + $containerParameters, + $containerAliases, + $paramArguments, + $found + ); + if ($found) { + continue; + } + // go to default fallback + if ($fallback && count($fallback) > $reflection->getNumberOfRequiredParameters()) { + return $fallback; + } + $paramArguments = []; + break; } if (($required = $reflection?->getNumberOfRequiredParameters()??0) > count($paramArguments)) { throw new UnResolveAbleException( diff --git a/src/Routing/AbstractController.php b/src/Routing/AbstractController.php index 4978ce2..903e046 100644 --- a/src/Routing/AbstractController.php +++ b/src/Routing/AbstractController.php @@ -4,6 +4,7 @@ namespace ArrayAccess\TrayDigita\Routing; use ArrayAccess\TrayDigita\Container\Container; +use ArrayAccess\TrayDigita\Container\ContainerWrapper; use ArrayAccess\TrayDigita\Event\Interfaces\ManagerAllocatorInterface; use ArrayAccess\TrayDigita\Event\Interfaces\ManagerInterface; use ArrayAccess\TrayDigita\Http\Code; @@ -30,6 +31,7 @@ use ReflectionMethod; use Stringable; use Throwable; +use function array_values; use function is_int; use function is_iterable; use function is_object; @@ -247,13 +249,33 @@ public function dispatch( $response = $this->getResponseFactory()->createResponse(); $refMethod = new ReflectionMethod($this, $method); $method = $refMethod->getName(); - + $resolver = ContainerWrapper::maybeContainerOrCreate( + $this->getContainer() + )->getResolver(); + $_arguments = [ + ServerRequestInterface::class => $request, + ResponseInterface::class => $response + ] + $arguments; + try { + $_arguments = $resolver->resolveArguments( + new ReflectionMethod($this, $method), + $_arguments, + array_values($_arguments) + ); + } catch (Throwable) { + $_arguments = array_values($_arguments); + } if ($refMethod->isPrivate()) { $response = (function ($method, ...$arguments) { return $this->$method(...$arguments); - })->call($this, $refMethod->getName(), $request, $response, ...$arguments); + })->call( + $this, + $resolver, + $refMethod->getName(), + ...$_arguments + ); } else { - $response = $this->{$refMethod->getName()}($request, $response, ...$arguments); + $response = $this->{$refMethod->getName()}(...$_arguments); } } diff --git a/src/Util/Filter/ContainerHelper.php b/src/Util/Filter/ContainerHelper.php index cf99c57..24c5222 100644 --- a/src/Util/Filter/ContainerHelper.php +++ b/src/Util/Filter/ContainerHelper.php @@ -106,18 +106,20 @@ public static function decorate( /** * @template T of object * @param callable|array|class-string|mixed $callable - * @param array $arguments * @param ContainerInterface|null $container + * @param array $arguments + * @param ?array $fallback * @return array|T|mixed * @throws Throwable */ public static function resolveCallable( mixed $callable, ?ContainerInterface $container = null, - array $arguments = [] + array $arguments = [], + ?array $fallback = null ): mixed { $container ??= Decorator::container(); return ContainerWrapper::maybeContainerOrCreate($container) - ->getResolver()->resolveCallable($callable, $arguments); + ->getResolver()->resolveCallable($callable, $arguments, $fallback); } }