Completed
Pull Request — master (#24)
by Alexander
02:04
created

Entity::data()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 3
nop 1
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
1
<?php
2
3
namespace alkemann\h2l\traits;
4
5
/**
6
 * Class Entity
7
 *
8
 * @package alkemann\h2l
9
 */
10
trait Entity
11
{
12
13
    /**
14
     * @var array
15
     */
16
    protected $data = [];
17
18
    /**
19
     * Cache of loaded relationships
20
     *
21
     * @var array
22
     */
23
    protected $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 $name
39
     * @return mixed
40
     */
41
    public function __get(string $name)
42
    {
43
        return array_key_exists($name, $this->data) ? $this->data[$name] : null;
44
    }
45
46
    public function __call(string $method, array $args = [])
47
    {
48
        $refresh = (bool) array_shift($args);
49
        return $this->getRelatedModel($method, $refresh);
50
    }
51
52
    private function getRelatedModel(string $name, bool $refresh = false)
53
    {
54
        $this->checkIfRelationIsSet($name);
55
        if ($refresh === false && array_key_exists($name, $this->relationships)) {
56
            return $this->relationships[$name];
57
        }
58
        return $this->populateRelation($name);
59
    }
60
61
    private function checkIfRelationIsSet(string $name): void
62
    {
63
        if (isset(static::$relations) === false || is_array(static::$relations) === false ||
0 ignored issues
show
Bug Best Practice introduced by
The property relations does not exist on alkemann\h2l\traits\Entity. Since you implemented __get, consider adding a @property annotation.
Loading history...
64
            array_key_exists($name, static::$relations) === false) {
0 ignored issues
show
Bug introduced by
It seems like static::relations can also be of type null; however, parameter $search of array_key_exists() 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

64
            array_key_exists($name, /** @scrutinizer ignore-type */ static::$relations) === false) {
Loading history...
65
            $class = get_class($this);
66
            throw new \Error("Call to undefined method {$class}::{$name}()");
1 ignored issue
show
Unused Code introduced by
The call to Error::__construct() has too many arguments starting with EncapsedNode. ( Ignorable by Annotation )

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

66
            throw /** @scrutinizer ignore-call */ new \Error("Call to undefined method {$class}::{$name}()");

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
67
        }
68
    }
69
70
    /**
71
     * @param string[] ...$relation_names any list of relations to return
72
     * @return object instance of class that has Entity trait
73
     */
74
    public function with(string ...$relation_names)
75
    {
76
        foreach ($relation_names as $name) {
77
            $this->populateRelation($name);
78
        }
79
        return $this;
1 ignored issue
show
Bug Best Practice introduced by
The expression return $this returns the type alkemann\h2l\traits\Entity which is incompatible with the documented return type object.
Loading history...
80
    }
81
82
    /**
83
     * @param string $relation_name
84
     * @param array|object|null $data
85
     * @return array|object|null array in case of has_many
86
     * @throws \Exception
87
     */
88
    public function populateRelation(string $relation_name, $data = null)
89
    {
90
        if ($data !== null) {
91
            $this->relationships[$relation_name] = $data;
92
            return;
93
        }
94
        $relationship = $this->describeRelationship($relation_name);
95
        $relation_class = $relationship['class'];
96
        $relation_id = $this->{$relationship['local']};
97
        if ($relationship['type'] === 'belongs_to') {
98
            $related = $relation_class::get($relation_id);
99
        } elseif ($relationship['type'] === 'has_one') {
100
            $related_by = $relationship['foreign'];
101
            $result = $relation_class::findAsArray([$related_by => $relation_id], ['limit' => 1]);
102
            $related = $result ? current($result) : null;
103
        } elseif ($relationship['type'] === 'has_many') { // type must be has_many
104
            $related_by = $relationship['foreign'];
105
            $related = $relation_class::findAsArray([$related_by => $relation_id]);
106
        } else {
107
            throw new \Exception("Not a valid relationship type [" . $relationship['type'] . ']');
108
        }
109
        $this->relationships[$relation_name] = $related;
110
        return $related;
111
    }
112
113
    /**
114
     * @param string $name
115
     * @return array
116
     * @throws \Error if $name is for an unspecified relation
117
     */
118
    public function describeRelationship(string $name): array
119
    {
120
        $this->checkIfRelationIsSet($name);
121
        $settings = static::$relations[$name];
0 ignored issues
show
Bug Best Practice introduced by
The property relations does not exist on alkemann\h2l\traits\Entity. Since you implemented __get, consider adding a @property annotation.
Loading history...
122
        if (sizeof($settings) === 1) {
123
            return $this->generateRelationshipFromShorthand($settings);
124
        }
125
        if (!array_key_exists('local', $settings)) {
126
            $settings['local'] = 'id';
127
        }
128
        if (!array_key_exists('foreign', $settings)) {
129
            $settings['foreign'] = 'id';
130
        }
131
        if (!array_key_exists('type', $settings)) {
132
            $settings['type'] = $settings['local'] === 'id' ? 'has_many' : 'belongs_to';
133
        }
134
135
        return $settings;
136
    }
137
138
    private function generateRelationshipFromShorthand(array $settings): array
139
    {
140
        $field = current($settings);
141
        $fields = static::fields() ?? [];
142
        $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

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