Passed
Branch master (018ba4)
by Mathieu
06:43
created

StorableTrait::loadFrom()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 2
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Charcoal\Source;
4
5
use RuntimeException;
6
use InvalidArgumentException;
7
8
// From 'charcoal-factory'
9
use Charcoal\Factory\FactoryInterface;
10
11
// From 'charcoal-core'
12
use Charcoal\Source\SourceInterface;
13
use Charcoal\Source\StorableInterface;
14
15
/**
16
 * Provides an object with storage interaction.
17
 *
18
 * Full implementation, as trait, of the {@see StorableInterface}
19
 *
20
 * @property-read \Psr\Log\LoggerInterface $logger The PSR-3 logger instance.
21
 */
22
trait StorableTrait
23
{
24
    /**
25
     * The object's unique identifier.
26
     *
27
     * @var mixed
28
     */
29
    protected $id;
30
31
    /**
32
     * The object's property for uniquely identifying it in storage.
33
     *
34
     * @var string
35
     */
36
    protected $key = 'id';
37
38
    /**
39
     * The datasource repository factory.
40
     *
41
     * @var FactoryInterface
42
     */
43
    protected $sourceFactory;
44
45
    /**
46
     * The object's datasource repository.
47
     *
48
     * @var SourceInterface
49
     */
50
    private $source;
51
52
    /**
53
     * Set the object's unique ID.
54
     *
55
     * The actual property set depends on `key()`.
56
     *
57
     * @param  mixed $id The object's ID.
58
     * @throws InvalidArgumentException If the argument is not scalar.
59
     * @return self
60
     */
61
    public function setId($id)
62
    {
63
        if (!is_scalar($id)) {
64
            throw new InvalidArgumentException(sprintf(
65
                'ID for "%s" must be a scalar (integer, float, string, or boolean); received %s',
66
                get_class($this),
67
                (is_object($id) ? get_class($id) : gettype($id))
68
            ));
69
        }
70
71
        $key = $this->key();
72
        if ($key === 'id') {
73
            $this->id = $id;
74
        } else {
75
            $this[$key] = $id;
76
        }
77
78
        return $this;
79
    }
80
81
    /**
82
     * Get the object's unique ID.
83
     *
84
     * The actualy property get depends on `key()`.
85
     *
86
     * @return mixed
87
     */
88
    public function id()
89
    {
90
        $key = $this->key();
91
        if ($key === 'id') {
92
            return $this->id;
93
        } else {
94
            return $this[$key];
95
        }
96
    }
97
98
    /**
99
     * Set the primary property key.
100
     *
101
     * For uniquely identifying this object in storage.
102
     *
103
     * Note: For security reason, only alphanumeric characters (and underscores)
104
     * are valid key names. Although SQL can support more, there's really no reason to.
105
     *
106
     * @param  string $key The object's primary key.
107
     * @throws InvalidArgumentException If the argument is not scalar.
108
     * @return self
109
     */
110 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...
111
    {
112
        if (!is_string($key)) {
113
            throw new InvalidArgumentException(sprintf(
114
                'Key must be a string; received %s',
115
                (is_object($key) ? get_class($key) : gettype($key))
116
            ));
117
        }
118
119
        if (!preg_match_all('/^[A-Za-z0-9_]+$/', $key)) {
120
            throw new InvalidArgumentException(
121
                sprintf('Key "%s" is invalid: must be alphanumeric / underscore.', $key)
122
            );
123
        }
124
125
        $this->key = $key;
126
        return $this;
127
    }
128
129
    /**
130
     * Get the primary property key.
131
     *
132
     * @return string
133
     */
134
    public function key()
135
    {
136
        return $this->key;
137
    }
138
139
    /**
140
     * Set the datasource repository factory.
141
     *
142
     * @param  FactoryInterface $factory The source factory.
143
     * @return self
144
     */
145
    protected function setSourceFactory(FactoryInterface $factory)
146
    {
147
        $this->sourceFactory = $factory;
148
        return $this;
149
    }
150
151
    /**
152
     * Get the datasource repository factory.
153
     *
154
     * @throws RuntimeException If the source factory was not previously set.
155
     * @return FactoryInterface
156
     */
157
    protected function sourceFactory()
158
    {
159
        if (!isset($this->sourceFactory)) {
160
            throw new RuntimeException(
161
                sprintf('Source factory is not set for "%s"', get_class($this))
162
            );
163
        }
164
        return $this->sourceFactory;
165
    }
166
167
    /**
168
     * Set the object's datasource repository.
169
     *
170
     * @todo   This method needs to be protected.
171
     * @param  SourceInterface $source The storable object's source.
172
     * @return self
173
     */
174
    public function setSource(SourceInterface $source)
175
    {
176
        $this->source = $source;
177
        return $this;
178
    }
179
180
    /**
181
     * Get the object's datasource repository.
182
     *
183
     * @return SourceInterface
184
     */
185
    public function source()
186
    {
187
        if ($this->source === null) {
188
            $this->source = $this->createSource();
189
        }
190
        return $this->source;
191
    }
192
193
    /**
194
     * Create a datasource repository for the model
195
     * (using the {@see self::$sourceFactory source factory}).
196
     *
197
     * @return SourceInterface A new repository.
198
     */
199
    abstract protected function createSource();
200
201
    /**
202
     * Load an object from the database from its ID.
203
     *
204
     * @param  mixed $id The identifier to load.
205
     * @return self
206
     */
207
    public function load($id = null)
208
    {
209
        if ($id === null) {
210
            $id = $this->id();
211
        }
212
        $this->source()->loadItem($id, $this);
0 ignored issues
show
Bug introduced by
$this of type Charcoal\Source\StorableTrait is incompatible with the type null|Charcoal\Source\StorableInterface expected by parameter $item of Charcoal\Source\SourceInterface::loadItem(). ( Ignorable by Annotation )

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

212
        $this->source()->loadItem($id, /** @scrutinizer ignore-type */ $this);
Loading history...
213
        return $this;
214
    }
215
216
    /**
217
     * Load an object from the repository from its key $key.
218
     *
219
     * @param  string $key   Key pointing a column's name.
220
     * @param  mixed  $value Value of said column.
221
     * @return self
222
     */
223
    public function loadFrom($key = null, $value = null)
224
    {
225
        $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()? ( Ignorable by Annotation )

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

225
        $this->source()->/** @scrutinizer ignore-call */ loadItemFromKey($key, $value, $this);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
226
        return $this;
227
    }
228
229
    /**
230
     * Load an object from the repository from a custom SQL query.
231
     *
232
     * @param  string $query The SQL query.
233
     * @param  array  $binds Optional. The SQL query parameters.
234
     * @return self
235
     */
236
    public function loadFromQuery($query, array $binds = [])
237
    {
238
        $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()? ( Ignorable by Annotation )

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

238
        $this->source()->/** @scrutinizer ignore-call */ loadItemFromQuery($query, $binds, $this);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
239
        return $this;
240
    }
241
242
    /**
243
     * Insert the object's current state in storage.
244
     *
245
     * @return boolean TRUE on success.
246
     */
247 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...
248
    {
249
        $pre = $this->preSave();
250
        if ($pre === false) {
251
            $this->logger->error(sprintf(
252
                'Can not save object "%s:%s"; cancelled by %s::preSave()',
253
                $this->objType(),
254
                $this->id(),
255
                get_called_class()
256
            ));
257
            return false;
258
        }
259
260
        $ret = $this->source()->saveItem($this);
0 ignored issues
show
Bug introduced by
$this of type Charcoal\Source\StorableTrait is incompatible with the type Charcoal\Source\StorableInterface expected by parameter $item of Charcoal\Source\SourceInterface::saveItem(). ( Ignorable by Annotation )

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

260
        $ret = $this->source()->saveItem(/** @scrutinizer ignore-type */ $this);
Loading history...
261
        if ($ret === false) {
262
            $this->logger->error(sprintf(
263
                'Can not save object "%s:%s"; repository failed for %s',
264
                $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? ( Ignorable by Annotation )

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

264
                $this->/** @scrutinizer ignore-call */ 
265
                       objType(),
Loading history...
265
                $this->id(),
266
                get_called_class()
267
            ));
268
            return false;
269
        } else {
270
            $this->setId($ret);
271
        }
272
273
        $post = $this->postSave();
274
        if ($post === false) {
275
            $this->logger->error(sprintf(
276
                'Saved object "%s:%s" but %s::postSave() failed',
277
                $this->objType(),
278
                $this->id(),
279
                get_called_class()
280
            ));
281
            return false;
282
        }
283
284
        return true;
285
    }
286
287
    /**
288
     * Update the object in storage with the current state.
289
     *
290
     * @param  string[] $keys If provided, only update the properties specified.
291
     * @return boolean TRUE on success.
292
     */
293 View Code Duplication
    public function update(array $keys = null)
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...
294
    {
295
        $pre = $this->preUpdate($keys);
296
        if ($pre === false) {
297
            $this->logger->error(sprintf(
298
                'Can not update object "%s:%s"; cancelled by %s::preUpdate()',
299
                $this->objType(),
300
                $this->id(),
301
                get_called_class()
302
            ));
303
            return false;
304
        }
305
306
        $ret = $this->source()->updateItem($this, $keys);
0 ignored issues
show
Bug introduced by
$this of type Charcoal\Source\StorableTrait is incompatible with the type Charcoal\Source\StorableInterface expected by parameter $item of Charcoal\Source\SourceInterface::updateItem(). ( Ignorable by Annotation )

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

306
        $ret = $this->source()->updateItem(/** @scrutinizer ignore-type */ $this, $keys);
Loading history...
307
        if ($ret === false) {
308
            $this->logger->error(sprintf(
309
                'Can not update object "%s:%s"; repository failed for %s',
310
                $this->objType(),
311
                $this->id(),
312
                get_called_class()
313
            ));
314
            return false;
315
        }
316
317
        $post = $this->postUpdate($keys);
318
        if ($post === false) {
319
            $this->logger->warning(sprintf(
320
                'Updated object "%s:%s" but %s::postUpdate() failed',
321
                $this->objType(),
322
                $this->id(),
323
                get_called_class()
324
            ));
325
            return false;
326
        }
327
328
        return true;
329
    }
330
331
    /**
332
     * Delete an object from storage.
333
     *
334
     * @return boolean TRUE on success.
335
     */
336 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...
337
    {
338
        $pre = $this->preDelete();
339
        if ($pre === false) {
340
            $this->logger->error(sprintf(
341
                'Can not delete object "%s:%s"; cancelled by %s::preDelete()',
342
                $this->objType(),
343
                $this->id(),
344
                get_called_class()
345
            ));
346
            return false;
347
        }
348
349
        $ret = $this->source()->deleteItem($this);
0 ignored issues
show
Bug introduced by
$this of type Charcoal\Source\StorableTrait is incompatible with the type null|Charcoal\Source\StorableInterface expected by parameter $item of Charcoal\Source\SourceInterface::deleteItem(). ( Ignorable by Annotation )

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

349
        $ret = $this->source()->deleteItem(/** @scrutinizer ignore-type */ $this);
Loading history...
350
        if ($ret === false) {
351
            $this->logger->error(sprintf(
352
                'Can not delete object "%s:%s"; repository failed for %s',
353
                $this->objType(),
354
                $this->id(),
355
                get_called_class()
356
            ));
357
            return false;
358
        }
359
360
        $del = $this->postDelete();
361
        if ($del === false) {
362
            $this->logger->warning(sprintf(
363
                'Deleted object "%s:%s" but %s::postDelete() failed',
364
                $this->objType(),
365
                $this->id(),
366
                get_called_class()
367
            ));
368
            return false;
369
        }
370
371
        return true;
372
    }
373
374
    /**
375
     * Event called before {@see self::save() creating} the object.
376
     *
377
     * @return boolean TRUE to proceed with creation; FALSE to stop creation.
378
     */
379
    protected function preSave()
380
    {
381
        return true;
382
    }
383
384
    /**
385
     * Event called after {@see self::save() creating} the object.
386
     *
387
     * @return boolean TRUE to indicate object was created.
388
     */
389
    protected function postSave()
390
    {
391
        return true;
392
    }
393
394
    /**
395
     * Event called before {@see self::update() updating} the object.
396
     *
397
     * @param  string[] $keys Optional list of properties to update.
398
     * @return boolean TRUE to proceed with update; FALSE to stop update.
399
     */
400
    protected function preUpdate(array $keys = null)
0 ignored issues
show
Unused Code introduced by
The parameter $keys is not used and could be removed. ( Ignorable by Annotation )

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

400
    protected function preUpdate(/** @scrutinizer ignore-unused */ array $keys = null)

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

Loading history...
401
    {
402
        return true;
403
    }
404
405
    /**
406
     * Event called after {@see self::update() updating} the object.
407
     *
408
     * @param  string[] $keys Optional list of properties to update.
409
     * @return boolean TRUE to indicate object was updated.
410
     */
411
    protected function postUpdate(array $keys = null)
0 ignored issues
show
Unused Code introduced by
The parameter $keys is not used and could be removed. ( Ignorable by Annotation )

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

411
    protected function postUpdate(/** @scrutinizer ignore-unused */ array $keys = null)

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

Loading history...
412
    {
413
        return true;
414
    }
415
416
    /**
417
     * Event called before {@see self::delete() deleting} the object.
418
     *
419
     * @return boolean TRUE to proceed with deletion; FALSE to stop deletion.
420
     */
421
    protected function preDelete()
422
    {
423
        return true;
424
    }
425
426
    /**
427
     * Event called after {@see self::delete() deleting} the object.
428
     *
429
     * @return boolean TRUE to indicate object was deleted.
430
     */
431
    protected function postDelete()
432
    {
433
        return true;
434
    }
435
}
436