Completed
Push — develop ( a083a7...ae7e37 )
by Nate
01:53
created

Scope::getIdentifier()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 13
ccs 0
cts 13
cp 0
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 7
nc 1
nop 1
crap 2
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\TransformerHelper;
13
use Flipbox\Transform\Transformers\TransformerInterface;
14
use InvalidArgumentException;
15
use ReflectionMethod;
16
use ReflectionParameter;
17
18
/**
19
 * @author Flipbox Factory <[email protected]>
20
 * @since 1.0.0
21
 */
22
class Scope
23
{
24
    const IGNORE_EXTRA_PARAMS = ['data', 'scope', 'identifier'];
25
26
    /**
27
     * @var string
28
     */
29
    protected $scopeIdentifier;
30
31
    /**
32
     * @var Transform
33
     */
34
    protected $transform;
35
36
    /**
37
     * @var array
38
     */
39
    protected $parentScopes = [];
40
41
    /**
42
     * Scope constructor.
43
     * @param Transform $transform
44
     * @param string|null $scopeIdentifier
45
     * @param array $parentScopes
46
     */
47
    public function __construct(
48
        Transform $transform,
49
        string $scopeIdentifier = null,
50
        array $parentScopes = []
51
    ) {
52
        $this->transform = $transform;
53
        $this->scopeIdentifier = $scopeIdentifier;
54
        $this->parentScopes = $parentScopes;
55
    }
56
57
    /**
58
     * @return Transform
59
     */
60
    public function getTransform(): Transform
61
    {
62
        return $this->transform;
63
    }
64
65
    /**
66
     * @param $key
67
     * @return ParamBag
68
     */
69
    public function getParams(string $key = null): ParamBag
70
    {
71
        return $this->getTransform()->getParams(
72
            $this->getIdentifier($key)
73
        );
74
    }
75
76
    /**
77
     * Get the current identifier.
78
     *
79
     * @return string|null
80
     */
81
    public function getScopeIdentifier()
82
    {
83
        return $this->scopeIdentifier;
84
    }
85
86
    /**
87
     * Get the unique identifier for this scope.
88
     *
89
     * @param string $appendIdentifier
90
     *
91
     * @return string
92
     */
93
    public function getIdentifier(string $appendIdentifier = null): string
94
    {
95
        return implode(
96
            '.',
97
            array_filter(array_merge(
98
                $this->parentScopes,
99
                [
100
                    $this->scopeIdentifier,
101
                    $appendIdentifier
102
                ]
103
            ))
104
        );
105
    }
106
107
    /**
108
     * Getter for parentScopes.
109
     *
110
     * @return array
111
     */
112
    public function getParentScopes(): array
113
    {
114
        return $this->parentScopes;
115
    }
116
117
    /**
118
     * Is Requested.
119
     *
120
     * Check if - in relation to the current scope - this specific segment is allowed.
121
     * That means, if a.b.c is requested and the current scope is a.b, then c is allowed. If the current
122
     * scope is a then c is not allowed, even if it is there and potentially transformable.
123
     *
124
     * @internal
125
     *
126
     * @param string $checkScopeSegment
127
     *
128
     * @return bool Returns the new number of elements in the array.
129
     */
130
    public function isRequested($checkScopeSegment): bool
131
    {
132
        return in_array(
133
            $this->scopeString($checkScopeSegment),
134
            $this->transform->getIncludes()
135
        );
136
    }
137
138
    /**
139
     * Is Excluded.
140
     *
141
     * Check if - in relation to the current scope - this specific segment should
142
     * be excluded. That means, if a.b.c is excluded and the current scope is a.b,
143
     * then c will not be allowed in the transformation whether it appears in
144
     * the list of default or available, requested includes.
145
     *
146
     * @internal
147
     *
148
     * @param string $checkScopeSegment
149
     *
150
     * @return bool
151
     */
152
    public function isExcluded($checkScopeSegment): bool
153
    {
154
        return in_array(
155
            $this->scopeString($checkScopeSegment),
156
            $this->transform->getExcludes()
157
        );
158
    }
159
160
    /**
161
     * @param TransformerInterface|callable $transformer
162
     * @param mixed $data
163
     * @param array $extra
164
     * @return mixed
165
     */
166
    public function transform(callable $transformer, $data, array $extra = [])
167
    {
168
        return $this->parseValue($transformer, $data, null, $extra);
169
    }
170
171
    /**
172
     * @param callable $transformer
173
     * @param string $key
174
     * @return bool
175
     */
176
    public function includeValue(callable $transformer, string $key): bool
177
    {
178
        // Ignore optional (that have not been explicitly requested)
179
        if ($transformer instanceof TransformerInterface &&
180
            in_array($key, $transformer->getIncludes(), true) &&
181
            !$this->isRequested($key)
182
        ) {
183
            return false;
184
        }
185
186
        // Ignore excludes
187
        if ($this->isExcluded($key)) {
188
            return false;
189
        }
190
191
        return true;
192
    }
193
194
    /**
195
     * @param $val
196
     * @param $data
197
     * @param string|null $key
198
     * @param array $extra
199
     * @return mixed
200
     */
201
    public function parseValue($val, $data, string $key = null, array $extra = [])
202
    {
203
        if (TransformerHelper::isTransformer($val)) {
204
            $args = [$data, $this, $key];
205
206
            if (!empty($extra)) {
207
                $args = array_merge(
208
                    $args,
209
                    $this->validParams($val, $extra)
210
                );
211
            }
212
213
            return call_user_func_array($val, $args);
214
        }
215
216
        return $val;
217
    }
218
219
    /**
220
     * @param $transformer
221
     * @param array $params
222
     * @return array
223
     */
224
    private function validParams($transformer, array $params): array
225
    {
226
        if (!is_object($transformer)) {
227
            return $params;
228
        }
229
230
        $method = new ReflectionMethod($transformer, '__invoke');
231
232
        $args = $missing = [];
233
        foreach ($method->getParameters() as $param) {
234
            $this->validParam($param, $params, $args, $missing);
235
        }
236
237
        if (!empty($missing)) {
238
            throw new InvalidArgumentException(sprintf(
239
                'Missing required parameters "%s".',
240
                implode(', ', $missing)
241
            ));
242
        }
243
244
        return $args;
245
    }
246
247
    /**
248
     * @param ReflectionParameter $param
249
     * @param array $params
250
     * @param array $args
251
     * @param array $missing
252
     */
253
    private function validParam(
254
        ReflectionParameter $param,
255
        array $params,
256
        array &$args,
257
        array &$missing
258
    ) {
259
        $name = $param->name;
260
        if (true === in_array($name, self::IGNORE_EXTRA_PARAMS, true)) {
261
            return;
262
        }
263
        if (array_key_exists($name, $params)) {
264
            $args[] = $this->argType($param, $params[$name]);
265
        } elseif ($param->isDefaultValueAvailable()) {
266
            $args[] = $param->getDefaultValue();
267
        } else {
268
            $missing[] = $name;
269
        }
270
    }
271
272
    /**
273
     * @param ReflectionParameter $param
274
     * @param $value
275
     * @return mixed
276
     */
277
    private function argType(
278
        ReflectionParameter $param,
279
        $value
280
    ) {
281
        if (!$param->hasType()) {
282
            return $value;
283
        }
284
285
        if ($param->isArray()) {
286
            return (array)$value;
287
        }
288
289
        if ($param->isCallable() && is_callable($value)) {
290
            return $value;
291
        }
292
293
        if (!is_array($value)) {
294
            return $value;
295
        }
296
297
        throw new InvalidArgumentException(sprintf(
298
            'Invalid data received for parameter "%s".',
299
            $param->name
300
        ));
301
    }
302
303
    /**
304
     * @param string $identifier
305
     * @return Scope
306
     */
307
    public function childScope(string $identifier): Scope
308
    {
309
        $parentScopes = $this->getParentScopes();
310
        $parentScopes[] = $this->getScopeIdentifier();
311
312
        return new static(
313
            $this->getTransform(),
314
            $identifier,
315
            $parentScopes
316
        );
317
    }
318
319
    /**
320
     * Check, if this is the root scope.
321
     *
322
     * @return bool
323
     */
324
    protected function isRootScope(): bool
325
    {
326
        return empty($this->parentScopes);
327
    }
328
329
    /**
330
     * Filter the provided data with the requested filter fields for
331
     * the scope resource
332
     *
333
     * @param array $data
334
     *
335
     * @return array
336
     */
337
    public function filterFields(array $data): array
338
    {
339
        $fields = $this->getFilterFields();
340
341
        if ($fields === null) {
342
            return $data;
343
        }
344
345
        return array_intersect_key(
346
            $data,
347
            array_flip(
348
                iterator_to_array($fields)
349
            )
350
        );
351
    }
352
353
    /**
354
     * Return the requested filter fields for the scope resource
355
     *
356
     * @internal
357
     *
358
     * @return ParamBag|null
359
     */
360
    protected function getFilterFields()
361
    {
362
        return $this->transform->getField(
363
            $this->getScopeIdentifier()
364
        );
365
    }
366
367
    /**
368
     * @param string $checkScopeSegment
369
     * @return string
370
     */
371
    private function scopeString(string $checkScopeSegment): string
372
    {
373
        if (!empty($this->parentScopes)) {
374
            $scopeArray = array_slice($this->parentScopes, 1);
375
            array_push($scopeArray, $this->scopeIdentifier, $checkScopeSegment);
376
        } else {
377
            $scopeArray = [$checkScopeSegment];
378
        }
379
380
        return implode('.', (array)$scopeArray);
381
    }
382
}
383