Test Setup Failed
Push — master ( 18733c...444929 )
by Chauncey
02:04
created

ModelStructureProperty::hasStructureInterfaces()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
namespace Charcoal\Property;
4
5
use PDO;
6
use ArrayAccess;
7
use RuntimeException;
8
use InvalidArgumentException;
9
use UnexpectedValueException;
10
11
// From Pimple
12
use Pimple\Container;
13
14
// From 'charcoal-core'
15
use Charcoal\Model\DescribableInterface;
16
use Charcoal\Model\MetadataInterface;
17
use Charcoal\Model\ModelInterface;
18
use Charcoal\Model\Model;
19
20
// From 'charcoal-factory'
21
use Charcoal\Factory\FactoryInterface;
22
23
// From 'charcoal-property'
24
use Charcoal\Property\StructureProperty;
25
use Charcoal\Property\Structure\StructureMetadata;
26
use Charcoal\Property\Structure\StructureModel;
27
28
/**
29
 * Model Structure Data Property
30
 *
31
 * Allows for multiple complex entries to a property, which are stored
32
 * as a JSON string in the model's storage source. Typical use cases would be
33
 * {@see \Charcoal\Cms\Property\TemplateOptionsProperty template options},
34
 * {@see \Charcoal\Property\MapStructureProperty geolocation coordinates},
35
 * details for a log, or a list of addresses or people.
36
 *
37
 * The property's "structured_metadata" attribute allows one to build a virtual
38
 * model using much of the same specifications used for defining object models.
39
 * This allows you to constrain the kind of structure you need to store.
40
 * For any values that can't be bound to a model-like structure, consider using
41
 * {@see StructureProperty}.
42
 *
43
 * ## Examples
44
 *
45
 * **Example #1 — Address**
46
 *
47
 * With the use of the {@see \Charcoal\Admin\Widget\FormGroup\StructureFormGroup Structure Form Group},
48
 * a form UI can be embedded in the object form widget.
49
 *
50
 * ```json
51
 * {
52
 *     "properties": {
53
 *         "street_address": {
54
 *             "type": "string",
55
 *             "input_type": "charcoal/admin/property/input/textarea",
56
 *             "label": "Street Address"
57
 *         },
58
 *         "locality": {
59
 *             "type": "string",
60
 *             "label": "Municipality"
61
 *         },
62
 *         "administrative_area": {
63
 *             "type": "string",
64
 *             "multiple": true,
65
 *             "label": "Administrative Division(s)"
66
 *         },
67
 *         "postal_code": {
68
 *             "type": "string",
69
 *             "label": "Postal Code"
70
 *         },
71
 *         "country": {
72
 *             "type": "string",
73
 *             "label": "Country"
74
 *         }
75
 *     },
76
 *     "admin": {
77
 *         "form_group": {
78
 *             "title": "Address",
79
 *             "show_header": false,
80
 *             "properties": [
81
 *                 "street_address",
82
 *                 "locality",
83
 *                 "postal_code",
84
 *                 "administrative_area",
85
 *                 "country"
86
 *             ],
87
 *             "layout": {
88
 *                 "structure": [
89
 *                     { "columns": [ 1 ] },
90
 *                     { "columns": [ 5, 1 ] },
91
 *                     { "columns": [ 1, 1 ] }
92
 *                 ]
93
 *             }
94
 *         }
95
 *     }
96
 * }
97
 * ```
98
 */
99
class ModelStructureProperty extends StructureProperty
100
{
101
    /**
102
     * Track the state of loaded metadata for the structure.
103
     *
104
     * @var boolean
105
     */
106
    private $isStructureFinalized = false;
107
108
    /**
109
     * The metadata interfaces to use as the structure.
110
     *
111
     * These are paths (PSR-4) to import.
112
     *
113
     * @var array
114
     */
115
    private $structureInterfaces = [];
116
117
    /**
118
     * Store the property's structure.
119
     *
120
     * @var MetadataInterface|array|null
121
     */
122
    private $structureMetadata;
123
124
    /**
125
     * Store the property's "terminal" structure.
126
     *
127
     * This represents the value of "structure_metadata" key on a property definition.
128
     * This should always be merged last, after the interfaces are imported.
129
     *
130
     * @var MetadataInterface|array|null
131
     */
132
    private $terminalStructureMetadata;
133
134
    /**
135
     * Store the property's model prototype.
136
     *
137
     * @var ArrayAccess|DescribableInterface|null
138
     */
139
    private $structurePrototype;
140
141
    /**
142
     * The object type of the "structure" collection to use.
143
     *
144
     * @var string
145
     */
146
    private $structureModelType;
147
148
    /**
149
     * The class name of the "structure" collection to use.
150
     *
151
     * Must be a fully-qualified PHP namespace and an implementation of {@see ArrayAccess}.
152
     *
153
     * @var string
154
     */
155
    private $structureModelClass = StructureModel::class;
156
157
    /**
158
     * Store the factory instance.
159
     *
160
     * @var FactoryInterface
161
     */
162
    protected $structureModelFactory;
163
164
    /**
165
     * Retrieve the property's type identifier.
166
     *
167
     * @return string
168
     */
169
    public function type()
170
    {
171
        return 'model-structure';
172
    }
173
174
    /**
175
     * Retrieve the property's structure.
176
     *
177
     * @return MetadataInterface|null
178
     */
179
    public function getStructureMetadata()
180
    {
181
        if ($this->structureMetadata === null || $this->isStructureFinalized === false) {
182
            $this->structureMetadata = $this->loadStructureMetadata();
183
        }
184
185
        return $this->structureMetadata;
186
    }
187
188
    /**
189
     * Set the property's structure.
190
     *
191
     * @param  MetadataInterface|array|null $data The property's structure (fields, data).
192
     * @throws InvalidArgumentException If the structure is invalid.
193
     * @return self
194
     */
195
    public function setStructureMetadata($data)
196
    {
197
        if ($data === null) {
198
            $this->structureMetadata = $data;
199
            $this->terminalStructureMetadata = $data;
200
        } elseif (is_array($data)) {
201
            $struct = $this->createStructureMetadata();
202
            $struct->merge($data);
203
204
            $this->structureMetadata = $struct;
205
            $this->terminalStructureMetadata = $data;
206
        } elseif ($data instanceof MetadataInterface) {
207
            $this->structureMetadata = $data;
208
            $this->terminalStructureMetadata = $data;
209
        } else {
210
            throw new InvalidArgumentException(sprintf(
211
                'Structure [%s] is invalid (must be array or an instance of %s).',
212
                (is_object($data) ? get_class($data) : gettype($data)),
213
                StructureMetadata::class
214
            ));
215
        }
216
217
        $this->isStructureFinalized = false;
218
219
        return $this;
220
    }
221
222
    /**
223
     * Retrieve the metadata interfaces used by the property as a structure.
224
     *
225
     * @return array
226
     */
227
    public function getStructureInterfaces()
228
    {
229
        if (empty($this->structureInterfaces)) {
230
            return $this->structureInterfaces;
231
        }
232
233
        return array_keys($this->structureInterfaces);
234
    }
235
236
    /**
237
     * Determine if the property has any structure metadata interfaces.
238
     *
239
     * @return boolean
240
     */
241
    public function hasStructureInterfaces()
242
    {
243
        return !empty($this->structureInterfaces);
244
    }
245
246
    /**
247
     * Set the given metadata interfaces for the property to use as a structure.
248
     *
249
     * @param  array $interfaces One or more metadata interfaces to use.
250
     * @return self
251
     */
252
    public function setStructureInterfaces(array $interfaces)
253
    {
254
        $this->structureInterfaces = [];
255
256
        $this->addStructureInterfaces($interfaces);
257
258
        return $this;
259
    }
260
261
    /**
262
     * Add the given metadata interfaces for the property to use as a structure.
263
     *
264
     * @param  array $interfaces One or more metadata interfaces to use.
265
     * @return self
266
     */
267
    public function addStructureInterfaces(array $interfaces)
268
    {
269
        foreach ($interfaces as $interface) {
270
            $this->addStructureInterface($interface);
271
        }
272
273
        return $this;
274
    }
275
276
    /**
277
     * Add the given metadata interfaces for the property to use as a structure.
278
     *
279
     * @param  string $interface A metadata interface to use.
280
     * @throws InvalidArgumentException If the interface is not a string.
281
     * @return self
282
     */
283
    public function addStructureInterface($interface)
284
    {
285
        if (!is_string($interface)) {
286
            throw new InvalidArgumentException(sprintf(
287
                'Structure interface must to be a string, received %s',
288
                is_object($interface) ? get_class($interface) : gettype($interface)
289
            ));
290
        }
291
292
        if (!empty($interface)) {
293
            $interface = $this->parseStructureInterface($interface);
294
295
            $this->structureInterfaces[$interface] = true;
296
            $this->isStructureFinalized = false;
297
        }
298
299
        return $this;
300
    }
301
302
    /**
303
     * Load the property's structure.
304
     *
305
     * @return MetadataInterface
306
     */
307
    protected function loadStructureMetadata()
308
    {
309
        $structureMetadata = null;
310
311
        if ($this->isStructureFinalized === false) {
312
            $this->isStructureFinalized = true;
313
314
            if ($this->hasStructureInterfaces()) {
315
                $structureInterfaces = $this->getStructureInterfaces();
316
            } elseif ($this->hasCustomStructureModelClass()) {
317
                $structureInterfaces = (array)$this->getStructureModelType();
318
            }
319
320
            if (!empty($structureInterfaces)) {
321
                $metadataLoader = $this->metadataLoader();
322
                $metadataClass  = $this->getStructureMetadataClass();
323
324
                $structureKey = $structureInterfaces;
325
                array_unshift($structureKey, $this->ident());
326
                $structureKey = 'property/structure='.$metadataLoader->serializeMetaKey($structureKey);
327
328
                $structureMetadata = $metadataLoader->load(
0 ignored issues
show
Bug Compatibility introduced by
The expression $metadataLoader->load($s... $structureInterfaces); of type Charcoal\Model\MetadataInterface|array adds the type array to the return on line 344 which is incompatible with the return type documented by Charcoal\Property\ModelS...::loadStructureMetadata of type Charcoal\Model\MetadataInterface.
Loading history...
329
                    $structureKey,
330
                    $metadataClass,
331
                    $structureInterfaces
332
                );
333
            }
334
        }
335
336
        if ($structureMetadata === null) {
337
            $structureMetadata = $this->createStructureMetadata();
338
        }
339
340
        if ($this->terminalStructureMetadata) {
341
            $structureMetadata->merge($this->terminalStructureMetadata);
0 ignored issues
show
Bug introduced by
It seems like $this->terminalStructureMetadata can also be of type object<Charcoal\Model\MetadataInterface>; however, Charcoal\Config\ConfigInterface::merge() does only seem to accept array|object<Traversable>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
342
        }
343
344
        return $structureMetadata;
345
    }
346
347
    /**
348
     * Retrieve a singleton of the structure model for prototyping.
349
     *
350
     * @return ArrayAccess|DescribableInterface
351
     */
352
    public function structureProto()
353
    {
354
        if ($this->structurePrototype === null) {
355
            $model = $this->createStructureModel();
356
357
            if ($model instanceof DescribableInterface) {
358
                $model->setMetadata($this->getStructureMetadata());
359
            }
360
361
            $this->structurePrototype = $model;
362
        }
363
364
        return $this->structurePrototype;
365
    }
366
367
    /**
368
     * Retrieve the default data-model structure class name.
369
     *
370
     * @return string
371
     */
372
    public static function getDefaultStructureModelClass()
373
    {
374
        return StructureModel::class;
375
    }
376
377
    /**
378
     * Set the class name of the data-model structure.
379
     *
380
     * A model type (kebab-case) is converted to a FQN.
381
     *
382
     * @param  string $className The class name of the structure.
383
     * @throws InvalidArgumentException If the class name is invalid.
384
     * @return self
385
     */
386
    protected function setStructureModelClass($className)
387
    {
388
        if ($className === null) {
389
            $this->structureModelClass = static::getDefaultStructureModelClass();
390
391
            return $this;
392
        }
393
394
        if (!is_string($className)) {
395
            throw new InvalidArgumentException(
396
                'Structure class name must be a string.'
397
            );
398
        }
399
400
        if (strpos($className, '/') !== false) {
401
            try {
402
                $this->structureModelType = $className;
403
                $prototype = $this->structureModelFactory()->get($className);
404
                $className = get_class($prototype);
405
            } catch (Exception $e) {
0 ignored issues
show
Bug introduced by
The class Charcoal\Property\Exception does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
406
                throw new InvalidArgumentException(sprintf(
407
                    'Invalid structure class name: %s',
408
                    $className
409
                ), 0, $e);
410
            }
411
        }
412
413
        $this->structureModelClass = $className;
414
        $this->structureModelType  = $this->parseStructureInterface($className);
415
416
        return $this;
417
    }
418
419
    /**
420
     * Determine if the property is using a custom data-model.
421
     *
422
     * @return boolean
423
     */
424
    public function hasCustomStructureModelClass()
425
    {
426
        return $this->getStructureModelClass() !== static::getDefaultStructureModelClass();
427
    }
428
429
    /**
430
     * Retrieve the class name of the data-model structure.
431
     *
432
     * @return string
433
     */
434
    public function getStructureModelClass()
435
    {
436
        return $this->structureModelClass;
437
    }
438
439
    /**
440
     * Retrieve the class name of the data-model structure.
441
     *
442
     * @return string
443
     */
444
    public function getStructureModelType()
445
    {
446
        if ($this->structureModelType === null) {
447
            $this->structureModelType = $this->parseStructureInterface($this->getStructureModelClass());
448
        }
449
450
        return $this->structureModelType;
451
    }
452
453
    /**
454
     * Convert the given value into a structure.
455
     *
456
     * Options:
457
     * - `default_data` (_boolean_|_array_) — If TRUE, the default data defined
458
     *   in the structure's metadata is merged. If an array, that is merged.
459
     *
460
     * @param  mixed                   $val     The value to "structurize".
461
     * @param  array|MetadataInterface $options Optional structure options.
462
     * @throws InvalidArgumentException If the options are invalid.
463
     * @return ModelInterface|ModelInterface[]
464
     */
465
    public function structureVal($val, $options = [])
466
    {
467
        if ($val === null) {
468
            return ($this['multiple'] ? [] : null);
469
        }
470
471
        $metadata = clone $this->getStructureMetadata();
472
473
        if ($options instanceof MetadataInterface) {
474
            $metadata->merge($options);
0 ignored issues
show
Documentation introduced by
$options is of type object<Charcoal\Model\MetadataInterface>, but the function expects a array|object<Traversable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
475
        } elseif ($options === null) {
476
            $options = [];
477
        } elseif (is_array($options)) {
478
            if (isset($options['metadata'])) {
479
                $metadata->merge($options['metadata']);
480
            }
481
        } else {
482
            throw new InvalidArgumentException(sprintf(
483
                'Structure value options must to be an array or an instance of %2$s, received %1$s',
484
                is_object($options) ? get_class($options) : gettype($options),
485
                StructureMetadata::class
486
            ));
487
        }
488
489
        $defaultData = [];
490
        if (isset($options['default_data'])) {
491
            if (is_bool($options['default_data'])) {
492
                $withDefaultData = $options['default_data'];
493
                if ($withDefaultData) {
494
                    $defaultData = $metadata->defaultData();
495
                }
496
            } elseif (is_array($options['default_data'])) {
497
                $withDefaultData = true;
0 ignored issues
show
Unused Code introduced by
$withDefaultData is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
498
                $defaultData     = $options['default_data'];
499
            }
500
        }
501
502
        $val = $this->parseVal($val);
503
504
        if ($this['multiple']) {
505
            $entries = [];
506
            foreach ($val as $v) {
507
                $entries[] = $this->createStructureModelWith($metadata, $defaultData, $v);
508
            }
509
510
            return $entries;
511
        } else {
512
            return $this->createStructureModelWith($metadata, $defaultData, $val);
513
        }
514
    }
515
516
    /**
517
     * Retrieve the structure as a plain array.
518
     *
519
     * @return array
520
     */
521
    public function toStructure()
522
    {
523
        return $this->structureVal($this->val());
0 ignored issues
show
Deprecated Code introduced by
The method Charcoal\Property\AbstractProperty::val() has been deprecated.

This method has been deprecated.

Loading history...
524
    }
525
526
    /**
527
     * @param null|string $model Model ident.
528
     * @return ArrayAccess|DescribableInterface|mixed
529
     * @throws UnexpectedValueException If the structure is invalid.
530
     */
531 View Code Duplication
    public function toModel($model = 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...
532
    {
533
        if ($model) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $model of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
534
            $structure = $this->structureModelFactory()->create($model);
535
536
            if (!$structure instanceof ArrayAccess) {
537
                throw new UnexpectedValueException(sprintf(
538
                    'Structure [%s] must implement [%s]',
539
                    $model,
540
                    ArrayAccess::class
541
                ));
542
            }
543
544
            return $structure;
545
        }
546
547
        return $this->structureProto();
548
    }
549
550
    /**
551
     * PropertyInterface::save().
552
     * @param  mixed $val The value, at time of saving.
553
     * @return mixed
554
     */
555
    public function save($val)
556
    {
557
        $val = parent::save($val);
558
559
        if ($this['multiple']) {
560
            $proto = $this->structureProto();
561
            if ($proto instanceof ModelInterface) {
562
                $objs = (array)$this->structureVal($val);
563
                $val  = [];
564
                if (!empty($objs)) {
565
                    $val  = [];
566
                    foreach ($objs as $obj) {
567
                        $obj->saveProperties();
568
                        $val[] = $obj->data();
569
                    }
570
                }
571
            }
572
        } else {
573
            $obj = $this->structureVal($val);
574
            if ($obj instanceof ModelInterface) {
575
                $obj->saveProperties();
0 ignored issues
show
Bug introduced by
The method saveProperties() does not exist on Charcoal\Model\ModelInterface. Did you maybe mean properties()?

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...
576
                $val = $obj->data();
577
            }
578
        }
579
580
        return $val;
581
    }
582
583
    /**
584
     * Inject dependencies from a DI Container.
585
     *
586
     * @param  Container $container A dependencies container instance.
587
     * @return void
588
     */
589
    protected function setDependencies(Container $container)
590
    {
591
        parent::setDependencies($container);
592
593
        $this->setStructureModelFactory($container['model/factory']);
594
    }
595
596
    /**
597
     * Retrieve the structure model factory.
598
     *
599
     * @throws RuntimeException If the model factory was not previously set.
600
     * @return FactoryInterface
601
     */
602
    protected function structureModelFactory()
603
    {
604
        if (!isset($this->structureModelFactory)) {
605
            throw new RuntimeException(sprintf(
606
                'Model Factory is not defined for "%s"',
607
                get_class($this)
608
            ));
609
        }
610
611
        return $this->structureModelFactory;
612
    }
613
614
    /**
615
     * Set an structure model factory.
616
     *
617
     * @param FactoryInterface $factory The model factory, to create objects.
618
     * @return self
619
     */
620
    private function setStructureModelFactory(FactoryInterface $factory)
621
    {
622
        $this->structureModelFactory = $factory;
623
624
        return $this;
625
    }
626
627
    /**
628
     * Parse a metadata identifier from given interface.
629
     *
630
     * Change `\` and `.` to `/` and force lowercase
631
     *
632
     * @param  string $interface A metadata interface to convert.
633
     * @return string
634
     */
635
    protected function parseStructureInterface($interface)
636
    {
637
        $ident = preg_replace('/([a-z])([A-Z])/', '$1-$2', $interface);
638
        $ident = strtolower(str_replace('\\', '/', $ident));
639
640
        return $ident;
641
    }
642
643
    /**
644
     * Create a new metadata object for structures.
645
     *
646
     * Similar to {@see \Charcoal\Model\DescribableTrait::createMetadata()}.
647
     *
648
     * @return MetadataInterface
649
     */
650
    protected function createStructureMetadata()
651
    {
652
        $class = $this->getStructureMetadataClass();
653
        return new $class();
654
    }
655
656
    /**
657
     * Retrieve the class name of the metadata object.
658
     *
659
     * @return string
660
     */
661
    protected function getStructureMetadataClass()
662
    {
663
        return StructureMetadata::class;
664
    }
665
666
    /**
667
     * Create a data-model structure.
668
     *
669
     * @todo   Add support for simple {@see ArrayAccess} models.
670
     * @throws UnexpectedValueException If the structure is invalid.
671
     * @return ArrayAccess
672
     */
673 View Code Duplication
    private function createStructureModel()
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...
674
    {
675
        $structClass = $this->getStructureModelClass();
676
        $structure   = $this->structureModelFactory()->create($structClass);
677
678
        if (!$structure instanceof ArrayAccess) {
679
            throw new UnexpectedValueException(sprintf(
680
                'Structure [%s] must implement [%s]',
681
                $structClass,
682
                ArrayAccess::class
683
            ));
684
        }
685
686
        return $structure;
687
    }
688
689
    /**
690
     * Create a data-model structure.
691
     *
692
     * @param  MetadataInterface $metadata    The model's definition.
693
     * @param  array             ...$datasets The dataset(s) to modelize.
694
     * @throws UnexpectedValueException If the structure is invalid.
695
     * @return DescribableInterface
696
     */
697
    private function createStructureModelWith(
698
        MetadataInterface $metadata,
699
        array ...$datasets
700
    ) {
701
        $model = $this->createStructureModel();
702
        if (!$model instanceof DescribableInterface) {
703
            throw new UnexpectedValueException(sprintf(
704
                'Structure [%s] must implement [%s]',
705
                get_class($model),
706
                DescribableInterface::class
707
            ));
708
        }
709
710
        $model->setMetadata($metadata);
711
712
        if ($datasets) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $datasets 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...
713
            foreach ($datasets as $data) {
714
                $model->setData($data);
715
            }
716
        }
717
718
        return $model;
719
    }
720
}
721