Completed
Push — master ( b7290a...f2cd13 )
by Mihail
04:20
created

Form   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 209
Duplicated Lines 1.44 %

Coupling/Cohesion

Components 2
Dependencies 14

Importance

Changes 31
Bugs 15 Features 1
Metric Value
wmc 31
c 31
b 15
f 1
lcom 2
cbo 14
dl 3
loc 209
rs 9.8

6 Methods

Rating   Name   Duplication   Size   Complexity  
C __construct() 0 30 8
A start() 0 4 1
A csrfToken() 0 13 2
C field() 3 66 11
A submitButton() 0 7 1
C finish() 0 27 8

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
3
namespace Ffcms\Core\Helper\HTML;
4
5
use Ffcms\Core\App;
6
use Ffcms\Core\Exception\NativeException;
7
use Ffcms\Core\Exception\SyntaxException;
8
use Ffcms\Core\Helper\HTML\System\NativeGenerator;
9
use Ffcms\Core\Helper\Security;
10
use Ffcms\Core\Helper\Type\Arr;
11
use Ffcms\Core\Helper\FileSystem\File;
12
use Ffcms\Core\Helper\Type\Obj;
13
use Ffcms\Core\Helper\Type\Str;
14
use Ffcms\Core\Arch\Model;
15
use Ffcms\Core\Helper\HTML\Form\Constructor;
16
17
/**
18
 * Class Form. Simple HTML model generator for fast form building.
19
 */
20
class Form extends NativeGenerator
21
{
22
    /** @var array */
23
    private static $structLayer = [
24
        'base' => 'native/form/base_layer',
25
        'checkbox' => 'native/form/checkbox_layer',
26
        'checkboxes' => 'native/form/checkboxes_layer',
27
        'radio' => 'native/form/radio_layer',
28
        'jsnotify' => 'native/form/jsnotify'
29
    ];
30
31
    /** @var string */
32
    private $name;
33
    private $formProperty = [];
34
    /** @var Model */
35
    private $model;
36
37
38
    /**
39
     * Form constructor. Build form based on model properties
40
     * @param Model $model
41
     * @param array|null $property
42
     * @param array|null $layerFiles
43
     * @throws SyntaxException
44
     */
45
    public function __construct($model, array $property = null, array $layerFiles = null)
46
    {
47
        // prevent white-screen locks when model is not passed or passed wrong
48
        if (!$model instanceof Model) {
49
            throw new SyntaxException('Bad model type passed in form builder. Check for init: new Form()');
50
        }
51
52
        $this->model = $model;
53
        $this->name = $model->getFormName();
54
        
55
        // check if passed custom layer file
56
        if (Obj::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]) && Obj::isString($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
        }
63
        // set model submit method
64
        $property['method'] = $this->model->getSubmitMethod();
65
66
        $property['id'] = $this->name; // define form id
67
        // if action is not defined - define it
68
        if (Str::likeEmpty($property['action'])) {
69
            $property['action'] = App::$Request->getFullUrl();
70
        }
71
72
        // set property global for this form
73
        $this->formProperty = $property;
74
    }
75
76
    /**
77
     * Open form tag with prepared properties
78
     * @return string
79
     */
80
    public function start()
81
    {
82
        return '<form' . self::applyProperty($this->formProperty) . '>';
83
    }
84
85
    /**
86
     * Build csrf token key and return input[type=hidden] tag
87
     * @param string $name
88
     * @return string
89
     */
90
    public function csrfToken($name)
91
    {
92
        // get token from session data (will be destoyed after form validation)
93
        $token = App::$Session->get($name, false);
94
        // if no token data in session - generate it and save in session data
95
        if ($token === false) {
96
            $token = Str::randomLatinNumeric(mt_rand(32, 64));
97
            App::$Session->set($name, $token);
98
        }
99
100
        // build input[type=hidden] with token value
101
        return $this->field($name, 'hidden', ['value' => $token]);
102
    }
103
104
    /**
105
     * Display form field. Allowed type: text, password, textarea, checkbox, select, checkboxes, file, captcha, email, hidden
106
     * @param $object
107
     * @param $type
108
     * @param null|array $property
109
     * @param null|string $helper
110
     * @param null|string $layerFile
111
     * @return null|string
112
     * @throws NativeException
113
     * @throws SyntaxException
114
     */
115
    public function field($object, $type, $property = null, $helper = null, $layerFile = null)
116
    {
117
        if ($this->model === null) {
118
            if (App::$Debug !== null) {
119
                App::$Debug->addMessage('Form model is not defined for field name: [' . strip_tags($object) . ']');
120
            }
121
            return null;
122
        }
123
124
        // can be dots separated object
125
        $propertyName = $object;
126
        if (Str::contains('.', $propertyName)) {
127
            $propertyName = strstr($propertyName, '.', true);
128
        }
129
130
        // check if model contains current tag name as property
131
        if (!property_exists($this->model, $propertyName)) {
132 View Code Duplication
            if (App::$Debug !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
133
                App::$Debug->addMessage('Form field ["' . $object . '"] is not defined in model: [' . get_class($this->model) . ']', 'error');
134
            }
135
            return null;
136
        }
137
        
138
        // prepare layer template file path
139
        if ($layerFile === null) {
140
            switch ($type) {
141
                case 'checkbox':
142
                    $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...
143
                    break;
144
                case 'radio':
145
                    $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...
146
                    break;
147
                default:
148
                    $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...
149
                    break;
150
            }
151
        }
152
        
153
        // prepare labels text and label "for" attr 
154
        $labelFor = $this->name . '-' . $propertyName;
155
        $labelText = $this->model->getLabel($object);
156
        $itemValue = $this->model->{$propertyName};
157
        // sounds like a dot-separated $object
158
        if ($propertyName !== $object) {
159
            $nesting = trim(strstr($object, '.'), '.');
160
            $labelFor .= '-' . Str::replace('.', '-', $nesting);
161
            $itemValue = Arr::getByPath($nesting, $itemValue);
162
        }
163
164
        // initialize form fields constructor and build output dom html value
165
        $constructor = new Constructor($this->model, $this->name, $type);
0 ignored issues
show
Documentation introduced by
$this->name is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
166
        $elementDOM = $constructor->makeTag($object, $itemValue, $property);
167
        
168
        // if item is hidden - return tag without assign of global template
169
        if ($type === 'hidden') {
170
            return $elementDOM;
171
        }
172
        
173
        // render output viewer
174
        return App::$View->render($layerFile, [
175
            'name' => $labelFor,
176
            'label' => $labelText,
177
            'item' => $elementDOM,
178
            'help' => self::nohtml($helper)
179
        ]);
180
    }
181
182
    /**
183
     * Display submit button for current form
184
     * @param string $title
185
     * @param array $property
186
     * @return string
187
     */
188
    public function submitButton($title, array $property = [])
189
    {
190
        $property['type'] = 'submit';
191
        $property['name'] = $this->name . '[submit]';
192
        $property['value'] = $title;
193
        return self::buildSingleTag('input', $property);
194
    }
195
196
    /**
197
     * Finish current form.
198
     * @param bool $validate
199
     * @return string
200
     */
201
    public function finish($validate = true)
202
    {
203
        // pre-validate form fields based on model rules and jquery.validation
204
        if ($validate) {
205
            App::$Alias->addPlainCode('js', '$().ready(function() { $("#' . $this->name . '").validate(); });');
206
            App::$Alias->setCustomLibrary('js', '/vendor/bower/jquery-validation/dist/jquery.validate.min.js');
207
            if (App::$Request->getLanguage() !== 'en') {
208
                $localeFile = '/vendor/bower/jquery-validation/src/localization/messages_' . App::$Request->getLanguage() . '.js';
209
                if (File::exist($localeFile)) {
210
                    App::$Alias->setCustomLibrary('js', $localeFile);
211
                }
212
            }
213
            // if model is not empty - add js error color notification
214
            if ($this->model !== null) {
215
                $badAttr = $this->model->getBadAttribute();
216
                $formName = $this->model->getFormName();
217
                if (Obj::isArray($badAttr) && count($badAttr) > 0) {
218
                    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...
219
                        $itemId = $formName . '-' . $attr;
220
                        $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...
221
                        App::$Alias->addPlainCode('js', $render);
222
                    }
223
                }
224
            }
225
        }
226
        return '</form>';
227
    }
228
}