Completed
Push — master ( ceb694...183c93 )
by Rudie
01:46
created

CollectionType::generatePrototype()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 5.025

Importance

Changes 0
Metric Value
cc 5
nc 4
nop 1
dl 0
loc 16
ccs 9
cts 10
cp 0.9
crap 5.025
rs 9.4222
c 0
b 0
f 0
1
<?php
2
3
namespace Kris\LaravelFormBuilder\Fields;
4
5
use Illuminate\Support\Collection;
6
7
class CollectionType extends ParentType
8
{
9
    /**
10
     * Contains template for a collection element.
11
     *
12
     * @var FormField
13
     */
14
    protected $proto;
15
16
    /**
17
     * @inheritdoc
18
     */
19
    protected $valueProperty = 'data';
20
21
    /**
22
     * @return string
23
     */
24 12
    protected function getTemplate()
25
    {
26 12
        return 'collection';
27
    }
28
29
    /**
30
     * @inheritdoc
31
     */
32 12
    protected function getDefaults()
33
    {
34
        return [
35 12
            'type' => null,
36
            'options' => ['is_child' => true],
37
            'prototype' => true,
38
            'data' => null,
39
            'property' => 'id',
40
            'prototype_name' => '__NAME__',
41
            'empty_row' => true,
42
            'prefer_input' => false,
43
        ];
44
    }
45
46
    /**
47
     * Get the prototype object.
48
     *
49
     * @return FormField
50
     * @throws \Exception
51
     */
52 4
    public function prototype()
53
    {
54
55 4
        if ($this->getOption('prototype') === false) {
56 1
            throw new \Exception(
57 1
                'Prototype for collection field [' . $this->name .'] is disabled.'
58
            );
59
        }
60
61 3
        return $this->proto;
62
    }
63
64
    /**
65
     * @inheritdoc
66
     */
67
    public function getAllAttributes()
68
    {
69
        // Collect all children's attributes.
70
        return $this->parent->getFormHelper()->mergeAttributes($this->children);
71
    }
72
73
    /**
74
     * @inheritdoc
75
     */
76 12
    protected function createChildren()
77
    {
78 12
        $this->children = [];
79 12
        $type = $this->getOption('type');
80 12
        $oldInput = $this->parent->getRequest()->old($this->getNameKey());
81 12
        $currentInput = $this->parent->getRequest()->input($this->getNameKey());
82
83 12
        is_array($oldInput) or $oldInput = [];
84 12
        is_array($currentInput) or $currentInput = [];
85
86
        try {
87 12
            $fieldType = $this->formHelper->getFieldType($type);
88 1
        } catch (\Exception $e) {
89 1
            throw new \Exception(
90 1
                'Collection field ['.$this->name.'] requires [type] option'. "\n\n".
91 1
                $e->getMessage()
92
            );
93
        }
94
95 11
        $data = $this->getOption($this->valueProperty, []);
96
97
        // If no value is provided, get values from current request.
98 11
        if (!is_null($data) && count($data) === 0) {
99
            $data = $currentInput;
100
        }
101
        // Or if the current request input is preferred over original data.
102 10
        elseif ($this->getOption('prefer_input') && count($currentInput)) {
103
            $data = $currentInput;
104
        }
105
106
        // Needs to have more than 1 item because 1 is rendered by default.
107
        // This overrides current request in situations when validation fails.
108 10
        if ($oldInput && count($oldInput) > 1) {
109 1
            $data = $oldInput;
110
        }
111
112 10
        if ($data instanceof Collection) {
113 2
            $data = $data->all();
114
        }
115
116 10
        $field = new $fieldType($this->name, $type, $this->parent, $this->getOption('options'));
117
118 10
        if ($this->getOption('prototype')) {
119 9
            $this->generatePrototype(clone $field);
120
        }
121
122 10
        if (!$data || empty($data)) {
123 4
            if ($empty = $this->getOption('empty_row')) {
124 3
                $val = $empty === true ? null : $empty;
125 3
                return $this->children[] = $this->setupChild(clone $field, '[0]', $val);
126
            }
127
128 1
            return $this->children = [];
129
        }
130
131 6
        if (!is_array($data) && !$data instanceof \Traversable) {
132
            throw new \Exception(
133
                'Data for collection field ['.$this->name.'] must be iterable.'
134
            );
135
        }
136
137 6
        foreach ($data as $key => $val) {
138 6
            $this->children[] = $this->setupChild(clone $field, '['.$key.']', $val);
139
        }
140 6
    }
141
142
    /**
143
     * Set up a single child element for a collection.
144
     *
145
     * @param FormField $field
146
     * @param           $name
147
     * @param null      $value
148
     * @return FormField
149
     */
150 10
    protected function setupChild(FormField $field, $name, $value = null)
151
    {
152 10
        $newFieldName = $field->getName().$name;
153
154 10
        $firstFieldOptions = $this->formHelper->mergeOptions(
155 10
            $this->getOption('options'),
156 10
            ['attr' => ['id' => $newFieldName]]
157
        );
158
159 10
        $field->setName($newFieldName);
160 10
        $field->setOptions($firstFieldOptions);
161
162 10
        if ($value && !$field instanceof ChildFormType) {
163 2
            $value = $this->getModelValueAttribute(
164 2
                $value,
165 2
                $this->getOption('property')
166
            );
167
        }
168
169 10
        $field->setValue($value);
170
171
172 10
        return $field;
173
    }
174
175
    /**
176
     * Generate prototype for regular form field.
177
     *
178
     * @param FormField $field
179
     * @return void
180
     */
181 9
    protected function generatePrototype(FormField $field)
182
    {
183 9
        $value = $field instanceof ChildFormType ? false : null;
184 9
        $field->setOption('is_prototype', true);
185 9
        $field = $this->setupChild($field, $this->getPrototypeName(), $value);
186
187 9
        if ($field instanceof ChildFormType) {
188 5
            foreach ($field->getChildren() as $child) {
189 4
                if ($child instanceof CollectionType) {
190
                    $child->preparePrototype($child->prototype());
191
                }
192
            }
193
        }
194
195 9
        $this->proto = $field;
196 9
    }
197
198
    /**
199
     * Generate array like prototype name.
200
     *
201
     * @return string
202
     */
203 9
    protected function getPrototypeName()
204
    {
205 9
        return '[' . $this->getOption('prototype_name') . ']';
206
    }
207
208
    /**
209
     * Prepare collection for prototype by adding prototype as child.
210
     *
211
     * @param FormField $field
212
     * @return void
213
     */
214
    public function preparePrototype(FormField $field)
215
    {
216
        if (!$field->getOption('is_prototype')) {
217
            throw new \InvalidArgumentException(
218
                'Field ['.$field->getRealName().'] is not a valid prototype object.'
219
            );
220
        }
221
222
        $this->children = [];
223
        $this->children[] = $field;
224
    }
225
}
226