1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
|
<?php
namespace Drupal\Core\Utility;
use Drupal\Core\DependencyInjection\ClassResolverInterface;
/**
* Resolves PHP callables.
*
* The callable resolver service aims to provide a standardized approach to how
* callables are resolved and invoked from various subsystems. The callable
* resolver will return or invoke a callable defined in any of the following
* definition formats:
*
* - Service notation:
* @code
* 'some.service:method'
* @endcode
* - Static methods:
* @code
* '\FooClass::staticMethod'
* @endcode
* - Non-static methods, instantiated with the class resolver:
* @code
* '\DependencyInjectedClass::method'
* @endcode
* - Object calls:
* @code
* [$object, 'method']
* @endcode
* - Classes with an __invoke method:
* @code
* '\ClassWithInvoke'
* @endcode
* - Closures.
*/
class CallableResolver {
/**
* Constructs a CallableResolver object.
*
* @param \Drupal\Core\DependencyInjection\ClassResolverInterface $classResolver
* The class resolver.
*/
public function __construct(
protected readonly ClassResolverInterface $classResolver,
) {
}
/**
* Gets a callable from a definition.
*
* @param callable|array|string $definition
* A callable definition.
*
* @return callable
* A callable.
*
* @throws \InvalidArgumentException
* Thrown when no valid callable could be resolved from the definition.
*/
public function getCallableFromDefinition(callable|array|string $definition): callable {
// If the definition is natively a callable, we can return it immediately.
if (is_callable($definition)) {
return $definition;
}
if (is_array($definition)) {
throw new \InvalidArgumentException(sprintf('The callable definition provided "[%s]" is not a valid callable.', implode(",", $definition)));
}
if (!is_string($definition)) {
throw new \InvalidArgumentException(sprintf('The callable definition provided was invalid. Illegal format of type: %s', gettype($definition)));
}
// Callable with __invoke().
if (!str_contains($definition, ':')) {
$instance = $this->classResolver->getInstanceFromDefinition($definition);
if (!is_callable($instance)) {
throw new \InvalidArgumentException(sprintf('The callable definition provided was invalid. Class "%s" does not have a method "__invoke" and is not callable.', $instance::class));
}
return $instance;
}
// Callable in the service:method notation.
$class_or_service = NULL;
$method = NULL;
$count = substr_count($definition, ':');
if ($count == 1) {
[$class_or_service, $method] = explode(':', $definition, 2);
}
// Callable in the class::method notation.
if (str_contains($definition, '::')) {
[$class_or_service, $method] = explode('::', $definition, 2);
}
if (empty($class_or_service) || empty($method)) {
throw new \InvalidArgumentException(sprintf('The callable definition provided was invalid. Could not get class and method from definition "%s".', $definition));
}
$instance = $this->classResolver->getInstanceFromDefinition($class_or_service);
if (!is_callable([$instance, $method])) {
throw new \InvalidArgumentException(sprintf('The callable definition provided was invalid. Either class "%s" does not have a method "%s", or it is not callable.', $instance::class, $method));
}
return [$instance, $method];
}
}
|