Completed
Pull Request — master (#500)
by Matt
03:36
created

Manager::getRequestedExcludes()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
/*
4
 * This file is part of the League\Fractal package.
5
 *
6
 * (c) Phil Sturgeon <[email protected]>
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 League\Fractal;
13
14
use League\Fractal\Resource\ResourceInterface;
15
use League\Fractal\Serializer\DataArraySerializer;
16
use League\Fractal\Serializer\SerializerAbstract;
17
18
/**
19
 * Manager
20
 *
21
 * Not a wildly creative name, but the manager is what a Fractal user will interact
22
 * with the most. The manager has various configurable options, and allows users
23
 * to create the "root scope" easily.
24
 */
25
class Manager
26
{
27
    /**
28
     * Array of scope identifiers for resources to include.
29
     *
30
     * @var array
31
     */
32
    protected $requestedIncludes = [];
33
34
    /**
35
     * Array of scope identifiers for resources to exclude.
36
     *
37
     * @var array
38
     */
39
    protected $requestedExcludes = [];
40
41
    /**
42
     * Array of requested fieldsets.
43
     *
44
     * @var array
45
     */
46
    protected $requestedFieldsets = [];
47
48
    /**
49
     * Array containing modifiers as keys and an array value of params.
50
     *
51
     * @var array
52
     */
53
    protected $includeParams = [];
54
55
    /**
56
     * The character used to separate modifier parameters.
57
     *
58
     * @var string
59
     */
60
    protected $paramDelimiter = '|';
61
62
    /**
63
     * Upper limit to how many levels of included data are allowed.
64
     *
65
     * @var int
66
     */
67
    protected $recursionLimit = 10;
68
69
    /**
70
     * Serializer.
71
     *
72
     * @var SerializerAbstract
73
     */
74
    protected $serializer;
75
76
    /**
77
     * Factory used to create new configured scopes.
78
     *
79
     * @var ScopeFactoryInterface
80
     */
81
    private $scopeFactory;
82
83 86
    public function __construct(ScopeFactoryInterface $scopeFactory = null)
84
    {
85 86
        $this->scopeFactory = $scopeFactory ?: new ScopeFactory();
86 86
    }
87
88
    /**
89
     * Create Data.
90
     *
91
     * Main method to kick this all off. Make a resource then pass it over, and use toArray()
92
     *
93
     * @param ResourceInterface $resource
94
     * @param string            $scopeIdentifier
95
     * @param Scope             $parentScopeInstance
96
     *
97
     * @return Scope
98
     */
99 42
    public function createData(ResourceInterface $resource, $scopeIdentifier = null, Scope $parentScopeInstance = null)
100
    {
101 42
        if ($parentScopeInstance !== null) {
102 36
            return $this->scopeFactory->createChildScopeFor($this, $parentScopeInstance, $resource, $scopeIdentifier);
103
        }
104
105 6
        return $this->scopeFactory->createScopeFor($this, $resource, $scopeIdentifier);
106
    }
107
108
    /**
109
     * Get Include Params.
110
     *
111
     * @param string $include
112
     *
113
     * @return \League\Fractal\ParamBag
114
     */
115 32
    public function getIncludeParams($include)
116
    {
117 32
        $params = isset($this->includeParams[$include]) ? $this->includeParams[$include] : [];
118
119 32
        return new ParamBag($params);
120
    }
121
122
    /**
123
     * Get Requested Includes.
124
     *
125
     * @return array
126
     */
127 46
    public function getRequestedIncludes()
128
    {
129 46
        return $this->requestedIncludes;
130
    }
131
132
    /**
133
     * Get Requested Excludes.
134
     *
135
     * @return array
136
     */
137 33
    public function getRequestedExcludes()
138
    {
139 33
        return $this->requestedExcludes;
140
    }
141
142
    /**
143
     * Get Serializer.
144
     *
145
     * @return SerializerAbstract
146
     */
147 60
    public function getSerializer()
148
    {
149 60
        if (! $this->serializer) {
150 10
            $this->setSerializer(new DataArraySerializer());
151 10
        }
152
153 60
        return $this->serializer;
154
    }
155
156
    /**
157
     * Parse Include String.
158
     *
159
     * @param array|string $includes Array or csv string of resources to include
160
     *
161
     * @return $this
162
     */
163 44
    public function parseIncludes($includes)
164
    {
165
        // Wipe these before we go again
166 44
        $this->requestedIncludes = $this->includeParams = [];
167
        $subRelations = '';
168 44
169 34
        if (is_string($includes)) {
170 34
            $includes = explode(',', $includes);
171
        }
172 44
173 2
        if (! is_array($includes)) {
174 2
            throw new \InvalidArgumentException(
175 2
                'The parseIncludes() method expects a string or an array. '.gettype($includes).' given'
176
            );
177
        }
178 42
179 42
        foreach ($includes as $include) {
180
            list($includeName, $allModifiersStr) = array_pad(explode(':', $include, 2), 2, null);
181
            list($allModifiersStr, $subRelations) = array_pad(explode('.', $allModifiersStr, 2), 2, null);
182 42
183
            // Trim it down to a cool level of recursion
184 42
            $includeName = $this->trimToAcceptableRecursionLevel($includeName);
185 1
186
            if (in_array($includeName, $this->requestedIncludes)) {
187 42
                continue;
188
            }
189
            $this->requestedIncludes[] = $includeName;
190 42
191 41
            // No Params? Bored
192
            if ($allModifiersStr === null) {
193
                continue;
194
            }
195
196 2
            // Matches multiple instances of 'something(foo|bar|baz)' in the string
197
            // I guess it ignores : so you could use anything, but probably don't do that
198
            preg_match_all('/([\w]+)(\(([^\)]+)\))?/', $allModifiersStr, $allModifiersArr);
199 2
200
            // [0] is full matched strings...
201 2
            $modifierCount = count($allModifiersArr[0]);
202
203 2
            $modifierArr = [];
204
205 2
            for ($modifierIt = 0; $modifierIt < $modifierCount; $modifierIt++) {
206
                // [1] is the modifier
207
                $modifierName = $allModifiersArr[1][$modifierIt];
208 2
209
                // and [3] is delimited params
210
                $modifierParamStr = $allModifiersArr[3][$modifierIt];
211 2
212 2
                // Make modifier array key with an array of params as the value
213
                $modifierArr[$modifierName] = explode($this->paramDelimiter, $modifierParamStr);
214 2
            }
215 42
216
            $this->includeParams[$includeName] = $modifierArr;
217
218 42
            if ($subRelations) {
219
                $this->requestedIncludes[] = $this->trimToAcceptableRecursionLevel($includeName . '.' . $subRelations);
220 42
            }
221
        }
222
223
        // This should be optional and public someday, but without it includes would never show up
224
        $this->autoIncludeParents();
225
226
        return $this;
227
    }
228
229
    /**
230
     * Parse field parameter.
231
     *
232 13
     * @param array $fieldsets Array of fields to include. It must be an array whose keys
233
     *                         are resource types and values an array or a string
234 13
     *                         of the fields to return, separated by a comma
235 13
     *
236 13
     * @return $this
237 13
     */
238 13
    public function parseFieldsets(array $fieldsets)
239
    {
240
        $this->requestedFieldsets = [];
241 13
        foreach ($fieldsets as $type => $fields) {
242 13
            if (is_string($fields)) {
243 13
                $fields = explode(',', $fields);
244
            }
245
246
            //Remove empty and repeated fields
247
            $this->requestedFieldsets[$type] = array_unique(array_filter($fields));
248
        }
249
        return $this;
250
    }
251 1
252
    /**
253 1
     * Get requested fieldsets.
254
     *
255
     * @return array
256
     */
257
    public function getRequestedFieldsets()
258
    {
259
        return $this->requestedFieldsets;
260
    }
261
262
    /**
263 59
     * Get fieldset params for the specified type.
264
     *
265 59
     * @param string $type
266 59
     *
267 59
     * @return \League\Fractal\ParamBag|null
268
     */
269
    public function getFieldset($type)
270
    {
271
        return !isset($this->requestedFieldsets[$type]) ?
272
            null :
273
            new ParamBag($this->requestedFieldsets[$type]);
274
    }
275
276
    /**
277 5
     * Parse Exclude String.
278
     *
279 5
     * @param array|string $excludes Array or csv string of resources to exclude
280
     *
281 5
     * @return $this
282 2
     */
283 2
    public function parseExcludes($excludes)
284
    {
285 5
        $this->requestedExcludes = [];
286 2
287 2
        if (is_string($excludes)) {
288 2
            $excludes = explode(',', $excludes);
289
        }
290
291 3
        if (! is_array($excludes)) {
292 3
            throw new \InvalidArgumentException(
293
                'The parseExcludes() method expects a string or an array. '.gettype($excludes).' given'
294 3
            );
295 1
        }
296
297
        foreach ($excludes as $excludeName) {
298 3
            $excludeName = $this->trimToAcceptableRecursionLevel($excludeName);
299 3
300
            if (in_array($excludeName, $this->requestedExcludes)) {
301 3
                continue;
302
            }
303
304
            $this->requestedExcludes[] = $excludeName;
305
        }
306
307
        return $this;
308
    }
309
310
    /**
311 1
     * Set Recursion Limit.
312
     *
313 1
     * @param int $recursionLimit
314
     *
315 1
     * @return $this
316
     */
317
    public function setRecursionLimit($recursionLimit)
318
    {
319
        $this->recursionLimit = $recursionLimit;
320
321
        return $this;
322
    }
323
324
    /**
325 60
     * Set Serializer
326
     *
327 60
     * @param SerializerAbstract $serializer
328
     *
329 60
     * @return $this
330
     */
331
    public function setSerializer(SerializerAbstract $serializer)
332
    {
333
        $this->serializer = $serializer;
334
335
        return $this;
336
    }
337
338
    /**
339
     * Auto-include Parents
340
     *
341
     * Look at the requested includes and automatically include the parents if they
342 42
     * are not explicitly requested. E.g: [foo, bar.baz] becomes [foo, bar, bar.baz]
343
     *
344 42
     * @internal
345
     *
346 42
     * @return void
347 42
     */
348
    protected function autoIncludeParents()
349 42
    {
350 42
        $parsed = [];
351
352 42
        foreach ($this->requestedIncludes as $include) {
353 11
            $nested = explode('.', $include);
354 11
355 11
            $part = array_shift($nested);
356 42
            $parsed[] = $part;
357
358 42
            while (count($nested) > 0) {
359 42
                $part .= '.'.array_shift($nested);
360
                $parsed[] = $part;
361
            }
362
        }
363
364
        $this->requestedIncludes = array_values(array_unique($parsed));
365
    }
366
367
    /**
368
     * Trim to Acceptable Recursion Level
369
     *
370
     * Strip off any requested resources that are too many levels deep, to avoid DiCaprio being chased
371
     * by trains or whatever the hell that movie was about.
372
     *
373 44
     * @internal
374
     *
375 44
     * @param string $includeName
376
     *
377
     * @return string
378
     */
379
    protected function trimToAcceptableRecursionLevel($includeName)
380
    {
381
        return implode('.', array_slice(explode('.', $includeName), 0, $this->recursionLimit));
382
    }
383
}
384