Completed
Pull Request — master (#24)
by Alexander
01:59
created

Entity::getRelatedModel()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 4
nc 2
nop 2
dl 0
loc 7
rs 9.4285
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
    /**
15
     * @var array
16
     */
17
    protected $data = [];
18
19
    /**
20
     * Cache of loaded relationships
21
     *
22
     * @var array
23
     */
24
    protected $relationships = [];
25
26
    /**
27
     * Entity constructor.
28
     *
29
     * @param array $data
30
     */
31
    public function __construct(array $data = [])
32
    {
33
        $this->data = $data;
34
    }
35
36
    abstract public static function fields(): ?array;
37
38
    /**
39
     * @param $name
40
     * @return mixed
41
     */
42
    public function __get(string $name)
43
    {
44
        return array_key_exists($name, $this->data) ? $this->data[$name] : null;
45
    }
46
47
    public function __call(string $method, array $args = [])
48
    {
49
        $refresh = (bool) array_shift($args);
50
        return $this->getRelatedModel($method, $refresh);
51
    }
52
53
    private function getRelatedModel(string $name, bool $refresh = false)
54
    {
55
        $this->checkIfRelationIsSet($name);
56
        if ($refresh === false && array_key_exists($name, $this->relationships)) {
57
            return $this->relationships[$name];
58
        }
59
        return $this->populateRelation($name);
60
    }
61
62
    private function checkIfRelationIsSet(string $name): void
63
    {
64
        if (isset(static::$relations) === false || is_array(static::$relations) === false ||
65
            array_key_exists($name, static::$relations) === false) {
66
            $class = get_class($this);
67
            throw new \Error("Call to undefined method {$class}::{$name}()");
68
        }
69
    }
70
71
    /**
72
     * @param string[] ...$relation_names any list of relations to return
73
     * @return object instance of class that has Entity trait
74
     */
75
    public function with(string ...$relation_names)
76
    {
77
        foreach ($relation_names as $name) {
78
            $this->populateRelation($name);
79
        }
80
        return $this;
81
    }
82
83
    /**
84
     * @param string $relation_name
85
     * @param array|object|null $data
86
     * @return array|object|null array in case of has_many
87
     * @throws \Exception
88
     */
89
    public function populateRelation(string $relation_name, $data = null)
90
    {
91
        if ($data !== null) {
92
            $this->relationships[$relation_name] = $data;
93
            return;
94
        }
95
        $relationship = $this->describeRelationship($relation_name);
96
        $relation_class = $relationship['class'];
97
        $relation_id = $this->{$relationship['local']};
98
        if ($relationship['type'] === 'belongs_to') {
99
            $related = $relation_class::get($relation_id);
100
        } elseif ($relationship['type'] === 'has_one') {
101
            $related_by = $relationship['foreign'];
102
            $result = $relation_class::findAsArray([$related_by => $relation_id], ['limit' => 1]);
103
            $related = $result ? current($result) : null;
104
        } elseif ($relationship['type'] === 'has_many') { // type must be has_many
105
            $related_by = $relationship['foreign'];
106
            $related = $relation_class::findAsArray([$related_by => $relation_id]);
107
        } else {
108
            throw new \Exception("Not a valid relationship type [" . $relationship['type'] . ']');
109
        }
110
        $this->relationships[$relation_name] = $related;
111
        return $related;
112
    }
113
114
    /**
115
     * @param string $name
116
     * @return array
117
     * @throws \Error if $name is for an unspecified relation
118
     */
119
    public function describeRelationship(string $name): array
120
    {
121
        $this->checkIfRelationIsSet($name);
122
        $settings = static::$relations[$name];
123
        if (sizeof($settings) === 1) {
124
            return $this->generateRelationshipFromShorthand($settings);
125
        }
126
        if (!array_key_exists('local', $settings)) {
127
            $settings['local'] = 'id';
128
        }
129
        if (!array_key_exists('foreign', $settings)) {
130
            $settings['foreign'] = 'id';
131
        }
132
        if (!array_key_exists('type', $settings)) {
133
            $settings['type'] = $settings['local'] === 'id' ? 'has_many' : 'belongs_to';
134
        }
135
136
        return $settings;
137
    }
138
139
    private function generateRelationshipFromShorthand(array $settings): array
140
    {
141
        $field = current($settings);
142
        $fields = static::fields() ?? [];
143
        $field_is_local = in_array($field, $fields);
0 ignored issues
show
Bug introduced by
It seems like $fields can also be of type null; however, parameter $haystack of in_array() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

143
        $field_is_local = in_array($field, /** @scrutinizer ignore-type */ $fields);
Loading history...
144
        if ($field_is_local) {
145
            return [
146
                'class' => key($settings),
147
                'type' => 'belongs_to',
148
                'local' => $field,
149
                'foreign' => 'id'
150
            ];
151
        } else {
152
            return [
153
                'class' => key($settings),
154
                'type' => 'has_many',
155
                'local' => 'id',
156
                'foreign' => $field
157
            ];
158
        }
159
    }
160
161
    /**
162
     * @param string $name
163
     * @param mixed $value
164
     */
165
    public function __set(string $name, $value): void
166
    {
167
        $this->data[$name] = $value;
168
    }
169
170
    /**
171
     * @param $name
172
     * @return bool
173
     */
174
    public function __isset(string $name): bool
175
    {
176
        return isset($this->data[$name]);
177
    }
178
179
    /**
180
     * @param array|null $data
181
     * @return array
182
     */
183
    public function data(array $data = null): array
184
    {
185
        if (is_null($data)) {
186
            return $this->data;
187
        }
188
        foreach ($data as $key => $value) {
189
            $this->data[$key] = $value;
190
        }
191
        return $this->data;
192
    }
193
194
    /**
195
     * Reset object by removing all data
196
     */
197
    public function reset(): void
198
    {
199
        $this->data = [];
200
        $this->relationships = [];
201
    }
202
203
    /**
204
     * Cast the data array to $type and return this
205
     *
206
     * @param string $type json|array
207
     * @return mixed
208
     * @throws \InvalidArgumentException on unsupported type
209
     */
210
    public function to(string $type)
211
    {
212
        switch ($type) {
213
            case 'array':
214
                return $this->data;
215
            case 'json':
216
                return json_encode($this->data);
217
            default:
218
                throw new \InvalidArgumentException("Unkown type $type");
219
        }
220
    }
221
222
    /**
223
     * @return array
224
     */
225
    public function jsonSerialize(): array
226
    {
227
        return $this->data;
228
    }
229
}
230