Passed
Push — master ( 199ece...6bc7da )
by y
02:13
created

Data::isDiff()   B

Complexity

Conditions 7
Paths 8

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 9
c 1
b 0
f 0
nc 8
nop 0
dl 0
loc 15
rs 8.8333
1
<?php
2
3
namespace Helix\Shopify\Base;
4
5
use Base\Data\JsonSerializableTrait;
6
use Base\Data\MagicMethodTrait;
7
use Helix\Shopify\Api;
8
use Helix\Shopify\Api\Pool;
9
use JsonSerializable;
10
use Serializable;
11
12
/**
13
 * A data-object supporting annotated magic access.
14
 */
15
class Data extends \Base\Data implements JsonSerializable, Serializable {
16
17
    use MagicMethodTrait;
18
    use JsonSerializableTrait;
19
20
    /**
21
     * @var array
22
     */
23
    const MAP = [];
24
25
    /**
26
     * Fields always added to diffs.
27
     *
28
     * @var string[]
29
     */
30
    const PATCH = [];
31
32
    /**
33
     * @var Api
34
     */
35
    protected $api;
36
37
    /**
38
     * @var Pool
39
     */
40
    protected $pool;
41
42
    /**
43
     * @param mixed $caller
44
     * @return Api
45
     * @internal
46
     */
47
    final protected static function _getApi ($caller) {
48
        if ($caller instanceof self) {
49
            return $caller->api;
50
        }
51
        assert($caller instanceof Api);
52
        return $caller;
53
    }
54
55
    /**
56
     * @param Api|Data $caller
57
     * @param array $data
58
     */
59
    public function __construct ($caller, array $data = []) {
60
        parent::__construct();
61
        $this->api = self::_getApi($caller);
62
        $this->pool = $this->api->getPool();
63
        $this->_setData($data);
64
    }
65
66
    /**
67
     * @param string $method
68
     * @param array $args
69
     * @return mixed
70
     */
71
    public function __call (string $method, array $args) {
72
        static $magic = [];
73
        if (!$call =& $magic[$method]) {
74
            preg_match('/^([a-z]+)(.+)$/', $method, $call);
75
            $call[1] = '_' . $call[1];
76
            if ($call[1] !== '_select') { // _select() calls getters
77
                $call[2] = preg_replace_callback('/[A-Z]/', function(array $match) {
78
                    return '_' . lcfirst($match[0]);
79
                }, lcfirst($call[2]));
80
            }
81
        }
82
        return $this->{$call[1]}($call[2], ...$args);
83
    }
84
85
    /**
86
     * A factory that also hydrates / caches entities.
87
     *
88
     * @param string $class
89
     * @param mixed $item
90
     * @return mixed
91
     */
92
    protected function _hydrate (string $class, $item) {
93
        if (!isset($item) or $item instanceof self) {
94
            return $item;
95
        }
96
        // hydrate entities
97
        if (is_subclass_of($class, AbstractEntity::class)) {
98
            if (is_string($item)) { // convert ids to lazy stubs
99
                $item = ['id' => $item];
100
            }
101
            return $this->pool->get($item['id'], $this, function() use ($class, $item) {
102
                return $this->api->factory($this, $class, $item);
103
            });
104
        }
105
        // hydrate simple
106
        return $this->api->factory($this, $class, $item);
107
    }
108
109
    /**
110
     * Magic method: `selectField(callable $filter)`
111
     *
112
     * Where `Field` has an accessor at `getField()`, either real or magic.
113
     *
114
     * This can also be used to select from an arbitrary iterable.
115
     *
116
     * @see __call()
117
     *
118
     * @param string|iterable $subject
119
     * @param callable $filter `fn( Data $object ): bool`
120
     * @param array $args
121
     * @return array
122
     */
123
    protected function _select ($subject, callable $filter, ...$args) {
124
        if (is_string($subject)) {
125
            $subject = $this->{'get' . $subject}(...$args) ?? [];
126
        }
127
        $selected = [];
128
        foreach ($subject as $item) {
129
            if (call_user_func($filter, $item)) {
130
                $selected[] = $item;
131
            }
132
        }
133
        return $selected;
134
    }
135
136
    /**
137
     * Clears all diffs and sets all data, hydrating mapped fields.
138
     *
139
     * @param array $data
140
     * @return $this
141
     */
142
    protected function _setData (array $data) {
143
        $this->data = $this->diff = [];
144
        foreach ($data as $field => $value) {
145
            $this->_setField($field, $value);
146
        }
147
        return $this;
148
    }
149
150
    /**
151
     * Sets a value, hydrating if mapped, and clears the diff.
152
     *
153
     * @param string $field
154
     * @param mixed $value
155
     * @return $this
156
     */
157
    protected function _setField (string $field, $value) {
158
        if (isset(static::MAP[$field])) {
159
            $class = static::MAP[$field];
160
            if (is_array($class)) {
161
                $value = array_map(function($each) use ($class) {
162
                    return $this->_hydrate($class[0], $each);
163
                }, $value);
164
            }
165
            elseif (isset($value)) {
166
                $value = $this->_hydrate($class, $value);
167
            }
168
        }
169
        $this->data[$field] = $value;
170
        unset($this->diff[$field]);
171
        return $this;
172
    }
173
174
    /**
175
     * Dehydrated JSON encode.
176
     *
177
     * @return string
178
     */
179
    public function serialize (): string {
180
        return json_encode($this, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
181
    }
182
183
    /**
184
     * @return array
185
     */
186
    public function toArray (): array {
187
        return array_map(function($value) {
188
            if ($value instanceof self) {
189
                return $value->toArray();
190
            }
191
            return $value;
192
        }, $this->data);
193
    }
194
195
    /**
196
     * @param $serialized
197
     */
198
    public function unserialize ($serialized): void {
199
        $this->data = json_decode($serialized, true);
200
    }
201
}