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
32 changes: 24 additions & 8 deletions src/DiContainer/DiDefinition/Arguments/ArgumentBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use Kaspi\DiContainer\Traits\ParameterTypeByReflectionTrait;
use ReflectionFunctionAbstract;
use ReflectionParameter;
use Throwable;

use function array_column;
use function array_filter;
Expand Down Expand Up @@ -136,9 +137,10 @@ private function basedOnPhpAttributes(): array
continue;
}
} catch (AutowireAttributeException|AutowireParameterTypeException $e) {
throw new ArgumentBuilderException(
throw $this->exceptionWithContext(
message: sprintf('Cannot build argument via php attribute for %s in %s.', $param, $param->getDeclaringFunction()),
previous: $e
previous: $e,
context_param: $param
);
}

Expand Down Expand Up @@ -175,9 +177,10 @@ private function basedOnBindArgumentsAsPriorityAndPhpAttributes(): array
continue;
}
} catch (AutowireAttributeException|AutowireParameterTypeException $e) {
throw new ArgumentBuilderException(
throw $this->exceptionWithContext(
message: sprintf('Cannot build argument via php attribute for %s in %s.', $param, $param->getDeclaringFunction()),
previous: $e
previous: $e,
context_param: $param
);
}

Expand All @@ -204,9 +207,10 @@ private function pushFromParameterType(array &$args, ReflectionParameter $param)
}
} catch (AutowireParameterTypeException $e) {
if (!$param->isDefaultValueAvailable()) {
throw new ArgumentBuilderException(
throw $this->exceptionWithContext(
message: sprintf('Cannot build argument via type hint for %s in %s.', $param, functionName($param->getDeclaringFunction())),
previous: $e
previous: $e,
context_param: $param
);
}
}
Expand Down Expand Up @@ -272,8 +276,9 @@ private function getTailArguments(): array

foreach ($tailArgs as $key => $value) {
if (is_string($key)) {
throw new ArgumentBuilderException(
sprintf('Cannot build arguments for %s. Does not accept unknown named parameter $%s.', functionName($this->functionOrMethod), $key)
throw $this->exceptionWithContext(
message: sprintf('Cannot build arguments for %s. Does not accept unknown named parameter $%s.', functionName($this->functionOrMethod), $key),
context_tail_args: $tailArgs
);
}
}
Expand Down Expand Up @@ -338,4 +343,15 @@ private function getDefinitionByAttributes(ReflectionParameter $param): Generato
yield $definition;
}
}

private function exceptionWithContext(string $message, ?Throwable $previous = null, mixed ...$context): ArgumentBuilderException
{
return (new ArgumentBuilderException(message: $message, previous: $previous))
->setContext(
...$context,
context_reflection_function: $this->functionOrMethod,
context_bind_arguments: $this->bindArguments,
)
;
}
}
115 changes: 77 additions & 38 deletions src/DiContainer/DiDefinition/DiDefinitionAutowire.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,17 @@
use Kaspi\DiContainer\Interfaces\DiDefinition\DiDefinitionSingletonInterface;
use Kaspi\DiContainer\Interfaces\DiDefinition\DiDefinitionTagArgumentInterface;
use Kaspi\DiContainer\Interfaces\DiDefinition\DiTaggedDefinitionInterface;
use Kaspi\DiContainer\Interfaces\Exceptions\AutowireExceptionInterface;
use Kaspi\DiContainer\Interfaces\Exceptions\ArgumentBuilderExceptionInterface;
use Kaspi\DiContainer\Interfaces\Exceptions\DiDefinitionExceptionInterface;
use Kaspi\DiContainer\Traits\AttributeReaderTrait;
use Kaspi\DiContainer\Traits\BindArgumentsTrait;
use Kaspi\DiContainer\Traits\SetupConfigureTrait;
use Kaspi\DiContainer\Traits\TagsTrait;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use ReflectionClass;
use ReflectionException;
use Throwable;

use function call_user_func;
use function get_class;
use function get_debug_type;
use function is_callable;
Expand Down Expand Up @@ -136,14 +136,17 @@ public function resolve(DiContainerInterface $container, mixed $context = null):
$object = $this->newInstance();

foreach ($this->getSetups($this->getDefinition(), $container) as $method => $calls) {
if (!$this->getDefinition()->hasMethod($method)) {
throw new AutowireException(sprintf('The setter method "%s::%s()" does not exist.', $this->getDefinition()->getName(), $method));
try {
$reflectionMethod = $this->getDefinition()->getMethod($method);
} catch (ReflectionException $e) {
throw $this->exceptionWhenClassExist(
message: sprintf('The setter method "%s::%s()" does not exist.', $this->getDefinition()->getName(), $method),
previous: $e
);
}

$reflectionMethod = $this->getDefinition()->getMethod($method);

if ($reflectionMethod->isConstructor() || $reflectionMethod->isDestructor()) {
throw new AutowireException(sprintf('Cannot use %s::%s() as setter.', $this->getDefinition()->name, $method));
throw new DiDefinitionException(sprintf('Cannot use %s::%s() as setter.', $this->getDefinition()->name, $method));
}

foreach ($calls as $index => [$setupConfigureType, $callArguments]) {
Expand All @@ -157,7 +160,11 @@ public function resolve(DiContainerInterface $container, mixed $context = null):
? $arg->resolve($container, $this)
: $arg;
} catch (ContainerExceptionInterface $e) {
throw $this->exceptionWhenResolveArgument($argNameOrIndex, $argBuilder, $e);
throw $this->exceptionWhenClassExist(
message: $this->exceptionMessageWhenResolveArgument($argNameOrIndex, $argBuilder),
previous: $e,
context_argument: $arg
);
}
}

Expand All @@ -177,14 +184,15 @@ public function resolve(DiContainerInterface $container, mixed $context = null):
continue;
}

throw new AutowireException(
sprintf(
throw $this->exceptionWhenClassExist(
message: sprintf(
'The immutable setter "%s::%s()" must return same class "%s". Got type: %s',
$this->getDefinition()->getName(),
$method,
$this->getDefinition()->getName(),
get_debug_type($result)
)
),
context_method_result: $result
);
}
}
Expand All @@ -197,7 +205,9 @@ public function getDefinition(): ReflectionClass // @phpstan-ignore throws.unuse
try {
return $this->reflectionClass ??= new ReflectionClass($this->definition);
} catch (ReflectionException $e) { // @phpstan-ignore catch.neverThrown
throw new AutowireException(message: $e->getMessage());
throw (new DiDefinitionException(message: $e->getMessage()))
->setContext(context_definition: $this->definition)
;
}
}

Expand Down Expand Up @@ -247,18 +257,30 @@ public function geTagPriority(string $name, array $operationOptions = []): int|s
? 'value with key "priority.method" in the $options parameter in '.DiDefinitionTagArgumentInterface::class.'::bindTag()'
: 'the $priorityMethod parameter or the value with key "priority.method" in the $options parameter in the php attribute #[Tag]';

throw new DiDefinitionException(
sprintf('Cannot get tag priority for tag "%s" via method in class %s. The name of the priority method is specified by %s. Priority method must be present none-empty string. Got: %s', $name, $this->getIdentifier(), $wherePriorityMethod, var_export($method, true))
);
throw (
new DiDefinitionException(
sprintf('Cannot get tag priority for tag "%s" via method in class %s. The name of the priority method is specified by %s. Priority method must be present none-empty string. Got: %s', $name, $this->getIdentifier(), $wherePriorityMethod, var_export($method, true))
)
)
->setContext(context_tag_options: $tagOptions)
;
}

try {
return $this->getTagPriorityFromMethod($method, $name, $tagOptions);
} catch (AutowireExceptionInterface|InvalidArgumentException $e) {
throw new DiDefinitionException(
message: sprintf('Cannot get tag priority for tag "%s" via method %s::%s().', $name, $this->getIdentifier(), $method),
previous: $e
);
} catch (AutowireException|InvalidArgumentException $e) {
throw (
new DiDefinitionException(
message: sprintf('Cannot get tag priority for tag "%s" via method %s::%s().', $name, $this->getIdentifier(), $method),
previous: $e
)
)
->setContext(
context_callable: [$this->getIdentifier(), $method],
context_operation_options: $operationOptions,
context_tag_options: $tagOptions
)
;
}
}

Expand All @@ -273,11 +295,19 @@ public function geTagPriority(string $name, array $operationOptions = []): int|s
return $this->getTagPriorityFromMethod($priorityDefaultMethod, $name, $tagOptions);
} catch (InvalidArgumentException) {
return null;
} catch (AutowireExceptionInterface $e) {
throw new DiDefinitionException(
message: sprintf('Cannot get tag priority for tag "%s" via default priority method %s::%s().', $name, $this->getIdentifier(), $priorityDefaultMethod),
previous: $e
);
} catch (AutowireException $e) {
throw (
new DiDefinitionException(
message: sprintf('Cannot get tag priority for tag "%s" via default priority method %s::%s().', $name, $this->getIdentifier(), $priorityDefaultMethod),
previous: $e
)
)
->setContext(
context_callable: [$this->getIdentifier(), $priorityDefaultMethod],
context_operation_options: $operationOptions,
context_tag_options: $tagOptions
)
;
}
}

Expand All @@ -293,14 +323,12 @@ private function getContainer(): DiContainerInterface
}

/**
* @throws AutowireExceptionInterface|ContainerExceptionInterface|NotFoundExceptionInterface
* @throws ArgumentBuilderExceptionInterface|DiDefinitionExceptionInterface
*/
private function newInstance(): object
{
if (!$this->getDefinition()->isInstantiable()) {
throw new AutowireException(
sprintf('The "%s" class is not instantiable.', $this->getDefinition()->getName())
);
throw $this->exceptionWhenClassExist(sprintf('The "%s" class is not instantiable.', $this->getDefinition()->getName()));
}

if (!isset($this->constructArgBuilder)) {
Expand All @@ -321,7 +349,11 @@ private function newInstance(): object
? $arg->resolve($this->getContainer(), $this)
: $arg;
} catch (ContainerExceptionInterface $e) {
throw $this->exceptionWhenResolveArgument($argNameOrIndex, $this->constructArgBuilder, $e);
throw $this->exceptionWhenClassExist(
message: $this->exceptionMessageWhenResolveArgument($argNameOrIndex, $this->constructArgBuilder),
previous: $e,
context_argument: $arg
);
}
}

Expand All @@ -331,7 +363,7 @@ private function newInstance(): object
/**
* @return array<non-empty-string, TagOptions>
*
* @throws AutowireExceptionInterface
* @throws DiDefinitionExceptionInterface
*/
private function getTagsByAttribute(): array
{
Expand All @@ -358,16 +390,23 @@ private function getTagsByAttribute(): array
return $this->tagsByAttribute;
}

private function exceptionWhenResolveArgument(int|string $argNameOrIndex, ArgumentBuilder $argBuilder, ContainerExceptionInterface $e): AutowireException
private function exceptionMessageWhenResolveArgument(int|string $argNameOrIndex, ArgumentBuilder $argBuilder): string
{
$argMessage = is_int($argPresentedBy = $argBuilder->getArgumentNameOrIndexFromBindArguments($argNameOrIndex))
? sprintf('at position #%d', $argPresentedBy)
: sprintf('by named argument $%s', $argPresentedBy);

return new AutowireException(
message: sprintf('Cannot resolve parameter %s in %s.', $argMessage, functionName($argBuilder->getFunctionOrMethod())),
previous: $e
);
return sprintf('Cannot resolve parameter %s in %s.', $argMessage, functionName($argBuilder->getFunctionOrMethod()));
}

private function exceptionWhenClassExist(string $message, ?Throwable $previous = null, mixed ...$context): DiDefinitionException
{
return (new DiDefinitionException(message: $message, previous: $previous))
->setContext(
...$context,
context_reflection_class: $this->getDefinition(),
)
;
}

/**
Expand All @@ -383,7 +422,7 @@ private function getTagPriorityFromMethod(string $method, string $tag, array $ta
throw new InvalidArgumentException('Method must be declared with public and static modifiers.');
}

$priority = call_user_func($callable, $tag, $tagOptions);
$priority = $callable($tag, $tagOptions);

if (is_int($priority) || is_string($priority) || is_null($priority)) {
return $priority;
Expand Down
51 changes: 35 additions & 16 deletions src/DiContainer/DiDefinition/DiDefinitionCallable.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

use Closure;
use Kaspi\DiContainer\DiDefinition\Arguments\ArgumentBuilder;
use Kaspi\DiContainer\Exception\AutowireException;
use Kaspi\DiContainer\Exception\DiDefinitionCallableException;
use Kaspi\DiContainer\Interfaces\DiContainerCallInterface;
use Kaspi\DiContainer\Interfaces\DiContainerInterface;
Expand Down Expand Up @@ -100,9 +99,10 @@ public function resolve(DiContainerInterface $container, mixed $context = null):
? sprintf('at position #%d', $argPresentedBy)
: sprintf('by named argument $%s', $argPresentedBy);

throw new AutowireException(
throw $this->exceptionWhenCallableFunction(
message: sprintf('Cannot resolve parameter %s in %s.', $argMessage, functionName($this->argBuilder->getFunctionOrMethod())),
previous: $e
previous: $e,
context_argument: $arg
);
}
}
Expand All @@ -121,13 +121,18 @@ public function resolve(DiContainerInterface $container, mixed $context = null):
try {
$class = $container->get($class);
} catch (ContainerExceptionInterface $e) {
throw $this->throw($e, sprintf('Cannot get entry by container identifier "%s"', $class));
throw $this->exceptionWhenCallableFunction(
message: sprintf('Cannot get entry via container identifier "%s" for create callable definition.', $class),
previous: $e,
context_callable: [$class, $this->reflectionFn->method]
);
}
}

if (!is_callable($callable = [$class, $this->reflectionFn->name])) {
throw $this->throw(
prefixMessage: sprintf('Cannot create callable from %s', var_export($callable, true)),
throw $this->exceptionWhenCallableFunction(
message: sprintf('Cannot create callable from %s.', var_export($callable, true)),
context_callable: $callable
);
}

Expand Down Expand Up @@ -157,7 +162,14 @@ private function reflectionFn(): ReflectionFunction|ReflectionMethodByDefinition

return new ReflectionFunction($this->getDefinition()); // @phpstan-ignore argument.type
} catch (ReflectionException $e) {
throw $this->throw($e);
throw (
new DiDefinitionCallableException(
message: sprintf('Cannot create callable from %s.', var_export($this->getDefinition(), true)),
previous: $e,
)
)
->setContext(context_definition: $this->definition)
;
}
}

Expand All @@ -181,20 +193,27 @@ private function parseDefinition(array|callable|string $definition): array|calla
}

if (is_array($definition)) {
// @phpstan-ignore booleanAnd.rightAlwaysTrue, isset.offset, isset.offset
return isset($definition[0], $definition[1]) && is_string($definition[0]) && is_string($definition[1])
? [$definition[0], $definition[1]]
: throw $this->throw(prefixMessage: 'When the definition is an array, two array elements must be provided as none empty string');
// @phpstan-ignore isset.offset, isset.offset
if (isset($definition[0], $definition[1]) && is_string($definition[0]) && is_string($definition[1])) {
return [$definition[0], $definition[1]];
}

throw (
new DiDefinitionCallableException(
message: sprintf('When the definition present is an array, two array elements must be provided as none empty string. Got: %s', var_export($definition, true)),
)
)
->setContext(context_definition: $definition)
;
}

return [$definition, '__invoke'];
}

private function throw(?Throwable $previous = null, string $prefixMessage = 'Cannot convert definition to callable type'): DiDefinitionCallableException
private function exceptionWhenCallableFunction(string $message, ?Throwable $previous = null, mixed ...$context): DiDefinitionCallableException
{
return new DiDefinitionCallableException(
message: sprintf('%s. Definition value as: %s.', $prefixMessage, var_export($this->definition, true)),
previous: $previous,
);
return (new DiDefinitionCallableException(message: $message, previous: $previous))
->setContext(...$context, context_reflection_function: $this->reflectionFn, context_definition: $this->definition)
;
}
}
Loading