Entity::reset()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace alkemann\h2l\traits;
4
5
/**
6
 * Class Entity
7
 *
8
 * @property array $relations
9
 * @package alkemann\h2l
10
 */
11
trait Entity
12
{
13
    /**
14
     * @var array
15
     */
16
    protected array $data = [];
17
18
    /**
19
     * Cache of loaded relationships
20
     *
21
     * @var array
22
     */
23
    protected array $relationships = [];
24
25
    /**
26
     * Entity constructor.
27
     *
28
     * @param array $data
29
     */
30
    public function __construct(array $data = [])
31
    {
32
        $this->data = $data;
33
    }
34
35
    abstract public static function fields(): ?array;
36
37
    /**
38
     * @param string $name
39
     * @return null|mixed
40
     */
41
    public function __get(string $name)
42
    {
43
        return array_key_exists($name, $this->data) ? $this->data[$name] : null;
44
    }
45
46
    /**
47
     * @param string $method
48
     * @param array $args
49
     * @return null|array|object
50
     */
51
    public function __call(string $method, array $args = [])
52
    {
53
        $refresh = (bool) array_shift($args);
54
        return $this->getRelatedModel($method, $refresh);
55
    }
56
57
    /**
58
     * @param string $name
59
     * @param bool $refresh
60
     * @return null|array|object
61
     */
62
    private function getRelatedModel(string $name, bool $refresh = false)
63
    {
64
        $this->checkIfRelationIsSet($name);
65
        if ($refresh === false && array_key_exists($name, $this->relationships)) {
66
            return $this->relationships[$name];
67
        }
68
        return $this->populateRelation($name);
69
    }
70
71
    /**
72
     * @param string $name
73
     */
74
    private function checkIfRelationIsSet(string $name): void
75
    {
76
        if (isset(static::$relations) === false || is_array(static::$relations) === false ||
77
            array_key_exists($name, static::$relations) === false) {
78
            $class = get_class($this);
79
            throw new \Error("Call to undefined method {$class}::{$name}()");
80
        }
81
    }
82
83
    /**
84
     * @param string[] ...$relation_names any list of relations to return
85
     * @return object instance of class that has Entity trait
86
     */
87
    public function with(string ...$relation_names): object
88
    {
89
        foreach ($relation_names as $name) {
90
            $this->populateRelation($name);
91
        }
92
        return $this;
93
    }
94
95
    /**
96
     * @param string $relation_name
97
     * @param array|object|null $data
98
     * @return array|object|null array in case of has_many
99
     * @throws \Exception
100
     */
101
    public function populateRelation(string $relation_name, $data = null)
102
    {
103
        if ($data !== null) {
104
            $this->relationships[$relation_name] = $data;
105
            return;
106
        }
107
        $relationship = $this->describeRelationship($relation_name);
108
        $relation_class = $relationship['class'];
109
        $relation_id = $this->{$relationship['local']};
110
        if ($relationship['type'] === 'belongs_to') {
111
            $related = $relation_class::get($relation_id);
112
        } elseif ($relationship['type'] === 'has_one') {
113
            $related_by = $relationship['foreign'];
114
            $result = $relation_class::findAsArray([$related_by => $relation_id], ['limit' => 1]);
115
            $related = $result ? current($result) : null;
116
        } elseif ($relationship['type'] === 'has_many') { // type must be has_many
117
            $related_by = $relationship['foreign'];
118
            $related = $relation_class::findAsArray([$related_by => $relation_id]);
119
        } else {
120
            throw new \Exception("Not a valid relationship type [" . $relationship['type'] . ']');
121
        }
122
        $this->relationships[$relation_name] = $related;
123
        return $related;
124
    }
125
126
    /**
127
     * @param string $name
128
     * @return array
129
     * @throws \Error if $name is for an unspecified relation
130
     */
131
    public function describeRelationship(string $name): array
132
    {
133
        $this->checkIfRelationIsSet($name);
134
        $settings = static::$relations[$name];
135
        if (sizeof($settings) === 1) {
136
            return $this->generateRelationshipFromShorthand($settings);
137
        }
138
        if (!array_key_exists('local', $settings)) {
139
            $settings['local'] = 'id';
140
        }
141
        if (!array_key_exists('foreign', $settings)) {
142
            $settings['foreign'] = 'id';
143
        }
144
        if (!array_key_exists('type', $settings)) {
145
            $settings['type'] = $settings['local'] === 'id' ? 'has_many' : 'belongs_to';
146
        }
147
148
        return $settings;
149
    }
150
151
    /**
152
     * @param array $settings
153
     * @return array
154
     */
155
    private function generateRelationshipFromShorthand(array $settings): array
156
    {
157
        $field = current($settings);
158
        $fields = static::fields() ?? [];
159
        $field_is_local = in_array($field, $fields);
160
        if ($field_is_local) {
161
            return [
162
                'class' => key($settings),
163
                'type' => 'belongs_to',
164
                'local' => $field,
165
                'foreign' => 'id'
166
            ];
167
        } else {
168
            return [
169
                'class' => key($settings),
170
                'type' => 'has_many',
171
                'local' => 'id',
172
                'foreign' => $field
173
            ];
174
        }
175
    }
176
177
    /**
178
     * @param string $name
179
     * @param mixed $value
180
     */
181
    public function __set(string $name, $value): void
182
    {
183
        $this->data[$name] = $value;
184
    }
185
186
    /**
187
     * @param $name
188
     * @return bool
189
     */
190
    public function __isset(string $name): bool
191
    {
192
        return isset($this->data[$name]);
193
    }
194
195
    /**
196
     * @param array|null $data
197
     * @return array
198
     */
199
    public function data(array $data = null): array
200
    {
201
        if (is_null($data)) {
202
            return $this->data;
203
        }
204
        foreach ($data as $key => $value) {
205
            $this->data[$key] = $value;
206
        }
207
        return $this->data;
208
    }
209
210
    /**
211
     * Reset object by removing all data
212
     */
213
    public function reset(): void
214
    {
215
        $this->data = [];
216
        $this->relationships = [];
217
    }
218
219
    /**
220
     * Cast the data array to $type and return this
221
     *
222
     * @param string $type json|array
223
     * @return string|array
224
     * @throws \InvalidArgumentException on unsupported type
225
     */
226
    public function to(string $type)
227
    {
228
        switch ($type) {
229
            case 'array': return $this->data;
230
            case 'json': return json_encode($this->data);
231
            default:
232
                throw new \InvalidArgumentException("Unkown type $type");
233
        }
234
    }
235
236
    /**
237
     * @return array
238
     */
239
    public function jsonSerialize(): array
240
    {
241
        return $this->data;
242
    }
243
}
244