Completed
Push — master ( 6afe7c...34b094 )
by Woody
59:49 queued 43:17
created

ImmutableValueObjectTrait::apply()   B

Complexity

Conditions 5
Paths 10

Size

Total Lines 33
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 5
Metric Value
dl 0
loc 33
ccs 21
cts 21
cp 1
rs 8.439
cc 5
eloc 17
nc 10
nop 1
crap 5
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 15
    public function __construct(array $data = [])
15
    {
16 15
        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 13
            $this->apply($data);
18 11
        }
19 13
    }
20
21
    /**
22
     * Create a copy of the object with new values
23
     *
24
     * @param array $data
25
     *
26
     * @return static
27
     */
28 1
    public function withData(array $data)
29
    {
30 1
        $copy = clone $this;
31 1
        $copy->apply($data);
32 1
        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 2
    private function types()
49
    {
50 2
        return [];
51
    }
52
53
    /**
54
     * Expected types for object properties
55
     *
56
     *     return [
57
     *         'user' => User::class,
58
     *     ];
59
     *
60
     * @return array
61
     */
62 6
    private function expects()
63
    {
64 6
        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 7
    private function validate()
75
    {
76 7
    }
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 13
    private function apply(array $data)
94
    {
95
        // Discard any values that do not exist in this object
96 13
        $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 13
            return null !== $value;
101 13
        });
102
103 13
        $types   = array_intersect_key($this->types(), $values);
104 13
        $expects = array_intersect_key($this->expects(), $values);
105
106 13
        foreach ($types as $key => $type) {
107 12
            settype($data[$key], $type);
108 13
        }
109
110 13
        foreach ($expects as $key => $class) {
111 3
            if (false === $data[$key] instanceof $class) {
112 1
                throw new \DomainException(sprintf(
113 1
                    'Expected value of `%s` to be an object of `%s` type',
114 1
                    $key,
115
                    $class
116 1
                ));
117
            }
118 12
        }
119
120 12
        foreach ($data as $key => $value) {
121 12
            $this->$key = $value;
122 12
        }
123
124 12
        $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 11
    }
126
127
    /**
128
     * Check if the current object has a property
129
     *
130
     * @param string $key
131
     *
132
     * @return boolean
133
     */
134 2
    public function has($key)
135
    {
136 2
        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 7
        return array_map(static function ($value) {
147 7
            if ($value instanceof ArraySerializableInterface) {
148 1
                $value = $value->toArray();
149 1
            }
150 7
            return $value;
151 7
        }, 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 1
    public function __isset($key)
165
    {
166 1
        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 3
    public function __get($key)
177
    {
178 3
        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 1
    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 1
        throw new \RuntimeException(sprintf(
194 1
            'Modification of immutable object `%s` is not allowed',
195 1
            get_class($this)
196 1
        ));
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 1
    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 1
        throw new \RuntimeException(sprintf(
212 1
            'Modification of immutable object `%s` is not allowed',
213 1
            get_class($this)
214 1
        ));
215
    }
216
}
217