Completed
Push — master ( 87fe1e...a72d7d )
by A
02:27
created

AbstractForm::response()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 6
rs 9.4286
cc 3
eloc 4
nc 2
nop 1
1
<?php
2
namespace Code4\Forms;
3
4
use Illuminate\Contracts\Support\Arrayable;
5
use Illuminate\Http\JsonResponse;
6
use Illuminate\Http\Request;
7
use Illuminate\Support\Collection;
8
use Validator;
9
use Symfony\Component\Yaml\Yaml;
10
use Illuminate\Support\MessageBag;
11
12
class AbstractForm  {
13
14
    /**
15
     * @var Collection;
16
     */
17
    protected $fields;
18
19
    protected $configPath;
20
21
    /**
22
     * @var array
23
     */
24
    protected $config;
25
26
    protected $values;
27
28
    /**
29
     * @var Request
30
     */
31
    protected $request;
32
33
    /**
34
     * Custom error messages
35
     * @var array
36
     */
37
    protected $messages = [];
38
39
    /**
40
     * @var MessageBag
41
     */
42
    protected $messageBag;
43
44
    /**
45
     * Custom rules (callback or method name)
46
     * @var array
47
     */
48
    protected $customRules = [];
49
50
    protected $customFieldRules = [];
51
52
    public function __construct() {
53
        $this->fields = new Collection();
54
        $this->messageBag = new MessageBag();
55
    }
56
57
    /**
58
     * Reads config from yaml file
59
     * @param null $configPath
60
     * @throws \Exception
61
     */
62
    public function loadFromConfigYaml($configPath = null) {
63
        if (is_null($configPath)) {
64
            $configPath = $this->configPath;
65
        }
66
        if (is_null($configPath) || !is_string($configPath) || $configPath == '') {
67
            throw new \Exception('Config path should be string');
68
        }
69
        $configPath = rtrim($configPath, '/');
70
        $yaml = \File::get($configPath);
71
        $this->config = Yaml::parse($yaml);
72
        $this->loadFromConfigArray($this->config);
73
    }
74
75
    /**
76
     * Loads fields from array
77
     * @param array $config
78
     * @throws \Exception
79
     */
80
    public function loadFromConfigArray($config) {
81
        if (!is_array($config)) {
82
            throw new \Exception('Provided config is not an array');
83
        }
84
85
        foreach($config as $fieldName => $field) {
86
            //check for field type or default to text
87
            $type = array_key_exists('type', $field) ? $field['type'] : 'text';
88
89
            //Constructs or creates fields
90
            $this->$type($fieldName, $field);
91
        }
92
    }
93
94
    /**
95
     * Gets field from collection
96
     * @param $fieldName
97
     * @return Fields\FieldsInterface
98
     */
99
    public function get($fieldName) {
100
        return $this->fields->get($fieldName);
101
    }
102
103
    /**
104
     * Gets title of field
105
     * @param $fieldName
106
     */
107
    public function title($fieldName) {
108
        return $this->get($fieldName)->title();
109
    }
110
111
112
    /**
113
     * Sets values for all fields from passed array or object (eg. model).
114
     * @param array|Arrayable $values
115
     * @return null
116
     */
117
    public function values($values) {
118
        if (is_object($values) && $values instanceof Arrayable) {
119
            $values = $values->toArray();
120
        }
121
        if (!is_array($values)) {
122
            return null;
123
        }
124
        foreach($this->fields as $fieldName => $field) {
125
            $name = $field->name();
126
            if (array_key_exists($name, $values)) {
127
                $this->fields[$fieldName]->value($values[$name]);
128
            }
129
        }
130
    }
131
132
133
    /**
134
     * Validates all fields with Laravel and custom rules
135
     * @param Request $request
136
     * @param array $passedRules
137
     * @return bool
138
     */
139
    public function validate(Request $request, $passedRules = []) {
140
141
        $this->request = $request;
142
143
        //Collect rules from fields
144
        list($rules, $customFieldRules) = $this->collectRules();
145
146
        //Combine passed rules with those extracted from fields
147
        $rules = array_merge($rules, $passedRules);
148
149
        //Make validator
150
        $validator = Validator::make($request->all(), $rules, $this->messages);
151
152
        //Set fields names for messages
153
        $validator->setAttributeNames($this->getAttributeNames());
154
155
        //Run validation
156
        $validator->passes();
157
158
        //Get messages
159
        $this->messageBag = $validator->messages();
160
161
        //Do custom validation in fields ($field->customRules property)
162
        $customFieldRules = array_merge_recursive($customFieldRules, $this->customFieldRules);
163
        foreach($customFieldRules as $fieldName => $rule) {
164
            foreach($rule as $cr) {
165
                if ($message = $this->callCustomRules($cr, $request)) {
166
                    $this->messageBag->add($fieldName, $message);
167
                }
168
            }
169
        }
170
171
        //Do custom validation in form class ($form->customRules property)
172
        foreach ($this->customRules as $fieldName => $cr) {
173
            if ($message = $this->callCustomRules($cr, $request)) {
174
                $this->messageBag->add($fieldName, $message);
175
            }
176
        }
177
178
        return $this->passed();
179
    }
180
181
    /**
182
     * Run validate() before this
183
     * @return bool
184
     */
185
    public function passed() {
186
        return count($this->messageBag) === 0;
187
    }
188
189
    /**
190
     * Returns messages generated during validation
191
     * @return MessageBag
192
     */
193
    public function messages() {
194
        return $this->messageBag;
195
    }
196
197
    /**
198
     * Returns response for browser
199
     */
200
    public function response($redirect = null) {
201
        if ($this->request->ajax() || $this->request->wantsJson()) {
202
            return new JsonResponse(['formErrors' =>$this->messageBag->toArray()], 422);
203
        }
204
        return \Redirect::to($redirect)->withErrors($this->messageBag->toArray());
205
    }
206
207
208
    /**
209
     * Calls custom rule which can be an callback or private method
210
     * @param $rule
211
     * @param $request
212
     * @return mixed
213
     */
214
    protected function callCustomRules($rule, $request) {
215
        if (is_callable($rule))
216
        {
217
            if ($message = call_user_func($rule, $request))
218
            {
219
                return $message;
220
            }
221
        }
222
        else if (method_exists($this, $rule))
223
        {
224
            if ($message = call_user_func( array($this, $rule), $request ))
225
            {
226
                return $message;
227
            }
228
        }
229
    }
230
231
    /**
232
     * Gets titles of attributes from fields (used in error messages in validation)
233
     * @return array
234
     */
235
    public function getAttributeNames() {
236
        $attributeNames = [];
237
        foreach($this->fields as $field) {
238
            $attributeNames[$field->name()] = $field->title();
239
        }
240
        return $attributeNames;
241
    }
242
243
244
    /**
245
     * Add custom callable rule to field
246
     * @param string $fieldName
247
     * @param $rule
248
     * @return AbstractForm
249
     */
250
    public function customFieldRule($fieldName, $rule) {
251
        $this->customFieldRules[$fieldName][] = $rule;
252
        return $this;
253
    }
254
255
256
    /**
257
     * Collects rules from all fields
258
     * @return array
259
     */
260
    public function collectRules() {
261
        $rules = [];
262
        $customRules = [];
263
        foreach($this->fields as $field) {
264
265
            $fieldRule = $field->getRulesForValidator();
266
            $rules = array_merge($rules, $fieldRule);
267
268
            if ($field->customRules()) {
269
                $customRules[$field->name()] = $field->customRules();
270
            }
271
        }
272
        return [$rules, $customRules];
273
    }
274
275
    /**
276
     * Makes new field from passed class
277
     * @param string $fieldName
278
     * @param string $fieldClass
279
     * @param array $args
280
     * @return Fields\FieldsInterface
281
     */
282
    public function make($fieldName, $fieldClass, $args = []) {
283
        $this->fields->put($fieldName, \FormsFactory::makeField($fieldClass, $fieldName, $args));
284
        return $this->get($fieldName);
285
    }
286
287
    /**
288
     * Calls requested element from fields array or creates it
289
     * @param string $method
290
     * @param mixed $args
291
     * @return mixed|Fields\FieldsInterface
292
     * @throws \Exception
293
     */
294
    public function __call($method, $args) {
295
296
        if (count($args) > 0) {
297
            $fieldName = $args[0];
298
299
            //Looking for existing field by its name
300
            if ($this->fields->has($fieldName)) {
301
                return $this->get($fieldName);
302
            }
303
304
            //No field? Create one!
305
306
            //If there is no second argument (no initial data for field)
307
            $data = array_key_exists(1, $args) ? $args[1] : [];
308
309
            //Method used defines field type
310
            $type = $method;
311
312
            return $this->make($fieldName, $type, $data);
313
        }
314
    }
315
316
}