Completed
Branch dynamicfields (2a748f)
by Derek Stephen
02:55
created

AbstractFormRenderer   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 266
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 100%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 33
lcom 1
cbo 7
dl 0
loc 266
ccs 105
cts 105
cp 1
rs 9.3999
c 2
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A resetDom() 0 6 1
A render() 0 15 1
A setFormAttributes() 0 14 2
A getMethod() 0 4 2
A getId() 0 4 2
A processFields() 0 14 3
A renderField() 0 17 3
A createNewDynamicContainerBlockIfNeeded() 0 11 4
A dynamicFormCheck() 0 15 3
A finaliseDynamicBlockIfNeeded() 0 7 3
A renderError() 0 9 3
A createLabelElement() 0 9 2
A addRequiredAsterisk() 0 9 1
B addDynamicFormJavascript() 0 26 2
1
<?php
2
/**
3
 * User: delboy1978uk
4
 * Date: 07/12/2016
5
 * Time: 01:54
6
 */
7
8
namespace Del\Form\Renderer;
9
10
use Del\Form\Collection\FieldCollection;
11
use Del\Form\AbstractForm;
12
use Del\Form\Field\FieldInterface;
13
use Del\Form\FormInterface;
14
use Del\Form\Renderer\Error\DefaultErrorRender;
15
use Del\Form\Renderer\Error\ErrorRendererInterface;
16
use Del\Form\Traits\HasDomTrait;
17
use DOMDocument;
18
use DomElement;
19
20
abstract class AbstractFormRenderer implements FormRendererInterface
21
{
22
    use HasDomTrait;
23
24
    /** @var DomElement $form */
25
    protected $form;
26
27
    /** @var bool $displayErrors */
28
    protected $displayErrors;
29
30
    /** @var ErrorRendererInterface $errorRenderer */
31
    protected $errorRenderer;
32
33
    /** @var DomElement $label The label element */
34
    protected $label;
35
36
    /** @var DomElement $element the field element */
37
    protected $element;
38
39
    /** @var DomElement $errors The error block html */
40
    protected $errors;
41
42
    /** @var DomElement $block The containing html block */
43
    protected $block;
44
45
    /** @var DomElement $dynamicContainerBlock */
46
    protected $dynamicContainerBlock;
47
48
    /** @var FieldInterface $field The current field being processed */
49
    protected $field;
50
51
    /** @var bool $includeDynamicFormJavascript */
52
    private $includeDynamicFormJavascript = false;
53
54
    /** @var string $dynamicFormParentName */
55
    private $dynamicFormParentName = '';
56
57
    /** @var bool $dynamicFormVisible */
58
    private $dynamicFormVisible = false;
59
60 50
    public function __construct()
61
    {
62 50
        $this->resetDom();
63 50
    }
64
65
    /**
66
     *  resets dom
67
     */
68 50
    private function resetDom()
69
    {
70 50
        $this->setDom(new DOMDocument());
71 50
        $this->form = $this->getDom()->createElement('form');
72 50
        $this->errorRenderer = new DefaultErrorRender($this->dom);
73 50
    }
74
75
    /**
76
     * @param FormInterface $form
77
     * @param bool $displayErrors
78
     * @return string
79
     */
80 29
    public function render(FormInterface $form, $displayErrors = true)
81
    {
82 29
        $this->displayErrors = $displayErrors;
83 29
        $this->setFormAttributes($form);
84
85 29
        $fields = $form->getFields();
86 29
        $this->processFields($fields);
87
88 23
        $this->getDom()->appendChild($this->form);
89 23
        $html = $this->getDom()->saveHTML();
90 23
        $this->resetDom();
91
92 23
        $html .= $this->addDynamicFormJavascript();
93 23
        return $html;
94
    }
95
96
    /**
97
     * @param FormInterface $form
98
     */
99 29
    private function setFormAttributes(FormInterface $form)
100
    {
101 29
        $attributes = $form->getAttributes();
102 29
        foreach ($attributes as $key => $value) {
103 29
            $this->form->setAttribute($key, $value);
104
        }
105
106
        // set Id as name or method as post if not set
107 29
        $method = $this->getMethod($form);
108 29
        $id = $this->getId($form);
109
110 29
        $this->form->setAttribute('id', $id);
111 29
        $this->form->setAttribute('method', $method);
112 29
    }
113
114
    /**
115
     * @param FormInterface $form
116
     * @return string
117
     */
118 29
    private function getMethod(FormInterface $form)
119
    {
120 29
        return $form->getMethod() ?: AbstractForm::METHOD_POST;
121
    }
122
123
    /**
124
     * @param FormInterface $form
125
     * @return string
126
     */
127 29
    private function getId(FormInterface $form)
128
    {
129 29
        return $form->getId() ?: $this->form->getAttribute('name');
130
    }
131
132 29
    private function processFields(FieldCollection $fields, $dynamicTriggerValue = null)
133
    {
134 29
        $count = $fields->count();
135 29
        $x = 1;
136 29
        $fields->rewind();
137 29
        while ($fields->valid()) {
138 27
            $this->field = $fields->current();
139 27
            $finaliseDynamicBlock = ($x == $count) ? true : false;
140 27
            $this->renderField($dynamicTriggerValue, $finaliseDynamicBlock);
141 21
            $x++;
142 21
            $fields->next();
143
        }
144 23
        $fields->rewind();
145 23
    }
146
147
    /**
148
     * @param null $dynamicTriggerValue
149
     * @param bool $finaliseDynamicBlock
150
     */
151 27
    public function renderField($dynamicTriggerValue = null, $finaliseDynamicBlock = false)
152
    {
153 27
        $this->createNewDynamicContainerBlockIfNeeded($dynamicTriggerValue);
154
155 27
        $this->block = $this->createElement('div');
156 27
        $this->label = $this->renderFieldLabel();
157 27
        $this->element = $this->field->getRenderer()->render($this->dom, $this->field);
158 21
        $this->errors = $this->field->isValid() ? null : $this->renderError();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->field->isValid() ... : $this->renderError() can also be of type object<Del\Form\Renderer\DOMElement>. However, the property $errors is declared as type object<DOMElement>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
159 21
        $this->block = $this->renderFieldBlock();
160
161 21
        is_null($dynamicTriggerValue)
162 21
            ? $this->form->appendChild($this->block)
163 1
            : $this->dynamicContainerBlock->appendChild($this->block);
164
165 21
        $this->dynamicFormCheck();
166 21
        $this->finaliseDynamicBlockIfNeeded($finaliseDynamicBlock);
167 21
    }
168
169
    /**
170
     * This creates a containing div for dynamic fields which appear only on another fields value
171
     * @param null $dynamicTriggerValue
172
     */
173 27
    private function createNewDynamicContainerBlockIfNeeded($dynamicTriggerValue)
174
    {
175 27
        if (!isset($this->dynamicContainerBlock) && $dynamicTriggerValue !== null) {
176 1
            $this->dynamicContainerBlock = $this->createElement('div');
177 1
            $this->dynamicContainerBlock->setAttribute('data-dynamic-form', $this->dynamicFormParentName);
178 1
            $this->dynamicContainerBlock->setAttribute('data-dynamic-form-trigger-value', $dynamicTriggerValue);
179 1
            $this->dynamicContainerBlock->setAttribute('class', 'dynamic-form-block trigger' . $this->dynamicFormParentName);
180 1
            $this->dynamicContainerBlock->setAttribute('id', $this->dynamicFormParentName . $dynamicTriggerValue);
181 1
            $this->dynamicFormVisible === false ? $this->dynamicContainerBlock->setAttribute('style', 'display: none;') : null;
182
        }
183 27
    }
184
185
    /**
186
     *  Checks current field being processed for dynamic sub forms
187
     */
188 21
    private function dynamicFormCheck()
189
    {
190 21
        if ($this->field->hasDynamicForms()) {
191 1
            $this->dynamicFormParentName = $this->field->getName();
192 1
            $value = $this->field->getValue();
193 1
            $forms = $this->field->getDynamicForms();
194 1
            $this->includeDynamicFormJavascript = true;
195 1
            foreach ($forms as $dynamicTriggerValue => $form) {
196 1
                $this->dynamicFormVisible = ($value == $dynamicTriggerValue);
197 1
                $dynamicFields = $form->getFields();
198 1
                $this->processFields($dynamicFields, $dynamicTriggerValue);
199
            }
200 1
            unset($this->dynamicFormParentName);
201
        }
202 21
    }
203
204
    /**
205
     * @param bool $finaliseDynamicBlock
206
     */
207 21
    private function finaliseDynamicBlockIfNeeded($finaliseDynamicBlock)
208
    {
209 21
        if (isset($this->dynamicContainerBlock) && $finaliseDynamicBlock === true) {
210 1
            $this->form->appendChild($this->dynamicContainerBlock);
211 1
            unset($this->dynamicContainerBlock);
212
        }
213 21
    }
214
215
216
    /**
217
     * @return DOMElement|null
218
     */
219 6
    public function renderError()
220
    {
221 6
        $errorBlock = null;
222 6
        if ($this->errorRenderer->shouldRender($this->field) && $this->displayErrors === true) {
223 4
            $this->block->setAttribute('class', 'has-error ');
224 4
            $errorBlock = $this->errorRenderer->render($this->field);
225
        }
226 6
        return $errorBlock;
227
    }
228
229
    /**
230
     * @return DOMElement
231
     */
232 27
    protected function createLabelElement()
233
    {
234 27
        $label = $this->createElement('label');
235 27
        $label->setAttribute('for', $this->field->getId());
236 27
        if ($this->field->isRequired()) {
237 6
            $label = $this->addRequiredAsterisk($label);
238
        }
239 27
        return $label;
240
    }
241
242
    /**
243
     * @param DomElement $label
244
     * @return DomElement
245
     */
246 6
    public function addRequiredAsterisk(DomElement $label)
247
    {
248 6
        $span = $this->createElement('span');
249 6
        $span->setAttribute('class', 'text-danger');
250 6
        $text = $this->createText('* ');
251 6
        $span->appendChild($text);
252 6
        $label->appendChild($span);
253 6
        return $label;
254
    }
255
256
    /**
257
     * @return string
258
     */
259 23
    private function addDynamicFormJavascript()
260
    {
261 23
        if ($this->includeDynamicFormJavascript === true) {
262
            return "<script type=\"text/javascript\">
263
                $(document).ready(function(){
264
                    $('.dynamic-form-block').each(function(){
265
                        var Id = $(this).prop('id');
266
                        var parentField = $(this).attr('data-dynamic-form');
267
                        var parentValue = $(this).attr('data-dynamic-form-trigger-value');
268
            
269
                        $('input[name=\"'+parentField+'\"]').change(function(){
270
                            var val = $(this).val();
271
                            if (val == parentValue) {
272
                                $('.trigger'+parentField).each(function(){
273
                                    $(this).attr('style', 'display: none;');
274
                                });
275
                                $('#'+Id).attr('style', 'display: block;');
276
                            }
277
                        });
278
                    });
279
                });
280
            </script>
281 1
            ";
282
        }
283 22
        return '';
284
    }
285
}