Passed
Push — master ( 097ab9...fbd180 )
by y
02:12
created

AbstractEntity::_delete()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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