Completed
Push — master ( 19db0b...f5d9b1 )
by Mathieu
03:09
created

AbstractPropertyInput::setLang()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 1
Metric Value
c 1
b 1
f 1
dl 0
loc 5
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
1
<?php
2
3
namespace Charcoal\Admin\Property;
4
5
use \InvalidArgumentException;
6
use \Exception;
7
8
// PSR-3 logger dependencies
9
use \Psr\Log\LoggerAwareInterface;
10
use \Psr\Log\LoggerAwareTrait;
11
use \Psr\Log\NullLogger;
12
13
// Module `charcoal-property` dependencies
14
use \Charcoal\Property\PropertyInterface;
15
16
// Module `charcoal-translation` dependencies
17
use \Charcoal\Translation\TranslationConfig;
18
19
// Intra-module (`charcoal-admin`) dependencies
20
use \Charcoal\Admin\Property\PropertyInputInterface;
21
22
/**
23
 *
24
 */
25
abstract class AbstractPropertyInput implements
26
    PropertyInputInterface,
27
    LoggerAwareInterface
28
{
29
    use LoggerAwareTrait;
30
31
    /**
32
     * @var string $lang
33
     */
34
    private $lang;
35
36
    /**
37
     * @var string $ident
38
     */
39
    private $ident;
40
41
    /**
42
     * @var boolean $readOnly
43
     */
44
    private $readOnly;
45
    /**
46
     * @var boolean $required
47
     */
48
    private $required;
49
    /**
50
     * @var boolean $disabled
51
     */
52
    private $disabled;
53
    /**
54
     * @var boolean $multiple
55
     */
56
    private $multiple;
57
58
    /**
59
     * @var string $type
60
     */
61
    protected $type;
62
    /**
63
     * @var string $inputType
64
     */
65
    protected $inputType;
66
67
    /**
68
     * @var string $inputId
69
     */
70
    protected $inputId;
71
    /**
72
     * @var string $inputClass
73
     */
74
    protected $inputClass = '';
75
76
    /**
77
     * @var array $propertyData
78
     */
79
    private $propertyData = [];
80
81
    private $propertyVal;
82
83
    /**
84
     * @var PropertyInterface $property
85
     */
86
    private $property;
87
88
    /**
89
     * @param array|\ArrayAccess $data Constructor data.
90
     */
91 View Code Duplication
    public function __construct($data = 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...
92
    {
93
        if (!isset($data['logger'])) {
94
            $data['logger'] = new NullLogger();
95
        }
96
        $this->setLogger($data['logger']);
97
    }
98
99
    /**
100
     * This function takes an array and fill the model object with its value.
101
     *
102
     * This method either calls a setter for each key (`set_{$key}()`) or sets a public member.
103
     *
104
     * For example, calling with `setData(['properties'=>$properties])` would call
105
     * `setProperties($properties)`, becasue `setProperties()` exists.
106
     *
107
     * But calling with `setData(['foobar'=>$foo])` would set the `$foobar` member
108
     * on the metadata object, because the method `set_foobar()` does not exist.
109
     *
110
     * @param array $data The input data.
111
     * @return Input Chainable
112
     */
113 View Code Duplication
    public function setData(array $data)
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...
114
    {
115
        foreach ($data as $prop => $val) {
116
            $func = [$this, $this->setter($prop)];
117
            if (is_callable($func)) {
118
                call_user_func($func, $val);
119
                unset($data[$prop]);
120
            } else {
121
                $this->{$prop} = $val;
122
            }
123
        }
124
125
        $this->propertyData = $data;
126
127
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (Charcoal\Admin\Property\AbstractPropertyInput) is incompatible with the return type declared by the interface Charcoal\Admin\Property\...InputInterface::setData of type Charcoal\Admin\Property\Input.

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...
128
    }
129
130
    public function setPropertyVal($val)
131
    {
132
        $this->propertyVal = $val;
133
        return $this;
134
    }
135
136
    public function propertyVal()
137
    {
138
        return $this->propertyVal;
139
    }
140
141
    /**
142
     * @
143
     */
144
    public function setLang($lang)
145
    {
146
        $this->lang = $lang;
147
        return $this;
148
    }
149
150
    public function lang()
151
    {
152
        if ($this->lang === null) {
153
            return TranslationConfig::instance()->currentLanguage();
154
        }
155
        return $this->lang;
156
    }
157
158
    /**
159
     * @param string $ident Input identifier.
160
     * @throws InvalidArgumentException If the ident is not a string.
161
     * @return Widget Chainable
162
     */
163 View Code Duplication
    public function setIdent($ident)
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...
164
    {
165
        if (!is_string($ident)) {
166
            throw new InvalidArgumentException(
167
                __CLASS__.'::'.__FUNCTION__.'() - Ident must be a string.'
168
            );
169
        }
170
        $this->ident = $ident;
171
        return $this;
172
    }
173
174
    /**
175
     * @return string
176
     */
177
    public function ident()
178
    {
179
        return $this->ident;
180
    }
181
182
    /**
183
     * @param boolean $readOnly The read-only flag.
184
     * @return Widget (Chainable)
185
     */
186
    public function setReadOnly($readOnly)
187
    {
188
        $this->readOnly = !!$readOnly;
189
        return $this;
190
    }
191
192
    /**
193
     * @return boolean
194
     */
195
    public function readOnly()
196
    {
197
        return $this->readOnly;
198
    }
199
200
    /**
201
     * @param boolean $required Required flag.
202
     * @return Widget (Chainable)
203
     */
204
    public function setRequired($required)
205
    {
206
        $this->required = !!$required;
207
        return $this;
208
    }
209
210
    /**
211
     * @return boolean
212
     */
213
    public function required()
214
    {
215
        return $this->required;
216
    }
217
218
219
    /**
220
     * @param boolean $disabled Disabled flag.
221
     * @return Widget (Chainable)
222
     */
223
    public function setDisabled($disabled)
224
    {
225
        $this->disabled = !!$disabled;
226
        return $this;
227
    }
228
229
    /**
230
     * @return boolean
231
     */
232
    public function disabled()
233
    {
234
        return $this->disabled;
235
    }
236
237
    /**
238
     * @param boolean $multiple Multiple flag.
239
     * @return Widget (Chainable)
240
     */
241
    public function setMultiple($multiple)
242
    {
243
        $this->multiple = !!$multiple;
244
        return $this;
245
    }
246
247
    /**
248
     * @return boolean
249
     */
250
    public function multiple()
251
    {
252
        return $this->multiple;
253
    }
254
255
    /**
256
     * @param string $inputId HTML input id attribute.
257
     * @return Input Chainable
258
     */
259
    public function setInputId($inputId)
260
    {
261
        $this->inputId = $inputId;
262
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (Charcoal\Admin\Property\AbstractPropertyInput) is incompatible with the return type declared by the interface Charcoal\Admin\Property\...utInterface::setInputId of type Charcoal\Admin\Property\Input.

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...
263
    }
264
265
    /**
266
     * Get the input ID.
267
     *
268
     * If none was previously set, than a unique random one will be generated.
269
     *
270
     * @return string
271
     */
272
    public function inputId()
273
    {
274
        if (!$this->inputId) {
275
            $this->inputId = 'input_'.uniqid();
276
        }
277
        return $this->inputId;
278
    }
279
280
    /**
281
     * @param string $inputClass The input class attribute.
282
     * @throws InvalidArgumentException If the class is not a string.
283
     * @return AbstractPropertyInput Chainable
284
     */
285
    public function setInputClass($inputClass)
286
    {
287
        if (!is_string($inputClass)) {
288
            throw new InvalidArgumentException(
289
                'Input class must be a string'
290
            );
291
        }
292
        $this->inputClass = $inputClass;
293
        return $this;
294
    }
295
296
    /**
297
     * @return string
298
     */
299
    public function inputClass()
300
    {
301
        return $this->inputClass;
302
    }
303
304
    /**
305
     * The input name should always be the property's ident.
306
     *
307
     * @return string
308
     */
309 View Code Duplication
    public function inputName()
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...
310
    {
311
        $name = $this->p()->ident();
312
        if ($this->p()->l10n()) {
313
            $name .= '['.$this->lang().']';
314
        }
315
        if ($this->multiple()) {
316
            $name .= '[]';
317
        }
318
        return $name;
319
    }
320
321
    /**
322
     * @uses   AbstractProperty::inputVal() Must handle string sanitization of value.
323
     * @throws Exception If the value is invalid.
324
     * @return string
325
     */
326
    public function inputVal()
327
    {
328
        $prop = $this->p();
329
        $val  = $prop->inputVal($this->propertyVal(), ['lang'=>$this->lang()]);
0 ignored issues
show
Unused Code introduced by
The call to PropertyInterface::inputVal() has too many arguments starting with array('lang' => $this->lang()).

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...
330
331
        if ($val === null) {
332
            return '';
333
        }
334
335 View Code Duplication
        if (!is_scalar($val)) {
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...
336
            throw new Exception(
337
                sprintf(
338
                    'Input value must be a string, received %s',
339
                    (is_object($val) ? get_class($val) : gettype($val))
340
                )
341
            );
342
        }
343
344
        return $val;
0 ignored issues
show
Bug Compatibility introduced by
The expression return $val; of type integer|double|string|boolean is incompatible with the return type declared by the interface Charcoal\Admin\Property\...nputInterface::inputVal of type string as it can also be of type boolean which is not included in this return type.
Loading history...
345
    }
346
347
    /**
348
     * @param string $inputType The input type.
349
     * @throws InvalidArgumentException If provided argument is not of type 'string'.
350
     * @return  AbstractPropertyInput Chainable
351
     */
352
    public function setInputType($inputType)
353
    {
354
        if (!is_string($inputType)) {
355
            throw new InvalidArgumentException(
356
                'Input type must be a string.'
357
            );
358
        }
359
        $this->inputType = $inputType;
360
        return $this;
361
    }
362
363
    /**
364
     * @return string
365
     */
366
    public function inputType()
367
    {
368
        if ($this->inputType === null) {
369
            $this->inputType = 'charcoal/admin/property/input/text';
370
        }
371
        return $this->inputType;
372
    }
373
374
    /**
375
     * @param PropertyInterface $p The property.
376
     * @return AbstractPropertyInput Chainable
377
     */
378
    public function setProperty(PropertyInterface $p)
379
    {
380
        $this->property = $p;
381
        return $this;
382
    }
383
384
    /**
385
     * @return PropertyInterface
386
     */
387
    public function property()
388
    {
389
        return $this->property;
390
    }
391
392
    /**
393
     * Alias of the `property` method.
394
     *
395
     * @return PropertyInterface
396
     */
397
    public function p()
398
    {
399
        return $this->property();
400
    }
401
402
    /**
403
     * Allow an object to define how the key getter are called.
404
     *
405
     * @param string $key The key to get the getter from.
406
     * @return string The getter method name, for a given key.
407
     */
408
    protected function getter($key)
409
    {
410
        $getter = $key;
411
        return $this->camelize($getter);
412
    }
413
414
    /**
415
     * Allow an object to define how the key setter are called.
416
     *
417
     * @param string $key The key to get the setter from.
418
     * @return string The setter method name, for a given key.
419
     */
420
    protected function setter($key)
421
    {
422
        $setter = 'set_'.$key;
423
        return $this->camelize($setter);
424
425
    }
426
427
    /**
428
     * Transform a snake_case string to camelCase.
429
     *
430
     * @param string $str The snake_case string to camelize.
431
     * @return string The camelCase string.
432
     */
433
    private function camelize($str)
434
    {
435
        return lcfirst(implode('', array_map('ucfirst', explode('_', $str))));
436
    }
437
}
438