Test Setup Failed
Push — master ( 4762d2...40d590 )
by Mathieu
03:03
created

StorableTrait::update()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 31
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 31
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 23
nc 4
nop 1
1
<?php
2
3
namespace Charcoal\Source;
4
5
use \Exception;
6
use \InvalidArgumentException;
7
8
// Module `charcoal-factory` dependencies
9
use \Charcoal\Factory\FactoryInterface;
10
11
// Local namespace dependencies
12
use \Charcoal\Source\SourceInterface;
13
14
/**
15
 * Full implementation, as trait, of the StorableInterface
16
 */
17
trait StorableTrait
18
{
19
    /**
20
     * @var mixed $id The object (unique) identifier
21
     */
22
    protected $id;
23
24
    /**
25
     * @var string $key The object key
26
     */
27
    protected $key = 'id';
28
29
    /**
30
     * @var FactoryInterface $sourceFactory
31
     */
32
    protected $sourceFactory;
33
34
    /**
35
     * @var SourceInterface $source
36
     */
37
    private $source;
38
39
    /**
40
     * Set the object's ID. The actual property set depends on `key()`
41
     *
42
     * @param mixed $id The object id (identifier / primary key value).
43
     * @throws InvalidArgumentException If the argument is not scalar.
44
     * @return StorableInterface Chainable
45
     */
46
    public function setId($id)
47
    {
48
        if (!is_scalar($id)) {
49
            throw new InvalidArgumentException(
50
                sprintf(
51
                    'ID for "%s" must be a scalar (integer, float, string, or boolean); received %s',
52
                    get_class($this),
53
                    (is_object($id) ? get_class($id) : gettype($id))
54
                )
55
            );
56
        }
57
58
        $key = $this->key();
59
        if ($key == 'id') {
60
            $this->id = $id;
61
        } else {
62
            $this[$key] = $id;
63
        }
64
65
        return $this;
66
    }
67
68
    /**
69
     * Get the object's (unique) ID. The actualy property get depends on `key()`
70
     *
71
     * @throws Exception If the set key is invalid.
72
     * @return mixed
73
     */
74
    public function id()
75
    {
76
        $key = $this->key();
77
        if ($key == 'id') {
78
            return $this->id;
79
        } else {
80
            return $this[$key];
81
        }
82
    }
83
84
    /**
85
     * Set the key property.
86
     *
87
     * @param string $key The object key, or identifier "name".
88
     * @throws InvalidArgumentException If the argument is not scalar.
89
     * @return StorableInterface Chainable
90
     */
91 View Code Duplication
    public function setKey($key)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
92
    {
93
        if (!is_string($key)) {
94
            throw new InvalidArgumentException(
95
                sprintf(
96
                    'Key must be a string; received %s',
97
                    (is_object($key) ? get_class($key) : gettype($key))
98
                )
99
            );
100
        }
101
        // For security reason, only alphanumeric characters (+ underscores) are valid key names.
102
        // Although SQL can support more, there's really no reason to.
103
        if (!preg_match_all('/^[A-Za-z0-9_]+$/', $key)) {
104
            throw new InvalidArgumentException(
105
                sprintf('Key "%s" is invalid: must be alphanumeric / underscore.', $key)
106
            );
107
        }
108
        $this->key = $key;
109
110
        return $this;
111
    }
112
113
    /**
114
     * Get the key property.
115
     *
116
     * @return string
117
     */
118
    public function key()
119
    {
120
        return $this->key;
121
    }
122
123
    /**
124
     * @param FactoryInterface $factory The source factory, which is useful to create source.
125
     * @return StorableInterface Chainable
126
     */
127
    protected function setSourceFactory(FactoryInterface $factory)
128
    {
129
        $this->sourceFactory = $factory;
130
        return $this;
131
    }
132
133
    /**
134
     * @throws Exception If the source factory was not previously set.
135
     * @return FactoryInterface
136
     */
137
    protected function sourceFactory()
138
    {
139
        if (!isset($this->sourceFactory)) {
140
            throw new Exception(
141
                sprintf('Source factory is not set for "%s"', get_class($this))
142
            );
143
        }
144
        return $this->sourceFactory;
145
    }
146
147
    /**
148
     * Set the object's source.
149
     *
150
     * @param SourceInterface $source The storable object's source.
151
     * @return StorableInterface Chainable
152
     * @todo This method needs to go protected.
153
     */
154
    public function setSource(SourceInterface $source)
155
    {
156
        $this->source = $source;
157
        return $this;
158
    }
159
160
    /**
161
     * Get the object's source.
162
     *
163
     * @return SourceInterface
164
     */
165
    public function source()
166
    {
167
        if ($this->source === null) {
168
            $this->source = $this->createSource();
169
        }
170
        return $this->source;
171
    }
172
173
    /**
174
     * Create the model's source, with the Source Factory.
175
     *
176
     * @return SourceInterface
177
     */
178
    abstract protected function createSource();
179
180
    /**
181
     * Load an object from the database from its ID.
182
     *
183
     * @param mixed $id The identifier to load.
184
     * @return StorableInterface Chainable
185
     */
186
    public function load($id = null)
187
    {
188
        if ($id === null) {
189
            $id = $this->id();
190
        }
191
        $this->source()->loadItem($id, $this);
192
        return $this;
193
    }
194
195
    /**
196
     * Load an object from the database from its key $key.
197
     *
198
     * @param string $key   Key pointing a column's name.
199
     * @param mixed  $value Value of said column.
200
     * @return StorableInterface Chainable.
201
     */
202
    public function loadFrom($key = null, $value = null)
203
    {
204
        $this->source()->loadItemFromKey($key, $value, $this);
0 ignored issues
show
Bug introduced by
The method loadItemFromKey() does not exist on Charcoal\Source\SourceInterface. Did you maybe mean loadItem()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
205
        return $this;
206
    }
207
208
    /**
209
     * Load an object from the database from a custom SQL query.
210
     *
211
     * @param string $query The SQL query.
212
     * @param array  $binds Optional. The SQL query parameters.
213
     * @return StorableInterface Chainable.
214
     */
215
    public function loadFromQuery($query, array $binds = [])
216
    {
217
        $this->source()->loadItemFromQuery($query, $binds, $this);
0 ignored issues
show
Bug introduced by
The method loadItemFromQuery() does not exist on Charcoal\Source\SourceInterface. Did you maybe mean loadItem()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
218
        return $this;
219
    }
220
221
    /**
222
     * Save an object current state to storage
223
     *
224
     * @return boolean
225
     */
226 View Code Duplication
    public function save()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
227
    {
228
        $pre = $this->preSave();
229
        if ($pre === false) {
230
            $this->logger()->error(sprintf(
0 ignored issues
show
Bug introduced by
It seems like logger() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
231
                'Can not save object (%s:%s). The preSave() method failed.',
232
                $this->objType(),
0 ignored issues
show
Bug introduced by
It seems like objType() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
233
                $this->id()
234
            ));
235
            return false;
236
        }
237
        $ret = $this->source()->saveItem($this);
238
        if ($ret === false) {
239
            $this->logger()->error(sprintf(
0 ignored issues
show
Bug introduced by
It seems like logger() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
240
                'Can not save object (%s:%s). The source\'s saveItem() method failed.',
241
                $this->objType(),
0 ignored issues
show
Bug introduced by
It seems like objType() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
242
                $this->id()
243
            ));
244
            return false;
245
        }
246
        $post = $this->postSave();
247
        if ($post === false) {
248
            $this->logger()->warning(sprintf(
0 ignored issues
show
Bug introduced by
It seems like logger() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
249
                'Although the object (%s:%s) was saved, the postSave() method had an error. Expect problems later.',
250
                $this->objType(),
0 ignored issues
show
Bug introduced by
It seems like objType() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
251
                $this->id()
252
            ));
253
            return false;
254
        }
255
        return true;
256
    }
257
258
    /**
259
     * Update the object in storage to the current object state.
260
     *
261
     * @param array $properties If set, only update the properties specified in this array.
262
     * @return boolean
263
     */
264
    public function update(array $properties = null)
265
    {
266
        $pre = $this->preUpdate($properties);
0 ignored issues
show
Bug introduced by
It seems like $properties defined by parameter $properties on line 264 can also be of type array; however, Charcoal\Source\StorableTrait::preUpdate() does only seem to accept null|array<integer,string>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
267
        if ($pre === false) {
268
            $this->logger()->error(sprintf(
0 ignored issues
show
Bug introduced by
It seems like logger() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
269
                'Can not update object (%s:%s). The preUpdate() method failed.',
270
                $this->objType(),
0 ignored issues
show
Bug introduced by
It seems like objType() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
271
                $this->id()
272
            ));
273
            return false;
274
        }
275
        $ret = $this->source()->updateItem($this, $properties);
276
        if ($ret === false) {
277
            $this->logger()->error(sprintf(
0 ignored issues
show
Bug introduced by
It seems like logger() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
278
                'Can not update object (%s:%s). The source\'s updateItem() method failed.',
279
                $this->objType(),
0 ignored issues
show
Bug introduced by
It seems like objType() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
280
                $this->id()
281
            ));
282
            return false;
283
        }
284
        $post = $this->postUpdate($properties);
0 ignored issues
show
Bug introduced by
It seems like $properties defined by parameter $properties on line 264 can also be of type array; however, Charcoal\Source\StorableTrait::postUpdate() does only seem to accept null|array<integer,string>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
285
        if ($post === false) {
286
            $this->logger()->warning(sprintf(
0 ignored issues
show
Bug introduced by
It seems like logger() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
287
                'Although the object (%s:%s) was updated, the postUpdate() method had an error. Expect problems later.',
288
                $this->objType(),
0 ignored issues
show
Bug introduced by
It seems like objType() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
289
                $this->id()
290
            ));
291
            return false;
292
        }
293
        return true;
294
    }
295
296
    /**
297
     * Delete an object from storage.
298
     *
299
     * @return boolean
300
     */
301 View Code Duplication
    public function delete()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
302
    {
303
        $pre = $this->preDelete();
304
        if ($pre === false) {
305
            $this->logger()->error(sprintf(
0 ignored issues
show
Bug introduced by
It seems like logger() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
306
                'Can not update object (%s:%s). The preUpdate() method failed.',
307
                $this->objType(),
0 ignored issues
show
Bug introduced by
It seems like objType() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
308
                $this->id()
309
            ));
310
            return false;
311
        }
312
        $ret = $this->source()->deleteItem($this);
313
        if ($ret === false) {
314
            $this->logger()->error(sprintf(
0 ignored issues
show
Bug introduced by
It seems like logger() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
315
                'Can not delete object (%s:%s). The source\'s deleteItem() method failed.',
316
                $this->objType(),
0 ignored issues
show
Bug introduced by
It seems like objType() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
317
                $this->id()
318
            ));
319
            return false;
320
        }
321
        $del = $this->postDelete();
322
        if ($del === false) {
323
            $this->logger()->warning(sprintf(
0 ignored issues
show
Bug introduced by
It seems like logger() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
324
                'Although the object (%s:%s) was deleted, the postDelete() method had an error. Expect problems later.',
325
                $this->objType(),
0 ignored issues
show
Bug introduced by
It seems like objType() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
326
                $this->id()
327
            ));
328
            return false;
329
        }
330
        return true;
331
    }
332
333
    /**
334
     * @return boolean
335
     */
336
    protected function preSave()
337
    {
338
        return true;
339
    }
340
341
    /**
342
     * @return boolean
343
     */
344
    protected function postSave()
345
    {
346
        return true;
347
    }
348
349
    /**
350
     * @param string[] $keys Optional. The list of keys to update.
351
     * @return boolean
352
     */
353
    protected function preUpdate(array $keys = null)
0 ignored issues
show
Unused Code introduced by
The parameter $keys 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...
354
    {
355
        return true;
356
    }
357
358
    /**
359
     * @param string[] $keys Optional. The list of keys to update.
360
     * @return boolean
361
     */
362
    protected function postUpdate(array $keys = null)
0 ignored issues
show
Unused Code introduced by
The parameter $keys 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...
363
    {
364
        return true;
365
    }
366
367
    /**
368
     * @return boolean
369
     */
370
    protected function preDelete()
371
    {
372
        return true;
373
    }
374
375
    /**
376
     * @return boolean
377
     */
378
    protected function postDelete()
379
    {
380
        return true;
381
    }
382
}
383