Complex classes like GridFieldDetailForm_ItemRequest 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
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 GridFieldDetailForm_ItemRequest, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
228 | class GridFieldDetailForm_ItemRequest extends RequestHandler { |
||
229 | |||
230 | private static $allowed_actions = array( |
||
231 | 'edit', |
||
232 | 'view', |
||
233 | 'ItemEditForm' |
||
234 | ); |
||
235 | |||
236 | /** |
||
237 | * |
||
238 | * @var GridField |
||
239 | */ |
||
240 | protected $gridField; |
||
241 | |||
242 | /** |
||
243 | * |
||
244 | * @var GridField_URLHandler |
||
245 | */ |
||
246 | protected $component; |
||
247 | |||
248 | /** |
||
249 | * |
||
250 | * @var DataObject |
||
251 | */ |
||
252 | protected $record; |
||
253 | |||
254 | /** |
||
255 | * This represents the current parent RequestHandler (which does not necessarily need to be a Controller). |
||
256 | * It allows us to traverse the RequestHandler chain upwards to reach the Controller stack. |
||
257 | * |
||
258 | * @var RequestHandler |
||
259 | */ |
||
260 | protected $popupController; |
||
261 | |||
262 | /** |
||
263 | * |
||
264 | * @var string |
||
265 | */ |
||
266 | protected $popupFormName; |
||
267 | |||
268 | /** |
||
269 | * @var String |
||
270 | */ |
||
271 | protected $template = 'GridFieldItemEditView'; |
||
272 | |||
273 | private static $url_handlers = array( |
||
274 | '$Action!' => '$Action', |
||
275 | '' => 'edit', |
||
276 | ); |
||
277 | |||
278 | /** |
||
279 | * |
||
280 | * @param GridFIeld $gridField |
||
281 | * @param GridField_URLHandler $component |
||
282 | * @param DataObject $record |
||
283 | * @param RequestHandler $requestHandler |
||
284 | * @param string $popupFormName |
||
285 | */ |
||
286 | public function __construct($gridField, $component, $record, $requestHandler, $popupFormName) { |
||
294 | |||
295 | public function Link($action = null) { |
||
299 | |||
300 | public function view($request) { |
||
322 | |||
323 | public function edit($request) { |
||
342 | |||
343 | /** |
||
344 | * Builds an item edit form. The arguments to getCMSFields() are the popupController and |
||
345 | * popupFormName, however this is an experimental API and may change. |
||
346 | * |
||
347 | * @todo In the future, we will probably need to come up with a tigher object representing a partially |
||
348 | * complete controller with gaps for extra functionality. This, for example, would be a better way |
||
349 | * of letting Security/login put its log-in form inside a UI specified elsewhere. |
||
350 | * |
||
351 | * @return Form |
||
352 | */ |
||
353 | public function ItemEditForm() { |
||
354 | $list = $this->gridField->getList(); |
||
355 | |||
356 | if (empty($this->record)) { |
||
357 | $controller = $this->getToplevelController(); |
||
358 | $url = $controller->getRequest()->getURL(); |
||
359 | $noActionURL = $controller->removeAction($url); |
||
360 | $controller->getResponse()->removeHeader('Location'); //clear the existing redirect |
||
361 | return $controller->redirect($noActionURL, 302); |
||
362 | } |
||
363 | |||
364 | $canView = $this->record->canView(); |
||
365 | $canEdit = $this->record->canEdit(); |
||
366 | $canDelete = $this->record->canDelete(); |
||
367 | $canCreate = $this->record->canCreate(); |
||
368 | |||
369 | if(!$canView) { |
||
370 | $controller = $this->getToplevelController(); |
||
371 | // TODO More friendly error |
||
372 | return $controller->httpError(403); |
||
373 | } |
||
374 | |||
375 | // Build actions |
||
376 | $actions = $this->getFormActions(); |
||
377 | |||
378 | // If we are creating a new record in a has-many list, then |
||
379 | // pre-populate the record's foreign key. |
||
380 | if($list instanceof HasManyList && !$this->record->isInDB()) { |
||
381 | $key = $list->getForeignKey(); |
||
382 | $id = $list->getForeignID(); |
||
383 | $this->record->$key = $id; |
||
384 | } |
||
385 | |||
386 | $fields = $this->component->getFields(); |
||
387 | if(!$fields) $fields = $this->record->getCMSFields(); |
||
388 | |||
389 | // If we are creating a new record in a has-many list, then |
||
390 | // Disable the form field as it has no effect. |
||
391 | if($list instanceof HasManyList) { |
||
392 | $key = $list->getForeignKey(); |
||
393 | |||
394 | if($field = $fields->dataFieldByName($key)) { |
||
395 | $fields->makeFieldReadonly($field); |
||
396 | } |
||
397 | } |
||
398 | |||
399 | // Caution: API violation. Form expects a Controller, but we are giving it a RequestHandler instead. |
||
400 | // Thanks to this however, we are able to nest GridFields, and also access the initial Controller by |
||
401 | // dereferencing GridFieldDetailForm_ItemRequest->getController() multiple times. See getToplevelController |
||
402 | // below. |
||
403 | $form = new Form( |
||
404 | $this, |
||
405 | 'ItemEditForm', |
||
406 | $fields, |
||
407 | $actions, |
||
408 | $this->component->getValidator() |
||
409 | ); |
||
410 | |||
411 | $form->loadDataFrom($this->record, $this->record->ID == 0 ? Form::MERGE_IGNORE_FALSEISH : Form::MERGE_DEFAULT); |
||
412 | |||
413 | if($this->record->ID && !$canEdit) { |
||
414 | // Restrict editing of existing records |
||
415 | $form->makeReadonly(); |
||
416 | // Hack to re-enable delete button if user can delete |
||
417 | if ($canDelete) { |
||
418 | $form->Actions()->fieldByName('action_doDelete')->setReadonly(false); |
||
419 | } |
||
420 | } elseif(!$this->record->ID && !$canCreate) { |
||
421 | // Restrict creation of new records |
||
422 | $form->makeReadonly(); |
||
423 | } |
||
424 | |||
425 | // Load many_many extraData for record. |
||
426 | // Fields with the correct 'ManyMany' namespace need to be added manually through getCMSFields(). |
||
427 | if($list instanceof ManyManyList) { |
||
428 | $extraData = $list->getExtraData('', $this->record->ID); |
||
429 | $form->loadDataFrom(array('ManyMany' => $extraData)); |
||
430 | } |
||
431 | |||
432 | // TODO Coupling with CMS |
||
433 | $toplevelController = $this->getToplevelController(); |
||
434 | if($toplevelController && $toplevelController instanceof LeftAndMain) { |
||
435 | // Always show with base template (full width, no other panels), |
||
436 | // regardless of overloaded CMS controller templates. |
||
437 | // TODO Allow customization, e.g. to display an edit form alongside a search form from the CMS controller |
||
438 | $form->setTemplate('LeftAndMain_EditForm'); |
||
439 | $form->addExtraClass('cms-content cms-edit-form center'); |
||
440 | $form->setAttribute('data-pjax-fragment', 'CurrentForm Content'); |
||
441 | if($form->Fields()->hasTabset()) { |
||
442 | $form->Fields()->findOrMakeTab('Root')->setTemplate('CMSTabSet'); |
||
443 | $form->addExtraClass('cms-tabset'); |
||
444 | } |
||
445 | |||
446 | $form->Backlink = $this->getBackLink(); |
||
447 | } |
||
448 | |||
449 | $cb = $this->component->getItemEditFormCallback(); |
||
450 | if($cb) $cb($form, $this); |
||
451 | $this->extend("updateItemEditForm", $form); |
||
452 | return $form; |
||
453 | } |
||
454 | |||
455 | /** |
||
456 | * Build the set of form field actions for this DataObject |
||
457 | * |
||
458 | * @return FieldList |
||
459 | */ |
||
460 | protected function getFormActions() { |
||
501 | |||
502 | /** |
||
503 | * Traverse the nested RequestHandlers until we reach something that's not GridFieldDetailForm_ItemRequest. |
||
504 | * This allows us to access the Controller responsible for invoking the top-level GridField. |
||
505 | * This should be equivalent to getting the controller off the top of the controller stack via Controller::curr(), |
||
506 | * but allows us to avoid accessing the global state. |
||
507 | * |
||
508 | * GridFieldDetailForm_ItemRequests are RequestHandlers, and as such they are not part of the controller stack. |
||
509 | * |
||
510 | * @return Controller |
||
511 | */ |
||
512 | protected function getToplevelController() { |
||
519 | |||
520 | protected function getBackLink(){ |
||
536 | |||
537 | /** |
||
538 | * Get the list of extra data from the $record as saved into it by |
||
539 | * {@see Form::saveInto()} |
||
540 | * |
||
541 | * Handles detection of falsey values explicitly saved into the |
||
542 | * DataObject by formfields |
||
543 | * |
||
544 | * @param DataObject $record |
||
545 | * @param SS_List $list |
||
546 | * @return array List of data to write to the relation |
||
547 | */ |
||
548 | protected function getExtraSavedData($record, $list) { |
||
563 | |||
564 | public function doSave($data, $form) { |
||
596 | |||
597 | /** |
||
598 | * Response object for this request after a successful save |
||
599 | * |
||
600 | * @param bool $isNewRecord True if this record was just created |
||
601 | * @return SS_HTTPResponse|HTMLText |
||
602 | */ |
||
603 | protected function redirectAfterSave($isNewRecord) { |
||
620 | |||
621 | public function httpError($errorCode, $errorMessage = null) { |
||
625 | |||
626 | /** |
||
627 | * Loads the given form data into the underlying dataobject and relation |
||
628 | * |
||
629 | * @param array $data |
||
630 | * @param Form $form |
||
631 | * @throws ValidationException On error |
||
632 | * @return DataObject Saved record |
||
633 | */ |
||
634 | protected function saveFormIntoRecord($data, $form) { |
||
656 | |||
657 | /** |
||
658 | * Generate a response object for a form validation error |
||
659 | * |
||
660 | * @param Form $form The source form |
||
661 | * @param ValidationException $e The validation error message |
||
662 | * @return SS_HTTPResponse |
||
663 | * @throws SS_HTTPResponse_Exception |
||
664 | */ |
||
665 | protected function generateValidationResponse($form, $e) { |
||
682 | |||
683 | |||
684 | public function doDelete($data, $form) { |
||
718 | |||
719 | /** |
||
720 | * @param String |
||
721 | */ |
||
722 | public function setTemplate($template) { |
||
726 | |||
727 | /** |
||
728 | * @return String |
||
729 | */ |
||
730 | public function getTemplate() { |
||
733 | |||
734 | /** |
||
735 | * @return Controller |
||
736 | */ |
||
737 | public function getController() { |
||
740 | |||
741 | /** |
||
742 | * @return GridField |
||
743 | */ |
||
744 | public function getGridField() { |
||
747 | |||
748 | /** |
||
749 | * @return DataObject |
||
750 | */ |
||
751 | public function getRecord() { |
||
754 | |||
755 | /** |
||
756 | * CMS-specific functionality: Passes through navigation breadcrumbs |
||
757 | * to the template, and includes the currently edited record (if any). |
||
758 | * see {@link LeftAndMain->Breadcrumbs()} for details. |
||
759 | * |
||
760 | * @param boolean $unlinked |
||
761 | * @return ArrayData |
||
762 | */ |
||
763 | public function Breadcrumbs($unlinked = false) { |
||
782 | } |
||
783 |
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: