AccessorTrait   A
last analyzed

Complexity

Total Complexity 24

Size/Duplication

Total Lines 221
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 24
eloc 88
c 3
b 0
f 0
dl 0
loc 221
rs 10

9 Methods

Rating   Name   Duplication   Size   Complexity  
A assert_property_is_writable() 0 26 4
A __set() 0 3 1
A accessor_get() 0 22 3
A has_property() 0 27 5
A assert_no_accessor() 0 4 2
A assert_property_is_readable() 0 36 4
A has_method() 0 3 1
A accessor_set() 0 28 3
A __get() 0 3 1
1
<?php
2
3
/*
4
 * This file is part of the ICanBoogie package.
5
 *
6
 * (c) Olivier Laviale <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace ICanBoogie\Accessor;
13
14
use ICanBoogie\PropertyNotDefined;
15
use ICanBoogie\PropertyNotReadable;
16
use ICanBoogie\PropertyNotWritable;
17
use ReflectionClass;
18
use ReflectionException;
19
use ReflectionObject;
20
21
use function array_keys;
22
use function get_class;
23
use function get_object_vars;
24
use function implode;
25
use function method_exists;
26
use function property_exists;
27
use function sprintf;
28
29
/**
30
 * Implements ICanBoogie's accessor pattern.
31
 */
32
trait AccessorTrait
33
{
34
    use FormatAsSnake;
35
36
    public function __get($property)
37
    {
38
        return $this->accessor_get($property);
39
    }
40
41
    /**
42
     * @throws ReflectionException
43
     */
44
    public function __set($property, $value)
45
    {
46
        $this->accessor_set($property, $value);
47
    }
48
49
    public function has_property(string $property): bool
50
    {
51
        return property_exists($this, $property)
52
            || $this->has_method(
53
                static::accessor_format(
54
                    $property,
55
                    HasAccessor::ACCESSOR_TYPE_GETTER
56
                )
57
            )
58
            || $this->has_method(
59
                static::accessor_format(
60
                    $property,
61
                    HasAccessor::ACCESSOR_TYPE_GETTER,
62
                    HasAccessor::ACCESSOR_IS_LAZY
63
                )
64
            )
65
            || $this->has_method(
66
                static::accessor_format(
67
                    $property,
68
                    HasAccessor::ACCESSOR_TYPE_SETTER
69
                )
70
            )
71
            || $this->has_method(
72
                static::accessor_format(
73
                    $property,
74
                    HasAccessor::ACCESSOR_TYPE_SETTER,
75
                    HasAccessor::ACCESSOR_IS_LAZY
76
                )
77
            );
78
    }
79
80
    public function has_method(string $method): bool
81
    {
82
        return method_exists($this, $method);
83
    }
84
85
    /**
86
     * Returns the value of an inaccessible property.
87
     *
88
     * The method tries to get the property using the getter and lazy getter methods.
89
     *
90
     * @return mixed
91
     */
92
    private function accessor_get(string $property)
93
    {
94
        $method = static::accessor_format(
95
            $property,
96
            HasAccessor::ACCESSOR_TYPE_GETTER
97
        );
98
99
        if ($this->has_method($method)) {
100
            return $this->$method();
101
        }
102
103
        $method = static::accessor_format(
104
            $property,
105
            HasAccessor::ACCESSOR_TYPE_GETTER,
106
            HasAccessor::ACCESSOR_IS_LAZY
107
        );
108
109
        if ($this->has_method($method)) {
110
            return $this->$property = $this->$method();
111
        }
112
113
        $this->assert_property_is_readable($property);
114
    } //@codeCoverageIgnore
115
116
    /**
117
     * Sets the value of an inaccessible property.
118
     *
119
     * The method is called because the property does not exist, its visibility is
120
     * _protected_ or _private_, or because although it is visible it was unset and is no
121
     * longer accessible.
122
     *
123
     * A `set_<property>` method can be used the handle virtual properties, for instance a
124
     * `minute` property that would alter a `second` property.
125
     *
126
     * A `lazy_set_<property>` method can be used to set properties that are protected or
127
     * private, which can be used to make properties write-only for example.
128
     *
129
     * @param mixed $value
130
     *
131
     * @throws ReflectionException
132
     */
133
    private function accessor_set(string $property, $value): void
134
    {
135
        $method = static::accessor_format(
136
            $property,
137
            HasAccessor::ACCESSOR_TYPE_SETTER
138
        );
139
140
        if ($this->has_method($method)) {
141
            $this->$method($value);
142
143
            return;
144
        }
145
146
        $method = static::accessor_format(
147
            $property,
148
            HasAccessor::ACCESSOR_TYPE_SETTER,
149
            HasAccessor::ACCESSOR_IS_LAZY
150
        );
151
152
        if ($this->has_method($method)) {
153
            $this->$property = $this->$method($value);
154
155
            return;
156
        }
157
158
        $this->assert_property_is_writable($property);
159
160
        $this->$property = $value;
161
    }
162
163
    /**
164
     * Asserts that a property is readable.
165
     *
166
     * @throws PropertyNotDefined when the property is not defined.
167
     * @throws PropertyNotReadable when the property is not accessible or is write-only
168
     * (the property is not defined and only a setter is available).
169
     */
170
    private function assert_property_is_readable(string $property): void
171
    {
172
        try {
173
            $reflexion_class = new ReflectionClass($this);
174
            $reflexion_property = $reflexion_class->getProperty($property);
175
176
            if (!$reflexion_property->isPublic()) {
177
                throw new PropertyNotReadable([$property, $this]);
178
            }
179
        } catch (ReflectionException $e) {
180
            #
181
            # An exception may occur if the property is not defined, we don't care about that.
182
            #
183
        }
184
185
        $this->assert_no_accessor(
186
            $property,
187
            HasAccessor::ACCESSOR_TYPE_SETTER,
188
            PropertyNotReadable::class
189
        );
190
191
        $properties = array_keys(get_object_vars($this));
192
193
        if ($properties) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $properties of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
194
            throw new PropertyNotDefined(
195
                sprintf(
196
                    'Unknown or inaccessible property "%s"'
197
                    . ' for object of class "%s" (available properties: %s).',
198
                    $property,
199
                    get_class($this),
200
                    implode(', ', $properties)
201
                )
202
            );
203
        }
204
205
        throw new PropertyNotDefined([$property, $this]);
206
    }
207
208
    /**
209
     * Asserts that a property is writable.
210
     *
211
     * @throws PropertyNotWritable|ReflectionException when the property doesn't exist, has no lazy getter and is
212
     * not public; or when only a getter is implemented.
213
     */
214
    private function assert_property_is_writable(string $property): void
215
    {
216
        if (
217
            property_exists($this, $property) &&
218
            !$this->has_method(
219
                static::accessor_format(
220
                    $property,
221
                    HasAccessor::ACCESSOR_TYPE_GETTER,
222
                    HasAccessor::ACCESSOR_IS_LAZY
223
                )
224
            )
225
        ) {
226
            $reflection = new ReflectionObject($this);
227
            $property_reflection = $reflection->getProperty($property);
228
229
            if (!$property_reflection->isPublic()) {
230
                throw new PropertyNotWritable([$property, $this]);
231
            }
232
233
            return;
234
        }
235
236
        $this->assert_no_accessor(
237
            $property,
238
            HasAccessor::ACCESSOR_TYPE_GETTER,
239
            PropertyNotWritable::class
240
        );
241
    }
242
243
    /**
244
     * Asserts that an accessor is not implemented.
245
     *
246
     * @param string $type One of {@link HasAccessor::ACCESSOR_TYPE_GETTER}
247
     * and {@link HasAccessor::ACCESSOR_TYPE_SETTER}.
248
     */
249
    private function assert_no_accessor(string $property, string $type, string $exception_class): void
250
    {
251
        if ($this->has_method(static::accessor_format($property, $type))) {
252
            throw new $exception_class([$property, $this]);
253
        }
254
    }
255
}
256