Passed
Push — master ( 0658f3...f968a7 )
by y
02:18
created

AbstractEntity   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 205
Duplicated Lines 0 %

Importance

Changes 11
Bugs 2 Features 1
Metric Value
eloc 56
c 11
b 2
f 1
dl 0
loc 205
rs 10
wmc 26

13 Methods

Rating   Name   Duplication   Size   Complexity  
A _save() 0 14 3
A hasGid() 0 2 1
A getPoolKeys() 0 2 1
A _setData() 0 11 2
A _addWithPost() 0 5 2
A _setWithPost() 0 11 3
A getGid() 0 2 1
A getResourceType() 0 2 1
A __merge() 0 4 1
A reload() 0 17 4
A _delete() 0 3 1
A _get() 0 5 3
A _removeWithPost() 0 8 3
1
<?php
2
3
namespace Helix\Asana\Base;
4
5
use Closure;
6
use LogicException;
7
use RuntimeException;
8
9
/**
10
 * A resource with a GID.
11
 */
12
abstract class AbstractEntity extends Data {
13
14
    /**
15
     * All entity classes must redeclare this to match their `resource_type`.
16
     */
17
    const TYPE = '';
18
19
    /**
20
     * Defines `opt_fields` expressions for lazy-loading/reloading fields.
21
     *
22
     * If not set here, the field name is used as-is.
23
     *
24
     * @var string[] `fieldName => (expression)`
25
     */
26
    const OPT_FIELDS = [];
27
28
    /**
29
     * `GET/PUT/DELETE` path.
30
     *
31
     * @return string
32
     */
33
    abstract public function __toString (): string;
34
35
    /**
36
     * @param self $entity
37
     * @return bool
38
     * @internal pool
39
     */
40
    final public function __merge (self $entity): bool {
41
        $old = $this->toArray();
42
        $this->data = array_merge($this->data, array_diff_key($entity->data, $this->diff));
43
        return $this->toArray() !== $old;
44
    }
45
46
    /**
47
     * @param string $addPath
48
     * @param array $data
49
     * @param string $field
50
     * @param array $diff
51
     * @return $this
52
     * @internal
53
     */
54
    protected function _addWithPost (string $addPath, array $data, string $field, array $diff) {
55
        if ($this->hasGid()) {
56
            return $this->_setWithPost($addPath, $data, $field);
57
        }
58
        return $this->_set($field, array_merge($this->data[$field] ?? [], array_values($diff)));
59
    }
60
61
    /**
62
     * Activated by trait.
63
     *
64
     * @internal
65
     */
66
    protected function _delete (): void {
67
        $this->api->delete($this);
68
        $this->api->getPool()->remove($this->getPoolKeys());
69
    }
70
71
    /**
72
     * Lazy-loads missing fields.
73
     *
74
     * @param string $field
75
     * @return mixed
76
     */
77
    protected function _get (string $field) {
78
        if (!array_key_exists($field, $this->data) and $this->hasGid()) {
79
            $this->reload($field);
80
        }
81
        return parent::_get($field);
82
    }
83
84
    /**
85
     * @param string $rmPath
86
     * @param array $data
87
     * @param string $field
88
     * @param array|Closure $diff An array to diff, or a filter closure.
89
     * @return $this
90
     * @internal
91
     */
92
    protected function _removeWithPost (string $rmPath, array $data, string $field, $diff) {
93
        if ($this->hasGid()) {
94
            return $this->_setWithPost($rmPath, $data, $field);
95
        }
96
        elseif (is_array($diff)) {
97
            return $this->_set($field, array_values(array_diff($this->data[$field] ?? [], $diff)));
98
        }
99
        return $this->_set($field, array_values(array_filter($this->data[$field] ?? [], $diff)));
100
    }
101
102
    /**
103
     * Called by create/update traits.
104
     *
105
     * @param string $dir `POST` directory for creation.
106
     * @return $this
107
     * @internal
108
     */
109
    protected function _save (string $dir = null) {
110
        if (isset($dir)) { // create()
111
            $remote = $this->api->post($dir, $this->getDiff(), ['expand' => 'this']);
112
        }
113
        elseif ($this->isDiff()) { // update()
114
            $remote = $this->api->put($this, $this->getDiff(), ['expand' => 'this']);
115
        }
116
        else { // update() but no change
117
            return $this;
118
        }
119
        /** @var array $remote */
120
        $this->_setData($remote);
121
        $this->api->getPool()->add($this);
122
        return $this;
123
    }
124
125
    protected function _setData (array $data): void {
126
        // make sure the gid is consistently null|string across all entities.
127
        $data['gid'] = (string)$data['gid'];
128
        if (empty($data['gid'])) {
129
            $data['gid'] = null;
130
        }
131
132
        // meaningless once the entity is being created. it's constant.
133
        unset($data['resource_type'], $data['type']);
134
135
        parent::_setData($data);
136
    }
137
138
    /**
139
     * Sets/reloads data via `POST` for existing entities. Otherwise stages a value.
140
     *
141
     * @param string $path
142
     * @param array $data
143
     * @param string $field
144
     * @param mixed $value Ignored for existing entities.
145
     * @return $this
146
     * @internal
147
     */
148
    protected function _setWithPost (string $path, array $data, string $field, $value = null) {
149
        if ($this->hasGid()) {
150
            /** @var array $remote */
151
            $remote = $this->api->post($path, $data, ['fields' => static::OPT_FIELDS[$field] ?? $field]);
152
            if (array_key_exists($field, $this->data)) {
153
                $this->_setMapped($field, $remote[$field]);
154
                $this->api->getPool()->add($this);
155
            }
156
            return $this;
157
        }
158
        return $this->_set($field, $value);
159
    }
160
161
    /**
162
     * @return null|string
163
     */
164
    final public function getGid (): ?string {
165
        return $this->data['gid'];
166
    }
167
168
    /**
169
     * Identifiers the entity is pooled with.
170
     *
171
     * @return string[]
172
     */
173
    public function getPoolKeys () {
174
        return [$this->getGid(), (string)$this];
175
    }
176
177
    /**
178
     * All entity classes must declare a `TYPE` constant.
179
     *
180
     * @return string
181
     */
182
    final public function getResourceType (): string {
183
        return $this::TYPE;
184
    }
185
186
    /**
187
     * @return bool
188
     */
189
    final public function hasGid (): bool {
190
        return isset($this->data['gid']);
191
    }
192
193
    /**
194
     * Reloads a specific field, or the whole entity.
195
     *
196
     * @depends after-create
197
     * @param string $field
198
     * @return $this
199
     */
200
    public function reload (string $field = null) {
201
        if (!$this->hasGid()) { // guards against loading a remote dir.
202
            throw new LogicException(static::class . " has no GID, it can't be reloaded.");
203
        }
204
        if (isset($field)) {
205
            $remote = $this->api->get($this, [], ['fields' => static::OPT_FIELDS[$field] ?? $field]);
206
            $this->_setMapped($field, $remote[$field] ?? null);
207
        }
208
        elseif ($remote = $this->api->get($this, [], ['expand' => 'this'])) {
209
            $this->_setData($remote);
210
        }
211
        else { // deleted upstream.
212
            $this->api->getPool()->remove($this->getPoolKeys());
213
            throw new RuntimeException("{$this} was deleted upstream.");
214
        }
215
        $this->api->getPool()->add($this);
216
        return $this;
217
    }
218
219
}