Completed
Pull Request — master (#1168)
by Alessandro
06:13
created

Indexable::getCallback()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 2
crap 2
1
<?php
2
3
/**
4
 * This file is part of the FOSElasticaBundle project.
5
 *
6
 * (c) FriendsOfSymfony <https://github.com/FriendsOfSymfony/FOSElasticaBundle/graphs/contributors>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace FOS\ElasticaBundle\Provider;
13
14
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
15
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
16
use Symfony\Component\ExpressionLanguage\Expression;
17
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
18
use Symfony\Component\ExpressionLanguage\SyntaxError;
19
use Symfony\Component\PropertyAccess\PropertyAccess;
20
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
21
22
class Indexable implements IndexableInterface, ContainerAwareInterface
23
{
24
    use ContainerAwareTrait;
25
26
    /**
27
     * An array of raw configured callbacks for all types.
28
     *
29
     * @var array
30
     */
31
    private $callbacks = array();
32
33
    /**
34
     * An instance of ExpressionLanguage.
35
     *
36
     * @var ExpressionLanguage
37
     */
38
    private $expressionLanguage;
39
40
    /**
41
     * An array of initialised callbacks.
42
     *
43
     * @var array
44
     */
45
    private $initialisedCallbacks = array();
46
47
    /**
48
     * PropertyAccessor instance.
49
     *
50
     * @var PropertyAccessorInterface
51
     */
52
    private $propertyAccessor;
53
54
    /**
55
     * @param array $callbacks
56
     */
57 24
    public function __construct(array $callbacks)
58
    {
59 24
        $this->callbacks = $callbacks;
60 24
        $this->propertyAccessor = PropertyAccess::createPropertyAccessor();
61 24
    }
62
63
    /**
64
     * Return whether the object is indexable with respect to the callback.
65
     *
66
     * @param string $indexName
67
     * @param string $typeName
68
     * @param mixed  $object
69
     *
70
     * @return bool
71
     */
72 17
    public function isObjectIndexable($indexName, $typeName, $object)
73
    {
74 17
        $type = sprintf('%s/%s', $indexName, $typeName);
75 17
        $callback = $this->getCallback($type, $object);
76 12
        if (!$callback) {
77 1
            return true;
78
        }
79
80 11
        if ($callback instanceof Expression) {
81 6
            return (bool) $this->getExpressionLanguage()->evaluate($callback, array(
82 6
                'object' => $object,
83 6
                $this->getExpressionVar($object) => $object,
84
            ));
85
        }
86
87 6
        return is_string($callback)
88 2
            ? call_user_func(array($object, $callback))
89 6
            : call_user_func($callback, $object);
90
    }
91
92
    /**
93
     * Builds and initialises a callback.
94
     *
95
     * @param string $type
96
     * @param object $object
97
     *
98
     * @return mixed
99
     */
100 17
    private function buildCallback($type, $object)
101
    {
102 17
        if (!array_key_exists($type, $this->callbacks)) {
103 1
            return;
104
        }
105
106 16
        $callback = $this->callbacks[$type];
107
108 16
        if (is_callable($callback) or is_callable(array($object, $callback))) {
109 4
            return $callback;
110
        }
111
112 13
        if (is_array($callback) && !is_object($callback[0])) {
113 4
            return $this->processArrayToCallback($type, $callback);
114
        }
115
116 10
        if (is_string($callback)) {
117 8
            return $this->buildExpressionCallback($type, $object, $callback);
118
        }
119
120 2
        throw new \InvalidArgumentException(sprintf('Callback for type "%s" is not a valid callback.', $type));
121
    }
122
123
    /**
124
     * Processes a string expression into an Expression.
125
     *
126
     * @param string $type
127
     * @param mixed $object
128
     * @param string $callback
129
     *
130
     * @return Expression
131
     */
132 8
    private function buildExpressionCallback($type, $object, $callback)
133
    {
134 8
        $expression = $this->getExpressionLanguage();
135 8
        if (!$expression) {
136
            throw new \RuntimeException('Unable to process an expression without the ExpressionLanguage component.');
137
        }
138
139
        try {
140 8
            $callback = new Expression($callback);
141 8
            $expression->compile($callback, array(
142 8
                'object', $this->getExpressionVar($object)
143
            ));
144
145 6
            return $callback;
146 2
        } catch (SyntaxError $e) {
147 2
            throw new \InvalidArgumentException(sprintf(
148 2
                'Callback for type "%s" is an invalid expression',
149
                $type
150 2
            ), $e->getCode(), $e);
151
        }
152
    }
153
154
    /**
155
     * Retreives a cached callback, or creates a new callback if one is not found.
156
     *
157
     * @param string $type
158
     * @param object $object
159
     *
160
     * @return mixed
161
     */
162 17
    private function getCallback($type, $object)
163
    {
164 17
        if (!array_key_exists($type, $this->initialisedCallbacks)) {
165 17
            $this->initialisedCallbacks[$type] = $this->buildCallback($type, $object);
166
        }
167
168 12
        return $this->initialisedCallbacks[$type];
169
    }
170
171
    /**
172
     * Returns the ExpressionLanguage class if it is available.
173
     *
174
     * @return ExpressionLanguage|null
175
     */
176 8
    private function getExpressionLanguage()
177
    {
178 8
        if (null === $this->expressionLanguage) {
179 8
            $this->expressionLanguage = new ExpressionLanguage();
180
        }
181
182 8
        return $this->expressionLanguage;
183
    }
184
185
    /**
186
     * Returns the variable name to be used to access the object when using the ExpressionLanguage
187
     * component to parse and evaluate an expression.
188
     *
189
     * @param mixed $object
190
     *
191
     * @return string
192
     */
193 8
    private function getExpressionVar($object = null)
194
    {
195 8
        if (!is_object($object)) {
196
            return 'object';
197
        }
198
199 8
        $ref = new \ReflectionClass($object);
200
201 8
        return strtolower($ref->getShortName());
202
    }
203
204
    /**
205
     * Processes an array into a callback. Replaces the first element with a service if
206
     * it begins with an @.
207
     *
208
     * @param string $type
209
     * @param array $callback
210
     *
211
     * @return array
212
     */
213 4
    private function processArrayToCallback($type, array $callback)
214
    {
215 4
        list($class, $method) = $callback + array(null, '__invoke');
216
217 4
        if (strpos($class, '@') === 0) {
218 4
            $service = $this->container->get(substr($class, 1));
219 4
            $callback = array($service, $method);
220
221 4
            if (!is_callable($callback)) {
222 1
                throw new \InvalidArgumentException(sprintf(
223 1
                    'Method "%s" on service "%s" is not callable.',
224
                    $method,
225 1
                    substr($class, 1)
226
                ));
227
            }
228
229 3
            return $callback;
230
        }
231
232
        throw new \InvalidArgumentException(sprintf(
233
            'Unable to parse callback array for type "%s"',
234
            $type
235
        ));
236
    }
237
}
238