renderTranslatableTemplate()   B
last analyzed

Complexity

Conditions 10
Paths 12

Size

Total Lines 30
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 20
nc 12
nop 1
dl 0
loc 30
rs 7.6666
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Charcoal\Admin\Property;
4
5
use Traversable;
6
use UnexpectedValueException;
7
use InvalidArgumentException;
8
9
// From PSR-3
10
use Psr\Log\LoggerAwareInterface;
11
use Psr\Log\LoggerAwareTrait;
12
use Psr\Log\NullLogger;
13
14
// From Pimple
15
use Pimple\Container;
16
17
// From 'charcoal-core'
18
use Charcoal\Model\DescribableInterface;
19
use Charcoal\Model\DescribableTrait;
20
21
// From 'charcoal-view'
22
use Charcoal\View\ViewableInterface;
23
use Charcoal\View\ViewableTrait;
24
25
// From 'charcoal-translator'
26
use Charcoal\Translator\Translation;
27
use Charcoal\Translator\TranslatorAwareTrait;
28
29
// From 'charcoal-property'
30
use Charcoal\Property\PropertyInterface;
31
use Charcoal\Property\PropertyMetadata;
32
33
// From 'charcoal-admin'
34
use Charcoal\Admin\Property\PropertyInputInterface;
35
36
/**
37
 *
38
 */
39
abstract class AbstractPropertyInput implements
40
    DescribableInterface,
41
    PropertyInputInterface,
42
    LoggerAwareInterface,
43
    ViewableInterface
44
{
45
    use DescribableTrait;
46
    use LoggerAwareTrait;
47
    use TranslatorAwareTrait;
48
    use ViewableTrait;
49
50
    const DEFAULT_INPUT_TYPE = 'text';
51
52
    /**
53
     * @var string $lang
54
     */
55
    private $lang;
56
57
    /**
58
     * @var string $ident
59
     */
60
    private $ident;
61
62
    /**
63
     * @var boolean $readOnly
64
     */
65
    private $readOnly;
66
67
    /**
68
     * @var boolean $required
69
     */
70
    private $required;
71
72
    /**
73
     * @var boolean $disabled
74
     */
75
    private $disabled;
76
77
    /**
78
     * @var boolean $multiple
79
     */
80
    private $multiple;
81
82
    /**
83
     * The control type for the HTML element `<input>`.
84
     *
85
     * @var string $type
86
     */
87
    protected $type;
88
89
    /**
90
     * @var string $inputType
91
     */
92
    protected $inputType;
93
94
    /**
95
     * @var string $inputMode
96
     */
97
    protected $inputMode;
98
99
    /**
100
     * @var string $inputName
101
     */
102
    protected $inputName;
103
104
    /**
105
     * @var string $inputId
106
     */
107
    protected $inputId;
108
109
    /**
110
     * @var string $inputClass
111
     */
112
    protected $inputClass = '';
113
114
    /**
115
     * @var Translation|string|null $placeholder
116
     */
117
    private $placeholder;
118
119
    /**
120
     * The control's prefix.
121
     *
122
     * @var Translation|string|null
123
     */
124
    protected $inputPrefix;
125
126
    /**
127
     * The control's suffix.
128
     *
129
     * @var Translation|string|null
130
     */
131
    protected $inputSuffix;
132
133
    /**
134
     * @var array $propertyData
135
     */
136
    private $propertyData = [];
137
138
    /**
139
     * @var mixed $propertyVal
140
     */
141
    private $propertyVal;
142
143
    /**
144
     * @var PropertyInterface $property
145
     */
146
    private $property;
147
148
    /**
149
     * @param array|\ArrayAccess $data Constructor data.
150
     */
151
    public function __construct($data = null)
152
    {
153
        if (!isset($data['logger'])) {
154
            $data['logger'] = new NullLogger();
155
        }
156
        $this->setLogger($data['logger']);
157
158
        if (isset($data['metadata_loader'])) {
159
            $this->setMetadataLoader($data['metadata_loader']);
160
        }
161
162
        // DI Container can optionally be set in property constructor.
163
        if (isset($data['container'])) {
164
            $this->setDependencies($data['container']);
165
        }
166
    }
167
168
    /**
169
     * This function takes an array and fill the model object with its value.
170
     *
171
     * This method either calls a setter for each key (`set_{$key}()`) or sets a public member.
172
     *
173
     * For example, calling with `setData(['properties'=>$properties])` would call
174
     * `setProperties($properties)`, becasue `setProperties()` exists.
175
     *
176
     * But calling with `setData(['foobar'=>$foo])` would set the `$foobar` member
177
     * on the metadata object, because the method `set_foobar()` does not exist.
178
     *
179
     * @param array $data The input data.
180
     * @return self
181
     */
182
    public function setData(array $data)
183
    {
184
        foreach ($data as $prop => $val) {
185
            $func = [$this, $this->setter($prop)];
186
            if (is_callable($func)) {
187
                call_user_func($func, $val);
188
                unset($data[$prop]);
189
            } else {
190
                $this->{$prop} = $val;
191
            }
192
        }
193
194
        $this->propertyData = $data;
195
196
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type Charcoal\Admin\Property\AbstractPropertyInput which is incompatible with the return type mandated by Charcoal\Admin\Property\...putInterface::setData() of Charcoal\Admin\Property\Input.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
197
    }
198
199
    /**
200
     * Retrieve the control's data options for JavaScript components.
201
     *
202
     * @return array
203
     */
204
    public function controlDataForJs()
205
    {
206
        return [];
207
    }
208
209
    /**
210
     * Retrieve the control's {@see self::controlDataForJs() options} as a JSON string.
211
     *
212
     * @return string Returns data serialized with {@see json_encode()}.
213
     */
214
    public function controlDataForJsAsJson()
215
    {
216
        return json_encode($this->controlDataForJs(), JSON_UNESCAPED_UNICODE);
217
    }
218
219
    /**
220
     * Retrieve the control's {@see self::controlDataForJs() options} as a JSON string, protected from Mustache.
221
     *
222
     * @return string Returns a stringified JSON object, protected from Mustache rendering.
223
     */
224
    public function escapedControlDataForJsAsJson()
225
    {
226
        return '{{=<% %>=}}'.$this->controlDataForJsAsJson().'<%={{ }}=%>';
227
    }
228
229
    /**
230
     * @param mixed $val The property value.
231
     * @return PropertyInputInterface Chainable
232
     */
233
    public function setPropertyVal($val)
234
    {
235
        $this->propertyVal = $val;
236
        return $this;
237
    }
238
239
    /**
240
     * @return mixed
241
     */
242
    public function propertyVal()
243
    {
244
        return $this->propertyVal;
245
    }
246
247
    /**
248
     * @param string $lang The language code / ident.
249
     * @return PropertyInputInterface Chainable
250
     */
251
    public function setLang($lang)
252
    {
253
        $this->lang = $lang;
254
        return $this;
255
    }
256
257
    /**
258
     * Get the input language
259
     * @return string
260
     */
261
    public function lang()
262
    {
263
        if ($this->lang === null) {
264
            return $this->translator()->getLocale();
265
        }
266
267
        return $this->lang;
268
    }
269
270
    /**
271
     * @return boolean
272
     */
273
    public function hidden()
274
    {
275
        if ($this->p()->l10n()) {
276
            if ($this->lang() != $this->translator()->getLocale()) {
277
                return true;
278
            }
279
        }
280
281
        return false;
282
    }
283
284
    /**
285
     * @param string $ident Input identifier.
286
     * @throws InvalidArgumentException If the ident is not a string.
287
     * @return self
288
     */
289
    public function setIdent($ident)
290
    {
291
        if (!is_string($ident)) {
0 ignored issues
show
introduced by
The condition is_string($ident) is always true.
Loading history...
292
            throw new InvalidArgumentException(
293
                'Property Input identifier must be string'
294
            );
295
        }
296
        $this->ident = $ident;
297
        return $this;
298
    }
299
300
    /**
301
     * @return string
302
     */
303
    public function ident()
304
    {
305
        return $this->ident;
306
    }
307
308
    /**
309
     * @param boolean $readOnly The read-only flag.
310
     * @return self
311
     */
312
    public function setReadOnly($readOnly)
313
    {
314
        $this->readOnly = !!$readOnly;
315
        return $this;
316
    }
317
318
    /**
319
     * @return boolean
320
     */
321
    public function readOnly()
322
    {
323
        return $this->readOnly;
324
    }
325
326
    /**
327
     * @param boolean $required Required flag.
328
     * @return self
329
     */
330
    public function setRequired($required)
331
    {
332
        $this->required = !!$required;
333
        return $this;
334
    }
335
336
    /**
337
     * @return boolean
338
     */
339
    public function required()
340
    {
341
        return $this->required;
342
    }
343
344
    /**
345
     * @param boolean $disabled Disabled flag.
346
     * @return self
347
     */
348
    public function setDisabled($disabled)
349
    {
350
        $this->disabled = !!$disabled;
351
        return $this;
352
    }
353
354
    /**
355
     * @return boolean
356
     */
357
    public function disabled()
358
    {
359
        return $this->disabled;
360
    }
361
362
    /**
363
     * @param boolean $multiple Multiple flag.
364
     * @return self
365
     */
366
    public function setMultiple($multiple)
367
    {
368
        $this->multiple = !!$multiple;
369
        return $this;
370
    }
371
372
    /**
373
     * @return boolean
374
     */
375
    public function multiple()
376
    {
377
        return $this->multiple;
378
    }
379
380
    /**
381
     * Set the form control's placeholder.
382
     *
383
     * A placeholder is a hint to the user of what can be entered
384
     * in the property control.
385
     *
386
     * @param  mixed $placeholder The placeholder attribute.
387
     * @return self
388
     */
389
    public function setPlaceholder($placeholder)
390
    {
391
        if ($placeholder === null || $placeholder === '') {
392
            $this->placeholder = '';
393
            return $this;
394
        }
395
396
        $this->placeholder = $this->translator()->translation($placeholder);
397
        if ($this->placeholder instanceof Translation) {
398
            $this->placeholder->isRendered = false;
0 ignored issues
show
Bug introduced by
The property isRendered does not seem to exist on Charcoal\Translator\Translation.
Loading history...
399
        } else {
400
            $this->placeholder = '';
401
        }
402
403
        return $this;
404
    }
405
406
    /**
407
     * Retrieve the placeholder.
408
     *
409
     * @return Translation|string|null
410
     */
411
    public function placeholder()
412
    {
413
        if ($this->placeholder === null) {
414
            $metadata = $this->metadata();
415
416
            if (isset($metadata['data']['placeholder'])) {
417
                $this->setPlaceholder($metadata['data']['placeholder']);
418
            } else {
419
                $this->placeholder = '';
420
            }
421
        }
422
423
        if ($this->placeholder instanceof Translation) {
424
            if (isset($this->placeholder->isRendered) && $this->placeholder->isRendered === false) {
425
                $this->placeholder = $this->renderTranslatableTemplate($this->placeholder);
426
            }
427
428
            if ($this->lang()) {
429
                return $this->placeholder[$this->lang()];
430
            }
431
        }
432
433
        return $this->placeholder;
434
    }
435
436
    /**
437
     * Set the input ID.
438
     *
439
     * Used for the HTML "ID" attribute.
440
     *
441
     * @param  string $inputId HTML input id attribute.
442
     * @return self
443
     */
444
    public function setInputId($inputId)
445
    {
446
        $this->inputId = $inputId;
447
448
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type Charcoal\Admin\Property\AbstractPropertyInput which is incompatible with the return type mandated by Charcoal\Admin\Property\...Interface::setInputId() of Charcoal\Admin\Property\Input.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
449
    }
450
451
    /**
452
     * Retrieve the input ID.
453
     *
454
     * If none was previously set then a unique random one will be generated.
455
     *
456
     * @return string
457
     */
458
    public function inputId()
459
    {
460
        if (!$this->inputId) {
461
            $this->inputId = $this->generateInputId();
462
        }
463
464
        return $this->inputId;
465
    }
466
467
    /**
468
     * @param string $inputClass The input class attribute.
469
     * @throws InvalidArgumentException If the class is not a string.
470
     * @return self
471
     */
472
    public function setInputClass($inputClass)
473
    {
474
        if (!is_string($inputClass)) {
0 ignored issues
show
introduced by
The condition is_string($inputClass) is always true.
Loading history...
475
            throw new InvalidArgumentException('CSS Class(es) must be a string');
476
        }
477
        $this->inputClass = $inputClass;
478
        return $this;
479
    }
480
481
    /**
482
     * @return string
483
     */
484
    public function inputClass()
485
    {
486
        return $this->inputClass;
487
    }
488
489
    /**
490
     * Set the input name.
491
     *
492
     * Used for the HTML "name" attribute.
493
     *
494
     * @param  string $inputName HTML input id attribute.
495
     * @return self
496
     */
497
    public function setInputName($inputName)
498
    {
499
        $this->inputName = $inputName;
500
501
        return $this;
502
    }
503
504
    /**
505
     * Retrieve the input name.
506
     *
507
     * The input name should always be the property's ident.
508
     *
509
     * @return string
510
     */
511
    public function inputName()
512
    {
513
        if ($this->inputName) {
514
            $name = $this->inputName;
515
        } else {
516
            $name = $this->propertyIdent();
517
        }
518
519
        if ($this->p()->l10n()) {
520
            $name .= '['.$this->lang().']';
521
        }
522
523
        if ($this->multiple()) {
524
            $name .= '[]';
525
        }
526
527
        return $name;
528
    }
529
530
    /**
531
     * @uses   AbstractProperty::inputVal() Must handle string sanitization of value.
532
     * @throws UnexpectedValueException If the value is invalid.
533
     * @return string
534
     */
535
    public function inputVal()
536
    {
537
        $prop = $this->p();
538
        $val  = $prop->inputVal($this->propertyVal(), [
0 ignored issues
show
Unused Code introduced by
The call to Charcoal\Property\PropertyInterface::inputVal() has too many arguments starting with array('lang' => $this->lang()). ( Ignorable by Annotation )

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

538
        /** @scrutinizer ignore-call */ 
539
        $val  = $prop->inputVal($this->propertyVal(), [

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. Please note the @ignore annotation hint above.

Loading history...
539
            'lang' => $this->lang()
540
        ]);
541
542
        if ($val === null) {
0 ignored issues
show
introduced by
The condition $val === null is always false.
Loading history...
543
            return '';
544
        }
545
546
        if (!is_scalar($val)) {
0 ignored issues
show
introduced by
The condition is_scalar($val) is always true.
Loading history...
547
            throw new UnexpectedValueException(sprintf(
548
                'Property Input Value must be a string, received %s',
549
                (is_object($val) ? get_class($val) : gettype($val))
550
            ));
551
        }
552
553
        return $val;
554
    }
555
556
    /**
557
     * Set the hint to the browser for which keyboard to display.
558
     *
559
     * @param  string $inputMode The input type.
560
     * @throws InvalidArgumentException If the provided argument is not a string.
561
     * @return self
562
     */
563
    public function setInputMode($inputMode)
564
    {
565
        if (!is_string($inputMode)) {
0 ignored issues
show
introduced by
The condition is_string($inputMode) is always true.
Loading history...
566
            throw new InvalidArgumentException(
567
                'Property Input Mode must be a string.'
568
            );
569
        }
570
        $this->inputMode = $inputMode;
571
        return $this;
572
    }
573
574
    /**
575
     * Retrieve the hint to the browser for which keyboard to display.
576
     *
577
     * @return string
578
     */
579
    public function inputMode()
580
    {
581
        return $this->inputMode;
582
    }
583
584
    /**
585
     * @param  string $inputType The input type.
586
     * @throws InvalidArgumentException If the provided argument is not a string.
587
     * @return self
588
     * @todo   [mcaskill 2016-11-16]: Rename to `controlType` or `controlTemplate`.
589
     */
590
    public function setInputType($inputType)
591
    {
592
        if (!is_string($inputType)) {
0 ignored issues
show
introduced by
The condition is_string($inputType) is always true.
Loading history...
593
            throw new InvalidArgumentException(
594
                'Property Input Type must be a string.'
595
            );
596
        }
597
        $this->inputType = $inputType;
598
        return $this;
599
    }
600
601
    /**
602
     * @return string
603
     */
604
    public function inputType()
605
    {
606
        if ($this->inputType === null) {
607
            $this->inputType = 'charcoal/admin/property/input/text';
608
        }
609
        return $this->inputType;
610
    }
611
612
613
    /**
614
     * Determine if the property has an affix.
615
     *
616
     * ### Textual `<input>`s only
617
     *
618
     * Avoid using `<select>` elements here as they cannot be fully styled in WebKit browsers.
619
     *
620
     * Avoid using `<textarea>` elements here as their `rows` attribute will
621
     * not be respected in some cases.
622
     *
623
     * @return boolean
624
     */
625
    public function hasInputAffix()
626
    {
627
        return ($this->inputPrefix() || $this->inputSuffix());
628
    }
629
630
    /**
631
     * Retrieve the control's prefix.
632
     *
633
     * @param  mixed $affix Text to display before the control.
634
     * @throws InvalidArgumentException If the suffix is not translatable.
635
     * @return self
636
     */
637
    public function setInputPrefix($affix)
638
    {
639
        $this->inputPrefix = $this->translator()->translation($affix);
640
        $this->inputPrefix->isRendered = false;
0 ignored issues
show
Bug introduced by
The property isRendered does not seem to exist on Charcoal\Translator\Translation.
Loading history...
641
642
        return $this;
643
    }
644
645
    /**
646
     * Retrieve the control's prefix.
647
     *
648
     * @return Translation|string|null
649
     */
650
    public function inputPrefix()
651
    {
652
        if ($this->inputPrefix instanceof Translation) {
653
            if (isset($this->inputPrefix->isRendered) && $this->inputPrefix->isRendered === false) {
654
                $this->inputPrefix = $this->renderTranslatableTemplate($this->inputPrefix);
655
            }
656
657
            if ($this->lang()) {
658
                return $this->inputPrefix[$this->lang()];
659
            }
660
        }
661
662
        return $this->inputPrefix;
663
    }
664
665
    /**
666
     * Retrieve the control's suffix.
667
     *
668
     * @param  mixed $affix Text to display after the control.
669
     * @throws InvalidArgumentException If the suffix is not translatable.
670
     * @return self
671
     */
672
    public function setInputSuffix($affix)
673
    {
674
        $this->inputSuffix = $this->translator()->translation($affix);
675
        $this->inputSuffix->isRendered = false;
0 ignored issues
show
Bug introduced by
The property isRendered does not seem to exist on Charcoal\Translator\Translation.
Loading history...
676
677
        return $this;
678
    }
679
680
    /**
681
     * Retrieve the control's suffix.
682
     *
683
     * @return Translation|string|null
684
     */
685
    public function inputSuffix()
686
    {
687
        if ($this->inputSuffix instanceof Translation) {
688
            if (isset($this->inputSuffix->isRendered) && $this->inputSuffix->isRendered === false) {
689
                $this->inputSuffix = $this->renderTranslatableTemplate($this->inputSuffix);
690
            }
691
692
            if ($this->lang()) {
693
                return $this->inputSuffix[$this->lang()];
694
            }
695
        }
696
697
        return $this->inputSuffix;
698
    }
699
700
    /**
701
     * Set the control type for the HTML element `<input>`.
702
     *
703
     * @param  string $type The control type.
704
     * @throws InvalidArgumentException If the provided argument is not a string.
705
     * @return self
706
     * @todo   [mcaskill 2016-11-16]: Rename to `inputType`.
707
     */
708
    public function setType($type)
709
    {
710
        if (!is_string($type)) {
0 ignored issues
show
introduced by
The condition is_string($type) is always true.
Loading history...
711
            throw new InvalidArgumentException(
712
                'HTML Input Type must be a string.'
713
            );
714
        }
715
716
        $this->type = $type;
717
718
        return $this;
719
    }
720
721
    /**
722
     * Retrieve the control type for the HTML element `<input>`.
723
     *
724
     * @return string
725
     */
726
    public function type()
727
    {
728
        if ($this->type === null) {
729
            $this->type = self::DEFAULT_INPUT_TYPE;
730
        }
731
732
        return $this->type;
733
    }
734
735
    /**
736
     * @return string
737
     */
738
    public function propertyIdent()
739
    {
740
        return $this->p()->ident();
741
    }
742
743
    /**
744
     * @param PropertyInterface $p The property.
745
     * @return self
746
     */
747
    public function setProperty(PropertyInterface $p)
748
    {
749
        $this->property  = $p;
750
        $this->inputName = null;
751
752
        return $this;
753
    }
754
755
    /**
756
     * @return PropertyInterface
757
     */
758
    public function property()
759
    {
760
        return $this->property;
761
    }
762
763
    /**
764
     * Alias of {@see self::property()}
765
     *
766
     * @return PropertyInterface
767
     */
768
    public function p()
769
    {
770
        return $this->property();
771
    }
772
773
    /**
774
     * Render the given template from string.
775
     *
776
     * @see    \Charcoal\View\ViewableInterface::renderTemplate()
777
     * @param  mixed $templateString The template to render.
778
     * @return string The rendered template.
779
     */
780
    public function renderTranslatableTemplate($templateString)
781
    {
782
        if ($templateString instanceof Translation) {
783
            $origLang = $this->translator()->getLocale();
784
            foreach ($this->translator()->availableLocales() as $lang) {
785
                if (!isset($templateString[$lang])) {
786
                    continue;
787
                }
788
                $translation = $templateString[$lang];
789
                $isBlank = empty($translation) && !is_numeric($translation);
790
                if (!$isBlank) {
791
                    $this->translator()->setLocale($lang);
792
                    $translation = $this->renderTemplate($translation);
793
                    if ($translation !== null) {
794
                        $templateString[$lang] = $translation;
795
                    }
796
                }
797
            }
798
            $this->translator()->setLocale($origLang);
799
            $templateString->isRendered = true;
800
801
            return $templateString;
802
        } elseif (is_string($templateString)) {
803
            $isBlank = empty($templateString) && !is_numeric($templateString);
804
            if (!$isBlank) {
805
                return $this->renderTemplate($templateString);
806
            }
807
        }
808
809
        return '';
810
    }
811
812
    /**
813
     * Inject dependencies from a DI Container.
814
     *
815
     * @param Container $container A dependencies container instance.
816
     * @return void
817
     */
818
    protected function setDependencies(Container $container)
819
    {
820
        // Fullfills the DescribableTrait dependencies
821
        $this->setMetadataLoader($container['metadata/loader']);
822
823
        // Fulfills the TranslatorAwareTrait dependencies
824
        $this->setTranslator($container['translator']);
825
826
        // Fulfills the ViewableTrait dependencies
827
        $this->setView($container['view']);
828
    }
829
830
    /**
831
     * Generate a unique input ID.
832
     *
833
     * @return string
834
     */
835
    protected function generateInputId()
836
    {
837
        return 'input_'.uniqid();
838
    }
839
840
    /**
841
     * Create a new metadata object.
842
     *
843
     * @param  array $data Optional metadata to merge on the object.
844
     * @return PropertyMetadata
845
     */
846
    protected function createMetadata(array $data = null)
847
    {
848
        $class = $this->metadataClass();
849
        return new $class($data);
850
    }
851
852
    /**
853
     * Retrieve the class name of the metadata object.
854
     *
855
     * @return string
856
     */
857
    protected function metadataClass()
858
    {
859
        return PropertyMetadata::class;
860
    }
861
862
    /**
863
     * Allow an object to define how the key getter are called.
864
     *
865
     * @param string $key The key to get the getter from.
866
     * @return string The getter method name, for a given key.
867
     */
868
    protected function getter($key)
869
    {
870
        $getter = $key;
871
        return $this->camelize($getter);
872
    }
873
874
    /**
875
     * Allow an object to define how the key setter are called.
876
     *
877
     * @param string $key The key to get the setter from.
878
     * @return string The setter method name, for a given key.
879
     */
880
    protected function setter($key)
881
    {
882
        $setter = 'set_'.$key;
883
        return $this->camelize($setter);
884
    }
885
886
    /**
887
     * Transform a snake_case string to camelCase.
888
     *
889
     * @param string $str The snake_case string to camelize.
890
     * @return string The camelCase string.
891
     */
892
    private function camelize($str)
893
    {
894
        return lcfirst(implode('', array_map('ucfirst', explode('_', $str))));
895
    }
896
}
897