Completed
Push — master ( c9b7ea...c1fd74 )
by Mihail
02:38
created

Form::finish()   D

Complexity

Conditions 10
Paths 10

Size

Total Lines 32
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 32
rs 4.8196
cc 10
eloc 21
nc 10
nop 1

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Ffcms\Core\Helper\HTML;
4
5
use Ffcms\Core\App;
6
use Ffcms\Core\Arch\Model;
7
use Ffcms\Core\Exception\NativeException;
8
use Ffcms\Core\Exception\SyntaxException;
9
use Ffcms\Core\Helper\FileSystem\File;
10
use Ffcms\Core\Helper\HTML\Form\Constructor;
11
use Ffcms\Core\Helper\HTML\System\NativeGenerator;
12
use Ffcms\Core\Helper\Type\Any;
13
use Ffcms\Core\Helper\Type\Arr;
14
use Ffcms\Core\Helper\Type\Obj;
15
use Ffcms\Core\Helper\Type\Str;
16
17
/**
18
 * Class Form. Simple HTML model generator for fast form building.
19
 * @package Ffcms\Core\Helper\HTML
20
 */
21
class Form extends NativeGenerator
22
{
23
    /** @var array */
24
    private static $structLayer = [
25
        'base' => 'native/form/base_layer',
26
        'checkbox' => 'native/form/checkbox_layer',
27
        'checkboxes' => 'native/form/checkboxes_layer',
28
        'radio' => 'native/form/radio_layer',
29
        'jsnotify' => 'native/form/jsnotify'
30
    ];
31
32
    /** @var string */
33
    private $name;
34
    private $formProperty = [];
35
    /** @var Model */
36
    private $model;
37
38
39
    /**
40
     * Form constructor. Build form based on model properties
41
     * @param Model $model
42
     * @param array|null $property
43
     * @param array|null $layerFiles
44
     * @throws SyntaxException
45
     */
46
    public function __construct($model, array $property = null, array $layerFiles = null)
47
    {
48
        // prevent white-screen locks when model is not passed or passed wrong
49
        if (!$model instanceof Model)
50
            throw new SyntaxException('Bad model type passed in form builder. Check for init: new Form()');
51
52
        $this->model = $model;
53
        $this->name = $model->getFormName();
54
        
55
        // check if passed custom layer file
56
        if (Any::isArray($layerFiles) && count($layerFiles) > 0) {
57
            foreach (array_keys(static::$structLayer) as $type) {
0 ignored issues
show
Bug introduced by
Since $structLayer is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $structLayer to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
58
                if (isset($layerFiles[$type]) && Any::isStr($layerFiles[$type]))
59
                    static::$structLayer[$type] = $layerFiles[$type];
0 ignored issues
show
Bug introduced by
Since $structLayer is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $structLayer to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
60
            }
61
        }
62
        // set model submit method
63
        $property['method'] = $this->model->getSubmitMethod();
64
65
        $property['id'] = $this->name; // define form id
66
        // if action is not defined - define it
67
        if (!$property['action'])
68
            $property['action'] = App::$Request->getFullUrl();
69
70
        // set property global for this form
71
        $this->formProperty = $property;
72
    }
73
74
    /**
75
     * Open form tag with prepared properties
76
     * @return string
77
     */
78
    public function start()
79
    {
80
        $form = self::buildSingleTag('form', $this->formProperty, false);
81
        if ($this->model->_tokenRequired) {
82
            $form .= PHP_EOL . $this->field('_csrf_token', 'hidden', ['value' => $this->model->_csrf_token]);
83
        }
84
        return $form;
85
    }
86
87
    /**
88
     * Display form field. Allowed type: text, password, textarea, checkbox, select, checkboxes, file, captcha, email, hidden
89
     * @param string $object
90
     * @param string $type
91
     * @param null|array $property
92
     * @param null|string $helper
93
     * @param null|string $layerFile
94
     * @return null|string
95
     */
96
    public function field(string $object, string $type, ?array $property = null, ?string $helper = null, ?string $layerFile = null)
97
    {
98
        if ($this->model === null) {
99
            if (App::$Debug)
100
                App::$Debug->addMessage('Form model is not defined for field name: [' . strip_tags($object) . ']');
101
102
            return null;
103
        }
104
105
        // can be dots separated object
106
        $propertyName = $object;
107
        if (Str::contains('.', $propertyName))
108
            $propertyName = strstr($propertyName, '.', true);
109
110
        // check if model contains current tag name as property
111
        if (!property_exists($this->model, $propertyName)) {
112
            if (App::$Debug)
113
                App::$Debug->addMessage('Form field ["' . $object . '"] is not defined in model: [' . get_class($this->model) . ']', 'error');
114
115
            return null;
116
        }
117
        
118
        // prepare layer template file path
119
        if ($layerFile === null) {
120
            switch ($type) {
121
                case 'checkbox':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
122
                    $layerFile = static::$structLayer['checkbox'];
0 ignored issues
show
Bug introduced by
Since $structLayer is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $structLayer to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
123
                    break;
124
                case 'radio':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
125
                    $layerFile = static::$structLayer['radio'];
0 ignored issues
show
Bug introduced by
Since $structLayer is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $structLayer to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
126
                    break;
127
                default:
0 ignored issues
show
Coding Style introduced by
DEFAULT statements must be defined using a colon

As per the PSR-2 coding standard, default statements should not be wrapped in curly braces.

switch ($expr) {
    default: { //wrong
        doSomething();
        break;
    }
}

switch ($expr) {
    default: //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
128
                    $layerFile = static::$structLayer['base'];
0 ignored issues
show
Bug introduced by
Since $structLayer is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $structLayer to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
129
                    break;
130
            }
131
        }
132
        
133
        // prepare labels text and label "for" attr 
134
        $labelFor = $this->name . '-' . $propertyName;
135
        $labelText = $this->model->getLabel($object);
136
        $itemValue = $this->model->{$propertyName};
137
        // sounds like a dot-separated $object
138
        if ($propertyName !== $object) {
139
            $nesting = trim(strstr($object, '.'), '.');
140
            $labelFor .= '-' . Str::replace('.', '-', $nesting);
141
            $itemValue = Arr::getByPath($nesting, $itemValue);
142
        }
143
144
        // initialize form fields constructor and build output dom html value
145
        $constructor = new Constructor($this->model, $this->name, $type);
146
        $elementDOM = $constructor->makeTag($object, $itemValue, $property);
147
        
148
        // if item is hidden - return tag without assign of global template
149
        if ($type === 'hidden') {
150
            return $elementDOM;
151
        }
152
153
        // prepare output html
154
        try {
155
            $response = App::$View->render($layerFile, [
156
                'name' => $labelFor,
157
                'label' => $labelText,
158
                'item' => $elementDOM,
159
                'help' => self::nohtml($helper)
160
            ]);
161
        } catch (SyntaxException $e) {
162
            if (App::$Debug)
163
                App::$Debug->addException($e);
164
165
            $response = null;
166
        }
167
        
168
        // render output viewer
169
        return $response;
170
    }
171
172
    /**
173
     * Display submit button for current form
174
     * @param string $title
175
     * @param array $property
176
     * @return string
177
     */
178
    public function submitButton($title, array $property = [])
179
    {
180
        $property['type'] = 'submit';
181
        $property['name'] = $this->name . '[submit]';
182
        $property['value'] = $title;
183
        return self::buildSingleTag('input', $property);
184
    }
185
186
    /**
187
     * Finish current form.
188
     * @param bool $validate
189
     * @return string
190
     */
191
    public function finish($validate = true)
192
    {
193
        // pre-validate form fields based on model rules and jquery.validation
194
        if ($validate) {
195
            App::$Alias->addPlainCode('js', '$().ready(function() { $("#' . $this->name . '").validate(); });');
196
            App::$Alias->setCustomLibrary('js', '/vendor/bower/jquery-validation/dist/jquery.validate.min.js');
197
            if (App::$Request->getLanguage() !== 'en') {
198
                $localeFile = '/vendor/bower/jquery-validation/src/localization/messages_' . App::$Request->getLanguage() . '.js';
199
                if (File::exist($localeFile)) {
200
                    App::$Alias->setCustomLibrary('js', $localeFile);
201
                }
202
            }
203
            // if model is not empty - add js error color notification
204
            if ($this->model !== null) {
205
                $badAttr = $this->model->getBadAttribute();
206
                $formName = $this->model->getFormName();
207
                if (Any::isArray($badAttr) && count($badAttr) > 0) {
208
                    foreach ($badAttr as $attr) {
0 ignored issues
show
Bug introduced by
The expression $badAttr of type null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
209
                        $itemId = $formName . '-' . $attr;
210
                        try {
211
                            $render = App::$View->render(static::$structLayer['jsnotify'], ['itemId' => $itemId]);
0 ignored issues
show
Bug introduced by
Since $structLayer is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $structLayer to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
212
                            App::$Alias->addPlainCode('js', $render);
213
                        } catch (SyntaxException $e) {
214
                            if (App::$Debug)
215
                                App::$Debug->addException($e);
216
                        }
217
                    }
218
                }
219
            }
220
        }
221
        return '</form>';
222
    }
223
}