Passed
Push — master ( 24e1a2...15b43b )
by y
01:43
created

AbstractEntity::getResourceType()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Helix\Asana\Base;
4
5
use Closure;
6
use RuntimeException;
7
8
/**
9
 * A resource with a GID.
10
 *
11
 * @see https://developers.asana.com/docs/object-hierarchy
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
     * Defines `opt_fields` expressions for lazy-loading/reloading fields.
22
     *
23
     * If not set here, the field name is used as-is.
24
     *
25
     * @var string[] `fieldName => (expression)`
26
     */
27
    const OPT_FIELDS = [];
28
29
    /**
30
     * `GET/PUT/DELETE` path.
31
     *
32
     * @return string
33
     */
34
    abstract public function __toString (): string;
35
36
    /**
37
     * @param self $entity
38
     * @return bool
39
     * @internal pool
40
     */
41
    final public function __merge (self $entity): bool {
42
        $old = $this->toArray();
43
        $this->data = array_merge($this->data, array_diff_key($entity->data, $this->diff));
44
        return $this->toArray() !== $old;
45
    }
46
47
    /**
48
     * @param string $addPath
49
     * @param array $data
50
     * @param string $field
51
     * @param array $diff
52
     * @return $this
53
     * @internal
54
     */
55
    protected function _addWithPost (string $addPath, array $data, string $field, array $diff) {
56
        if ($this->hasGid()) {
57
            return $this->_setWithPost($addPath, $data, $field);
58
        }
59
        return $this->_set($field, array_merge($this->data[$field] ?? [], array_values($diff)));
60
    }
61
62
    /**
63
     * Lazy-loads missing fields.
64
     *
65
     * @param string $field
66
     * @return mixed
67
     */
68
    protected function _get (string $field) {
69
        if (!array_key_exists($field, $this->data) and $this->hasGid()) {
70
            $this->_reload($field);
71
        }
72
        return parent::_get($field);
73
    }
74
75
    /**
76
     * @param string $field
77
     */
78
    protected function _reload (string $field): void {
79
        $remote = $this->api->get($this, [], ['fields' => static::OPT_FIELDS[$field] ?? $field]);
80
        $this->_setField($field, $remote[$field] ?? null);
81
        $this->api->getPool()->add($this);
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
    protected function _setData (array $data): void {
103
        // make sure the gid is consistently null|string across all entities.
104
        $data['gid'] = (string)$data['gid'];
105
        if (empty($data['gid'])) {
106
            $data['gid'] = null;
107
        }
108
109
        // meaningless once the entity is being created. it's constant.
110
        unset($data['resource_type'], $data['type']);
111
112
        parent::_setData($data);
113
    }
114
115
    /**
116
     * Sets/reloads data via `POST` for existing entities. Otherwise stages a value.
117
     *
118
     * @param string $path
119
     * @param array $data
120
     * @param string $field
121
     * @param mixed $value Ignored for existing entities.
122
     * @return $this
123
     * @internal
124
     */
125
    protected function _setWithPost (string $path, array $data, string $field, $value = null) {
126
        if ($this->hasGid()) {
127
            /** @var array $remote */
128
            $remote = $this->api->post($path, $data, ['fields' => static::OPT_FIELDS[$field] ?? $field]);
129
            if (array_key_exists($field, $this->data)) {
130
                $this->_setField($field, $remote[$field]);
131
                $this->api->getPool()->add($this);
132
            }
133
            return $this;
134
        }
135
        return $this->_set($field, $value);
136
    }
137
138
    /**
139
     * @return null|string
140
     */
141
    final public function getGid (): ?string {
142
        return $this->data['gid'];
143
    }
144
145
    /**
146
     * Identifiers the entity is pooled with.
147
     *
148
     * @return string[]
149
     */
150
    public function getPoolKeys () {
151
        return [$this->getGid(), (string)$this];
152
    }
153
154
    /**
155
     * All entity classes must declare a `TYPE` constant.
156
     *
157
     * @return string
158
     */
159
    final public function getResourceType (): string {
160
        return $this::TYPE;
161
    }
162
163
    /**
164
     * @return bool
165
     */
166
    final public function hasGid (): bool {
167
        return isset($this->data['gid']);
168
    }
169
170
    /**
171
     * Fully reloads the entity from Asana.
172
     *
173
     * @depends after-create
174
     * @return $this
175
     */
176
    public function reload () {
177
        $remote = $this->api->get($this, [], ['expand' => 'this']);
178
        if (!isset($remote['gid'])) { // null and dir guard
179
            $this->api->getPool()->remove($this->getPoolKeys());
180
            throw new RuntimeException("{$this} was not found upstream.");
181
        }
182
        $this->_setData($remote);
183
        $this->api->getPool()->add($this);
184
        return $this;
185
    }
186
187
}