Completed
Push — master ( 605dcd...eeb8ed )
by Nate
02:13
created

Transform::collection()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 0
cts 6
cp 0
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 2
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\Resources\Collection;
13
use Flipbox\Transform\Resources\Item;
14
use Flipbox\Transform\Helpers\Object as ObjectHelper;
15
16
/**
17
 * @author Flipbox Factory <[email protected]>
18
 * @since 1.0.0
19
 */
20
class Transform
21
{
22
    /**
23
     * The character used to separate modifier parameters.
24
     *
25
     * @var string
26
     */
27
    public $paramDelimiter = '|';
28
29
    /**
30
     * Upper limit to how many levels of included data are allowed.
31
     *
32
     * @var int
33
     */
34
    public $recursionLimit = 10;
35
36
    /**
37
     * Scope identifiers that resources can optionally include.
38
     *
39
     * @var array
40
     */
41
    protected $includes = [];
42
43
    /**
44
     * Scope identifiers that resources must exclude.
45
     *
46
     * @var array
47
     */
48
    protected $excludes = [];
49
50
    /**
51
     * Scope identifiers that resources must return.
52
     *
53
     * @var array
54
     */
55
    protected $fields = [];
56
57
    /**
58
     * Array containing modifiers as keys and an array value of params.
59
     *
60
     * @var array
61
     */
62
    protected $params = [];
63
64
    /**
65
     * @param array $config
66
     */
67
    public function __construct(array $config = [])
68
    {
69
        ObjectHelper::configure($this, $config);
70
    }
71
72
    /**
73
     * @param string $include
74
     *
75
     * @return ParamBag
76
     */
77
    public function getParams($include): ParamBag
78
    {
79
        $params = isset($this->params[$include]) ? $this->params[$include] : [];
80
        return new ParamBag($params);
81
    }
82
83
    /*******************************************
84
     * INCLUDES
85
     *******************************************/
86
87
    /**
88
     * Get Requested Includes.
89
     *
90
     * @return array
91
     */
92
    public function getIncludes(): array
93
    {
94
        return $this->includes;
95
    }
96
97
    /**
98
     * Parse Include String.
99
     *
100
     * @param array|string $includes Array or csv string of resources to include
101
     *
102
     * @return $this
103
     */
104
    public function setIncludes($includes)
105
    {
106
        // Wipe these before we go again
107
        $this->includes = $this->params = [];
108
109
        if (is_string($includes)) {
110
            $includes = explode(',', $includes);
111
        }
112
113
        if (!is_array($includes)) {
114
            throw new \InvalidArgumentException(
115
                'The parseIncludes() method expects a string or an array. ' . gettype($includes) . ' given'
116
            );
117
        }
118
119
        foreach ($includes as $include) {
120
            list($includeName, $allModifiersStr) = array_pad(explode(':', $include, 2), 2, null);
121
122
            // Trim it down to a cool level of recursion
123
            $includeName = $this->trimToAcceptableRecursionLevel($includeName);
124
125
            if (in_array($includeName, $this->includes)) {
126
                continue;
127
            }
128
            $this->includes[] = $includeName;
129
130
            // No Params? Bored
131
            if ($allModifiersStr === null) {
132
                continue;
133
            }
134
135
            // Matches multiple instances of 'something(foo|bar|baz)' in the string
136
            // I guess it ignores : so you could use anything, but probably don't do that
137
            preg_match_all('/([\w]+)(\(([^\)]+)\))?/', $allModifiersStr, $allModifiersArr);
138
139
            // [0] is full matched strings...
140
            $modifierCount = count($allModifiersArr[0]);
141
142
            $modifierArr = [];
143
144
            for ($modifierIt = 0; $modifierIt < $modifierCount; $modifierIt++) {
145
                // [1] is the modifier
146
                $modifierName = $allModifiersArr[1][$modifierIt];
147
148
                // and [3] is delimited params
149
                $modifierParamStr = $allModifiersArr[3][$modifierIt];
150
151
                // Make modifier array key with an array of params as the value
152
                $modifierArr[$modifierName] = explode($this->paramDelimiter, $modifierParamStr);
153
            }
154
155
            $this->params[$includeName] = $modifierArr;
156
        }
157
158
        // This should be optional and public someday, but without it includes would never show up
159
        $this->autoIncludeParents();
160
161
        return $this;
162
    }
163
164
    /*******************************************
165
     * EXCLUDES
166
     *******************************************/
167
168
    /**
169
     * Get Requested Excludes.
170
     *
171
     * @return array
172
     */
173
    public function getExcludes(): array
174
    {
175
        return $this->excludes;
176
    }
177
178
    /**
179
     * Parse Exclude String.
180
     *
181
     * @param array|string $excludes Array or csv string of resources to exclude
182
     *
183
     * @return $this
184
     */
185
    public function setExcludes($excludes)
186
    {
187
        $this->excludes = [];
188
189
        if (is_string($excludes)) {
190
            $excludes = explode(',', $excludes);
191
        }
192
193
        if (!is_array($excludes)) {
194
            throw new \InvalidArgumentException(
195
                'The parseExcludes() method expects a string or an array. ' . gettype($excludes) . ' given'
196
            );
197
        }
198
199
        foreach ($excludes as $excludeName) {
200
            $excludeName = $this->trimToAcceptableRecursionLevel($excludeName);
201
202
            if (in_array($excludeName, $this->excludes)) {
203
                continue;
204
            }
205
206
            $this->excludes[] = $excludeName;
207
        }
208
209
        return $this;
210
    }
211
212
    /*******************************************
213
     * FIELDS
214
     *******************************************/
215
216
    /**
217
     * Parse field parameter.
218
     *
219
     * @param array $fields Array of fields to include. It must be an array
220
     *                         whose keys are resource types and values a string
221
     *                         of the fields to return, separated by a comma
222
     *
223
     * @return $this
224
     */
225
    public function setFields(array $fields)
226
    {
227
        $this->fields = [];
228
229
        foreach ($fields as $type => $field) {
230
            //Remove empty and repeated fields
231
            $this->fields[$type] = array_unique(array_filter(explode(',', $field)));
232
        }
233
234
        return $this;
235
    }
236
237
    /**
238
     * Get requested fields.
239
     *
240
     * @return array
241
     */
242
    public function getFields(): array
243
    {
244
        return $this->fields;
245
    }
246
247
    /**
248
     * Get field params for the specified type.
249
     *
250
     * @param string $type
251
     *
252
     * @return ParamBag|null
253
     */
254
    public function getField($type)
255
    {
256
        return !isset($this->fields[$type]) ?
257
            null :
258
            new ParamBag($this->fields[$type]);
259
    }
260
261
262
    /*******************************************
263
     * RESOURCES
264
     *******************************************/
265
266
    /**
267
     * @param callable $transformer
268
     * @param $data
269
     * @return mixed
270
     */
271
    public function item(callable $transformer, $data)
272
    {
273
        return (new Item(
274
            new Scope($this)
275
        ))->transform($transformer, $data);
276
    }
277
278
    /**
279
     * @param callable $transformer
280
     * @param $data
281
     * @return mixed
282
     */
283
    public function collection(callable $transformer, $data)
284
    {
285
        return (new Collection(
286
            new Scope($this)
287
        ))->transform($transformer, $data);
288
    }
289
290
291
    /**
292
     * Auto-include Parents
293
     *
294
     * Look at the requested includes and automatically include the parents if they
295
     * are not explicitly requested. E.g: [foo, bar.baz] becomes [foo, bar, bar.baz]
296
     *
297
     * @internal
298
     *
299
     * @return void
300
     */
301
    protected function autoIncludeParents()
302
    {
303
        $parsed = [];
304
305
        foreach ($this->includes as $include) {
306
            $nested = explode('.', $include);
307
308
            $part = array_shift($nested);
309
            $parsed[] = $part;
310
311
            while (count($nested) > 0) {
312
                $part .= '.' . array_shift($nested);
313
                $parsed[] = $part;
314
            }
315
        }
316
317
        $this->includes = array_values(array_unique($parsed));
318
    }
319
320
    /**
321
     * Trim to Acceptable Recursion Level
322
     *
323
     * @internal
324
     *
325
     * @param string $includeName
326
     *
327
     * @return string
328
     */
329
    protected function trimToAcceptableRecursionLevel($includeName)
330
    {
331
        return implode('.', array_slice(explode('.', $includeName), 0, $this->recursionLimit));
332
    }
333
}
334