Passed
Push — master ( ae9b6b...aec4ed )
by Wilmer
11:33 queued 09:14
created

BaseActiveRecordTrait::__unset()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 7.1941

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 11
ccs 5
cts 9
cp 0.5556
rs 9.6111
c 0
b 0
f 0
cc 5
nc 5
nop 1
crap 7.1941
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\ActiveRecord;
6
7
use Yiisoft\Db\Exception\InvalidArgumentException;
8
use Yiisoft\Db\Exception\UnknownMethodException;
9
use Yiisoft\Db\Exception\UnknownPropertyException;
10
11
trait BaseActiveRecordTrait
12
{
13
    /**
14
     * PHP getter magic method.
15
     *
16
     * This method is overridden so that attributes and related objects can be accessed like properties.
17
     *
18
     * @param string $name property name
19
     *
20
     * @throws UnknownPropertyException
21
     *
22
     * @return mixed property value
23
     *
24
     * {@see getAttribute()}
25
     */
26 153
    public function __get(string $name)
27
    {
28 153
        if (isset($this->attributes[$name]) || \array_key_exists($name, $this->attributes)) {
29 141
            return $this->attributes[$name];
30
        }
31
32 99
        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

32
        if ($this->/** @scrutinizer ignore-call */ hasAttribute($name)) {
Loading history...
33 18
            return null;
34
        }
35
36 90
        if (isset($this->related[$name]) || \array_key_exists($name, $this->related)) {
37 57
            return $this->related[$name];
38
        }
39
40 51
        $value = $this->checkRelation($name);
41
42 45
        if ($value instanceof ActiveQueryInterface) {
43 45
            $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

43
            $this->/** @scrutinizer ignore-call */ 
44
                   setRelationDependencies($name, $value);
Loading history...
44 45
            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\Act...eryInterface::findFor(). ( Ignorable by Annotation )

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

44
            return $this->related[$name] = $value->findFor($name, /** @scrutinizer ignore-type */ $this);
Loading history...
45
        }
46
47 3
        return $value;
48
    }
49
50 51
    public function checkRelation(string $name)
51
    {
52 51
        $getter = 'get' . $name;
53
54 51
        if (\method_exists($this, $getter)) {
55
            // read property, e.g. getName()
56 51
            return $this->$getter();
57
        }
58
59
        if (\method_exists($this, 'set' . $name)) {
60
            throw new InvalidCallException('Getting write-only property: ' . \get_class($this) . '::' . $name);
0 ignored issues
show
Bug introduced by
The type Yiisoft\ActiveRecord\InvalidCallException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
61
        }
62
63
        throw new UnknownPropertyException('Getting unknown property: ' . \get_class($this) . '::' . $name);
64
    }
65
66
67
    /**
68
     * Returns the relation object with the specified name.
69
     *
70
     * A relation is defined by a getter method which returns an {@see ActiveQueryInterface} object.
71
     *
72
     * It can be declared in either the Active Record class itself or one of its behaviors.
73
     *
74
     * @param string $name the relation name, e.g. `orders` for a relation defined via `getOrders()` method
75
     * (case-sensitive).
76
     * @param bool $throwException whether to throw exception if the relation does not exist.
77
     *
78
     * @throws InvalidArgumentException if the named relation does not exist.
79
     * @throws \ReflectionException
80
     *
81
     * @return ActiveQueryInterface|ActiveQuery the relational query object. If the relation does not exist and
82
     * `$throwException` is `false`, `null` will be returned.
83
     */
84 87
    public function getRelation(string $name, bool $throwException = true)
85
    {
86 87
        $getter = 'get' . $name;
87
88
        try {
89
            // the relation could be defined in a behavior
90 87
            $relation = $this->$getter();
91
        } catch (UnknownMethodException $e) {
92
            if ($throwException) {
93
                throw new InvalidArgumentException(
94
                    \get_class($this) . ' has no relation named "' . $name . '".',
95
                    0,
0 ignored issues
show
Bug introduced by
0 of type integer is incompatible with the type array|null expected by parameter $errorInfo of Yiisoft\Db\Exception\Inv...xception::__construct(). ( Ignorable by Annotation )

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

95
                    /** @scrutinizer ignore-type */ 0,
Loading history...
96
                    $e
0 ignored issues
show
Bug introduced by
$e of type Yiisoft\Db\Exception\UnknownMethodException is incompatible with the type integer expected by parameter $code of Yiisoft\Db\Exception\Inv...xception::__construct(). ( Ignorable by Annotation )

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

96
                    /** @scrutinizer ignore-type */ $e
Loading history...
97
                );
98
            }
99
100
            return null;
101
        }
102
103 87
        if (!$relation instanceof ActiveQueryInterface) {
104
            if ($throwException) {
105
                throw new InvalidArgumentException(\get_class($this) . ' has no relation named "' . $name . '".');
106
            }
107
108
            return null;
109
        }
110
111 87
        if (\method_exists($this, $getter)) {
112
            /* relation name is case sensitive, trying to validate it when the relation is defined within this class */
113 87
            $method = new \ReflectionMethod($this, $getter);
114 87
            $realName = \lcfirst(\substr($method->getName(), 3));
115
116 87
            if ($realName !== $name) {
117
                if ($throwException) {
118
                    throw new InvalidArgumentException(
119
                        'Relation names are case sensitive. ' . \get_class($this)
120
                        . " has a relation named \"$realName\" instead of \"$name\"."
121
                    );
122
                }
123
124
                return null;
125
            }
126
        }
127
128 87
        return $relation;
129
    }
130
131
    /**
132
     * Checks if a property value is null.
133
     *
134
     * This method overrides the parent implementation by checking if the named attribute is `null` or not.
135
     *
136
     * @param string $name the property name or the event name
137
     *
138
     * @return bool whether the property value is null
139
     */
140 36
    public function __isset(string $name): bool
141
    {
142
        try {
143 36
            return $this->__get($name) !== null;
144 6
        } catch (\Throwable $t) {
145 6
            return false;
146
        } catch (\Exception $e) {
147
            return false;
148
        }
149
    }
150
151
    /**
152
     * Sets a component property to be null.
153
     *
154
     * This method overrides the parent implementation by clearing the specified attribute value.
155
     *
156
     * @param string $name the property name or the event name
157
     *
158
     * @throws InvalidArgumentException
159
     * @throws \ReflectionException
160
     *
161
     * @return void
162
     */
163 6
    public function __unset($name): void
164
    {
165 6
        if ($this->hasAttribute($name)) {
166 6
            unset($this->attributes[$name]);
167 6
            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...
168 6
                $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

168
                $this->/** @scrutinizer ignore-call */ 
169
                       resetDependentRelations($name);
Loading history...
169
            }
170
        } elseif (\array_key_exists($name, $this->related)) {
171
            unset($this->related[$name]);
172
        } elseif ($this->getRelation($name, false) === null) {
173
            parent::__unset($name);
174
        }
175 6
    }
176
177
    /**
178
     * PHP setter magic method.
179
     *
180
     * This method is overridden so that AR attributes can be accessed like properties.
181
     *
182
     * @param string $name property name
183
     * @param mixed $value property value
184
     *
185
     * @return void
186
     */
187 57
    public function __set($name, $value): void
188
    {
189 57
        if ($this->hasAttribute($name)) {
190
            if (
191 57
                !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...
192 57
                && (!\array_key_exists($name, $this->attributes) || $this->attributes[$name] !== $value)
193
            ) {
194 12
                $this->resetDependentRelations($name);
195
            }
196 57
            $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...
197
        }
198 57
    }
199
200
    /**
201
     * Returns an iterator for traversing the attributes in the ActiveRecord.
202
     *
203
     * This method is required by the interface {@see \IteratorAggregate}.
204
     *
205
     * @return \ArrayIterator an iterator for traversing the items in the list.
206
     */
207 3
    public function getIterator(): \ArrayIterator
208
    {
209 3
        $attributes = $this->getAttributes();
0 ignored issues
show
Bug introduced by
It seems like getAttributes() 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

209
        /** @scrutinizer ignore-call */ 
210
        $attributes = $this->getAttributes();
Loading history...
210
211 3
        return new \ArrayIterator($attributes);
212
    }
213
214
    /**
215
     * Returns whether there is an element at the specified offset.
216
     *
217
     * This method is required by the SPL interface {@see \ArrayAccess}.
218
     *
219
     * It is implicitly called when you use something like `isset($model[$offset])`.
220
     *
221
     * @param mixed $offset the offset to check on.
222
     *
223
     * @return bool whether or not an offset exists.
224
     */
225 27
    public function offsetExists($offset): bool
226
    {
227 27
        return isset($this->$offset);
228
    }
229
230
    /**
231
     * Returns the element at the specified offset.
232
     *
233
     * This method is required by the SPL interface {@see \ArrayAccess}.
234
     *
235
     * It is implicitly called when you use something like `$value = $model[$offset];`.
236
     *
237
     * @param mixed $offset the offset to retrieve element.
238
     *
239
     * @return mixed the element at the offset, null if no element is found at the offset
240
     */
241 99
    public function offsetGet($offset)
242
    {
243 99
        return $this->$offset;
244
    }
245
246
    /**
247
     * Sets the element at the specified offset.
248
     *
249
     * This method is required by the SPL interface {@see \ArrayAccess}.
250
     *
251
     * It is implicitly called when you use something like `$model[$offset] = $item;`.
252
     *
253
     * @param int $offset the offset to set element
254
     * @param mixed $item the element value
255
     *
256
     * @return void
257
     */
258 3
    public function offsetSet($offset, $item)
259
    {
260 3
        $this->$offset = $item;
261 3
    }
262
263
    /**
264
     * Sets the element value at the specified offset to null.
265
     *
266
     * This method is required by the SPL interface {@see \ArrayAccess}.
267
     *
268
     * It is implicitly called when you use something like `unset($model[$offset])`.
269
     *
270
     * @param mixed $offset the offset to unset element
271
     */
272
    public function offsetUnset($offset): void
273
    {
274
        if (\property_exists($this, $offset)) {
275
            $this->$offset = null;
276
        } else {
277
            unset($this->$offset);
278
        }
279
    }
280
}
281