Completed
Push — master ( 51b7cd...11675b )
by A
07:50
created

AbstractForm::title()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 3
rs 10
cc 1
eloc 2
nc 1
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
     * Returns all fields
113
     * @return Collection
114
     */
115
    public function all() {
116
        return $this->fields;
117
    }
118
119
    /**
120
     * Sets values for all fields from passed array or object (eg. model).
121
     * @param array|Arrayable $values
122
     * @return null
123
     */
124
    public function values($values) {
125
        if (is_object($values) && $values instanceof Arrayable) {
126
            $values = $values->toArray();
127
        }
128
        if (!is_array($values)) {
129
            return null;
130
        }
131
        foreach($this->fields as $fieldName => $field) {
132
            $name = $field->name();
133
            if (array_key_exists($name, $values)) {
134
                $this->fields[$fieldName]->value($values[$name]);
135
            }
136
        }
137
    }
138
139
    /**
140
     * Validates all fields with Laravel and custom rules
141
     * @param Request $request
142
     * @param array $passedRules
143
     * @return bool
144
     */
145
    public function validate(Request $request, $passedRules = []) {
146
147
        $this->request = $request;
148
149
        //Collect rules from fields
150
        list($rules, $customFieldRules) = $this->collectRules();
151
152
        //Combine passed rules with those extracted from fields
153
        $rules = array_merge($rules, $passedRules);
154
155
        //Make validator
156
        $validator = Validator::make($request->all(), $rules, $this->messages);
157
158
        //Set fields names for messages
159
        $validator->setAttributeNames($this->getAttributeNames());
160
161
        //Run validation
162
        $validator->passes();
163
164
        //Get messages
165
        $this->messageBag = $validator->messages();
166
167
        //Do custom validation in fields ($field->customRules property)
168
        $customFieldRules = array_merge_recursive($customFieldRules, $this->customFieldRules);
169
        foreach($customFieldRules as $fieldName => $rule) {
170
            foreach($rule as $cr) {
171
                if ($message = $this->callCustomRules($cr, $request)) {
172
                    $this->messageBag->add($fieldName, $message);
173
                }
174
            }
175
        }
176
177
        //Do custom validation in form class ($form->customRules property)
178
        foreach ($this->customRules as $fieldName => $cr) {
179
            if ($message = $this->callCustomRules($cr, $request)) {
180
                $this->messageBag->add($fieldName, $message);
181
            }
182
        }
183
184
        return $this->passed();
185
    }
186
187
    /**
188
     * Run validate() before this
189
     * @return bool
190
     */
191
    public function passed() {
192
        return count($this->messageBag) === 0;
193
    }
194
195
    /**
196
     * Returns messages generated during validation
197
     * @return MessageBag
198
     */
199
    public function messages() {
200
        return $this->messageBag;
201
    }
202
203
    /**
204
     * Returns response for browser
205
     */
206
    public function response($redirect = null) {
207
        if ($this->request->ajax() || $this->request->wantsJson()) {
208
            return new JsonResponse(['formErrors' =>$this->messageBag->toArray()], 422);
209
        }
210
        return \Redirect::to($redirect)->withErrors($this->messageBag->toArray());
211
    }
212
213
214
    /**
215
     * Calls custom rule which can be an callback or private method
216
     * @param $rule
217
     * @param $request
218
     * @return mixed
219
     */
220
    protected function callCustomRules($rule, $request) {
221
        if (is_callable($rule))
222
        {
223
            if ($message = call_user_func($rule, $request))
224
            {
225
                return $message;
226
            }
227
        }
228
        else if (method_exists($this, $rule))
229
        {
230
            if ($message = call_user_func( array($this, $rule), $request ))
231
            {
232
                return $message;
233
            }
234
        }
235
    }
236
237
    /**
238
     * Gets titles of attributes from fields (used in error messages in validation)
239
     * @return array
240
     */
241
    public function getAttributeNames() {
242
        $attributeNames = [];
243
        foreach($this->fields as $field) {
244
            $attributeNames[$field->name()] = $field->title();
245
        }
246
        return $attributeNames;
247
    }
248
249
250
    /**
251
     * Add custom callable rule to field
252
     * @param string $fieldName
253
     * @param $rule
254
     * @return AbstractForm
255
     */
256
    public function customFieldRule($fieldName, $rule) {
257
        $this->customFieldRules[$fieldName][] = $rule;
258
        return $this;
259
    }
260
261
262
    /**
263
     * Collects rules from all fields
264
     * @return array
265
     */
266
    public function collectRules() {
267
        $rules = [];
268
        $customRules = [];
269
        foreach($this->fields as $field) {
270
271
            $fieldRule = $field->getRulesForValidator();
272
            $rules = array_merge($rules, $fieldRule);
273
274
            if ($field->customRules()) {
275
                $customRules[$field->name()] = $field->customRules();
276
            }
277
        }
278
        return [$rules, $customRules];
279
    }
280
281
    /**
282
     * Makes new field from passed class
283
     * @param string $fieldName
284
     * @param string $fieldClass
285
     * @param array $args
286
     * @return Fields\FieldsInterface
287
     */
288
    public function make($fieldName, $fieldClass, $args = []) {
289
        $this->fields->put($fieldName, \FormsFactory::makeField($fieldClass, $fieldName, $args));
290
        return $this->get($fieldName);
291
    }
292
293
    /**
294
     * Calls requested element from fields array or creates it
295
     * @param string $method
296
     * @param mixed $args
297
     * @return mixed|Fields\FieldsInterface
298
     * @throws \Exception
299
     */
300
    public function __call($method, $args) {
301
302
        if (count($args) > 0) {
303
            $fieldName = $args[0];
304
305
            //Looking for existing field by its name
306
            if ($this->fields->has($fieldName)) {
307
                return $this->get($fieldName);
308
            }
309
310
            //No field? Create one!
311
312
            //If there is no second argument (no initial data for field)
313
            $data = array_key_exists(1, $args) ? $args[1] : [];
314
315
            //Method used defines field type
316
            $type = $method;
317
318
            return $this->make($fieldName, $type, $data);
319
        }
320
    }
321
322
}