Completed
Push — master ( 79c477...9f3074 )
by Propa
13:18
created

TranslatableBootForm   B

Complexity

Total Complexity 37

Size/Duplication

Total Lines 391
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Test Coverage

Coverage 100%
Metric Value
wmc 37
lcom 1
cbo 0
dl 0
loc 391
ccs 108
cts 108
cp 1
rs 8.6

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A __call() 0 18 3
A __toString() 0 4 1
A reset() 0 10 1
A locales() 0 6 2
A element() 0 6 2
A arguments() 0 6 2
A methods() 0 6 2
A cloneElement() 0 6 2
A translatableIndicator() 0 6 2
A overwriteArgument() 0 8 1
A addMethod() 0 10 2
B render() 0 33 6
B createInput() 0 22 4
A applyElementBehavior() 0 8 3
A mapArguments() 0 6 2
A setTranslatableLabelIndicator() 0 5 1
1
<?php namespace Propaganistas\LaravelTranslatableBootForms;
2
3
use AdamWathan\BootForms\BootForm;
4
5
class TranslatableBootForm
6
{
7
8
    /**
9
     * BootForm implementation.
10
     *
11
     * @var \AdamWathan\BootForms\BootForm
12
     */
13
    protected $form;
14
15
    /**
16
     * Array holding config values.
17
     *
18
     * @var array
19
     */
20
    protected $config;
21
22
    /**
23
     * Array of locale keys.
24
     *
25
     * @var array
26
     */
27
    protected $locales;
28
29
    /**
30
     * The current element type this class is working on.
31
     *
32
     * @var string
33
     */
34
    protected $element;
35
36
    /**
37
     * The array of arguments to pass in when creating the element.
38
     *
39
     * @var array
40
     */
41
    protected $arguments = [];
42
43
    /**
44
     * A keyed array of method => arguments to call on the created input.
45
     *
46
     * @var array
47
     */
48
    protected $methods = [];
49
50
    /**
51
     * Boolean indicating if the element should be cloned with corresponding translation name attributes.
52
     *
53
     * @var bool
54
     */
55
    protected $cloneElement = false;
56
57
    /**
58
     * Boolean indicating if the element should have an indication that is it a translation.
59
     *
60
     * @var bool
61
     */
62
    protected $translatableIndicator = false;
63
64
    /**
65
     * Array holding the mappable element arguments.
66
     *
67
     * @var array
68
     */
69
    private $mappableArguments = [
70
        'text'           => ['label', 'name'],
71
        'textarea'       => ['label', 'name'],
72
        'password'       => ['label', 'name'],
73
        'date'           => ['label', 'name'],
74
        'email'          => ['label', 'name'],
75
        'file'           => ['label', 'name'],
76
        'inputGroup'     => ['label', 'name'],
77
        'radio'          => ['label', 'name'],
78
        'inlineRadio'    => ['label', 'name'],
79
        'checkbox'       => ['label', 'name'],
80
        'inlineCheckbox' => ['label', 'name'],
81
        'select'         => ['label', 'name', 'options'],
82
        'button'         => ['label', 'name', 'type'],
83
        'submit'         => ['value', 'type'],
84
        'hidden'         => ['name'],
85
        'label'          => ['label'],
86
        'open'           => [],
87
        'openHorizontal' => ['columnSizes'],
88
        'bind'           => ['model'],
89
        'close'          => [],
90
    ];
91
92
    /**
93
     * Array holding the methods to call during element behavior processing.
94
     *
95
     * @var array
96
     */
97
    private $elementBehaviors = [
98
        'text'           => ['cloneElement', 'translatableIndicator'],
99
        'textarea'       => ['cloneElement', 'translatableIndicator'],
100
        'password'       => ['cloneElement', 'translatableIndicator'],
101
        'date'           => ['cloneElement', 'translatableIndicator'],
102
        'email'          => ['cloneElement', 'translatableIndicator'],
103
        'file'           => ['cloneElement', 'translatableIndicator'],
104
        'inputGroup'     => ['cloneElement', 'translatableIndicator'],
105
        'radio'          => ['cloneElement', 'translatableIndicator'],
106
        'inlineRadio'    => ['cloneElement', 'translatableIndicator'],
107
        'checkbox'       => ['cloneElement', 'translatableIndicator'],
108
        'inlineCheckbox' => ['cloneElement', 'translatableIndicator'],
109
        'select'         => ['cloneElement', 'translatableIndicator'],
110
        'button'         => ['cloneElement'],
111
        'submit'         => ['cloneElement'],
112
        'hidden'         => ['cloneElement'],
113
        'label'          => [],
114
        'open'           => [],
115
        'openHorizontal' => [],
116
        'close'          => [],
117
    ];
118
119
    /**
120
     * Form constructor.
121
     *
122
     * @param \AdamWathan\BootForms\BootForm $form
123
     */
124 11
    public function __construct(BootForm $form)
125
    {
126 11
        $this->form = $form;
127 11
        $this->config = config('translatable-bootforms');
128 11
    }
129
130
    /**
131
     * Magic __call method.
132
     *
133
     * @param string $method
134
     * @param array  $parameters
135
     * @return \Propaganistas\LaravelTranslatableBootForms\TranslatableBootForm
136
     */
137 10
    public function __call($method, $parameters)
138
    {
139
        // New translatable form element.
140 10
        if (is_null($this->element())) {
141 10
            $this->element($method);
142 10
            $this->arguments($this->mapArguments($parameters));
143 10
        } // Calling methods on the translatable form element.
144
        else {
145 1
            $this->addMethod($method, $parameters);
146
        }
147
148
        // Execute bind or close immediately.
149 10
        if (in_array($method, ['bind', 'close'])) {
150 4
            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...
151
        }
152
153 8
        return $this;
154
    }
155
156
    /**
157
     * Magic __toString method.
158
     *
159
     * @return string
160
     */
161 1
    public function __toString()
162
    {
163 1
        return $this->render();
164
    }
165
166
    /**
167
     * Resets the properties.
168
     *
169
     * @return $this
170
     */
171 10
    protected function reset()
172
    {
173 10
        $this->element = null;
174 10
        $this->arguments = [];
175 10
        $this->methods = [];
176 10
        $this->cloneElement = false;
177 10
        $this->translatableIndicator = false;
178
179 10
        return $this;
180
    }
181
182
    /**
183
     * Get or set the available locales.
184
     *
185
     * @param array|null $locales
186
     * @return array
187
     */
188 11
    public function locales(array $locales = null)
189
    {
190 11
        return is_null($locales)
191 11
            ? $this->locales
192 11
            : ($this->locales = $locales);
193
    }
194
195
    /**
196
     * Get or set the current element.
197
     *
198
     * @param string|null $element
199
     * @return string
200
     */
201 10
    protected function element($element = null)
202
    {
203 10
        return is_null($element)
204 10
            ? $this->element
205 10
            : ($this->element = $element);
206
    }
207
208
    /**
209
     * Get or set the arguments.
210
     *
211
     * @param array|null $arguments
212
     * @return array
213
     */
214 10
    protected function arguments(array $arguments = null)
215
    {
216 10
        return is_null($arguments)
217 10
            ? $this->arguments
218 10
            : ($this->arguments = $arguments);
219
    }
220
221
    /**
222
     * Get or set the methods.
223
     *
224
     * @param array|null $methods
225
     * @return array
226
     */
227 9
    protected function methods(array $methods = null)
228
    {
229 9
        return is_null($methods)
230 9
            ? $this->methods
231 9
            : ($this->methods = $methods);
232
    }
233
234
    /**
235
     * Get or set the current element.
236
     *
237
     * @param bool|null $clone
238
     * @return bool
239
     */
240 10
    protected function cloneElement($clone = null)
241
    {
242 10
        return is_null($clone)
243 10
            ? $this->cloneElement
244 10
            : ($this->cloneElement = (bool) $clone);
245
    }
246
247
    /**
248
     * Get or set the translatable indicator boolean.
249
     *
250
     * @param bool|null $add
251
     * @return bool
252
     */
253 4
    protected function translatableIndicator($add = null)
254
    {
255 4
        return is_null($add)
256 4
            ? $this->translatableIndicator
257 4
            : ($this->translatableIndicator = (bool) $add);
258
    }
259
260
    /**
261
     * Overwrites an argument.
262
     *
263
     * @param string       $argument
264
     * @param string|array $value
265
     */
266 4
    protected function overwriteArgument($argument, $value)
267
    {
268 4
        $arguments = $this->arguments();
269
270 4
        $arguments[$argument] = $value;
271
272 4
        $this->arguments($arguments);
273 4
    }
274
275
    /**
276
     * Adds a method.
277
     *
278
     * @param string       $name
279
     * @param string|array $parameters
280
     */
281 4
    protected function addMethod($name, $parameters)
282
    {
283 4
        $methods = $this->methods();
284
285 4
        $parameters = is_array($parameters) ? $parameters : [$parameters];
286
287 4
        $methods[] = compact('name', 'parameters');
288
289 4
        $this->methods($methods);
290 4
    }
291
292
    /**
293
     * Renders the current translatable form element.
294
     *
295
     * @return string
296
     */
297 10
    public function render()
298
    {
299 10
        $this->applyElementBehavior();
300
301 10
        $elements = [];
302
303 10
        if ($this->cloneElement()) {
304 4
            $originalArguments = $this->arguments();
305 4
            $originalMethods = $this->methods();
306
307 4
            foreach ($this->locales() as $locale) {
308 4
                $this->arguments($originalArguments);
309 4
                $this->methods($originalMethods);
310 4
                $this->overwriteArgument('name', $locale . '[' . $originalArguments['name'] . ']');
311 4
                if ($this->translatableIndicator()) {
312 4
                    $this->setTranslatableLabelIndicator($locale);
313 4
                }
314 4
                if (!empty($this->config['form-group-class'])) {
315 4
                    $this->addMethod('addGroupClass', str_replace('%locale', $locale, 'form-group-translation'));
316 4
                }
317 4
                if (!empty($this->config['input-locale-attribute'])) {
318 4
                    $this->addMethod('attribute', [$this->config['input-locale-attribute'], $locale]);
319 4
                }
320 4
                $elements[] = $this->createInput();
321 4
            }
322 4
        } else {
323 10
            $elements[] = $this->createInput();
324
        }
325
326 10
        $this->reset();
327
328 10
        return implode('', $elements);
329
    }
330
331
    /**
332
     * Creates an input element using the supplied arguments and methods.
333
     *
334
     * @return mixed
335
     */
336 10
    protected function createInput()
337
    {
338
        // Create element using arguments.
339 10
        $element = $this->form->{$this->element()}(...array_values($this->arguments()));
340
341
        // Elements such as 'bind' do not return renderable stuff and do not accept methods.
342 10
        if ($element) {
343
            // Apply requested methods.
344 9
            foreach ($this->methods() as $method) {
345 4
                $methodName = $method['name'];
346 4
                $methodParameters = $method['parameters'];
347 4
                if (!empty($methodParameters)) {
348 4
                    $element->{$methodName}(...$methodParameters);
349 4
                } else {
350 1
                    $element->{$methodName}();
351
                }
352
353 9
            }
354 9
        }
355
356 10
        return $element;
357
    }
358
359
    /**
360
     * Add specific element behavior to the current translatable form element.
361
     */
362 10
    protected function applyElementBehavior()
363
    {
364 10
        $behaviors = isset($this->elementBehaviors[$this->element()]) ? $this->elementBehaviors[$this->element()] : [];
365
366 10
        foreach ($behaviors as $behavior) {
367 4
            $this->{$behavior}(true);
368 10
        }
369 10
    }
370
371
    /**
372
     * Maps the form element arguments to their name.
373
     *
374
     * @param array $arguments
375
     * @return array
376
     */
377 10
    protected function mapArguments(array $arguments)
378
    {
379 10
        $keys = isset($this->mappableArguments[$this->element()]) ? $this->mappableArguments[$this->element()] : [];
380
381 10
        return array_combine(array_slice($keys, 0, count($arguments)), $arguments);
382
    }
383
384
    /**
385
     * Add a locale indicator to the label.
386
     *
387
     * @param string $locale
388
     */
389 4
    protected function setTranslatableLabelIndicator($locale)
390
    {
391 4
        $localizedLabel = str_replace('%label', $this->arguments()['label'], $this->config['label-locale-indicator']);
392 4
        $this->overwriteArgument('label', str_replace('%locale', $locale, $localizedLabel));
393 4
    }
394
395
}