Completed
Push — develop ( a379bf...d8bd8a )
by Nate
14:21
created

Scope::prepareCallable()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 29
ccs 0
cts 20
cp 0
rs 9.456
c 0
b 0
f 0
cc 3
nc 3
nop 3
crap 12
1
<?php
2
3
/**
4
 * @author    Flipbox Factory
5
 * @copyright Copyright (c) 2017, Flipbox Digital
6
 * @link      https://github.com/flipbox/transform/releases/latest
7
 * @license   https://github.com/flipbox/transform/blob/master/LICENSE
8
 */
9
10
namespace Flipbox\Transform;
11
12
use Flipbox\Transform\Helpers\ArgumentHelper;
13
use Flipbox\Transform\Helpers\TransformerHelper;
14
use Flipbox\Transform\Resources\ResourceInterface;
15
use Flipbox\Transform\Transformers\TransformerInterface;
16
17
/**
18
 * @author Flipbox Factory <[email protected]>
19
 * @since 3.0.0
20
 */
21
class Scope
22
{
23
    /**
24
     * @var string
25
     */
26
    protected $scopeIdentifier;
27
28
    /**
29
     * @var Transform
30
     */
31
    protected $transform;
32
33
    /**
34
     * @var array
35
     */
36
    protected $parentScopes = [];
37
38
    /**
39
     * Scope constructor.
40
     * @param Transform $transform
41
     * @param string|null $scopeIdentifier
42
     * @param array $parentScopes
43
     */
44
    public function __construct(
45
        Transform $transform,
46
        string $scopeIdentifier = null,
47
        array $parentScopes = []
48
    ) {
49
        $this->transform = $transform;
50
        $this->scopeIdentifier = $scopeIdentifier;
51
        $this->parentScopes = $parentScopes;
52
    }
53
54
    /**
55
     * @return Transform
56
     */
57
    public function getTransform(): Transform
58
    {
59
        return $this->transform;
60
    }
61
62
    /**
63
     * @param $key
64
     * @return ParamBag
65
     */
66
    public function getParams(string $key = null): ParamBag
67
    {
68
        return $this->getTransform()->getParams(
69
            $this->getIdentifier($key)
70
        );
71
    }
72
73
74
    /**
75
     * Get the unique identifier for this scope.
76
     *
77
     * @param string $appendIdentifier
78
     *
79
     * @return string
80
     */
81
    public function getIdentifier(string $appendIdentifier = null): string
82
    {
83
        return implode(
84
            '.',
85
            array_filter(array_merge(
86
                $this->parentScopes,
87
                [
88
                    $this->scopeIdentifier,
89
                    $appendIdentifier
90
                ]
91
            ))
92
        );
93
    }
94
95
    /**
96
     * Is Requested.
97
     *
98
     * Check if - in relation to the current scope - this specific segment is allowed.
99
     * That means, if a.b.c is requested and the current scope is a.b, then c is allowed. If the current
100
     * scope is a then c is not allowed, even if it is there and potentially transformable.
101
     *
102
     * @param string $checkScopeSegment
103
     *
104
     * @return bool Returns the new number of elements in the array.
105
     */
106
    public function isRequested($checkScopeSegment): bool
107
    {
108
        return in_array(
109
            $this->scopeString($checkScopeSegment),
110
            $this->transform->getIncludes()
111
        );
112
    }
113
114
    /**
115
     * Is Excluded.
116
     *
117
     * Check if - in relation to the current scope - this specific segment should
118
     * be excluded. That means, if a.b.c is excluded and the current scope is a.b,
119
     * then c will not be allowed in the transformation whether it appears in
120
     * the list of default or available, requested includes.
121
     *
122
     * @param string $checkScopeSegment
123
     *
124
     * @return bool
125
     */
126
    protected function isExcluded($checkScopeSegment): bool
127
    {
128
        return in_array(
129
            $this->scopeString($checkScopeSegment),
130
            $this->transform->getExcludes()
131
        );
132
    }
133
134
    /**
135
     * @param callable $transformer
136
     * @param string $key
137
     * @return bool
138
     */
139
    public function includeValue(callable $transformer, string $key): bool
140
    {
141
        // Ignore optional (that have not been explicitly requested)
142
        if ($transformer instanceof TransformerInterface &&
143
            TransformerHelper::inInclude($transformer, $key) &&
144
            !$this->isRequested($key)
145
        ) {
146
            return false;
147
        }
148
149
        // Ignore excludes
150
        if ($this->isExcluded($key)) {
151
            return false;
152
        }
153
154
        return true;
155
    }
156
157
    /**
158
     * @param callable $transformer
159
     * @param mixed $data
160
     * @param array $extra
161
     * @return array
162
     */
163
    public function transform(callable $transformer, $data, array $extra = []): array
164
    {
165
        return (array)$this->prepareValue(
166
            $transformer,
167
            null,
168
            array_merge(
169
                $extra,
170
                ['data' => $data]
171
            )
172
        );
173
    }
174
175
    /**
176
     * @param callable $transformer
177
     * @param array $data
178
     * @param string|null $key
179
     * @param array $params
180
     * @return array
181
     */
182
    public function prepareData(callable $transformer, array $data, string $key = null, array $params = []): array
183
    {
184
        foreach ($data as $k => $val) {
185
            $newKey = ($key ? $key . '.' : '') . $k;
186
            if (!$this->includeValue($transformer, $newKey)) {
187
                unset($data[$k]);
188
                continue;
189
            }
190
191
            if (is_callable($val)) {
192
                $data[$k] = $this->prepareCallable($val, $newKey, $params);
193
            } elseif (is_array($val)) {
194
                $data[$k] = $this->prepareData($transformer, $val, $newKey, $params);
195
            } else {
196
                $data[$k] = $this->prepareValue($val, $newKey, $params);
197
            }
198
        }
199
200
        return $this->filterFields($data);
201
    }
202
203
    /**
204
     * @param $value
205
     * @param string|null $key
206
     * @param array $params
207
     * @return mixed
208
     */
209
    protected function prepareValue($value, string $key = null, array $params = [])
210
    {
211
        if ($value instanceof ResourceInterface) {
212
            return $this->prepareResource($value, $key, $params);
213
        }
214
215
        if (is_callable($value)) {
216
            return $this->prepareCallable($value, $key, $params);
217
        }
218
219
        return $value;
220
    }
221
222
    /**
223
     * @param ResourceInterface $transformer
224
     * @param string $key
225
     * @param array $params
226
     * @return mixed
227
     */
228
    protected function prepareResource(
229
        ResourceInterface $transformer,
230
        string $key,
231
        array $params = []
232
    ) {
233
        return call_user_func_array(
234
            $transformer,
235
            array_merge(
236
                [$this, $key],
237
                $params
238
            )
239
        );
240
    }
241
242
    /**
243
     * @param callable $callable
244
     * @param string|null $key
245
     * @param array $params
246
     * @return mixed
247
     */
248
    protected function prepareCallable(
249
        callable $callable,
250
        string $key = null,
251
        array $params = []
252
    ) {
253
        if ($callable instanceof TransformerInterface) {
254
            return $this->prepareTransformer($callable, $key, $params);
255
        }
256
257
        if (TransformerHelper::isClosure($callable)) {
258
            return $this->prepareClosure($callable, $key, $params);
1 ignored issue
show
Documentation introduced by
$callable is of type callable, but the function expects a object<Closure>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
259
        }
260
261
        $args = ArgumentHelper::callable(
262
            $callable,
263
            array_merge(
264
                $params,
265
                [
266
                    'scope' => $this,
267
                    'identifier' => $key
268
                ]
269
            )
270
        );
271
272
        return call_user_func_array(
273
            $callable,
274
            $args
275
        );
276
    }
277
278
    /**
279
     * @param \Closure $transformer
280
     * @param string|null $key
281
     * @param array $extra
282
     * @return mixed
283
     */
284
    protected function prepareClosure(
285
        \Closure $transformer,
286
        string $key = null,
287
        array $extra = []
288
    ) {
289
        $args = ArgumentHelper::closure(
290
            $transformer,
291
            array_merge(
292
                $extra,
293
                [
294
                    'scope' => $this,
295
                    'identifier' => $key
296
                ]
297
            )
298
        );
299
300
        return call_user_func_array(
301
            $transformer,
302
            $args
303
        );
304
    }
305
306
    /**
307
     * @param TransformerInterface $transformer
308
     * @param $data
309
     * @param string|null $key
310
     * @param array $extra
311
     * @return mixed
312
     */
313
    protected function prepareTransformer(
314
        TransformerInterface $transformer,
315
        string $key = null,
316
        array $extra = []
317
    ) {
318
        $args = ArgumentHelper::transformer(
319
            $transformer,
320
            array_merge(
321
                $extra,
322
                [
323
                    'scope' => $this,
324
                    'identifier' => $key
325
                ]
326
            )
327
        );
328
329
        return call_user_func_array(
330
            $transformer,
331
            $args
332
        );
333
    }
334
335
    /**
336
     * @param string $identifier
337
     * @return Scope
338
     */
339
    public function childScope(string $identifier): Scope
340
    {
341
        $parentScopes = $this->parentScopes;
342
        $parentScopes[] = $this->scopeIdentifier;
343
344
        return new static(
345
            $this->getTransform(),
346
            $identifier,
347
            $parentScopes
348
        );
349
    }
350
351
    /**
352
     * Check, if this is the root scope.
353
     *
354
     * @return bool
355
     */
356
    protected function isRootScope(): bool
357
    {
358
        return empty($this->parentScopes);
359
    }
360
361
    /**
362
     * Filter the provided data with the requested filter fields for
363
     * the scope resource
364
     *
365
     * @param array $data
366
     *
367
     * @return array
368
     */
369
    protected function filterFields(array $data): array
370
    {
371
        $fields = $this->getFilterFields();
372
373
        if ($fields === null) {
374
            return $data;
375
        }
376
377
        return array_intersect_key(
378
            $data,
379
            array_flip(
380
                iterator_to_array($fields)
381
            )
382
        );
383
    }
384
385
    /**
386
     * Return the requested filter fields for the scope resource
387
     *
388
     * @internal
389
     *
390
     * @return ParamBag|null
391
     */
392
    protected function getFilterFields()
393
    {
394
        return $this->transform->getField(
395
            $this->scopeIdentifier
396
        );
397
    }
398
399
    /**
400
     * @param string $checkScopeSegment
401
     * @return string
402
     */
403
    private function scopeString(string $checkScopeSegment): string
404
    {
405
        if (!empty($this->parentScopes)) {
406
            $scopeArray = array_slice($this->parentScopes, 1);
407
            array_push($scopeArray, $this->scopeIdentifier, $checkScopeSegment);
408
        } else {
409
            $scopeArray = [$checkScopeSegment];
410
        }
411
412
        return implode('.', (array)$scopeArray);
413
    }
414
}
415