1: <?php
2:
3: namespace Charcoal\App;
4:
5: use RuntimeException;
6: use Slim\Interfaces\CallableResolverInterface;
7:
8: /**
9: * Resolve Callable
10: *
11: * This trait enables the resolution of 'class:method' strings into a closure.
12: * This class is based on {@see \Slim\CallableResolverAwareTrait Slim's trait}.
13: *
14: * The resolver will attempt checks on the current class, an optional alternate
15: * object, the DI container.
16: *
17: * Can resolve the following string formats:
18: *
19: * - 'class:method'
20: * - 'parent:method'
21: * - 'static:method'
22: * - 'self:method' or ':method'
23: */
24: trait CallableResolverAwareTrait
25: {
26: /**
27: * The cache of string-based callables.
28: *
29: * @var array
30: */
31: protected static $resolvedCallableCache = [];
32:
33: /**
34: * A regular expression for matching 'class:method' callable strings.
35: *
36: * @var string
37: */
38: protected $callablePattern = '!^([^\:]+)?\:([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)$!';
39:
40: /**
41: * Store the callable resolver.
42: *
43: * @var CallableResolverInterface
44: */
45: protected $callableResolver;
46:
47: /**
48: * Set a callable resolver.
49: *
50: * @param CallableResolverInterface $resolver A callable resolver.
51: * @return self
52: */
53: protected function setCallableResolver(CallableResolverInterface $resolver)
54: {
55: $this->callableResolver = $resolver;
56:
57: return $this;
58: }
59:
60: /**
61: * Resolve a string of the format 'class:method' into a closure that the
62: * router can dispatch.
63: *
64: * @param callable|string $callable A callable function or method, either as a reference or string.
65: * @param object|null $context Optional. An additional context under to test $callable as a method.
66: * @throws RuntimeException If the string cannot be resolved as a callable
67: * or the resolver was not previously set.
68: * @return \Closure
69: */
70: protected function resolveCallable($callable, $context = null)
71: {
72: if (!isset($this->callableResolver)) {
73: throw new RuntimeException(
74: sprintf('Callable Resolver is not defined for "%s"', get_class($this))
75: );
76: }
77:
78: if (!is_callable($callable) && is_string($callable)) {
79: $key = $callable;
80:
81: if (isset(static::$resolvedCallableCache[$key])) {
82: return static::$resolvedCallableCache[$key];
83: }
84:
85: if (preg_match($this->callablePattern, $callable, $matches)) {
86: $class = $matches[1];
87: $method = $matches[2];
88:
89: if (is_object($context)) {
90: $callable = [ $context, $method ];
91: }
92:
93: if (!is_callable($callable)) {
94: switch ($class) {
95: case '':
96: case 'self':
97: $callable = [ $this, $method ];
98: break;
99:
100: case 'static':
101: $callable = [ static::class, $method ];
102: break;
103:
104: case 'parent':
105: $callable = [ $this, 'parent::'.$method ];
106: break;
107: }
108: }
109: }
110:
111: if (is_callable($callable)) {
112: static::$resolvedCallableCache[$key] = $callable;
113: }
114: }
115:
116: return $this->callableResolver->resolve($callable);
117: }
118: }
119: