Completed
Push — master ( 09494e...ad2be9 )
by Woody
17:38
created

ImmutableValueObjectTrait::apply()   B

Complexity

Conditions 5
Paths 10

Size

Total Lines 33
Code Lines 17

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 33
rs 8.439
cc 5
eloc 17
nc 10
nop 1
1
<?php
2
3
namespace Equip\Data\Traits;
4
5
use Equip\Data\ArraySerializableInterface;
6
7
trait ImmutableValueObjectTrait
8
{
9
    /**
10
     * Hydrate the object with new values
11
     *
12
     * @param array $data
13
     */
14
    public function __construct(array $data = [])
15
    {
16
        if ($data) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $data 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...
17
            $this->apply($data);
18
        }
19
    }
20
21
    /**
22
     * Create a copy of the object with new values
23
     *
24
     * @param array $data
25
     *
26
     * @return static
27
     */
28
    public function withData(array $data)
29
    {
30
        $copy = clone $this;
31
        $copy->apply($data);
32
        return $copy;
33
    }
34
35
    /**
36
     * Type definitions for object properties
37
     *
38
     *     return [
39
     *         'id'         => 'int',
40
     *         'email'      => 'string',
41
     *         'is_deleted' => 'bool',
42
     *      ];
43
     *
44
     * Overload this method to enable type coercion!
45
     *
46
     * @return array
47
     */
48
    private function types()
49
    {
50
        return [];
51
    }
52
53
    /**
54
     * Expected types for object properties
55
     *
56
     *     return [
57
     *         'user' => User::class,
58
     *     ];
59
     *
60
     * @return array
61
     */
62
    private function expects()
63
    {
64
        return [];
65
    }
66
67
    /**
68
     * Validates the current object
69
     *
70
     * @return void
71
     *
72
     * @throws \DomainException If the object is not valid
73
     */
74
    private function validate()
75
    {
76
    }
77
78
    /**
79
     * Update the current object with new values
80
     *
81
     * NOTE: Be careful not to violate immutability when using this method!
82
     *
83
     * @uses types()
84
     * @uses expects()
85
     * @uses validate()
86
     *
87
     * @param array $data
88
     *
89
     * @return void
90
     *
91
     * @throws \DomainException If a data value is not of the expected type
92
     */
93
    private function apply(array $data)
94
    {
95
        // Discard any values that do not exist in this object
96
        $data = array_intersect_key($data, get_object_vars($this));
97
98
        // Type coercion and class expectations are not run on null values
99
        $values = array_filter($data, static function ($value) {
100
            return null !== $value;
101
        });
102
103
        $types   = array_intersect_key($this->types(), $values);
104
        $expects = array_intersect_key($this->expects(), $values);
105
106
        foreach ($types as $key => $type) {
107
            settype($data[$key], $type);
108
        }
109
110
        foreach ($expects as $key => $class) {
111
            if (false === $data[$key] instanceof $class) {
112
                throw new \DomainException(sprintf(
113
                    'Expected value of `%s` to be an object of `%s` type',
114
                    $key,
115
                    $class
116
                ));
117
            }
118
        }
119
120
        foreach ($data as $key => $value) {
121
            $this->$key = $value;
122
        }
123
124
        $this->validate();
0 ignored issues
show
Unused Code introduced by
The call to the method Equip\Data\Traits\Immuta...ObjectTrait::validate() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
125
    }
126
127
    /**
128
     * Check if the current object has a property
129
     *
130
     * @param string $key
131
     *
132
     * @return boolean
133
     */
134
    public function has($key)
135
    {
136
        return property_exists($this, $key);
137
    }
138
139
    /**
140
     * Get the current object values as an array
141
     *
142
     * @return array
143
     */
144
    public function toArray()
145
    {
146
        return array_map(static function ($value) {
147
            if ($value instanceof ArraySerializableInterface) {
148
                $value = $value->toArray();
149
            }
150
            return $value;
151
        }, get_object_vars($this));
152
    }
153
154
    /**
155
     * Checks if a property is defined in the object
156
     *
157
     * This will return `false` if the value is `null`! To check if a value
158
     * exists in the object, use `has()`.
159
     *
160
     * @param string $key
161
     *
162
     * @return boolean
163
     */
164
    public function __isset($key)
165
    {
166
        return isset($this->$key);
167
    }
168
169
    /**
170
     * Allow read access to immutable object properties
171
     *
172
     * @param string $key
173
     *
174
     * @return mixed
175
     */
176
    public function __get($key)
177
    {
178
        return $this->$key;
179
    }
180
181
    /**
182
     * Protects against the object being modified
183
     *
184
     * @param string $key
185
     * @param mixed  $value
186
     *
187
     * @return void
188
     *
189
     * @throws \RuntimeException
190
     */
191
    public function __set($key, $value)
0 ignored issues
show
Unused Code introduced by
The parameter $key is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $value is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
192
    {
193
        throw new \RuntimeException(sprintf(
194
            'Modification of immutable object `%s` is not allowed',
195
            get_class($this)
196
        ));
197
    }
198
199
    /**
200
     * Protects against the object being modified
201
     *
202
     * @param string $key
203
     * @param mixed  $value
0 ignored issues
show
Bug introduced by
There is no parameter named $value. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
204
     *
205
     * @return void
206
     *
207
     * @throws \RuntimeException
208
     */
209
    public function __unset($key)
0 ignored issues
show
Unused Code introduced by
The parameter $key is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
210
    {
211
        throw new \RuntimeException(sprintf(
212
            'Modification of immutable object `%s` is not allowed',
213
            get_class($this)
214
        ));
215
    }
216
}
217