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
|
36 |
|
public function __construct($form) |
123
|
|
|
{ |
124
|
36 |
|
$this->form = $form; |
125
|
36 |
|
$this->config = config('translatable-bootforms'); |
126
|
36 |
|
} |
127
|
|
|
|
128
|
|
|
/** |
129
|
|
|
* Magic __call method. |
130
|
|
|
* |
131
|
|
|
* @param string $method |
132
|
|
|
* @param array $parameters |
133
|
|
|
* @return \Propaganistas\LaravelTranslatableBootForms\TranslatableBootForm |
134
|
|
|
*/ |
135
|
33 |
|
public function __call($method, $parameters) |
136
|
|
|
{ |
137
|
|
|
// New translatable form element. |
138
|
33 |
|
if (is_null($this->element())) { |
139
|
33 |
|
$this->element($method); |
140
|
33 |
|
$this->arguments($this->mapArguments($parameters)); |
141
|
33 |
|
} // Calling methods on the translatable form element. |
142
|
|
|
else { |
143
|
6 |
|
$this->addMethod($method, $parameters); |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
// Execute bind or close immediately. |
147
|
33 |
|
if (in_array($method, ['bind', 'close'])) { |
148
|
12 |
|
return $this->render(); |
|
|
|
|
149
|
|
|
} |
150
|
|
|
|
151
|
27 |
|
return $this; |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
/** |
155
|
|
|
* Magic __toString method. |
156
|
|
|
* |
157
|
|
|
* @return string |
158
|
|
|
*/ |
159
|
3 |
|
public function __toString() |
160
|
|
|
{ |
161
|
3 |
|
return $this->render(); |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* Resets the properties. |
166
|
|
|
* |
167
|
|
|
* @return $this |
168
|
|
|
*/ |
169
|
33 |
|
protected function reset() |
170
|
|
|
{ |
171
|
33 |
|
$this->element = null; |
172
|
33 |
|
$this->arguments = []; |
173
|
33 |
|
$this->methods = []; |
174
|
33 |
|
$this->cloneElement = false; |
175
|
33 |
|
$this->translatableIndicator = false; |
176
|
|
|
|
177
|
33 |
|
return $this; |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
/** |
181
|
|
|
* Get or set the available locales. |
182
|
|
|
* |
183
|
|
|
* @param array|null $locales |
184
|
|
|
* @return array |
185
|
|
|
*/ |
186
|
36 |
|
public function locales(array $locales = null) |
187
|
|
|
{ |
188
|
36 |
|
return is_null($locales) |
189
|
36 |
|
? $this->locales |
190
|
36 |
|
: ($this->locales = $locales); |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
/** |
194
|
|
|
* Get or set the current element. |
195
|
|
|
* |
196
|
|
|
* @param string|null $element |
197
|
|
|
* @return string |
198
|
|
|
*/ |
199
|
33 |
|
protected function element($element = null) |
200
|
|
|
{ |
201
|
33 |
|
return is_null($element) |
202
|
33 |
|
? $this->element |
203
|
33 |
|
: ($this->element = $element); |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
/** |
207
|
|
|
* Get or set the arguments. |
208
|
|
|
* |
209
|
|
|
* @param array|null $arguments |
210
|
|
|
* @return array |
211
|
|
|
*/ |
212
|
33 |
|
protected function arguments(array $arguments = null) |
213
|
|
|
{ |
214
|
33 |
|
return is_null($arguments) |
215
|
33 |
|
? $this->arguments |
216
|
33 |
|
: ($this->arguments = $arguments); |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
/** |
220
|
|
|
* Get or set the methods. |
221
|
|
|
* |
222
|
|
|
* @param array|null $methods |
223
|
|
|
* @return array |
224
|
|
|
*/ |
225
|
30 |
|
protected function methods(array $methods = null) |
226
|
|
|
{ |
227
|
30 |
|
return is_null($methods) |
228
|
30 |
|
? $this->methods |
229
|
30 |
|
: ($this->methods = $methods); |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
/** |
233
|
|
|
* Get or set the current element. |
234
|
|
|
* |
235
|
|
|
* @param bool|null $clone |
236
|
|
|
* @return bool |
237
|
|
|
*/ |
238
|
33 |
|
protected function cloneElement($clone = null) |
239
|
|
|
{ |
240
|
33 |
|
return is_null($clone) |
241
|
33 |
|
? $this->cloneElement |
242
|
33 |
|
: ($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
|
15 |
|
protected function translatableIndicator($add = null) |
252
|
|
|
{ |
253
|
15 |
|
return is_null($add) |
254
|
15 |
|
? $this->translatableIndicator |
255
|
15 |
|
: ($this->translatableIndicator = (bool) $add); |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
/** |
259
|
|
|
* Overwrites an argument. |
260
|
|
|
* |
261
|
|
|
* @param string $argument |
262
|
|
|
* @param string|array $value |
263
|
|
|
*/ |
264
|
15 |
|
protected function overwriteArgument($argument, $value) |
265
|
|
|
{ |
266
|
15 |
|
$arguments = $this->arguments(); |
267
|
|
|
|
268
|
15 |
|
$arguments[$argument] = $value; |
269
|
|
|
|
270
|
15 |
|
$this->arguments($arguments); |
271
|
15 |
|
} |
272
|
|
|
|
273
|
|
|
/** |
274
|
|
|
* Adds a method. |
275
|
|
|
* |
276
|
|
|
* @param string $name |
277
|
|
|
* @param string|array $parameters |
278
|
|
|
*/ |
279
|
15 |
|
protected function addMethod($name, $parameters) |
280
|
|
|
{ |
281
|
15 |
|
$methods = $this->methods(); |
282
|
|
|
|
283
|
15 |
|
$parameters = is_array($parameters) ? $parameters : [$parameters]; |
284
|
|
|
|
285
|
15 |
|
$methods[] = compact('name', 'parameters'); |
286
|
|
|
|
287
|
15 |
|
$this->methods($methods); |
288
|
15 |
|
} |
289
|
|
|
|
290
|
|
|
/** |
291
|
|
|
* Renders the current translatable form element. |
292
|
|
|
* |
293
|
|
|
* @return string |
294
|
|
|
*/ |
295
|
33 |
|
public function render() |
296
|
|
|
{ |
297
|
33 |
|
$this->applyElementBehavior(); |
298
|
|
|
|
299
|
33 |
|
$elements = []; |
300
|
|
|
|
301
|
33 |
|
if ($this->cloneElement()) { |
302
|
15 |
|
$originalArguments = $this->arguments(); |
303
|
15 |
|
$originalMethods = $this->methods(); |
304
|
|
|
|
305
|
15 |
|
foreach ($this->locales() as $locale) { |
306
|
15 |
|
$this->arguments($originalArguments); |
307
|
15 |
|
$this->methods($originalMethods); |
308
|
15 |
|
$this->overwriteArgument('name', $locale . '[' . $originalArguments['name'] . ']'); |
309
|
15 |
|
if ($this->translatableIndicator()) { |
310
|
15 |
|
$this->setTranslatableLabelIndicator($locale); |
311
|
15 |
|
} |
312
|
15 |
|
if (!empty($this->config['form-group-class'])) { |
313
|
15 |
|
$this->addMethod('addGroupClass', str_replace('%locale', $locale, 'form-group-translation')); |
314
|
15 |
|
} |
315
|
15 |
|
if (!empty($this->config['input-locale-attribute'])) { |
316
|
15 |
|
$this->addMethod('attribute', [$this->config['input-locale-attribute'], $locale]); |
317
|
15 |
|
} |
318
|
15 |
|
$elements[] = $this->createInput($locale); |
319
|
15 |
|
} |
320
|
15 |
|
} else { |
321
|
33 |
|
$elements[] = $this->createInput(); |
322
|
|
|
} |
323
|
|
|
|
324
|
33 |
|
$this->reset(); |
325
|
|
|
|
326
|
33 |
|
return implode('', $elements); |
327
|
|
|
} |
328
|
|
|
|
329
|
|
|
/** |
330
|
|
|
* Creates an input element using the supplied arguments and methods. |
331
|
|
|
* |
332
|
|
|
* @param string|null $currentLocale |
333
|
|
|
* @return mixed |
334
|
|
|
*/ |
335
|
33 |
|
protected function createInput($currentLocale = null) |
336
|
|
|
{ |
337
|
|
|
// Create element using arguments. |
338
|
33 |
|
$element = call_user_func_array([$this->form, $this->element()], array_values($this->arguments())); |
339
|
|
|
|
340
|
|
|
// Elements such as 'bind' do not return renderable stuff and do not accept methods. |
341
|
33 |
|
if ($element) { |
342
|
|
|
// Apply requested methods. |
343
|
30 |
|
foreach ($this->methods() as $method) { |
344
|
15 |
|
$methodName = $method['name']; |
345
|
15 |
|
$methodParameters = $method['parameters']; |
346
|
|
|
|
347
|
|
|
// Check if method is locale-specific. |
348
|
15 |
|
if (ends_with($methodName, 'ForLocale')) { |
349
|
3 |
|
$methodName = strstr($methodName, 'ForLocale', true); |
350
|
3 |
|
$locales = array_shift($methodParameters); |
351
|
3 |
|
$locales = is_array($locales) ? $locales : [$locales]; |
352
|
3 |
|
if (!is_null($currentLocale) && !in_array($currentLocale, $locales)) { |
353
|
|
|
// Method should not be applied for this locale. |
354
|
3 |
|
continue; |
355
|
|
|
} |
356
|
3 |
|
} |
357
|
|
|
|
358
|
|
|
// Call method. |
359
|
15 |
|
if (!empty($methodParameters)) { |
360
|
15 |
|
call_user_func_array([$element, $methodName], $this->replaceInputNameRecursively($methodParameters)); |
361
|
15 |
|
} else { |
362
|
6 |
|
$element->{$methodName}(); |
363
|
|
|
} |
364
|
|
|
|
365
|
30 |
|
} |
366
|
30 |
|
} |
367
|
|
|
|
368
|
33 |
|
return $element; |
369
|
|
|
} |
370
|
|
|
|
371
|
|
|
/** |
372
|
|
|
* Replaces %name recursively with the proper input name. |
373
|
|
|
* |
374
|
|
|
* @param $parameter |
375
|
|
|
* @return mixed |
376
|
|
|
*/ |
377
|
15 |
|
protected function replaceInputNameRecursively($parameter) |
378
|
|
|
{ |
379
|
15 |
|
if (is_array($parameter)) { |
380
|
15 |
|
foreach ($parameter as $param) { |
381
|
15 |
|
$this->replaceInputNameRecursively($param); |
382
|
15 |
|
} |
383
|
15 |
|
} |
384
|
|
|
|
385
|
15 |
|
return str_replace('%name', $this->arguments()['name'], $parameter); |
386
|
|
|
} |
387
|
|
|
|
388
|
|
|
/** |
389
|
|
|
* Add specific element behavior to the current translatable form element. |
390
|
|
|
*/ |
391
|
33 |
|
protected function applyElementBehavior() |
392
|
|
|
{ |
393
|
33 |
|
$behaviors = isset($this->elementBehaviors[$this->element()]) ? $this->elementBehaviors[$this->element()] : []; |
394
|
|
|
|
395
|
33 |
|
foreach ($behaviors as $behavior) { |
396
|
15 |
|
$this->{$behavior}(true); |
397
|
33 |
|
} |
398
|
33 |
|
} |
399
|
|
|
|
400
|
|
|
/** |
401
|
|
|
* Maps the form element arguments to their name. |
402
|
|
|
* |
403
|
|
|
* @param array $arguments |
404
|
|
|
* @return array |
405
|
|
|
*/ |
406
|
33 |
|
protected function mapArguments(array $arguments) |
407
|
|
|
{ |
408
|
33 |
|
$keys = isset($this->mappableArguments[$this->element()]) ? $this->mappableArguments[$this->element()] : []; |
409
|
|
|
|
410
|
33 |
|
return array_combine(array_slice($keys, 0, count($arguments)), $arguments); |
411
|
|
|
} |
412
|
|
|
|
413
|
|
|
/** |
414
|
|
|
* Add a locale indicator to the label. |
415
|
|
|
* |
416
|
|
|
* @param string $locale |
417
|
|
|
*/ |
418
|
15 |
|
protected function setTranslatableLabelIndicator($locale) |
419
|
|
|
{ |
420
|
15 |
|
$localizedLabel = str_replace('%label', $this->arguments()['label'], $this->config['label-locale-indicator']); |
421
|
15 |
|
$this->overwriteArgument('label', str_replace('%locale', $locale, $localizedLabel)); |
422
|
15 |
|
} |
423
|
|
|
|
424
|
|
|
} |
425
|
|
|
|
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:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.