5 * The resolver takes a string expression and returns an object or callable.
7 * The following patterns will resolve to objects:
8 * - 'obj://objectName' - An object from Civi\Core\Container
9 * - 'ClassName' - An instance of ClassName (with default constructor).
10 * If you need more control over construction, then register with the
13 * The following patterns will resolve to callables:
14 * - 'function_name' - A function(callable).
15 * - 'ClassName::methodName" - A static method of a class.
16 * - 'call://objectName/method' - A method on an object from Civi\Core\Container.
17 * - 'api3://EntityName/action' - A method call on an API.
18 * (Performance note: Requires full setup/teardown of API subsystem.)
19 * - 'api3://EntityName/action?first=@1&second=@2' - Call an API method, mapping the
20 * first & second args to named parameters.
21 * (Performance note: Requires parsing/interpolating arguments).
22 * - '0' or '1' - A dummy which returns the constant '0' or '1'.
24 * Note: To differentiate classes and functions, there is a hard requirement that
25 * class names begin with an uppercase letter.
27 * Note: If you are working in a context which requires a callable, it is legitimate to use
28 * an object notation ("obj://objectName" or "ClassName") if the object supports __invoke().
34 protected static $_singleton;
41 public static function singleton() {
42 if (self
::$_singleton === NULL) {
43 self
::$_singleton = new Resolver();
45 return self
::$_singleton;
49 * Convert a callback expression to a valid PHP callback.
51 * @param string|array $id
52 * A callback expression; any of the following.
55 * A PHP callback. Do not serialize (b/c it may include an object).
56 * @throws \RuntimeException
58 public function get($id) {
59 if (!is_string($id)) {
60 // An array or object does not need to be further resolved.
64 if (strpos($id, '::') !== FALSE) {
65 // Callback: Static method.
66 return explode('::', $id);
68 elseif (strpos($id, '://') !== FALSE) {
69 $url = parse_url($id);
70 switch ($url['scheme']) {
72 // Object: Lookup in container.
73 return Container
::singleton()->get($url['host']);
76 // Callback: Object/method in container.
77 $obj = Container
::singleton()->get($url['host']);
78 return array($obj, ltrim($url['path'], '/'));
82 return new ResolverApi($url);
85 throw new \
RuntimeException("Unsupported callback scheme: " . $url['scheme']);
88 elseif (in_array($id, array('0', '1'))) {
89 // Callback: Constant value.
90 return new ResolverConstantCallback((int) $id);
92 elseif ($id{0} >= 'A' && $id{0} <= 'Z') {
93 // Object: New/default instance.
97 // Callback: Function.
103 * Invoke a callback expression.
105 * @param string|callable $id
107 * Ordered parameters. To call-by-reference, set an array-parameter by reference.
111 public function call($id, $args) {
112 $cb = $this->get($id);
113 return $cb ?
call_user_func_array($cb, $args) : NULL;
119 * Private helper which produces a dummy callback.
123 class ResolverConstantCallback
{
132 * @param mixed $value
133 * The value to be returned by the dummy callback.
135 public function __construct($value) {
136 $this->value
= $value;
144 public function __invoke() {
151 * Private helper which treats an API as a callable function.
161 * - string query (optional)
169 * Parsed URL (e.g. "api3://EntityName/action?foo=bar").
173 public function __construct($url) {
180 public function __invoke() {
181 $apiParams = array();
182 if (isset($this->url
['query'])) {
183 parse_str($this->url
['query'], $apiParams);
186 if (count($apiParams)) {
187 $args = func_get_args();
189 $this->interpolate($apiParams, $this->createPlaceholders('@', $args));
193 $result = civicrm_api3($this->url
['host'], ltrim($this->url
['path'], '/'), $apiParams);
194 return isset($result['values']) ?
$result['values'] : NULL;
198 * Create placeholders.
200 * @param string $prefix
202 * Positional arguments.
205 * Named placeholders based on the positional arguments
206 * (e.g. "@1" => "firstValue").
208 protected function createPlaceholders($prefix, $args) {
210 foreach ($args as $offset => $arg) {
211 $result[$prefix . (1 +
$offset)] = $arg;
217 * Recursively interpolate values.
220 * $params = array('foo' => '@1');
221 * $this->interpolate($params, array('@1'=> $object))
222 * assert $data['foo'] == $object;
225 * @param array $array
226 * Array which may or many not contain a mix of tokens.
227 * @param array $replacements
228 * A list of tokens to substitute.
230 protected function interpolate(&$array, $replacements) {
231 foreach (array_keys($array) as $key) {
232 if (is_array($array[$key])) {
233 $this->interpolate($array[$key], $replacements);
236 foreach ($replacements as $oldVal => $newVal) {
237 if ($array[$key] === $oldVal) {
238 $array[$key] = $newVal;