1: <?php
2:
3: namespace LaravelUi5\Core\Services;
4:
5: use Illuminate\Contracts\Container\BindingResolutionException;
6: use Illuminate\Contracts\Container\Container;
7: use Illuminate\Foundation\Http\FormRequest;
8: use LaravelUi5\Core\Contracts\ExecutableInvokerInterface;
9: use LaravelUi5\Core\Contracts\ParameterResolverInterface;
10: use LaravelUi5\Core\Contracts\SettingResolverInterface;
11: use LaravelUi5\Core\Exceptions\InvalidParameterTypeException;
12: use LogicException;
13: use ReflectionMethod;
14: use ReflectionNamedType;
15:
16: final readonly class ExecutableInvoker implements ExecutableInvokerInterface
17: {
18: public function __construct(
19: private Container $container,
20: private ParameterResolverInterface $parameterResolver,
21: private SettingResolverInterface $settingResolver,
22: ) {}
23:
24: /**
25: * @throws BindingResolutionException
26: */
27: public function invoke(object $target, string $method): mixed
28: {
29: if (!method_exists($target, $method)) {
30: throw new LogicException(
31: sprintf(
32: 'Executable %s must define a %s() method.',
33: $target::class,
34: $method
35: )
36: );
37: }
38:
39: // 1. Resolve parameters
40: $parameters = $this->parameterResolver->resolve($target);
41:
42: // 2. Inject settings
43: $this->settingResolver->resolve($target);
44:
45: // 3. Build arguments from method signature
46: $reflection = new ReflectionMethod($target, $method);
47: $arguments = [];
48:
49: foreach ($reflection->getParameters() as $parameter) {
50: $type = $parameter->getType();
51:
52: if (!$type instanceof ReflectionNamedType) {
53: throw new LogicException(
54: 'Union or untyped parameters are not supported.'
55: );
56: }
57:
58: $paramClass = $type->getName();
59:
60: // FormRequest
61: if (is_subclass_of($paramClass, FormRequest::class)) {
62: /** @var FormRequest $request */
63: $request = $this->container->make($paramClass);
64:
65: // Triggers authorize() + validation()
66: $request->validateResolved();
67:
68: $arguments[$parameter->getName()] = $request;
69: continue;
70: }
71:
72: // Declarative parameters
73: if (!array_key_exists($parameter->getName(), $parameters)) {
74: throw new LogicException(
75: sprintf(
76: 'Unable to resolve parameter $%s (%s) for %s::%s().',
77: $parameter->getName(),
78: $paramClass,
79: $target::class,
80: $method
81: )
82: );
83: }
84:
85: $value = $parameters[$parameter->getName()];
86:
87: // Type guard against method signature
88: if (!is_a($value, $paramClass)) {
89: throw new InvalidParameterTypeException($parameter->getName());
90: }
91:
92: $arguments[$parameter->getName()] = $value;
93: }
94:
95: // 4. Invoke via container
96: return $this->container->call([$target, $method], $arguments);
97: }
98: }
99: