Complex classes like CampaignAdmin 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 CampaignAdmin, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
9 | class CampaignAdmin extends LeftAndMain implements PermissionProvider { |
||
10 | |||
11 | private static $allowed_actions = [ |
||
12 | 'set', |
||
13 | 'sets', |
||
14 | 'schema', |
||
15 | 'DetailEditForm', |
||
16 | 'readCampaigns', |
||
17 | 'readCampaign', |
||
18 | 'deleteCampaign', |
||
19 | 'publishCampaign', |
||
20 | ]; |
||
21 | |||
22 | private static $menu_priority = 11; |
||
23 | |||
24 | private static $menu_title = 'Campaigns'; |
||
25 | |||
26 | private static $tree_class = 'ChangeSet'; |
||
27 | |||
28 | private static $url_handlers = [ |
||
29 | 'GET sets' => 'readCampaigns', |
||
30 | 'POST set/$ID/publish' => 'publishCampaign', |
||
31 | 'GET set/$ID/$Name' => 'readCampaign', |
||
32 | 'DELETE set/$ID' => 'deleteCampaign', |
||
33 | ]; |
||
34 | |||
35 | private static $url_segment = 'campaigns'; |
||
36 | |||
37 | /** |
||
38 | * Size of thumbnail width |
||
39 | * |
||
40 | * @config |
||
41 | * @var int |
||
42 | */ |
||
43 | private static $thumbnail_width = 64; |
||
44 | |||
45 | /** |
||
46 | * Size of thumbnail height |
||
47 | * |
||
48 | * @config |
||
49 | * @var int |
||
50 | */ |
||
51 | private static $thumbnail_height = 64; |
||
52 | |||
53 | public function getClientConfig() { |
||
54 | return array_merge(parent::getClientConfig(), [ |
||
55 | 'form' => [ |
||
56 | // TODO Use schemaUrl instead |
||
57 | 'EditForm' => [ |
||
58 | 'schemaUrl' => $this->Link('schema/EditForm') |
||
59 | ], |
||
60 | 'DetailEditForm' => [ |
||
61 | 'schemaUrl' => $this->Link('schema/DetailEditForm') |
||
62 | ], |
||
63 | ], |
||
64 | 'campaignViewRoute' => $this->Link() . ':type?/:id?/:view?', |
||
65 | 'itemListViewEndpoint' => $this->Link() . 'set/:id/show', |
||
66 | 'publishEndpoint' => [ |
||
67 | 'url' => $this->Link() . 'set/:id/publish', |
||
68 | 'method' => 'post' |
||
69 | ] |
||
70 | ]); |
||
71 | } |
||
72 | |||
73 | public function schema($request) { |
||
74 | // TODO Hardcoding schema until we can get GridField to generate a schema dynamically |
||
75 | $json = <<<JSON |
||
76 | { |
||
77 | "id": "Form_EditForm", |
||
78 | "schema": { |
||
79 | "name": "EditForm", |
||
80 | "id": "Form_EditForm", |
||
81 | "action": "schema", |
||
82 | "method": "GET", |
||
83 | "schema_url": "admin\/campaigns\/schema\/EditForm", |
||
84 | "attributes": { |
||
85 | "id": "Form_EditForm", |
||
86 | "action": "admin\/campaigns\/EditForm", |
||
87 | "method": "POST", |
||
88 | "enctype": "multipart\/form-data", |
||
89 | "target": null |
||
90 | }, |
||
91 | "data": [], |
||
92 | "fields": [{ |
||
93 | "name": "ID", |
||
94 | "id": "Form_EditForm_ID", |
||
95 | "type": "Hidden", |
||
96 | "component": null, |
||
97 | "holder_id": null, |
||
98 | "title": false, |
||
99 | "source": null, |
||
100 | "extraClass": "hidden nolabel", |
||
101 | "description": null, |
||
102 | "rightTitle": null, |
||
103 | "leftTitle": null, |
||
104 | "readOnly": false, |
||
105 | "disabled": false, |
||
106 | "customValidationMessage": "", |
||
107 | "attributes": [], |
||
108 | "data": [] |
||
109 | }, { |
||
110 | "name": "ChangeSets", |
||
111 | "id": "Form_EditForm_ChangeSets", |
||
112 | "type": "Custom", |
||
113 | "component": "GridField", |
||
114 | "holder_id": null, |
||
115 | "title": "Campaigns", |
||
116 | "source": null, |
||
117 | "extraClass": null, |
||
118 | "description": null, |
||
119 | "rightTitle": null, |
||
120 | "leftTitle": null, |
||
121 | "readOnly": false, |
||
122 | "disabled": false, |
||
123 | "customValidationMessage": "", |
||
124 | "attributes": [], |
||
125 | "data": { |
||
126 | "recordType": "ChangeSet", |
||
127 | "collectionReadEndpoint": { |
||
128 | "url": "admin\/campaigns\/sets", |
||
129 | "method": "GET" |
||
130 | }, |
||
131 | "itemReadEndpoint": { |
||
132 | "url": "admin\/campaigns\/set\/:id", |
||
133 | "method": "GET" |
||
134 | }, |
||
135 | "itemUpdateEndpoint": { |
||
136 | "url": "admin\/campaigns\/set\/:id", |
||
137 | "method": "PUT" |
||
138 | }, |
||
139 | "itemCreateEndpoint": { |
||
140 | "url": "admin\/campaigns\/set\/:id", |
||
141 | "method": "POST" |
||
142 | }, |
||
143 | "itemDeleteEndpoint": { |
||
144 | "url": "admin\/campaigns\/set\/:id", |
||
145 | "method": "DELETE" |
||
146 | }, |
||
147 | "editFormSchemaEndpoint": "admin\/campaigns\/schema\/DetailEditForm", |
||
148 | "columns": [ |
||
149 | {"name": "Title", "field": "Name"}, |
||
150 | {"name": "Changes", "field": "_embedded.ChangeSetItems.length"}, |
||
151 | {"name": "Description", "field": "Description"} |
||
152 | ] |
||
153 | } |
||
154 | }, { |
||
155 | "name": "SecurityID", |
||
156 | "id": "Form_EditForm_SecurityID", |
||
157 | "type": "Hidden", |
||
158 | "component": null, |
||
159 | "holder_id": null, |
||
160 | "title": "Security ID", |
||
161 | "source": null, |
||
162 | "extraClass": "hidden", |
||
163 | "description": null, |
||
164 | "rightTitle": null, |
||
165 | "leftTitle": null, |
||
166 | "readOnly": false, |
||
167 | "disabled": false, |
||
168 | "customValidationMessage": "", |
||
169 | "attributes": [], |
||
170 | "data": [] |
||
171 | }], |
||
172 | "actions": [] |
||
173 | } |
||
174 | } |
||
175 | JSON; |
||
176 | |||
177 | $formName = $request->param('ID'); |
||
178 | if($formName == 'EditForm') { |
||
179 | $response = $this->getResponse(); |
||
180 | $response->addHeader('Content-Type', 'application/json'); |
||
181 | $response->setBody($json); |
||
182 | return $response; |
||
183 | } else { |
||
184 | return parent::schema($request); |
||
185 | } |
||
186 | } |
||
187 | |||
188 | /** |
||
189 | * REST endpoint to get a list of campaigns. |
||
190 | * |
||
191 | * @param SS_HTTPRequest $request |
||
192 | * |
||
193 | * @return SS_HTTPResponse |
||
194 | */ |
||
195 | public function readCampaigns(SS_HTTPRequest $request) { |
||
|
|||
196 | $response = new SS_HTTPResponse(); |
||
197 | $response->addHeader('Content-Type', 'application/json'); |
||
198 | $hal = $this->getListResource(); |
||
199 | $response->setBody(Convert::array2json($hal)); |
||
200 | return $response; |
||
201 | } |
||
202 | |||
203 | /** |
||
204 | * Get list contained as a hal wrapper |
||
205 | * |
||
206 | * @return array |
||
207 | */ |
||
208 | protected function getListResource() { |
||
228 | |||
229 | /** |
||
230 | * Build item resource from a changeset |
||
231 | * |
||
232 | * @param ChangeSet $changeSet |
||
233 | * @return array |
||
234 | */ |
||
235 | protected function getChangeSetResource(ChangeSet $changeSet) { |
||
263 | |||
264 | /** |
||
265 | * Build item resource from a changesetitem |
||
266 | * |
||
267 | * @param ChangeSetItem $changeSetItem |
||
268 | * @return array |
||
269 | */ |
||
270 | protected function getChangeSetItemResource(ChangeSetItem $changeSetItem) { |
||
311 | |||
312 | /** |
||
313 | * Gets viewable list of campaigns |
||
314 | * |
||
315 | * @return SS_List |
||
316 | */ |
||
317 | protected function getListItems() { |
||
324 | |||
325 | |||
326 | /** |
||
327 | * REST endpoint to get a campaign. |
||
328 | * |
||
329 | * @param SS_HTTPRequest $request |
||
330 | * |
||
331 | * @return SS_HTTPResponse |
||
332 | */ |
||
333 | public function readCampaign(SS_HTTPRequest $request) { |
||
334 | $response = new SS_HTTPResponse(); |
||
335 | |||
336 | if ($request->getHeader('Accept') == 'text/json') { |
||
337 | $response->addHeader('Content-Type', 'application/json'); |
||
338 | if (!$request->param('Name')) { |
||
339 | return (new SS_HTTPResponse(null, 400)); |
||
340 | } |
||
341 | |||
342 | $changeSet = ChangeSet::get()->byId($request->param('ID')); |
||
343 | if(!$changeSet) { |
||
344 | return (new SS_HTTPResponse(null, 404)); |
||
345 | } |
||
346 | |||
347 | if(!$changeSet->canView()) { |
||
348 | return (new SS_HTTPResponse(null, 403)); |
||
349 | } |
||
350 | |||
351 | $body = Convert::raw2json($this->getChangeSetResource($changeSet)); |
||
352 | return (new SS_HTTPResponse($body, 200)) |
||
353 | ->addHeader('Content-Type', 'application/json'); |
||
354 | } else { |
||
355 | return $this->index($request); |
||
356 | } |
||
357 | } |
||
358 | |||
359 | /** |
||
360 | * REST endpoint to delete a campaign. |
||
361 | * |
||
362 | * @param SS_HTTPRequest $request |
||
363 | * |
||
364 | * @return SS_HTTPResponse |
||
365 | */ |
||
366 | public function deleteCampaign(SS_HTTPRequest $request) { |
||
367 | $id = $request->param('ID'); |
||
368 | if (!$id || !is_numeric($id)) { |
||
369 | return (new SS_HTTPResponse(null, 400)); |
||
370 | } |
||
371 | |||
372 | $record = ChangeSet::get()->byID($id); |
||
373 | if(!$record) { |
||
374 | return (new SS_HTTPResponse(null, 404)); |
||
375 | } |
||
376 | |||
377 | if(!$record->canDelete()) { |
||
378 | return (new SS_HTTPResponse(null, 403)); |
||
379 | } |
||
380 | |||
381 | $record->delete(); |
||
382 | |||
383 | return (new SS_HTTPResponse(null, 204)); |
||
384 | } |
||
385 | |||
386 | /** |
||
387 | * REST endpoint to publish a {@link ChangeSet} and all of its items. |
||
388 | * |
||
389 | * @param SS_HTTPRequest $request |
||
390 | * |
||
391 | * @return SS_HTTPResponse |
||
392 | */ |
||
393 | public function publishCampaign(SS_HTTPRequest $request) { |
||
425 | |||
426 | /** |
||
427 | * Url handler for edit form |
||
428 | * |
||
429 | * @param SS_HTTPRequest $request |
||
430 | * @return Form |
||
431 | */ |
||
432 | public function DetailEditForm($request) { |
||
437 | |||
438 | /** |
||
439 | * @todo Use GridFieldDetailForm once it can handle structured data and form schemas |
||
440 | * |
||
441 | * @param int $id |
||
442 | * @return Form |
||
443 | */ |
||
444 | public function getDetailEditForm($id) { |
||
445 | // Get record-specific fields |
||
446 | $record = null; |
||
447 | if($id) { |
||
448 | $record = ChangeSet::get()->byId($id); |
||
449 | if(!$record || !$record->canView()) { |
||
450 | return null; |
||
451 | } |
||
452 | } |
||
453 | |||
454 | if(!$record) { |
||
455 | $record = ChangeSet::singleton(); |
||
456 | } |
||
457 | |||
458 | $fields = $record->getCMSFields(); |
||
459 | |||
460 | // Add standard fields |
||
461 | $fields->push(HiddenField::create('ID')); |
||
462 | $form = Form::create( |
||
463 | $this, |
||
464 | 'DetailEditForm', |
||
465 | $fields, |
||
466 | FieldList::create( |
||
467 | FormAction::create('save', _t('CMSMain.SAVE', 'Save')), |
||
468 | FormAction::create('cancel', _t('LeftAndMain.CANCEL', 'Cancel')) |
||
469 | ) |
||
470 | ); |
||
471 | // Configure form to respond to validation errors with form schema |
||
472 | // if requested via react. |
||
473 | $form->setValidationResponseCallback(function() use ($form) { |
||
474 | return $this->getSchemaResponse($form); |
||
475 | }); |
||
476 | |||
477 | return $form; |
||
478 | } |
||
479 | |||
480 | /** |
||
481 | * Gets user-visible url to edit a specific {@see ChangeSet} |
||
482 | * |
||
483 | * @param $itemID |
||
484 | * @return string |
||
485 | */ |
||
486 | public function SetLink($itemID) { |
||
492 | |||
493 | /** |
||
494 | * Gets user-visible url to edit a specific {@see ChangeSetItem} |
||
495 | * |
||
496 | * @param int $itemID |
||
497 | * @return string |
||
498 | */ |
||
499 | public function ItemLink($itemID) { |
||
505 | |||
506 | } |
||
507 |
This check looks from parameters that have been defined for a function or method, but which are not used in the method body.