Test Setup Failed
Push — master ( 40b3cc...1845a6 )
by Chauncey
02:32
created

AbstractProperty::parseVal()   B

Complexity

Conditions 9
Paths 14

Size

Total Lines 42

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 42
rs 7.6924
c 0
b 0
f 0
cc 9
nc 14
nop 1
1
<?php
2
3
namespace Charcoal\Property;
4
5
use PDO;
6
use Exception;
7
use LogicException;
8
use RuntimeException;
9
use InvalidArgumentException;
10
11
// From PSR-3
12
use Psr\Log\LoggerAwareInterface;
13
use Psr\Log\LoggerAwareTrait;
14
use Psr\Log\NullLogger;
15
16
// From Pimple
17
use Pimple\Container;
18
19
// From 'charcoal-config'
20
use Charcoal\Config\AbstractEntity;
21
22
// From 'charcoal-core'
23
use Charcoal\Model\DescribableInterface;
24
use Charcoal\Model\DescribableTrait;
25
use Charcoal\Validator\ValidatableInterface;
26
use Charcoal\Validator\ValidatableTrait;
27
use Charcoal\Validator\ValidatorInterface;
28
29
// From 'charcoal-translator'
30
use Charcoal\Translator\Translation;
31
use Charcoal\Translator\TranslatorAwareTrait;
32
33
// From 'charcoal-property'
34
use Charcoal\Property\DescribablePropertyInterface;
35
use Charcoal\Property\DescribablePropertyTrait;
36
use Charcoal\Property\PropertyInterface;
37
use Charcoal\Property\PropertyValidator;
38
use Charcoal\Property\StorablePropertyInterface;
39
use Charcoal\Property\StorablePropertyTrait;
40
41
/**
42
 * An abstract class that implements the full `PropertyInterface`.
43
 */
44
abstract class AbstractProperty extends AbstractEntity implements
45
    PropertyInterface,
46
    DescribableInterface,
47
    DescribablePropertyInterface,
48
    LoggerAwareInterface,
49
    StorablePropertyInterface,
50
    ValidatableInterface
51
{
52
    use LoggerAwareTrait;
53
    use DescribableTrait;
54
    use DescribablePropertyTrait;
55
    use StorablePropertyTrait;
56
    use TranslatorAwareTrait;
57
    use ValidatableTrait;
58
59
    const DEFAULT_L10N = false;
60
    const DEFAULT_MULTIPLE = false;
61
    const DEFAULT_HIDDEN = false;
62
    const DEFAULT_UNIQUE = false;
63
    const DEFAULT_REQUIRED = false;
64
    const DEFAULT_ALLOW_NULL = true;
65
    const DEFAULT_STORABLE = true;
66
    const DEFAULT_ACTIVE = true;
67
68
    /**
69
     * @var string
70
     */
71
    private $ident = '';
72
73
    /**
74
     * @var mixed
75
     */
76
    protected $val;
77
78
    /**
79
     * @var Translation|null
80
     */
81
    private $label;
82
83
    /**
84
     * @var boolean
85
     */
86
    private $l10n = self::DEFAULT_L10N;
87
88
    /**
89
     * @var boolean
90
     */
91
    private $multiple = self::DEFAULT_MULTIPLE;
92
93
    /**
94
     * Array of options for multiple properties
95
     * - `separator` (default=",") How the values will be separated in the storage (sql).
96
     * - `min` (default=null) The min number of values. If null, <0 or NaN, then this is not taken into consideration.
97
     * - `max` (default=null) The max number of values. If null, <0 or NaN, then there is not limit.
98
     * @var array|null
99
     */
100
    private $multipleOptions;
101
102
    /**
103
     * @var boolean
104
     */
105
    private $hidden = self::DEFAULT_HIDDEN;
106
107
    /**
108
     * If true, this property *must* have a value
109
     * @var boolean
110
     */
111
    private $required = self::DEFAULT_REQUIRED;
112
113
    /**
114
     * Unique properties should not share he same value across 2 objects
115
     * @var boolean
116
     */
117
    private $unique = self::DEFAULT_UNIQUE;
118
119
    /**
120
     * @var boolean $allowNull
121
     */
122
    private $allowNull = self::DEFAULT_ALLOW_NULL;
123
124
    /**
125
     * Only the storable properties should be saved in storage.
126
     * @var boolean
127
     */
128
    private $storable = self::DEFAULT_STORABLE;
129
130
    /**
131
     * Inactive properties should be hidden everywhere / unused
132
     * @var boolean
133
     */
134
    private $active = self::DEFAULT_ACTIVE;
135
136
    /**
137
     * @var Translation|null
138
     */
139
    private $description;
140
141
    /**
142
     * @var Translation|null
143
     */
144
    private $notes;
145
146
    /**
147
     * @var array|null
148
     */
149
    protected $viewOptions;
150
151
    /**
152
     * @var string
153
     */
154
    protected $displayType;
155
156
    /**
157
     * @var PDO
158
     */
159
    protected $pdo;
160
161
    /**
162
     * Required dependencies:
163
     * - `logger` a PSR3-compliant logger.
164
     * - `pdo` a PDO database.
165
     * - `translator` a Charcoal Translator (based on Symfony's).
166
     *
167
     * @param array $data Optional. Class Dependencies.
168
     */
169
    public function __construct(array $data = null)
170
    {
171
        $this->setLogger($data['logger']);
172
        $this->setPdo($data['database']);
173
        $this->setTranslator($data['translator']);
174
175
        // Optional DescribableInterface dependencies
176
        if (isset($data['property_factory'])) {
177
            $this->setPropertyFactory($data['property_factory']);
178
        }
179
180
        if (isset($data['metadata_loader'])) {
181
            $this->setMetadataLoader($data['metadata_loader']);
182
        }
183
184
        // DI Container can optionally be set in property constructor.
185
        if (isset($data['container'])) {
186
            $this->setDependencies($data['container']);
187
        }
188
    }
189
190
    /**
191
     * @return string
192
     * @deprecated
193
     */
194
    public function __toString()
195
    {
196
        $val = $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...
197
        if (is_string($val)) {
198
            return $val;
199
        } else {
200
            if (is_object($val)) {
201
                return (string)$val;
202
            } else {
203
                return '';
204
            }
205
        }
206
    }
207
208
    /**
209
     * Get the "property type" string.
210
     *
211
     * ## Notes
212
     * - Type can not be set, so it must be explicitely provided by each implementing property classes.
213
     *
214
     * @return string
215
     */
216
    abstract public function type();
217
218
    /**
219
     * Set the property's identifier.
220
     *
221
     * @param  string $ident The property identifier.
222
     * @throws InvalidArgumentException  If the identifier is not a string.
223
     * @return self
224
     */
225
    public function setIdent($ident)
226
    {
227
        if (!is_string($ident)) {
228
            throw new InvalidArgumentException(
229
                'Ident needs to be string.'
230
            );
231
        }
232
        $this->ident = $ident;
233
234
        return $this;
235
    }
236
237
    /**
238
     * Retrieve the property's identifier.
239
     *
240
     * @return string
241
     */
242
    public function getIdent()
243
    {
244
        return $this->ident;
245
    }
246
247
    /**
248
     * Legacy support of ident() instead of getIdent().
249
     *
250
     * @return string
251
     */
252
    public function ident()
253
    {
254
        return $this->getIdent();
255
    }
256
257
    /**
258
     * Retrieve the property's localized identifier.
259
     *
260
     * @param  string|null $lang The language code to return the identifier with.
261
     * @throws LogicException If the property is not multilingual.
262
     * @throws RuntimeException If the property has no identifier.
263
     * @throws InvalidArgumentException If the language code is invalid.
264
     * @return string
265
     */
266
    public function l10nIdent($lang = null)
267
    {
268
        if ($this->ident === '') {
269
            throw new RuntimeException('Missing Property Identifier');
270
        }
271
272
        if (!$this['l10n']) {
273
            throw new LogicException(sprintf(
274
                'Property "%s" is not multilingual',
275
                $this->ident
276
            ));
277
        }
278
279 View Code Duplication
        if ($lang === null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
280
            $lang = $this->translator()->getLocale();
281
        } elseif (!is_string($lang)) {
282
            throw new InvalidArgumentException(sprintf(
283
                'Language must be a string for Property "%s"',
284
                $this->ident
285
            ));
286
        }
287
288
        return sprintf('%1$s_%2$s', $this->ident, $lang);
289
    }
290
291
    /**
292
     * Set the property's value.
293
     *
294
     * @param  mixed $val The property (raw) value.
295
     * @return self
296
     * @deprecated
297
     */
298
    final public function setVal($val)
299
    {
300
        $this->val = $this->parseVal($val);
301
302
        return $this;
303
    }
304
305
    /**
306
     * Retrieve the property's value.
307
     *
308
     * @return mixed
309
     * @deprecated
310
     */
311
    final public function val()
312
    {
313
        return $this->val;
314
    }
315
316
    /**
317
     * Parse the given value.
318
     *
319
     * > Note: the base method (defined here) returns the current value intact.
320
     * > Other properties can reimplement this method to parse their values,
321
     * > such as {@see \Charcoal\Property\ObjectProperty::parseVal()} who could parse objects into object IDs.
322
     *
323
     * @param  mixed $val The value to be parsed (normalized).
324
     * @throws InvalidArgumentException If the value does not match property settings.
325
     * @return mixed Returns the parsed value.
326
     */
327
    final public function parseVal($val)
328
    {
329
        if ($this['allowNull']) {
330
            if ($val === null) {
331
                return null;
332
            }
333
        } elseif ($val === null) {
334
            throw new InvalidArgumentException(sprintf(
335
                'Property "%s" value can not be NULL (not allowed)',
336
                $this->ident()
337
            ));
338
        }
339
340
        if ($this['multiple']) {
341
            $val = $this->parseValAsMultiple($val);
342
343
            if (empty($val)) {
344
                if ($this['allowNull'] === false) {
345
                    throw new InvalidArgumentException(sprintf(
346
                        'Property "%s" value can not be NULL or empty (not allowed)',
347
                        $this->ident()
348
                    ));
349
                }
350
351
                return $val;
352
            }
353
354
            $val = array_map([ $this, 'parseOne' ], $val);
355
        } else {
356
            if ($this['l10n']) {
357
                $val = $this->parseValAsL10n($val);
358
359
                if ($val) {
360
                    $val->sanitize([ $this, 'parseOne' ]);
361
                }
362
            } else {
363
                $val = $this->parseOne($val);
364
            }
365
        }
366
367
        return $val;
368
    }
369
370
    /**
371
     * @param mixed $val A single value to parse.
372
     * @return mixed The parsed value.
373
     */
374
    public function parseOne($val)
375
    {
376
        return $val;
377
    }
378
379
    /**
380
     * @param   mixed $val     Optional. The value to to convert for input.
381
     * @param   array $options Optional input options.
382
     * @return  string
383
     */
384
    public function inputVal($val, array $options = [])
385
    {
386
        if ($val === null) {
387
            return '';
388
        }
389
390
        if (is_string($val)) {
391
            return $val;
392
        }
393
394
        /** Parse multilingual values */
395 View Code Duplication
        if ($this['l10n']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
396
            $propertyValue = $this->l10nVal($val, $options);
397
            if ($propertyValue === null) {
398
                return '';
399
            }
400
        } elseif ($val instanceof Translation) {
401
            $propertyValue = (string)$val;
402
        } else {
403
            $propertyValue = $val;
404
        }
405
406
        /** Parse multiple values / ensure they are of array type. */
407
        if ($this['multiple']) {
408
            if (is_array($propertyValue)) {
409
                $propertyValue = implode($this->multipleSeparator(), $propertyValue);
410
            }
411
        }
412
413
        if (!is_scalar($propertyValue)) {
414
            $propertyValue = json_encode($propertyValue, JSON_PRETTY_PRINT);
415
        }
416
417
        return (string)$propertyValue;
418
    }
419
420
    /**
421
     * @param  mixed $val     The value to to convert for display.
422
     * @param  array $options Optional display options.
423
     * @return string
424
     */
425
    public function displayVal($val, array $options = [])
426
    {
427
        if ($val === null || $val === '') {
428
            return '';
429
        }
430
431
        /** Parse multilingual values */
432 View Code Duplication
        if ($this['l10n']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
433
            $propertyValue = $this->l10nVal($val, $options);
434
            if ($propertyValue === null) {
435
                return '';
436
            }
437
        } elseif ($val instanceof Translation) {
438
            $propertyValue = (string)$val;
439
        } else {
440
            $propertyValue = $val;
441
        }
442
443
        /** Parse multiple values / ensure they are of array type. */
444
        if ($this['multiple']) {
445
            if (!is_array($propertyValue)) {
446
                $propertyValue = $this->parseValAsMultiple($propertyValue);
447
            }
448
        }
449
450
        if (is_array($propertyValue)) {
451
            $separator = $this->multipleSeparator();
452
            if ($separator === ',') {
453
                $separator = ', ';
454
            }
455
456
            $propertyValue = implode($separator, $propertyValue);
457
        }
458
459
        return (string)$propertyValue;
460
    }
461
462
463
464
    /**
465
     * @param mixed $label The property label.
466
     * @return self
467
     */
468
    public function setLabel($label)
469
    {
470
        $this->label = $this->translator()->translation($label);
471
472
        return $this;
473
    }
474
475
    /**
476
     * @return Translation
477
     */
478
    public function getLabel()
479
    {
480
        if ($this->label === null) {
481
            return ucwords(str_replace([ '.', '_' ], ' ', $this->ident()));
0 ignored issues
show
Bug Best Practice introduced by
The return type of return ucwords(str_repla... ' ', $this->ident())); (string) is incompatible with the return type documented by Charcoal\Property\AbstractProperty::getLabel of type Charcoal\Translator\Translation.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
482
        }
483
484
        return $this->label;
485
    }
486
487
    /**
488
     * @param boolean $l10n The l10n, or "translatable" flag.
489
     * @return self
490
     */
491
    public function setL10n($l10n)
492
    {
493
        $this->l10n = !!$l10n;
494
495
        return $this;
496
    }
497
498
    /**
499
     * The l10n flag sets the property as being translatable, meaning the data is held for multple languages.
500
     *
501
     * @return boolean
502
     */
503
    public function getL10n()
504
    {
505
        return $this->l10n;
506
    }
507
508
    /**
509
     * @param  mixed $val A L10N variable.
510
     * @return Translation The translation value.
511
     */
512
    public function parseValAsL10n($val)
513
    {
514
        return $this->translator()->translation($val);
515
    }
516
517
    /**
518
     * @param boolean $hidden The hidden flag.
519
     * @return self
520
     */
521
    public function setHidden($hidden)
522
    {
523
        $this->hidden = !!$hidden;
524
525
        return $this;
526
    }
527
528
    /**
529
     * @return boolean
530
     */
531
    public function getHidden()
532
    {
533
        return $this->hidden;
534
    }
535
536
    /**
537
     * Set whether this property accepts multiple values or a single value.
538
     *
539
     * @param  boolean $multiple The multiple flag.
540
     * @return self
541
     */
542
    public function setMultiple($multiple)
543
    {
544
        if (!is_bool($multiple)) {
545
            if (is_array($multiple)) {
546
                $this->setMultipleOptions($multiple);
547
            } elseif (is_int($multiple)) {
548
                $this->setMultipleOptions([
549
                    'min' => $multiple,
550
                    'max' => $multiple
551
                ]);
552
            }
553
        }
554
555
        $this->multiple = !!$multiple;
556
557
        return $this;
558
    }
559
560
    /**
561
     * Determine if this property accepts multiple values or a single value.
562
     *
563
     * The multiple flag sets the property as being "repeatable", or allow to represent an array of multiple values.
564
     *
565
     * ## Notes
566
     * - The multiple flag can be forced to false (or true) in implementing property class.
567
     * - How a multiple behaves also depend on `multipleOptions`.
568
     *
569
     * @return boolean
570
     */
571
    public function getMultiple()
572
    {
573
        return $this->multiple;
574
    }
575
576
    /**
577
     * Set the multiple options / configuration, when property is `multiple`.
578
     *
579
     * ## Options structure
580
     * - `separator` (string) The separator charactor.
581
     * - `min` (integer) The minimum number of values. (0 = no limit).
582
     * - `max` (integer) The maximum number of values. (0 = no limit).
583
     *
584
     * @param array $multipleOptions The property multiple options.
585
     * @return self
586
     */
587
    public function setMultipleOptions(array $multipleOptions)
588
    {
589
        // The options are always merged with the defaults, to ensure minimum required array structure.
590
        $options = array_merge($this->defaultMultipleOptions(), $multipleOptions);
591
        $this->multipleOptions = $options;
592
593
        return $this;
594
    }
595
596
    /**
597
     * The options defining the property behavior when the multiple flag is set to true.
598
     *
599
     * @see    self::defaultMultipleOptions
600
     * @param  string|null $key Optional setting to retrieve from the options.
601
     * @return array|mixed|null
602
     */
603
    public function getMultipleOptions($key = null)
604
    {
605
        if ($this->multipleOptions === null) {
606
            $this->multipleOptions = $this->defaultMultipleOptions();
607
        }
608
609
        if (is_string($key)) {
610
            if (isset($this->multipleOptions[$key])) {
611
                return $this->multipleOptions[$key];
612
            } else {
613
                return null;
614
            }
615
        }
616
617
        return $this->multipleOptions;
618
    }
619
620
    /**
621
     * Output the property multiple options as json.
622
     *
623
     * @return string
624
     */
625
    public function multipleOptionsAsJson()
626
    {
627
        return json_encode($this->getMultipleOptions());
628
    }
629
630
    /**
631
     * Retrieve the default settings for a multi-value property.
632
     *
633
     * @return array
634
     */
635
    public function defaultMultipleOptions()
636
    {
637
        return [
638
            'separator' => ',',
639
            'min'       => 0,
640
            'max'       => 0
641
        ];
642
    }
643
644
    /**
645
     * Retrieve the value delimiter for a multi-value property.
646
     *
647
     * @return string
648
     */
649
    public function multipleSeparator()
650
    {
651
        return $this->getMultipleOptions('separator');
652
    }
653
654
    /**
655
     * @param  mixed $val A multi-value variable.
656
     * @return array The array of values.
657
     */
658
    public function parseValAsMultiple($val)
659
    {
660
        if (is_array($val)) {
661
            return $val;
662
        }
663
664
        if ($val === null || $val === '') {
665
            return [];
666
        }
667
668
        if (!is_string($val)) {
669
            return (array)$val;
670
        }
671
672
        return explode($this->multipleSeparator(), $val);
673
    }
674
675
    /**
676
     * @param boolean $allow The property allow null flag.
677
     * @return self
678
     */
679
    public function setAllowNull($allow)
680
    {
681
        $this->allowNull = !!$allow;
682
683
        return $this;
684
    }
685
686
    /**
687
     * The allow null flag sets the property as being able to be of a "null" value.
688
     *
689
     * ## Notes
690
     * - This flag typically modifies the storage database to also allow null values.
691
     *
692
     * @return boolean
693
     */
694
    public function getAllowNull()
695
    {
696
        return $this->allowNull;
697
    }
698
699
    /**
700
     * @param boolean $required The property required flag.
701
     * @return self
702
     */
703
    public function setRequired($required)
704
    {
705
        $this->required = !!$required;
706
707
        return $this;
708
    }
709
710
    /**
711
     * Required flag sets the property as being required, meaning not allowed to be null / empty.
712
     *
713
     * ## Notes
714
     * - The actual meaning of "required" might be different for implementing property class.
715
     *
716
     * @return boolean
717
     */
718
    public function getRequired()
719
    {
720
        return $this->required;
721
    }
722
723
    /**
724
     * @param boolean $unique The property unique flag.
725
     * @return self
726
     */
727
    public function setUnique($unique)
728
    {
729
        $this->unique = !!$unique;
730
731
        return $this;
732
    }
733
734
    /**
735
     * @return boolean
736
     */
737
    public function getUnique()
738
    {
739
        return $this->unique;
740
    }
741
742
    /**
743
     * @param boolean $active The property active flag. Inactive properties should have no effects.
744
     * @return self
745
     */
746
    public function setActive($active)
747
    {
748
        $this->active = !!$active;
749
750
        return $this;
751
    }
752
753
    /**
754
     * @return boolean
755
     */
756
    public function getActive()
757
    {
758
        return $this->active;
759
    }
760
761
    /**
762
     * Legacy support of active() instead of getActive().
763
     *
764
     * @return string
765
     */
766
    public function active()
767
    {
768
        return $this->getActive();
769
    }
770
771
    /**
772
     * @param boolean $storable The storable flag.
773
     * @return self
774
     */
775
    public function setStorable($storable)
776
    {
777
        $this->storable = !!$storable;
778
779
        return $this;
780
    }
781
782
    /**
783
     * @return boolean
784
     */
785
    public function getStorable()
786
    {
787
        return $this->storable;
788
    }
789
790
    /**
791
     * @param mixed $description The property description.
792
     * @return self
793
     */
794
    public function setDescription($description)
795
    {
796
        $this->description = $this->translator()->translation($description);
797
        return $this;
798
    }
799
800
    /**
801
     * @return Translation|null
802
     */
803
    public function getDescription()
804
    {
805
        return $this->description;
806
    }
807
808
    /**
809
     * @param mixed $notes The property notes.
810
     * @return self
811
     */
812
    public function setNotes($notes)
813
    {
814
        $this->notes = $this->translator()->translation($notes);
815
        return $this;
816
    }
817
818
    /**
819
     * @return Translation|null
820
     */
821
    public function getNotes()
822
    {
823
        return $this->notes;
824
    }
825
826
    /**
827
     * The property's default validation methods.
828
     *
829
     * - `required`
830
     * - `unique`
831
     * - `allowNull`
832
     *
833
     * ## Notes
834
     * - Those 3 base validation methods should always be merged, in implementing factory class.
835
     *
836
     * @return string[]
837
     */
838
    public function validationMethods()
839
    {
840
        return [
841
            'required',
842
            'unique',
843
            'allowNull'
844
        ];
845
    }
846
847
    /**
848
     * @return boolean
849
     */
850
    public function validateRequired()
851
    {
852
        if ($this['required'] && !$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...
853
            $this->validator()->error('Value is required.', 'required');
0 ignored issues
show
Unused Code introduced by
The call to ValidatorInterface::error() has too many arguments starting with 'required'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
854
855
            return false;
856
        }
857
858
        return true;
859
    }
860
861
    /**
862
     * @return boolean
863
     */
864
    public function validateUnique()
865
    {
866
        if (!$this['unique']) {
867
            return true;
868
        }
869
870
        /** @todo Check in the model's storage if the value already exists. */
871
        return true;
872
    }
873
874
    /**
875
     * @return boolean
876
     */
877
    public function validateAllowNull()
878
    {
879
        if (!$this['allowNull'] && $this->val() === null) {
0 ignored issues
show
Deprecated Code introduced by
The method Charcoal\Property\AbstractProperty::val() has been deprecated.

This method has been deprecated.

Loading history...
880
            $this->validator()->error('Value can not be null.', 'allowNull');
0 ignored issues
show
Unused Code introduced by
The call to ValidatorInterface::error() has too many arguments starting with 'allowNull'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
881
882
            return false;
883
        }
884
885
        return true;
886
    }
887
888
889
890
    /**
891
     * @param mixed $val The value, at time of saving.
892
     * @return mixed
893
     */
894
    public function save($val)
895
    {
896
        // By default, nothing to do
897
        return $this->parseVal($val);
898
    }
899
900
    /**
901
     * @param string $type The display type.
902
     * @return self
903
     */
904
    public function setDisplayType($type)
905
    {
906
        $this->displayType = $type;
907
908
        return $this;
909
    }
910
911
    /**
912
     * @return string
913
     */
914
    public function getDisplayType()
915
    {
916
        if (!$this->displayType) {
917
            $meta = $this->metadata();
918
919
            // This default would be defined in type-property.json (@see charcoal-property/metadata)
920
            if (isset($meta['admin']) && isset($meta['admin']['display_type'])) {
921
                $default = $meta['admin']['display_type'];
922
            } else {
923
                $default = 'charcoal/admin/property/display/text';
924
            }
925
            $this->setDisplayType($default);
926
        }
927
928
        return $this->displayType;
929
    }
930
931
    /**
932
     * View options.
933
     * @param string $ident The display ident (ex: charcoal/admin/property/display/text).
934
     * @return array Should ALWAYS be an array.
935
     */
936
    final public function viewOptions($ident = null)
937
    {
938
        // No options defined
939
        if (!$this->viewOptions) {
940
            return [];
941
        }
942
943
        // No ident defined
944
        if (!$ident) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $ident of type string|null is loosely compared to false; 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...
945
            return $this->viewOptions;
946
        }
947
948
        // Invalid ident
949
        if (!isset($this->viewOptions[$ident])) {
950
            return [];
951
        }
952
953
        // Success!
954
        return $this->viewOptions[$ident];
955
    }
956
957
    /**
958
     * Set view options for both display and input
959
     *
960
     * @param array $viewOpts View options.
961
     * @return self
962
     */
963
    final public function setViewOptions(array $viewOpts = [])
964
    {
965
        $this->viewOptions = $viewOpts;
966
967
        return $this;
968
    }
969
970
    /**
971
     * @param Container $container A Pimple DI container.
972
     * @return void
973
     */
974
    protected function setDependencies(Container $container)
975
    {
976
        $this->setPropertyFactory($container['property/factory']);
977
        $this->setMetadataLoader($container['metadata/loader']);
978
    }
979
980
    /**
981
     * Attempt to get the multilingual value in the requested language.
982
     *
983
     * @param  mixed $val  The multilingual value to lookup.
984
     * @param  mixed $lang The language to return the value in.
985
     * @return string|null
986
     */
987
    protected function l10nVal($val, $lang = null)
988
    {
989
        if (!is_string($lang)) {
990
            if (is_array($lang) && isset($lang['lang'])) {
991
                $lang = $lang['lang'];
992
            } else {
993
                $lang = $this->translator()->getLocale();
994
            }
995
        }
996
997
        if (isset($val[$lang])) {
998
            return $val[$lang];
999
        } else {
1000
            return null;
1001
        }
1002
    }
1003
1004
    /**
1005
     * Create a new metadata object.
1006
     *
1007
     * @param  array $data Optional metadata to merge on the object.
1008
     * @see DescribableTrait::createMetadata()
1009
     * @return PropertyMetadata
1010
     */
1011
    protected function createMetadata(array $data = null)
1012
    {
1013
        $class = $this->metadataClass();
1014
        return new $class($data);
1015
    }
1016
1017
    /**
1018
     * Retrieve the class name of the metadata object.
1019
     *
1020
     * @see DescribableTrait::metadataClass()
1021
     * @return string
1022
     */
1023
    protected function metadataClass()
1024
    {
1025
        return PropertyMetadata::class;
1026
    }
1027
1028
    /**
1029
     * Create a Validator object
1030
     *
1031
     * @see ValidatableTrait::createValidator()
1032
     * @return ValidatorInterface
1033
     */
1034
    protected function createValidator()
1035
    {
1036
        $validator = new PropertyValidator($this);
1037
1038
        return $validator;
1039
    }
1040
1041
    /**
1042
     * @param PDO $pdo The database connection (PDO) instance.
1043
     * @return void
1044
     */
1045
    private function setPdo(PDO $pdo)
1046
    {
1047
        $this->pdo = $pdo;
1048
    }
1049
}
1050