Passed
Push — master ( f4966c...dd3b2f )
by Sergei
03:41 queued 45s
created

BaseActiveRecordTrait::getIterator()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 5
ccs 1
cts 1
cp 1
crap 1
rs 10
c 0
b 0
f 0

1 Method

Rating   Name   Duplication   Size   Complexity  
A BaseActiveRecordTrait::offsetExists() 0 3 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\ActiveRecord;
6
7
use ArrayAccess;
8
use Error;
9
use Exception;
10
use ReflectionException;
11
use ReflectionMethod;
12
use Throwable;
13
use Yiisoft\Db\Exception\InvalidArgumentException;
14
use Yiisoft\Db\Exception\InvalidCallException;
15
use Yiisoft\Db\Exception\InvalidConfigException;
16
use Yiisoft\Db\Exception\UnknownPropertyException;
17
18
use function array_key_exists;
19
use function lcfirst;
20
use function method_exists;
21
use function property_exists;
22
use function substr;
23
use function ucfirst;
24
25
trait BaseActiveRecordTrait
26
{
27
    private static string|null $connectionId = null;
28
29
    /**
30
     * PHP getter magic method.
31
     *
32
     * This method is overridden so that attributes and related objects can be accessed like properties.
33
     *
34
     * @param string $name property name.
35
     *
36
     * @throws InvalidArgumentException|InvalidCallException|InvalidConfigException|ReflectionException|Throwable
37
     * @throws UnknownPropertyException
38
     *
39
     * @return mixed property value.
40
     *
41
     * {@see getAttribute()}
42
     */
43
    public function __get(string $name)
44
    {
45
        if (isset($this->attributes[$name]) || array_key_exists($name, $this->attributes)) {
46 406
            return $this->attributes[$name];
47
        }
48 406
49 368
        if ($this->hasAttribute($name)) {
0 ignored issues
show
Bug introduced by
It seems like hasAttribute() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

49
        if ($this->/** @scrutinizer ignore-call */ hasAttribute($name)) {
Loading history...
50
            return null;
51
        }
52 251
53 39
        if (isset($this->related[$name]) || array_key_exists($name, $this->related)) {
54
            return $this->related[$name];
55
        }
56 241
57 152
        /** @var mixed $value */
58
        $value = $this->checkRelation($name);
59
60 147
        if ($value instanceof ActiveQuery) {
61
            $this->setRelationDependencies($name, $value);
0 ignored issues
show
Bug introduced by
It seems like setRelationDependencies() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

61
            $this->/** @scrutinizer ignore-call */ 
62
                   setRelationDependencies($name, $value);
Loading history...
62 131
            return $this->related[$name] = $value->findFor($name, $this);
0 ignored issues
show
Bug Best Practice introduced by
The property related does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
Bug introduced by
$this of type Yiisoft\ActiveRecord\BaseActiveRecordTrait is incompatible with the type Yiisoft\ActiveRecord\ActiveRecordInterface expected by parameter $model of Yiisoft\ActiveRecord\ActiveQuery::findFor(). ( Ignorable by Annotation )

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

62
            return $this->related[$name] = $value->findFor($name, /** @scrutinizer ignore-type */ $this);
Loading history...
63 101
        }
64 101
65
        return $value;
66
    }
67 44
68
    public function checkRelation(string $name): mixed
69
    {
70 147
        $getter = 'get' . ucfirst($name);
71
72 147
        if (method_exists($this, $getter)) {
73
            /** read property, e.g. getName() */
74 147
            return $this->$getter();
75
        }
76 139
77
        if (method_exists($this, 'set' . ucfirst($name))) {
78
            throw new InvalidCallException('Getting write-only property: ' . static::class . '::' . $name);
79 8
        }
80 4
81
        throw new UnknownPropertyException('Getting unknown property: ' . static::class . '::' . $name);
82
    }
83 4
84
    /**
85
     * Returns the relation object with the specified name.
86
     *
87
     * A relation is defined by a getter method which returns an {@see ActiveQueryInterface} object.
88
     *
89
     * It can be declared in either the Active Record class itself or one of its behaviors.
90
     *
91
     * @param string $name the relation name, e.g. `orders` for a relation defined via `getOrders()` method
92
     * (case-sensitive).
93
     * @param bool $throwException whether to throw exception if the relation does not exist.
94
     *
95
     * @throws InvalidArgumentException if the named relation does not exist.
96
     * @throws ReflectionException
97
     *
98
     * @return ActiveQueryInterface|null the relational query object. If the relation does not exist and
99
     * `$throwException` is `false`, `null` will be returned.
100
     */
101
    public function getRelation(string $name, bool $throwException = true): ActiveQueryInterface|null
102 228
    {
103
        $getter = 'get' . ucfirst($name);
104 228
105
        try {
106
            /** the relation could be defined in a behavior */
107
            $relation = $this->$getter();
108 228
        } catch (Error) {
109 4
            if ($throwException) {
110 4
                throw new InvalidArgumentException(static::class . ' has no relation named "' . $name . '".');
111 4
            }
112
113
            return null;
114 4
        }
115
116
        if (!$relation instanceof ActiveQueryInterface) {
117 224
            if ($throwException) {
118 4
                throw new InvalidArgumentException(static::class . ' has no relation named "' . $name . '".');
119 4
            }
120
121
            return null;
122 4
        }
123
124
        if (method_exists($this, $getter)) {
125 220
            /** relation name is case sensitive, trying to validate it when the relation is defined within this class */
126
            $method = new ReflectionMethod($this, $getter);
127 220
            $realName = lcfirst(substr($method->getName(), 3));
128 220
129
            if ($realName !== $name) {
130 220
                if ($throwException) {
131 4
                    throw new InvalidArgumentException(
132 4
                        'Relation names are case sensitive. ' . static::class
133 4
                        . " has a relation named \"$realName\" instead of \"$name\"."
134 4
                    );
135
                }
136
137
                return null;
138 4
            }
139
        }
140
141
        return $relation;
142 216
    }
143
144
    /**
145
     * Checks if a property value is null.
146
     *
147
     * This method overrides the parent implementation by checking if the named attribute is `null` or not.
148
     *
149
     * @param string $name the property name or the event name.
150
     *
151
     * @return bool whether the property value is null.
152
     */
153
    public function __isset(string $name): bool
154 81
    {
155
        try {
156
            return $this->__get($name) !== null;
157 81
        } catch (InvalidCallException|UnknownPropertyException) {
0 ignored issues
show
Coding Style introduced by
Expected at least 1 space before "|"; 0 found
Loading history...
Coding Style introduced by
Expected at least 1 space after "|"; 0 found
Loading history...
158 8
            return false;
159 8
        }
160
    }
161
162
    /**
163
     * Sets a component property to be null.
164
     *
165
     * This method overrides the parent implementation by clearing the specified attribute value.
166
     *
167
     * @param string $name the property name or the event name.
168
     */
169
    public function __unset(string $name): void
170 24
    {
171
        if ($this->hasAttribute($name)) {
172 24
            unset($this->attributes[$name]);
173 14
            if (!empty($this->relationsDependencies[$name])) {
0 ignored issues
show
Bug Best Practice introduced by
The property relationsDependencies does not exist on Yiisoft\ActiveRecord\BaseActiveRecordTrait. Since you implemented __get, consider adding a @property annotation.
Loading history...
174 14
                $this->resetDependentRelations($name);
0 ignored issues
show
Bug introduced by
It seems like resetDependentRelations() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

174
                $this->/** @scrutinizer ignore-call */ 
175
                       resetDependentRelations($name);
Loading history...
175 14
            }
176
        } elseif (array_key_exists($name, $this->related)) {
177 10
            unset($this->related[$name]);
178 10
        }
179
    }
180 24
181
    /**
182
     * PHP setter magic method.
183
     *
184
     * This method is overridden so that AR attributes can be accessed like properties.
185
     *
186
     * @param string $name property name.
187
     * @param mixed $value property value.
188
     *
189
     * @throws InvalidCallException
190
     */
191
    public function __set(string $name, mixed $value): void
192 186
    {
193
        if ($this->hasAttribute($name)) {
194 186
            if (
195
                !empty($this->relationsDependencies[$name])
0 ignored issues
show
Bug Best Practice introduced by
The property relationsDependencies does not exist on Yiisoft\ActiveRecord\BaseActiveRecordTrait. Since you implemented __get, consider adding a @property annotation.
Loading history...
196 186
                && (!array_key_exists($name, $this->attributes) || $this->attributes[$name] !== $value)
197 186
            ) {
198
                $this->resetDependentRelations($name);
199 24
            }
200
            $this->attributes[$name] = $value;
0 ignored issues
show
Bug Best Practice introduced by
The property attributes does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
201 186
            return;
202
        }
203
204 186
        if (method_exists($this, 'get' . ucfirst($name))) {
205 5
            throw new InvalidCallException('Setting read-only property: ' . static::class . '::' . $name);
206
        }
207 186
208
        throw new UnknownPropertyException('Setting unknown property: ' . static::class . '::' . $name);
209
    }
210
211
    /**
212
     * Returns whether there is an element at the specified offset.
213
     *
214
     * This method is required by the SPL interface {@see ArrayAccess}.
215
     *
216 5
     * It is implicitly called when you use something like `isset($model[$offset])`.
217
     *
218 5
     * @param mixed $offset the offset to check on.
219
     *
220 5
     * @return bool whether or not an offset exists.
221
     */
222
    public function offsetExists(mixed $offset): bool
223
    {
224
        return isset($this->$offset);
225
    }
226
227
    public function offsetGet(mixed $offset): mixed
228
    {
229
        return $this->$offset;
230
    }
231
232
    /**
233
     * Sets the element at the specified offset.
234 64
     *
235
     * This method is required by the SPL interface {@see ArrayAccess}.
236 64
     *
237
     * It is implicitly called when you use something like `$model[$offset] = $item;`.
238
     *
239
     * @param mixed $offset the offset to set element.
240
     * @param mixed $value the element value.
241
     */
242
    public function offsetSet(mixed $offset, mixed $value): void
243
    {
244
        $this->$offset = $value;
245
    }
246
247
    /**
248
     * Sets the element value at the specified offset to null.
249
     *
250 246
     * This method is required by the SPL interface {@see ArrayAccess}.
251
     *
252 246
     * It is implicitly called when you use something like `unset($model[$offset])`.
253
     *
254
     * @param mixed $offset the offset to unset element
255
     */
256
    public function offsetUnset(mixed $offset): void
257
    {
258
        if (is_string($offset) && property_exists($this, $offset)) {
259
            $this->$offset = null;
260
        } else {
261
            unset($this->$offset);
262
        }
263
    }
264
265 4
    /**
266
     * Returns a value indicating whether a property is defined for this component.
267 4
     *
268 4
     * A property is defined if:
269
     *
270
     * - the class has a getter or setter method associated with the specified name (in this case, property name is
271
     *   case-insensitive).
272
     * - the class has a member variable with the specified name (when `$checkVars` is true).
273
     * - an attached behavior has a property of the given name (when `$checkBehaviors` is true).
274
     *
275
     * @param string $name the property name.
276
     * @param bool $checkVars whether to treat member variables as properties.
277
     *
278
     * @return bool whether the property is defined.
279 5
     *
280
     * {@see canGetProperty()}
281 5
     * {@see canSetProperty()}
282
     */
283
    public function hasProperty(string $name, bool $checkVars = true): bool
284 5
    {
285
        return $this->canGetProperty($name, $checkVars)
286 5
            || $this->canSetProperty($name, false);
287
    }
288
289
    public function canGetProperty(string $name, bool $checkVars = true): bool
290
    {
291
        if (method_exists($this, 'get' . ucfirst($name)) || ($checkVars && property_exists($this, $name))) {
292
            return true;
293
        }
294
295
        try {
296
            return $this->hasAttribute($name);
297
        } catch (Exception) {
298
            /** `hasAttribute()` may fail on base/abstract classes in case automatic attribute list fetching used */
299
            return false;
300
        }
301
    }
302
303
    public function canSetProperty(string $name, bool $checkVars = true): bool
304
    {
305
        if (method_exists($this, 'set' . ucfirst($name)) || ($checkVars && property_exists($this, $name))) {
306 5
            return true;
307
        }
308 5
309 5
        try {
310
            return $this->hasAttribute($name);
311
        } catch (Exception) {
312 5
            /** `hasAttribute()` may fail on base/abstract classes in case automatic attribute list fetching used */
313
            return false;
314 5
        }
315 5
    }
316
}
317