Total Complexity | 105 |
Total Lines | 598 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like RecordsRequestHandler often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use RecordsRequestHandler, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
12 | abstract class RecordsRequestHandler extends RequestHandler |
||
13 | { |
||
14 | public static $config; |
||
15 | |||
16 | // configurables |
||
17 | public static $recordClass; |
||
18 | public static $accountLevelRead = false; |
||
19 | public static $accountLevelBrowse = 'Staff'; |
||
20 | public static $accountLevelWrite = 'Staff'; |
||
21 | public static $accountLevelAPI = false; |
||
22 | public static $browseOrder = false; |
||
23 | public static $browseConditions = false; |
||
24 | public static $browseLimitDefault = false; |
||
25 | public static $editableFields = false; |
||
26 | public static $searchConditions = false; |
||
27 | |||
28 | public static $calledClass = __CLASS__; |
||
29 | public static $responseMode = 'dwoo'; |
||
30 | |||
31 | public static function handleRequest() |
||
48 | } |
||
49 | |||
50 | |||
51 | public static function handleRecordsRequest($action = false) |
||
81 | } |
||
82 | } |
||
83 | } |
||
84 | } |
||
85 | |||
86 | public static function getRecordByHandle($handle) |
||
87 | { |
||
88 | $className = static::$recordClass; |
||
89 | |||
90 | if (method_exists($className, 'getByHandle')) { |
||
91 | return $className::getByHandle($handle); |
||
92 | } |
||
93 | } |
||
94 | |||
95 | public static function prepareBrowseConditions($conditions = []) |
||
104 | } |
||
105 | |||
106 | public static function prepareDefaultBrowseOptions() |
||
122 | } |
||
123 | |||
124 | public static function handleBrowseRequest($options = [], $conditions = [], $responseID = null, $responseData = []) |
||
125 | { |
||
126 | if (!static::checkBrowseAccess(func_get_args())) { |
||
127 | return static::throwUnauthorizedError(); |
||
128 | } |
||
129 | |||
130 | $conditions = static::prepareBrowseConditions($conditions); |
||
131 | |||
132 | $options = static::prepareDefaultBrowseOptions(); |
||
133 | |||
134 | // process sorter |
||
135 | if (!empty($_REQUEST['sort'])) { |
||
136 | $sort = json_decode($_REQUEST['sort'], true); |
||
137 | if (!$sort || !is_array($sort)) { |
||
138 | return static::respond('error', [ |
||
139 | 'success' => false, |
||
140 | 'failed' => [ |
||
141 | 'errors' => 'Invalid sorter.', |
||
142 | ], |
||
143 | ]); |
||
144 | } |
||
145 | |||
146 | if (is_array($sort)) { |
||
147 | foreach ($sort as $field) { |
||
148 | $options['order'][$field['property']] = $field['direction']; |
||
149 | } |
||
150 | } |
||
151 | } |
||
152 | |||
153 | // process filter |
||
154 | if (!empty($_REQUEST['filter'])) { |
||
155 | $filter = json_decode($_REQUEST['filter'], true); |
||
156 | if (!$filter || !is_array($filter)) { |
||
157 | return static::respond('error', [ |
||
158 | 'success' => false, |
||
159 | 'failed' => [ |
||
160 | 'errors' => 'Invalid filter.', |
||
161 | ], |
||
162 | ]); |
||
163 | } |
||
164 | |||
165 | foreach ($filter as $field) { |
||
166 | $conditions[$field['property']] = $field['value']; |
||
167 | } |
||
168 | } |
||
169 | |||
170 | $className = static::$recordClass; |
||
171 | |||
172 | return static::respond( |
||
173 | isset($responseID) ? $responseID : static::getTemplateName($className::$pluralNoun), |
||
174 | array_merge($responseData, [ |
||
175 | 'success' => true, |
||
176 | 'data' => $className::getAllByWhere($conditions, $options), |
||
177 | 'conditions' => $conditions, |
||
178 | 'total' => DB::foundRows(), |
||
179 | 'limit' => $options['limit'], |
||
180 | 'offset' => $options['offset'], |
||
181 | ]) |
||
182 | ); |
||
183 | } |
||
184 | |||
185 | |||
186 | public static function handleRecordRequest(ActiveRecord $Record, $action = false) |
||
217 | } |
||
218 | } |
||
219 | } |
||
220 | |||
221 | |||
222 | public static function prepareResponseModeJSON($methods = []) |
||
228 | } |
||
229 | } |
||
230 | } |
||
231 | |||
232 | public static function processDatumSave($datum) |
||
233 | { |
||
234 | $className = static::$recordClass; |
||
235 | $PrimaryKey = $className::getPrimaryKey(); |
||
236 | |||
237 | // get record |
||
238 | if (empty($datum[$PrimaryKey])) { |
||
239 | $Record = new $className::$defaultClass(); |
||
240 | static::onRecordCreated($Record, $datum); |
||
241 | } else { |
||
242 | if (!$Record = $className::getByID($datum[$PrimaryKey])) { |
||
243 | throw new Exception('Record not found'); |
||
244 | } |
||
245 | } |
||
246 | |||
247 | // check write access |
||
248 | if (!static::checkWriteAccess($Record)) { |
||
249 | throw new Exception('Write access denied'); |
||
250 | } |
||
251 | |||
252 | // apply delta |
||
253 | static::applyRecordDelta($Record, $datum); |
||
254 | |||
255 | // call template function |
||
256 | static::onBeforeRecordValidated($Record, $datum); |
||
257 | |||
258 | // try to save record |
||
259 | try { |
||
260 | // call template function |
||
261 | static::onBeforeRecordSaved($Record, $datum); |
||
262 | |||
263 | $Record->save(); |
||
264 | return (!$Record::fieldExists('Class') || get_class($Record) == $Record->Class) ? $Record : $Record->changeClass(); |
||
265 | |||
266 | // call template function |
||
267 | static::onRecordSaved($Record, $datum); |
||
|
|||
268 | } catch (Exception $e) { |
||
269 | $failed[] = [ |
||
270 | 'record' => $Record->data, |
||
271 | 'validationErrors' => $Record->validationErrors, |
||
272 | ]; |
||
273 | } |
||
274 | } |
||
275 | |||
276 | public static function handleMultiSaveRequest() |
||
277 | { |
||
278 | $className = static::$recordClass; |
||
279 | |||
280 | $PrimaryKey = $className::getPrimaryKey(); |
||
281 | |||
282 | static::prepareResponseModeJSON(['POST','PUT']); |
||
283 | |||
284 | if ($className::fieldExists(key($_REQUEST['data']))) { |
||
285 | $_REQUEST['data'] = [$_REQUEST['data']]; |
||
286 | } |
||
287 | |||
288 | if (empty($_REQUEST['data']) || !is_array($_REQUEST['data'])) { |
||
289 | return static::respond('error', [ |
||
290 | 'success' => false, |
||
291 | 'failed' => [ |
||
292 | 'errors' => 'Save expects "data" field as array of records.', |
||
293 | ], |
||
294 | ]); |
||
295 | } |
||
296 | |||
297 | $results = []; |
||
298 | $failed = []; |
||
299 | |||
300 | foreach ($_REQUEST['data'] as $datum) { |
||
301 | try { |
||
302 | $results[] = static::processDatumSave($datum); |
||
303 | } catch (Exception $e) { |
||
304 | $failed[] = [ |
||
305 | 'record' => $datum, |
||
306 | 'errors' => $e->getMessage(), |
||
307 | ]; |
||
308 | continue; |
||
309 | } |
||
310 | } |
||
311 | |||
312 | |||
313 | return static::respond(static::getTemplateName($className::$pluralNoun).'Saved', [ |
||
314 | 'success' => count($results) || !count($failed), |
||
315 | 'data' => $results, |
||
316 | 'failed' => $failed, |
||
317 | ]); |
||
318 | } |
||
319 | |||
320 | |||
321 | public static function handleMultiDestroyRequest() |
||
387 | ]); |
||
388 | } |
||
389 | |||
390 | |||
391 | public static function handleCreateRequest(ActiveRecord $Record = null) |
||
392 | { |
||
393 | // save static class |
||
394 | static::$calledClass = get_called_class(); |
||
395 | |||
396 | if (!$Record) { |
||
397 | $className = static::$recordClass; |
||
398 | $Record = new $className::$defaultClass(); |
||
399 | } |
||
400 | |||
401 | // call template function |
||
402 | static::onRecordCreated($Record, $_REQUEST); |
||
403 | |||
404 | return static::handleEditRequest($Record); |
||
405 | } |
||
406 | |||
407 | public static function handleEditRequest(ActiveRecord $Record) |
||
408 | { |
||
409 | $className = static::$recordClass; |
||
410 | |||
411 | if (!static::checkWriteAccess($Record)) { |
||
412 | return static::throwUnauthorizedError(); |
||
413 | } |
||
414 | |||
415 | if (in_array($_SERVER['REQUEST_METHOD'], ['POST','PUT'])) { |
||
416 | if (static::$responseMode == 'json') { |
||
417 | $_REQUEST = JSON::getRequestData(); |
||
418 | if (is_array($_REQUEST['data'])) { |
||
419 | $_REQUEST = $_REQUEST['data']; |
||
420 | } |
||
421 | } |
||
422 | $_REQUEST = $_REQUEST ? $_REQUEST : $_POST; |
||
423 | |||
424 | // apply delta |
||
425 | static::applyRecordDelta($Record, $_REQUEST); |
||
426 | |||
427 | // call template function |
||
428 | static::onBeforeRecordValidated($Record, $_REQUEST); |
||
429 | |||
430 | // validate |
||
431 | if ($Record->validate()) { |
||
432 | // call template function |
||
433 | static::onBeforeRecordSaved($Record, $_REQUEST); |
||
434 | |||
435 | try { |
||
436 | // save session |
||
437 | $Record->save(); |
||
438 | } catch (Exception $e) { |
||
439 | return static::respond('Error', [ |
||
440 | 'success' => false, |
||
441 | 'failed' => [ |
||
442 | 'errors' => $e->getMessage(), |
||
443 | ], |
||
444 | ]); |
||
445 | } |
||
446 | |||
447 | // call template function |
||
448 | static::onRecordSaved($Record, $_REQUEST); |
||
449 | |||
450 | // fire created response |
||
451 | $responseID = static::getTemplateName($className::$singularNoun).'Saved'; |
||
452 | $responseData = [ |
||
453 | 'success' => true, |
||
454 | 'data' => $Record, |
||
455 | ]; |
||
456 | return static::respond($responseID, $responseData); |
||
457 | } |
||
458 | |||
459 | // fall through back to form if validation failed |
||
460 | } |
||
461 | |||
462 | $responseID = static::getTemplateName($className::$singularNoun).'Edit'; |
||
463 | $responseData = [ |
||
464 | 'success' => false, |
||
465 | 'data' => $Record, |
||
466 | ]; |
||
467 | |||
468 | return static::respond($responseID, $responseData); |
||
469 | } |
||
470 | |||
471 | |||
472 | public static function handleDeleteRequest(ActiveRecord $Record) |
||
473 | { |
||
474 | $className = static::$recordClass; |
||
475 | |||
476 | if (!static::checkWriteAccess($Record)) { |
||
477 | return static::throwUnauthorizedError(); |
||
478 | } |
||
479 | |||
480 | if ($_SERVER['REQUEST_METHOD'] == 'POST') { |
||
481 | $data = $Record->data; |
||
482 | $Record->destroy(); |
||
483 | |||
484 | // call cleanup function after delete |
||
485 | static::onRecordDeleted($Record, $data); |
||
486 | |||
487 | // fire created response |
||
488 | return static::respond(static::getTemplateName($className::$singularNoun).'Deleted', [ |
||
489 | 'success' => true, |
||
490 | 'data' => $Record, |
||
491 | ]); |
||
492 | } |
||
493 | |||
494 | return static::respond('confirm', [ |
||
495 | 'question' => 'Are you sure you want to delete this '.$className::$singularNoun.'?', |
||
496 | 'data' => $Record, |
||
497 | ]); |
||
498 | } |
||
499 | |||
500 | |||
501 | public static function respond($responseID, $responseData = [], $responseMode = false) |
||
502 | { |
||
503 | // default to static property |
||
504 | if (!$responseMode) { |
||
505 | $responseMode = static::$responseMode; |
||
506 | } |
||
507 | |||
508 | return parent::respond($responseID, $responseData, $responseMode); |
||
509 | } |
||
510 | |||
511 | // access control template functions |
||
512 | public static function checkBrowseAccess($arguments) |
||
513 | { |
||
514 | return true; |
||
515 | } |
||
516 | |||
517 | public static function checkReadAccess(ActiveRecord $Record) |
||
518 | { |
||
519 | return true; |
||
520 | } |
||
521 | |||
522 | public static function checkWriteAccess(ActiveRecord $Record) |
||
525 | } |
||
526 | |||
527 | public static function checkAPIAccess() |
||
528 | { |
||
529 | return true; |
||
530 | } |
||
531 | |||
532 | public static function throwUnauthorizedError() |
||
533 | { |
||
534 | return static::respond('Unauthorized', [ |
||
535 | 'success' => false, |
||
536 | 'failed' => [ |
||
537 | 'errors' => 'Login required.', |
||
538 | ], |
||
539 | ]); |
||
540 | } |
||
541 | |||
542 | public static function throwAPIUnAuthorizedError() |
||
543 | { |
||
544 | return static::respond('Unauthorized', [ |
||
545 | 'success' => false, |
||
546 | 'failed' => [ |
||
547 | 'errors' => 'API access required.', |
||
548 | ], |
||
549 | ]); |
||
550 | } |
||
551 | |||
552 | public static function throwNotFoundError() |
||
553 | { |
||
554 | return static::respond('error', [ |
||
555 | 'success' => false, |
||
556 | 'failed' => [ |
||
557 | 'errors' => 'Record not found.', |
||
558 | ], |
||
559 | ]); |
||
560 | } |
||
561 | |||
562 | public static function onRecordRequestNotHandled(ActiveRecord $Record, $action) |
||
563 | { |
||
564 | return static::respond('error', [ |
||
565 | 'success' => false, |
||
566 | 'failed' => [ |
||
567 | 'errors' => 'Malformed request.', |
||
568 | ], |
||
569 | ]); |
||
570 | } |
||
571 | |||
572 | |||
573 | |||
574 | public static function getTemplateName($noun) |
||
575 | { |
||
576 | return preg_replace_callback('/\s+([a-zA-Z])/', function ($matches) { |
||
577 | return strtoupper($matches[1]); |
||
578 | }, $noun); |
||
579 | } |
||
580 | |||
581 | public static function applyRecordDelta(ActiveRecord $Record, $data) |
||
582 | { |
||
583 | if (is_array(static::$editableFields)) { |
||
584 | $Record->setFields(array_intersect_key($data, array_flip(static::$editableFields))); |
||
585 | } else { |
||
586 | $Record->setFields($data); |
||
587 | } |
||
588 | } |
||
589 | |||
590 | // event template functions |
||
591 | protected static function onRecordCreated(ActiveRecord $Record, $data) |
||
592 | { |
||
593 | } |
||
594 | protected static function onBeforeRecordValidated(ActiveRecord $Record, $data) |
||
595 | { |
||
596 | } |
||
597 | protected static function onBeforeRecordSaved(ActiveRecord $Record, $data) |
||
599 | } |
||
600 | protected static function onRecordDeleted(ActiveRecord $Record, $data) |
||
602 | } |
||
603 | protected static function onRecordSaved(ActiveRecord $Record, $data) |
||
604 | { |
||
605 | } |
||
606 | |||
607 | protected static function throwRecordNotFoundError() |
||
608 | { |
||
609 | return static::throwNotFoundError(); |
||
610 | } |
||
611 | } |
||
612 |
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.
Unreachable code is most often the result of
return
,die
orexit
statements that have been added for debug purposes.In the above example, the last
return false
will never be executed, because a return statement has already been met in every possible execution path.