Completed
Push — master ( b711ee...3ef19c )
by Propa
14:04
created

setTranslatableLabelIndicator()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 5
ccs 0
cts 5
cp 0
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
crap 2
1
<?php namespace Propaganistas\LaravelTranslatableBootForms;
2
3
class TranslatableBootForm
4
{
5
6
    /**
7
     * BootForm implementation.
8
     *
9
     * @var \AdamWathan\BootForms\BootForm
10
     */
11
    protected $form;
12
13
    /**
14
     * Array holding config values.
15
     *
16
     * @var array
17
     */
18
    protected $config;
19
20
    /**
21
     * Array of locale keys.
22
     *
23
     * @var array
24
     */
25
    protected $locales;
26
27
    /**
28
     * The current element type this class is working on.
29
     *
30
     * @var string
31
     */
32
    protected $element;
33
34
    /**
35
     * The array of arguments to pass in when creating the element.
36
     *
37
     * @var array
38
     */
39
    protected $arguments = [];
40
41
    /**
42
     * A keyed array of method => arguments to call on the created input.
43
     *
44
     * @var array
45
     */
46
    protected $methods = [];
47
48
    /**
49
     * Boolean indicating if the element should be cloned with corresponding translation name attributes.
50
     *
51
     * @var bool
52
     */
53
    protected $cloneElement = false;
54
55
    /**
56
     * Boolean indicating if the element should have an indication that is it a translation.
57
     *
58
     * @var bool
59
     */
60
    protected $translatableIndicator = false;
61
62
    /**
63
     * Array holding the mappable element arguments.
64
     *
65
     * @var array
66
     */
67
    private $mappableArguments = [
68
        'text'           => ['label', 'name', 'value'],
69
        'textarea'       => ['label', 'name'],
70
        'password'       => ['label', 'name'],
71
        'date'           => ['label', 'name', 'value'],
72
        'email'          => ['label', 'name', 'value'],
73
        'file'           => ['label', 'name', 'value'],
74
        'inputGroup'     => ['label', 'name', 'value'],
75
        'radio'          => ['label', 'name', 'value'],
76
        'inlineRadio'    => ['label', 'name', 'value'],
77
        'checkbox'       => ['label', 'name'],
78
        'inlineCheckbox' => ['label', 'name'],
79
        'select'         => ['label', 'name', 'options'],
80
        'button'         => ['label', 'name', 'type'],
81
        'submit'         => ['value', 'type'],
82
        'hidden'         => ['name'],
83
        'label'          => ['label'],
84
        'open'           => [],
85
        'openHorizontal' => ['columnSizes'],
86
        'bind'           => ['model'],
87
        'close'          => [],
88
    ];
89
90
    /**
91
     * Array holding the methods to call during element behavior processing.
92
     *
93
     * @var array
94
     */
95
    private $elementBehaviors = [
96
        'text'           => ['cloneElement', 'translatableIndicator'],
97
        'textarea'       => ['cloneElement', 'translatableIndicator'],
98
        'password'       => ['cloneElement', 'translatableIndicator'],
99
        'date'           => ['cloneElement', 'translatableIndicator'],
100
        'email'          => ['cloneElement', 'translatableIndicator'],
101
        'file'           => ['cloneElement', 'translatableIndicator'],
102
        'inputGroup'     => ['cloneElement', 'translatableIndicator'],
103
        'radio'          => ['cloneElement', 'translatableIndicator'],
104
        'inlineRadio'    => ['cloneElement', 'translatableIndicator'],
105
        'checkbox'       => ['cloneElement', 'translatableIndicator'],
106
        'inlineCheckbox' => ['cloneElement', 'translatableIndicator'],
107
        'select'         => ['cloneElement', 'translatableIndicator'],
108
        'button'         => ['cloneElement'],
109
        'submit'         => ['cloneElement'],
110
        'hidden'         => ['cloneElement'],
111
        'label'          => [],
112
        'open'           => [],
113
        'openHorizontal' => [],
114
        'close'          => [],
115
    ];
116
117
    /**
118
     * Form constructor.
119
     *
120
     * @param object $form
121
     */
122
    public function __construct($form)
123
    {
124
        $this->form = $form;
125
        $this->config = config('translatable-bootforms');
126
    }
127
128
    /**
129
     * Magic __call method.
130
     *
131
     * @param string $method
132
     * @param array  $parameters
133
     * @return \Propaganistas\LaravelTranslatableBootForms\TranslatableBootForm
134
     */
135
    public function __call($method, $parameters)
136
    {
137
        // New translatable form element.
138
        if (is_null($this->element())) {
139
            $this->element($method);
140
            $this->arguments($this->mapArguments($parameters));
141
        } // Calling methods on the translatable form element.
142
        else {
143
            $this->addMethod($method, $parameters);
144
        }
145
146
        // Execute bind or close immediately.
147
        if (in_array($method, ['bind', 'close'])) {
148
            return $this->render();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->render(); (string) is incompatible with the return type documented by Propaganistas\LaravelTra...latableBootForm::__call of type Propaganistas\LaravelTra...ms\TranslatableBootForm.

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...
149
        }
150
151
        return $this;
152
    }
153
154
    /**
155
     * Magic __toString method.
156
     *
157
     * @return string
158
     */
159
    public function __toString()
160
    {
161
        return $this->render();
162
    }
163
164
    /**
165
     * Resets the properties.
166
     *
167
     * @return $this
168
     */
169
    protected function reset()
170
    {
171
        $this->element = null;
172
        $this->arguments = [];
173
        $this->methods = [];
174
        $this->cloneElement = false;
175
        $this->translatableIndicator = false;
176
177
        return $this;
178
    }
179
180
    /**
181
     * Get or set the available locales.
182
     *
183
     * @param array|null $locales
184
     * @return array
185
     */
186
    public function locales(array $locales = null)
187
    {
188
        return is_null($locales)
189
            ? $this->locales
190
            : ($this->locales = $locales);
191
    }
192
193
    /**
194
     * Get or set the current element.
195
     *
196
     * @param string|null $element
197
     * @return string
198
     */
199
    protected function element($element = null)
200
    {
201
        return is_null($element)
202
            ? $this->element
203
            : ($this->element = $element);
204
    }
205
206
    /**
207
     * Get or set the arguments.
208
     *
209
     * @param array|null $arguments
210
     * @return array
211
     */
212
    protected function arguments(array $arguments = null)
213
    {
214
        return is_null($arguments)
215
            ? $this->arguments
216
            : ($this->arguments = $arguments);
217
    }
218
219
    /**
220
     * Get or set the methods.
221
     *
222
     * @param array|null $methods
223
     * @return array
224
     */
225
    protected function methods(array $methods = null)
226
    {
227
        return is_null($methods)
228
            ? $this->methods
229
            : ($this->methods = $methods);
230
    }
231
232
    /**
233
     * Get or set the current element.
234
     *
235
     * @param bool|null $clone
236
     * @return bool
237
     */
238
    protected function cloneElement($clone = null)
239
    {
240
        return is_null($clone)
241
            ? $this->cloneElement
242
            : ($this->cloneElement = (bool) $clone);
243
    }
244
245
    /**
246
     * Get or set the translatable indicator boolean.
247
     *
248
     * @param bool|null $add
249
     * @return bool
250
     */
251
    protected function translatableIndicator($add = null)
252
    {
253
        return is_null($add)
254
            ? $this->translatableIndicator
255
            : ($this->translatableIndicator = (bool) $add);
256
    }
257
258
    /**
259
     * Overwrites an argument.
260
     *
261
     * @param string       $argument
262
     * @param string|array $value
263
     */
264
    protected function overwriteArgument($argument, $value)
265
    {
266
        $arguments = $this->arguments();
267
268
        $arguments[$argument] = $value;
269
270
        $this->arguments($arguments);
271
    }
272
273
    /**
274
     * Adds a method.
275
     *
276
     * @param string       $name
277
     * @param string|array $parameters
278
     */
279
    protected function addMethod($name, $parameters)
280
    {
281
        $methods = $this->methods();
282
283
        $parameters = is_array($parameters) ? $parameters : [$parameters];
284
285
        $methods[] = compact('name', 'parameters');
286
287
        $this->methods($methods);
288
    }
289
290
    /**
291
     * Renders the current translatable form element.
292
     *
293
     * @return string
294
     */
295
    public function render()
296
    {
297
        $this->applyElementBehavior();
298
299
        $elements = [];
300
301
        if ($this->cloneElement()) {
302
            $originalArguments = $this->arguments();
303
            $originalMethods = $this->methods();
304
305
            $locales = $this->locales();
306
            // Check if a custom locale set is requested.
307
            if ($count = func_num_args()) {
308
                $args = ($count == 1 ? head(func_get_args()) : func_get_args());
309
                $locales = array_intersect($locales, (array) $args);
310
            }
311
312
            foreach ($locales as $locale) {
313
                $this->arguments($originalArguments);
314
                $this->methods($originalMethods);
315
                
316
                $name = str_contains($originalArguments['name'], '%locale') ? $originalArguments['name'] : '%locale[' . $originalArguments['name'] . ']';
317
                $this->overwriteArgument('name', str_replace('%locale', $locale, $name));
318
                
319
                if ($this->translatableIndicator()) {
320
                    $this->setTranslatableLabelIndicator($locale);
321
                }
322
                if (!empty($this->config['form-group-class'])) {
323
                    $this->addMethod('addGroupClass', str_replace('%locale', $locale, $this->config['form-group-class']));
324
                }
325
                if (!empty($this->config['input-locale-attribute'])) {
326
                    $this->addMethod('attribute', [$this->config['input-locale-attribute'], $locale]);
327
                }
328
                $elements[] = $this->createInput($locale);
329
            }
330
        } else {
331
            $elements[] = $this->createInput();
332
        }
333
334
        $this->reset();
335
336
        return implode('', $elements);
337
    }
338
339
    /**
340
     * Shortcut method for locale-specific rendering.
341
     *
342
     * @return string
343
     */
344
    public function renderLocale()
345
    {
346
        return call_user_func_array([$this, 'render'], func_get_args());
347
    }
348
349
    /**
350
     * Creates an input element using the supplied arguments and methods.
351
     *
352
     * @param string|null $currentLocale
353
     * @return mixed
354
     */
355
    protected function createInput($currentLocale = null)
356
    {
357
        // Create element using arguments.
358
        $element = call_user_func_array([$this->form, $this->element()], array_values($this->arguments()));
359
360
        // Elements such as 'bind' do not return renderable stuff and do not accept methods.
361
        if ($element) {
362
            // Apply requested methods.
363
            foreach ($this->methods() as $method) {
364
                $methodName = $method['name'];
365
                $methodParameters = $method['parameters'];
366
367
                // Check if method is locale-specific.
368
                if (ends_with($methodName, 'ForLocale')) {
369
                    $methodName = strstr($methodName, 'ForLocale', true);
370
                    $locales = array_shift($methodParameters);
371
                    $locales = is_array($locales) ? $locales : [$locales];
372
                    if (!is_null($currentLocale) && !in_array($currentLocale, $locales)) {
373
                        // Method should not be applied for this locale.
374
                        continue;
375
                    }
376
                }
377
378
                // Call method.
379
                if (!empty($methodParameters)) {
380
                    call_user_func_array([$element, $methodName], $this->replacePlaceholdersRecursively($methodParameters, $currentLocale));
381
                } else {
382
                    $element->{$methodName}();
383
                }
384
385
            }
386
        }
387
388
        return $element;
389
    }
390
391
    /**
392
     * Replaces %name recursively with the proper input name.
393
     *
394
     * @param mixed $parameter
395
     * @param string $currentLocale
396
     * @return mixed
397
     */
398
    protected function replacePlaceholdersRecursively($parameter, $currentLocale)
399
    {
400
        if (is_array($parameter)) {
401
            foreach ($parameter as $param) {
402
                $this->replacePlaceholdersRecursively($param, $currentLocale);
403
            }
404
        }
405
        
406
        $replacements = ['locale' => $currentLocale];
407
        
408
        if ($name = array_get($this->arguments(), 'name')) {
409
            array_set($replacements, 'name', $name);
410
        }
411
412
        return str_replace(array_keys($replacements), array_values($replacements), $parameter);
413
    }
414
415
    /**
416
     * Add specific element behavior to the current translatable form element.
417
     */
418
    protected function applyElementBehavior()
419
    {
420
        $behaviors = isset($this->elementBehaviors[$this->element()]) ? $this->elementBehaviors[$this->element()] : [];
421
422
        foreach ($behaviors as $behavior) {
423
            $this->{$behavior}(true);
424
        }
425
    }
426
427
    /**
428
     * Maps the form element arguments to their name.
429
     *
430
     * @param array $arguments
431
     * @return array
432
     */
433
    protected function mapArguments(array $arguments)
434
    {
435
        $keys = isset($this->mappableArguments[$this->element()]) ? $this->mappableArguments[$this->element()] : [];
436
437
        return array_combine(array_slice($keys, 0, count($arguments)), $arguments);
438
    }
439
440
    /**
441
     * Add a locale indicator to the label.
442
     *
443
     * @param string $locale
444
     */
445
    protected function setTranslatableLabelIndicator($locale)
446
    {
447
        $localizedLabel = str_replace('%label', $this->arguments()['label'], $this->config['label-locale-indicator']);
448
        $this->overwriteArgument('label', str_replace('%locale', $locale, $localizedLabel));
449
    }
450
451
}
452