Completed
Push — 3.1.x ( b8b6d2...fd2df9 )
by Karel
14:12 queued 11:28
created

Indexable::buildExpressionCallback()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 21
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 3.0041

Importance

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