StorableTrait::sourceFactory()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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

190
        $this->source()->loadItem($id, /** @scrutinizer ignore-type */ $this);
Loading history...
191
        return $this;
192
    }
193
194
    /**
195
     * Load an object from the repository from its key $key.
196
     *
197
     * @param  string $key   Key pointing a column's name.
198
     * @param  mixed  $value Value of said column.
199
     * @return self
200
     */
201
    final public function loadFrom($key = null, $value = null)
202
    {
203
        $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

203
        $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...
204
        return $this;
205
    }
206
207
    /**
208
     * Load an object from the repository from a custom SQL query.
209
     *
210
     * @param  string $query The SQL query.
211
     * @param  array  $binds Optional. The SQL query parameters.
212
     * @return self
213
     */
214
    final public function loadFromQuery($query, array $binds = [])
215
    {
216
        $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

216
        $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...
217
        return $this;
218
    }
219
220
    /**
221
     * Insert the object's current state in storage.
222
     *
223
     * @return boolean TRUE on success.
224
     */
225
    final public function save()
226
    {
227
        $pre = $this->preSave();
228
        if ($pre === false) {
0 ignored issues
show
introduced by
The condition $pre === false is always false.
Loading history...
229
            $this->logger->error(sprintf(
230
                'Can not save object "%s:%s"; cancelled by %s::preSave()',
231
                $this->objType(),
232
                $this->id(),
233
                get_called_class()
234
            ));
235
            return false;
236
        }
237
238
        $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

238
        $ret = $this->source()->saveItem(/** @scrutinizer ignore-type */ $this);
Loading history...
239
        if ($ret === false) {
240
            $this->logger->error(sprintf(
241
                'Can not save object "%s:%s"; repository failed for %s',
242
                $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

242
                $this->/** @scrutinizer ignore-call */ 
243
                       objType(),
Loading history...
243
                $this->id(),
244
                get_called_class()
245
            ));
246
            return false;
247
        } else {
248
            $this->setId($ret);
249
        }
250
251
        $post = $this->postSave();
252
        if ($post === false) {
0 ignored issues
show
introduced by
The condition $post === false is always false.
Loading history...
253
            $this->logger->error(sprintf(
254
                'Saved object "%s:%s" but %s::postSave() failed',
255
                $this->objType(),
256
                $this->id(),
257
                get_called_class()
258
            ));
259
            return false;
260
        }
261
262
        return true;
263
    }
264
265
    /**
266
     * Update the object in storage with the current state.
267
     *
268
     * @param  string[] $keys If provided, only update the properties specified.
269
     * @return boolean TRUE on success.
270
     */
271
    final public function update(array $keys = null)
272
    {
273
        $pre = $this->preUpdate($keys);
274
        if ($pre === false) {
0 ignored issues
show
introduced by
The condition $pre === false is always false.
Loading history...
275
            $this->logger->error(sprintf(
276
                'Can not update object "%s:%s"; cancelled by %s::preUpdate()',
277
                $this->objType(),
278
                $this->id(),
279
                get_called_class()
280
            ));
281
            return false;
282
        }
283
284
        $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

284
        $ret = $this->source()->updateItem(/** @scrutinizer ignore-type */ $this, $keys);
Loading history...
285
        if ($ret === false) {
286
            $this->logger->error(sprintf(
287
                'Can not update object "%s:%s"; repository failed for %s',
288
                $this->objType(),
289
                $this->id(),
290
                get_called_class()
291
            ));
292
            return false;
293
        }
294
295
        $post = $this->postUpdate($keys);
296
        if ($post === false) {
0 ignored issues
show
introduced by
The condition $post === false is always false.
Loading history...
297
            $this->logger->warning(sprintf(
298
                'Updated object "%s:%s" but %s::postUpdate() failed',
299
                $this->objType(),
300
                $this->id(),
301
                get_called_class()
302
            ));
303
            return false;
304
        }
305
306
        return true;
307
    }
308
309
    /**
310
     * Delete an object from storage.
311
     *
312
     * @return boolean TRUE on success.
313
     */
314
    final public function delete()
315
    {
316
        $pre = $this->preDelete();
317
        if ($pre === false) {
0 ignored issues
show
introduced by
The condition $pre === false is always false.
Loading history...
318
            $this->logger->error(sprintf(
319
                'Can not delete object "%s:%s"; cancelled by %s::preDelete()',
320
                $this->objType(),
321
                $this->id(),
322
                get_called_class()
323
            ));
324
            return false;
325
        }
326
327
        $ret = $this->source()->deleteItem($this);
0 ignored issues
show
Bug introduced by
$this of type Charcoal\Source\StorableTrait is incompatible with the type Charcoal\Source\StorableInterface|null 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

327
        $ret = $this->source()->deleteItem(/** @scrutinizer ignore-type */ $this);
Loading history...
328
        if ($ret === false) {
329
            $this->logger->error(sprintf(
330
                'Can not delete object "%s:%s"; repository failed for %s',
331
                $this->objType(),
332
                $this->id(),
333
                get_called_class()
334
            ));
335
            return false;
336
        }
337
338
        $del = $this->postDelete();
339
        if ($del === false) {
0 ignored issues
show
introduced by
The condition $del === false is always false.
Loading history...
340
            $this->logger->warning(sprintf(
341
                'Deleted object "%s:%s" but %s::postDelete() failed',
342
                $this->objType(),
343
                $this->id(),
344
                get_called_class()
345
            ));
346
            return false;
347
        }
348
349
        return true;
350
    }
351
352
    /**
353
     * Set the datasource repository factory.
354
     *
355
     * @param  FactoryInterface $factory The source factory.
356
     * @return self
357
     */
358
    protected function setSourceFactory(FactoryInterface $factory)
359
    {
360
        $this->sourceFactory = $factory;
361
        return $this;
362
    }
363
364
    /**
365
     * Get the datasource repository factory.
366
     *
367
     * @throws RuntimeException If the source factory was not previously set.
368
     * @return FactoryInterface
369
     */
370
    protected function sourceFactory()
371
    {
372
        if (!isset($this->sourceFactory)) {
373
            throw new RuntimeException(
374
                sprintf('Source factory is not set for "%s"', get_class($this))
375
            );
376
        }
377
        return $this->sourceFactory;
378
    }
379
380
    /**
381
     * Event called before {@see self::save() creating} the object.
382
     *
383
     * @return boolean TRUE to proceed with creation; FALSE to stop creation.
384
     */
385
    protected function preSave()
386
    {
387
        return true;
388
    }
389
390
    /**
391
     * Event called after {@see self::save() creating} the object.
392
     *
393
     * @return boolean TRUE to indicate object was created.
394
     */
395
    protected function postSave()
396
    {
397
        return true;
398
    }
399
400
    /**
401
     * Event called before {@see self::update() updating} the object.
402
     *
403
     * @param  string[] $keys Optional list of properties to update.
404
     * @return boolean TRUE to proceed with update; FALSE to stop update.
405
     */
406
    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

406
    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...
407
    {
408
        return true;
409
    }
410
411
    /**
412
     * Event called after {@see self::update() updating} the object.
413
     *
414
     * @param  string[] $keys Optional list of properties to update.
415
     * @return boolean TRUE to indicate object was updated.
416
     */
417
    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

417
    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...
418
    {
419
        return true;
420
    }
421
422
    /**
423
     * Event called before {@see self::delete() deleting} the object.
424
     *
425
     * @return boolean TRUE to proceed with deletion; FALSE to stop deletion.
426
     */
427
    protected function preDelete()
428
    {
429
        return true;
430
    }
431
432
    /**
433
     * Event called after {@see self::delete() deleting} the object.
434
     *
435
     * @return boolean TRUE to indicate object was deleted.
436
     */
437
    protected function postDelete()
438
    {
439
        return true;
440
    }
441
}
442