ViewModel::serialize()   B
last analyzed

Complexity

Conditions 5
Paths 7

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 22
rs 8.6737
c 0
b 0
f 0
cc 5
eloc 13
nc 7
nop 2
1
<?php
2
/*
3
 * This file is part of the Borobudur-Cqrs package.
4
 *
5
 * (c) Hexacodelabs <http://hexacodelabs.com>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Borobudur\Cqrs\ViewModel;
12
13
use Borobudur\Cqrs\Collection;
14
use Borobudur\Cqrs\Exception\InvalidArgumentException;
15
use Borobudur\Cqrs\ParameterBag;
16
use Borobudur\Cqrs\ReadModel\ReadModelInterface;
17
use ReflectionObject;
18
19
/**
20
 * @author      Iqbal Maulana <[email protected]>
21
 * @created     8/20/15
22
 */
23
class ViewModel extends AbstractViewModel
24
{
25
    /**
26
     * @var Aggregate|null
27
     */
28
    protected $aggregate;
29
30
    /**
31
     * @var string
32
     */
33
    protected $prefixMethod = 'normalize';
34
35
    /**
36
     * @var bool
37
     */
38
    protected $allowForceSingle = true;
39
40
    /**
41
     * @var ParameterBag
42
     */
43
    protected $parameter;
44
45
    /**
46
     * @var array
47
     */
48
    private $fields = array();
49
50
    /**
51
     * @var array
52
     */
53
    private $append = array();
54
55
    /**
56
     * @var array
57
     */
58
    private $hidden = array();
59
60
    /**
61
     * @var mixed
62
     */
63
    private $data;
64
65
    /**
66
     * @var array|null
67
     */
68
    private $built;
69
70
    /**
71
     * @var bool
72
     */
73
    private $forceSingle = false;
74
75
    /**
76
     * Constructor.
77
     *
78
     * @param mixed $data
79
     * @param array $parameter
80
     */
81
    public function __construct($data = null, array $parameter = array())
82
    {
83
        if ($data) {
84
            $this->parameter = new ParameterBag($parameter);
85
            $this->init($data);
86
        }
87
    }
88
89
    /**
90
     * {@inheritdoc}
91
     */
92
    public function init($data)
93
    {
94
        $this->fields = $this->fields() ?: array();
95
        $this->append = $this->append() ?: array();
96
        $this->hidden = $this->hidden() ?: array();
97
        $this->setData($data);
98
99
        return $this;
100
    }
101
102
    /**
103
     * {@inheritdoc}
104
     */
105
    public function build()
106
    {
107
        if (null !== $this->built) {
108
            return $this->built;
109
        }
110
111
        $record = $this->data;
112
        if ($record instanceof ReadModelInterface) {
113
            $built = $this->serialize($record, $record->serialize());
114
        } elseif (is_array($record)) {
115
            $built = $this->serialize($record, $record);
116
        } else {
117
            $built = array_map(function ($readModel) use ($record) {
118
                if ($readModel instanceof ReadModelInterface) {
119
                    $readModel = $readModel->serialize();
120
                }
121
122
                return $this->serialize($record, $readModel);
123
            }, $record->toArray());
124
        }
125
126
        if ($this->forceSingle) {
127
            $built = $built[0];
128
129
        }
130
131
        return $this->built = $built;
132
    }
133
134
    /**
135
     * {@inheritdoc}
136
     */
137
    public function isCollection()
138
    {
139
        $build = $this->build();
140
        if (isset($build[0]) && is_array($build[0])) {
141
            return true;
142
        }
143
144
        return false;
145
    }
146
147
    /**
148
     * {@inheritdoc}
149
     */
150
    public function addParameter(array $parameters)
151
    {
152
        $this->parameter->add($parameters);
153
    }
154
155
    /**
156
     * {@inheritdoc}
157
     */
158
    public function setForceSingle()
159
    {
160
        if ($this->allowForceSingle) {
161
            $this->forceSingle = true;
162
        }
163
    }
164
165
    /**
166
     * Add fields that should be viewed.
167
     *
168
     * @param array $fields
169
     */
170
    public function addFields(array $fields)
171
    {
172
        $this->fields = array_merge($this->fields, $fields);
173
    }
174
175
    /**
176
     * Add fields that should be appended.
177
     *
178
     * @param array $fields
179
     */
180
    public function addAppend(array $fields)
181
    {
182
        $this->append = array_merge($this->append, $fields);
183
    }
184
185
    /**
186
     * Add fields that should be hidden.
187
     *
188
     * @param array $fields
189
     */
190
    public function addHidden(array $fields)
191
    {
192
        $this->hidden = array_merge($this->fields, $fields);
193
    }
194
195
    /**
196
     * {@inheritdoc}
197
     */
198
    public function buildViewModelArgs($results, array $record, $field)
199
    {
200
        $args = array();
201
        if (array_key_exists($field, $record)) {
202
            $args[] = $record[$field];
203
        }
204
205
        $args[] = $record;
206
207
        if ($results instanceof Collection) {
208
            $args[] = $results;
209
        }
210
211
        return $args;
212
    }
213
214
    /**
215
     * Get view model raw data.
216
     *
217
     * @return mixed
218
     */
219
    public function getRawData()
220
    {
221
        return $this->data;
222
    }
223
224
    /**
225
     * {@inheritdoc}
226
     */
227
    protected function fields()
228
    {
229
        return array();
230
    }
231
232
    /**
233
     * {@inheritdoc}
234
     */
235
    protected function append()
236
    {
237
        return array();
238
    }
239
240
    /**
241
     * {@inheritdoc}
242
     */
243
    protected function hidden()
244
    {
245
        return array();
246
    }
247
248
    /**
249
     * Serialize record based on view model.
250
     *
251
     * @param mixed $results
252
     * @param array $record
253
     *
254
     * @return array
255
     */
256
    private function serialize($results, array $record)
257
    {
258
        if (empty($record)) {
259
            return array();
260
        }
261
        
262
        $fields = (array) $this->fields();
263
        $fields = !empty($fields) ? $fields : array_keys($record);
264
        $fields = $this->mergeFields($fields, (array) $this->append());
265
        $hidden = (array) $this->hidden();
266
267
        $data = array();
268
        foreach ($fields as $field) {
269
            if (in_array($field, $hidden)) {
270
                continue;
271
            }
272
273
            $data[$field] = $this->computeViewModelValue($results, $record, $field);
274
        }
275
276
        return $data;
277
    }
278
279
    /**
280
     * Normalize array record.
281
     *
282
     * @param mixed $record
283
     *
284
     * @return array
285
     */
286
    private function normalize($record)
287
    {
288
        if (is_array($record)) {
289
            foreach ($record as $index => $value) {
290
                $record[$index] = $this->normalize($value);
291
            }
292
        } elseif ($record instanceof Collection) {
293
            return $record->toArray();
294
        }
295
296
        return $record;
297
    }
298
299
    /**
300
     * Compute view model value.
301
     *
302
     * @param mixed  $results
303
     * @param array  $record
304
     * @param string $field
305
     *
306
     * @return mixed|null
307
     */
308
    private function computeViewModelValue($results, array $record, $field)
309
    {
310
        $method = $this->prefixMethod . ucfirst($field);
311
        if (method_exists($this, $method)) {
312
            return call_user_func_array(
313
                array($this, $method),
314
                $this->buildViewModelArgs($results, $record, $field)
315
            );
316
        }
317
318
        if (isset($record[$field])) {
319
            return $record[$field];
320
        }
321
322
        return null;
323
    }
324
325
    /**
326
     * Merged key fields.
327
     *
328
     * @param array $recordFields
329
     * @param array $viewModelFields
330
     *
331
     * @return array
332
     */
333
    private function mergeFields(array $recordFields, array $viewModelFields)
334
    {
335
        $merged = array_merge(array_flip($recordFields), array_flip($viewModelFields));
336
337
        return array_keys($merged);
338
    }
339
340
    /**
341
     * Set data.
342
     *
343
     * @param mixed $data
344
     */
345
    private function setData($data)
346
    {
347
        $data = $this->buildData($this->normalize($data));
348
        $this->assertDataType($data);
349
        $this->data = $data;
350
351
        if ($this->data instanceof Collection) {
352
            $this->aggregate = new Aggregate(
353
                $this->data->toArray(),
354
                $this,
355
                new ReflectionObject($this),
356
                $this->prefixMethod
357
            );
358
        }
359
    }
360
361
    /**
362
     * Build data.
363
     *
364
     * @param mixed $data
365
     *
366
     * @return mixed
367
     */
368
    private function buildData($data)
369
    {
370
        if (!$data instanceof ReadModelInterface) {
371
            if ($data instanceof ViewModel) {
372
                $data = $data->isCollection() ? new Collection($data->build()) : $data->build();
373
            } elseif (!$data instanceof Collection) {
374
                if (isset($data[0])) {
375
                    $data = new Collection($data);
376
                }
377
            }
378
        }
379
380
        return $data;
381
    }
382
383
    /**
384
     * Assert data type.
385
     *
386
     * @param mixed $data
387
     */
388
    private function assertDataType($data)
389
    {
390
        if (!$data instanceof ReadModelInterface && !$data instanceof Collection && !is_array($data)) {
391
            throw new InvalidArgumentException(sprintf(
392
                'Acceptable data type is \Borobudur\Cqrs\ReadModel\ReadModelInterface, ' .
393
                '\Borobudur\Cqrs\Collection, Borobudur\Cqrs\ViewModel\ViewModel or nested array, but got "%s".',
394
                gettype($data)
395
            ));
396
        }
397
    }
398
}
399