Completed
Push — master ( ec3f53...a1b38d )
by Iqbal
02:47
created

ViewModel::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
c 3
b 1
f 0
dl 0
loc 9
rs 9.6666
cc 2
eloc 6
nc 2
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, array $parameter = array())
82
    {
83
        if (empty($data)) {
84
            $this->built = array();
85
        } else {
86
            $this->parameter = new ParameterBag($parameter);
87
            $this->init($data);
88
        }
89
    }
90
91
    /**
92
     * {@inheritdoc}
93
     */
94
    public function init($data)
95
    {
96
        $this->fields = $this->fields() ?: array();
97
        $this->append = $this->append() ?: array();
98
        $this->hidden = $this->hidden() ?: array();
99
        $this->setData($data);
100
101
        return $this;
102
    }
103
104
    /**
105
     * {@inheritdoc}
106
     */
107
    public function build()
108
    {
109
        if (null !== $this->built) {
110
            return $this->built;
111
        }
112
113
        $record = $this->data;
114
115
        if ($record instanceof ReadModelInterface) {
116
            $built = $this->serialize($record, $record->serialize());
117
        } elseif (is_array($record)) {
118
            $built = $this->serialize($record, $record);
119
        } else {
120
            $built = array_map(function ($readModel) use ($record) {
121
                if ($readModel instanceof ReadModelInterface) {
122
                    $readModel = $readModel->serialize();
123
                }
124
125
                return $this->serialize($record, $readModel);
126
            }, $record->toArray());
127
        }
128
129
        if ($this->forceSingle) {
130
            $built = $built[0];
131
132
        }
133
134
        return $this->built = $built;
135
    }
136
137
    /**
138
     * {@inheritdoc}
139
     */
140
    public function isCollection()
141
    {
142
        $build = $this->build();
143
        if (isset($build[0]) && is_array($build[0])) {
144
            return true;
145
        }
146
147
        return false;
148
    }
149
150
    /**
151
     * {@inheritdoc}
152
     */
153
    public function addParameter(array $parameters)
154
    {
155
        $this->parameter->add($parameters);
156
    }
157
158
    /**
159
     * {@inheritdoc}
160
     */
161
    public function setForceSingle()
162
    {
163
        if ($this->allowForceSingle) {
164
            $this->forceSingle = true;
165
        }
166
    }
167
168
    /**
169
     * Add fields that should be viewed.
170
     *
171
     * @param array $fields
172
     */
173
    public function addFields(array $fields)
174
    {
175
        $this->fields = array_merge($this->fields, $fields);
176
    }
177
178
    /**
179
     * Add fields that should be appended.
180
     *
181
     * @param array $fields
182
     */
183
    public function addAppend(array $fields)
184
    {
185
        $this->append = array_merge($this->append, $fields);
186
    }
187
188
    /**
189
     * Add fields that should be hidden.
190
     *
191
     * @param array $fields
192
     */
193
    public function addHidden(array $fields)
194
    {
195
        $this->hidden = array_merge($this->fields, $fields);
196
    }
197
198
    /**
199
     * {@inheritdoc}
200
     */
201
    public function buildViewModelArgs($results, array $record, $field)
202
    {
203
        $args = array();
204
        if (array_key_exists($field, $record)) {
205
            $args[] = $record[$field];
206
        }
207
208
        $args[] = $record;
209
210
        if ($results instanceof Collection) {
211
            $args[] = $results;
212
        }
213
214
        return $args;
215
    }
216
217
    /**
218
     * Get view model raw data.
219
     *
220
     * @return mixed
221
     */
222
    public function getRawData()
223
    {
224
        return $this->data;
225
    }
226
227
    /**
228
     * {@inheritdoc}
229
     */
230
    protected function fields()
231
    {
232
        return array();
233
    }
234
235
    /**
236
     * {@inheritdoc}
237
     */
238
    protected function append()
239
    {
240
        return array();
241
    }
242
243
    /**
244
     * {@inheritdoc}
245
     */
246
    protected function hidden()
247
    {
248
        return array();
249
    }
250
251
    /**
252
     * Serialize record based on view model.
253
     *
254
     * @param mixed $results
255
     * @param array $record
256
     *
257
     * @return array
258
     */
259
    private function serialize($results, array $record)
260
    {
261
        $fields = (array) $this->fields();
262
        $fields = !empty($fields) ? $fields : array_keys($record);
263
        $fields = $this->mergeFields($fields, (array) $this->append());
264
        $hidden = (array) $this->hidden();
265
266
        $data = array();
267
        foreach ($fields as $field) {
268
            if (in_array($field, $hidden)) {
269
                continue;
270
            }
271
272
            $data[$field] = $this->computeViewModelValue($results, $record, $field);
273
        }
274
275
        return $data;
276
    }
277
278
    /**
279
     * Normalize array record.
280
     *
281
     * @param mixed $record
282
     *
283
     * @return array
284
     */
285
    private function normalize($record)
286
    {
287
        if (is_array($record)) {
288
            foreach ($record as $index => $value) {
289
                $record[$index] = $this->normalize($value);
290
            }
291
        } elseif ($record instanceof Collection) {
292
            return $record->toArray();
293
        }
294
295
        return $record;
296
    }
297
298
    /**
299
     * Compute view model value.
300
     *
301
     * @param mixed  $results
302
     * @param array  $record
303
     * @param string $field
304
     *
305
     * @return mixed|null
306
     */
307
    private function computeViewModelValue($results, array $record, $field)
308
    {
309
        $method = $this->prefixMethod . ucfirst($field);
310
        if (method_exists($this, $method)) {
311
            return call_user_func_array(
312
                array($this, $method),
313
                $this->buildViewModelArgs($results, $record, $field)
314
            );
315
        }
316
317
        if (isset($record[$field])) {
318
            return $record[$field];
319
        }
320
321
        return null;
322
    }
323
324
    /**
325
     * Merged key fields.
326
     *
327
     * @param array $recordFields
328
     * @param array $viewModelFields
329
     *
330
     * @return array
331
     */
332
    private function mergeFields(array $recordFields, array $viewModelFields)
333
    {
334
        $merged = array_merge(array_flip($recordFields), array_flip($viewModelFields));
335
336
        return array_keys($merged);
337
    }
338
339
    /**
340
     * Set data.
341
     *
342
     * @param mixed $data
343
     */
344
    private function setData($data)
345
    {
346
        $data = $this->buildData($this->normalize($data));
347
        $this->assertDataType($data);
348
        $this->data = $data;
349
350
        if ($this->data instanceof Collection) {
351
            $this->aggregate = new Aggregate(
352
                $this->data->toArray(),
353
                $this,
354
                new ReflectionObject($this),
355
                $this->prefixMethod
356
            );
357
        }
358
    }
359
360
    /**
361
     * Build data.
362
     *
363
     * @param mixed $data
364
     *
365
     * @return mixed
366
     */
367
    private function buildData($data)
368
    {
369
        if (!$data instanceof ReadModelInterface) {
370
            if ($data instanceof ViewModel) {
371
                $data = $data->isCollection() ? new Collection($data->build()) : $data->build();
372
            } elseif (!$data instanceof Collection) {
373
                $data = new Collection($data);
374
            }
375
        }
376
377
        return $data;
378
    }
379
380
    /**
381
     * Assert data type.
382
     *
383
     * @param mixed $data
384
     */
385
    private function assertDataType($data)
386
    {
387
        if (!$data instanceof ReadModelInterface && !$data instanceof Collection && !is_array($data)) {
388
            throw new InvalidArgumentException(sprintf(
389
                'Acceptable data type is \Borobudur\Cqrs\ReadModel\ReadModelInterface, ' .
390
                '\Borobudur\Cqrs\Collection, Borobudur\Cqrs\ViewModel\ViewModel or nested array, but got "%s".',
391
                gettype($data)
392
            ));
393
        }
394
    }
395
}
396