Completed
Push — master ( 0e7ea3...5e6e0b )
by Kristijan
08:21 queued 03:46
created

FormHelper::transformToBracketSyntax()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2.0185

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 2
eloc 6
c 1
b 0
f 1
nc 2
nop 1
dl 0
loc 10
ccs 5
cts 6
cp 0.8333
crap 2.0185
rs 9.4285
1
<?php  namespace Kris\LaravelFormBuilder;
2
3
use Illuminate\Support\Collection;
4
use Symfony\Component\Translation\TranslatorInterface;
5
use Illuminate\Database\Eloquent\Model;
6
use Kris\LaravelFormBuilder\Fields\FormField;
7
use Illuminate\Contracts\View\Factory as View;
8
9
class FormHelper
10
{
11
12
    /**
13
     * @var View
14
     */
15
    protected $view;
16
17
    /**
18
     * @var TranslatorInterface
19
     */
20
    protected $translator;
21
22
    /**
23
     * @var array
24
     */
25
    protected $config;
26
27
    /**
28
     * @var FormBuilder
29
     */
30
    protected $formBuilder;
31
32
    /**
33
     * @var array
34
     */
35
    protected static $reservedFieldNames = [
36
        'save'
37
    ];
38
39
    /**
40
     * All available field types
41
     *
42
     * @var array
43
     */
44
    protected static $availableFieldTypes = [
45
        'text'           => 'InputType',
46
        'email'          => 'InputType',
47
        'url'            => 'InputType',
48
        'tel'            => 'InputType',
49
        'search'         => 'InputType',
50
        'password'       => 'InputType',
51
        'hidden'         => 'InputType',
52
        'number'         => 'InputType',
53
        'date'           => 'InputType',
54
        'file'           => 'InputType',
55
        'image'          => 'InputType',
56
        'color'          => 'InputType',
57
        'datetime-local' => 'InputType',
58
        'month'          => 'InputType',
59
        'range'          => 'InputType',
60
        'time'           => 'InputType',
61
        'week'           => 'InputType',
62
        'select'         => 'SelectType',
63
        'textarea'       => 'TextareaType',
64
        'button'         => 'ButtonType',
65
        'submit'         => 'ButtonType',
66
        'reset'          => 'ButtonType',
67
        'radio'          => 'CheckableType',
68
        'checkbox'       => 'CheckableType',
69
        'choice'         => 'ChoiceType',
70
        'form'           => 'ChildFormType',
71
        'entity'         => 'EntityType',
72
        'collection'     => 'CollectionType',
73
        'repeated'       => 'RepeatedType',
74
        'static'         => 'StaticType'
75
    ];
76
77
    /**
78
     * Custom types
79
     *
80
     * @var array
81
     */
82
    private $customTypes = [];
83
84
    /**
85
     * @param View    $view
86
     * @param TranslatorInterface $translator
87
     * @param array   $config
88
     */
89 103
    public function __construct(View $view, TranslatorInterface $translator, array $config = [])
90
    {
91 103
        $this->view = $view;
92 103
        $this->translator = $translator;
93 103
        $this->config = $config;
94 103
        $this->loadCustomTypes();
95 103
    }
96
97
    /**
98
     * @param string $key
99
     * @param string $default
100
     * @return mixed
101
     */
102 103
    public function getConfig($key, $default = null)
103
    {
104 103
        return array_get($this->config, $key, $default);
105
    }
106
107
    /**
108
     * @return View
109
     */
110 34
    public function getView()
111
    {
112 34
        return $this->view;
113
    }
114
115
    /**
116
     * Merge options array
117
     *
118
     * @param array $first
119
     * @param array $second
120
     * @return array
121
     */
122 103
    public function mergeOptions(array $first, array $second)
123
    {
124 103
        return array_replace_recursive($first, $second);
125
    }
126
127
    /**
128
     * Get proper class for field type
129
     *
130
     * @param $type
131
     * @return string
132
     */
133 66
    public function getFieldType($type)
134
    {
135 66
        $types = array_keys(static::$availableFieldTypes);
136
137 66
        if (!$type || trim($type) == '') {
138 1
            throw new \InvalidArgumentException('Field type must be provided.');
139
        }
140
141 65
        if (array_key_exists($type, $this->customTypes)) {
142 2
            return $this->customTypes[$type];
143
        }
144
145 63
        if (!in_array($type, $types)) {
146 2
            throw new \InvalidArgumentException(
147 2
                sprintf(
148 2
                    'Unsupported field type [%s]. Available types are: %s',
149 2
                    $type,
150 2
                    join(', ', array_merge($types, array_keys($this->customTypes)))
151 2
                )
152 2
            );
153
        }
154
155 61
        $namespace = __NAMESPACE__.'\\Fields\\';
156
157 61
        return $namespace . static::$availableFieldTypes[$type];
158
    }
159
160
    /**
161
     * Convert array of attributes to html attributes
162
     *
163
     * @param $options
164
     * @return string
165
     */
166 82
    public function prepareAttributes($options)
167
    {
168 82
        if (!$options) {
169 4
            return null;
170
        }
171
172 82
        $attributes = [];
173
174 82
        foreach ($options as $name => $option) {
175 82
            if ($option !== null) {
176 82
                $name = is_numeric($name) ? $option : $name;
177 82
                $attributes[] = $name.'="'.$option.'" ';
178 82
            }
179 82
        }
180
181 82
        return join('', $attributes);
182
    }
183
184
    /**
185
     * Add custom field
186
     *
187
     * @param $name
188
     * @param $class
189
     */
190 3
    public function addCustomField($name, $class)
191
    {
192 3
        if (!array_key_exists($name, $this->customTypes)) {
193 3
            return $this->customTypes[$name] = $class;
194
        }
195
196 1
        throw new \InvalidArgumentException('Custom field ['.$name.'] already exists on this form object.');
197
    }
198
199
    /**
200
     * Load custom field types from config file
201
     */
202 103
    private function loadCustomTypes()
203
    {
204 103
        $customFields = (array) $this->getConfig('custom_fields');
205
206 103
        if (!empty($customFields)) {
207 1
            foreach ($customFields as $fieldName => $fieldClass) {
208 1
                $this->addCustomField($fieldName, $fieldClass);
209 1
            }
210 1
        }
211 103
    }
212
213 5
    public function convertModelToArray($model)
214
    {
215 5
        if (!$model) {
216 1
            return null;
217
        }
218
219 5
        if ($model instanceof Model) {
220 1
            return $model->toArray();
221
        }
222
223 5
        if ($model instanceof Collection) {
224 2
            return $model->all();
225
        }
226
227 5
        return $model;
228
    }
229
230
    /**
231
     * Format the label to the proper format
232
     *
233
     * @param $name
234
     * @return string
235
     */
236 80
    public function formatLabel($name)
237
    {
238 80
        if (!$name) {
239 1
            return null;
240
        }
241
242 80
        if ($this->translator->has($name)) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Symfony\Component\Translation\TranslatorInterface as the method has() does only exist in the following implementations of said interface: Illuminate\Translation\Translator.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
243 1
            $translatedName = $this->translator->get($name);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Symfony\Component\Translation\TranslatorInterface as the method get() does only exist in the following implementations of said interface: Illuminate\Translation\Translator.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
244
245 1
            if (is_string($translatedName)) {
246 1
                return $translatedName;
247
            }
248
        }
249
250 79
        return ucfirst(str_replace('_', ' ', $name));
251
    }
252
253
    /**
254
     * @param FormField[] $fields
255
     * @return array
256
     */
257 7
    public function mergeFieldsRules($fields)
258
    {
259 7
        $rules = [];
260 7
        $attributes = [];
261 7
        $messages = [];
262
263 7
        foreach ($fields as $field) {
264 7
            if ($fieldRules = $field->getValidationRules()) {
265 7
                $rules = array_merge($rules, $fieldRules['rules']);
266 7
                $attributes = array_merge($attributes, $fieldRules['attributes']);
267 7
                $messages = array_merge($messages, $fieldRules['error_messages']);
268 7
            }
269 7
        }
270
271
        return [
272 7
            'rules' => $rules,
273 7
            'attributes' => $attributes,
274
            'error_messages' => $messages
275 7
        ];
276
    }
277
278
    /**
279
     * @return array
280
     */
281 1
    public function mergeAttributes($fields)
282
    {
283 1
        $attributes = [];
284 1
        foreach ($fields as $field) {
285 1
            $attributes = array_merge($attributes, $field->getAllAttributes());
286 1
        }
287
288 1
        return $attributes;
289
    }
290
291
    /**
292
     * @param string $string
293
     * @return string
294
     */
295 81
    public function transformToDotSyntax($string)
296
    {
297 81
        return str_replace(['.', '[]', '[', ']'], ['_', '', '.', ''], $string);
298
    }
299
300
    /**
301
     * @param string $string
302
     * @return string
303
     */
304 6
    public function transformToBracketSyntax($string)
305
    {
306 6
        $name = explode('.', $string);
307 6
        if (count($name) == 1) {
308
            return $name[0];
309
        }
310
311 6
        $first = array_shift($name);
312 6
        return $first . '[' . implode('][', $name) . ']';
313
    }
314
315
    /**
316
     * @return TranslatorInterface
317
     */
318 3
    public function getTranslator()
319
    {
320 3
        return $this->translator;
321
    }
322
323
    /**
324
     * Check if field name is valid and not reserved
325
     *
326
     * @throws \InvalidArgumentException
327
     * @param string $name
328
     * @param string $className
329
     */
330 52
    public function checkFieldName($name, $className)
331
    {
332 52
        if (!$name || trim($name) == '') {
333 2
            throw new \InvalidArgumentException(
334 2
                "Please provide valid field name for class [{$className}]"
335 2
            );
336
        }
337
338 50
        if (in_array($name, static::$reservedFieldNames)) {
339 2
            throw new \InvalidArgumentException(
340 2
                "Field name [{$name}] in form [{$className}] is a reserved word. Please use a different field name." .
341 2
                "\nList of all reserved words: " . join(', ', static::$reservedFieldNames)
342 2
            );
343
        }
344
345 48
        return true;
346
    }
347
}
348