Total Complexity | 46 |
Total Lines | 426 |
Duplicated Lines | 0 % |
Changes | 3 | ||
Bugs | 0 | Features | 1 |
Complex classes like AddLineItem 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 AddLineItem, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
32 | class AddLineItem extends GridFieldAddExistingAutocompleter |
||
33 | { |
||
34 | |||
35 | /** |
||
36 | * Default field to create the dataobject by should be Title. |
||
37 | * |
||
38 | * @var string |
||
39 | **/ |
||
40 | protected $dataObjectField = "Title"; |
||
41 | |||
42 | /** |
||
43 | * Default field to create the dataobject from. |
||
44 | * |
||
45 | * @var string |
||
46 | **/ |
||
47 | protected $source_class = "Product"; |
||
48 | |||
49 | /** |
||
50 | * When we check for existing items, should we check based on all |
||
51 | * filters or any of the chosen (setting this to true uses |
||
52 | * $list->filter() where as false uses $list->filterAny()) |
||
53 | * |
||
54 | * @var boolean |
||
55 | */ |
||
56 | protected $strict_filter = true; |
||
57 | |||
58 | /** |
||
59 | * What filter should the initial search use? |
||
60 | * |
||
61 | * @var string |
||
62 | */ |
||
63 | protected $search_filter = 'PartialMatch'; |
||
64 | |||
65 | /** |
||
66 | * Fields that we try and find our source object based on |
||
67 | * |
||
68 | * @var array |
||
69 | **/ |
||
70 | protected $filter_fields = [ |
||
71 | "Title", |
||
72 | "StockID" |
||
73 | ]; |
||
74 | |||
75 | /** |
||
76 | * Fields that we use to filter items for our autocomplete |
||
77 | * |
||
78 | * @var array |
||
79 | **/ |
||
80 | protected $autocomplete_fields = [ |
||
81 | "Title", |
||
82 | "StockID" |
||
83 | ]; |
||
84 | |||
85 | /** |
||
86 | * If filter fails, set this field when creating |
||
87 | * |
||
88 | * @var String |
||
89 | **/ |
||
90 | protected $create_field = "Title"; |
||
91 | |||
92 | /** |
||
93 | * Fields that we are mapping from the source object to our item |
||
94 | * |
||
95 | * @var array |
||
96 | **/ |
||
97 | protected $source_fields = [ |
||
98 | "Title" => "Title", |
||
99 | "StockID" => "StockID", |
||
100 | "BasePrice" => "BasePrice" |
||
101 | ]; |
||
102 | |||
103 | /** |
||
104 | * This is the field that we attempt to match a TAX rate to |
||
105 | * when setting up an order item |
||
106 | * |
||
107 | * @var string |
||
108 | **/ |
||
109 | protected $source_tax_field = "TaxPercentage"; |
||
110 | |||
111 | public function getSourceClass() |
||
112 | { |
||
113 | return $this->source_class; |
||
114 | } |
||
115 | |||
116 | public function setSourceClass($class) |
||
117 | { |
||
118 | $this->source_class = $class; |
||
119 | return $this; |
||
120 | } |
||
121 | |||
122 | public function getStrictFilter() |
||
123 | { |
||
124 | return $this->strict_filter; |
||
125 | } |
||
126 | |||
127 | public function setStrictFilter($bool) |
||
131 | } |
||
132 | |||
133 | public function getFilterFields() |
||
134 | { |
||
135 | return $this->filter_fields; |
||
136 | } |
||
137 | |||
138 | public function setFilterFields($fields) |
||
139 | { |
||
140 | $this->filter_fields = $fields; |
||
141 | return $this; |
||
142 | } |
||
143 | |||
144 | public function getAutocompleteFields() |
||
145 | { |
||
146 | return $this->autocomplete_fields; |
||
147 | } |
||
148 | |||
149 | public function setAutocompleteFields($fields) |
||
150 | { |
||
151 | $this->autocomplete_fields = $fields; |
||
152 | return $this; |
||
153 | } |
||
154 | |||
155 | public function getCreateField() |
||
156 | { |
||
157 | return $this->create_field; |
||
158 | } |
||
159 | |||
160 | public function setCreateField($field) |
||
161 | { |
||
162 | $this->create_field = $field; |
||
163 | return $this; |
||
164 | } |
||
165 | |||
166 | public function getSourceFields() |
||
167 | { |
||
168 | return $this->source_fields; |
||
169 | } |
||
170 | |||
171 | public function setSourceFields($fields) |
||
172 | { |
||
173 | $this->source_fields = $fields; |
||
174 | return $this; |
||
175 | } |
||
176 | |||
177 | public function getSourceTaxField() |
||
178 | { |
||
179 | return $this->source_tax_field; |
||
180 | } |
||
181 | |||
182 | public function setSourceTaxField($field) |
||
183 | { |
||
184 | $this->source_tax_field = $field; |
||
185 | return $this; |
||
186 | } |
||
187 | |||
188 | public function getSearchFilter() |
||
191 | } |
||
192 | |||
193 | public function setSearchFilter(string $search_filter) |
||
194 | { |
||
195 | $this->search_filter = $search_filter; |
||
196 | return $this; |
||
197 | } |
||
198 | |||
199 | |||
200 | /** |
||
201 | * Handles the add action for the given DataObject |
||
202 | * |
||
203 | * @param $grid GridFIeld |
||
204 | * @param $actionName string |
||
205 | * @param $arguments mixed |
||
206 | * @param $data array |
||
207 | **/ |
||
208 | public function handleAction(GridField $grid, $actionName, $arguments, $data) |
||
209 | { |
||
210 | if ($actionName == "addto") { |
||
211 | // Get our submitted fields and object class |
||
212 | $dbField = $this->getDataObjectField(); |
||
213 | $objClass = $grid->getModelClass(); |
||
214 | $source_class = $this->getSourceClass(); |
||
215 | $source_item = null; |
||
216 | $list = $grid->getList(); |
||
217 | $filter = []; |
||
218 | |||
219 | // Has the user used autocomplete |
||
220 | if (isset($data['relationID']) && $data['relationID']) { |
||
221 | $string = $data['relationID']; |
||
222 | } else { |
||
223 | $string = null; |
||
224 | } |
||
225 | |||
226 | foreach ($this->getFilterFields() as $filter_field) { |
||
227 | $filter[$filter_field] = $string; |
||
228 | } |
||
229 | |||
230 | // First check if we already have an object or if we need to |
||
231 | // create one |
||
232 | if ($this->getStrictFilter()) { |
||
233 | $existing_obj = $list |
||
234 | ->filter($filter) |
||
|
|||
235 | ->first(); |
||
236 | } else { |
||
237 | $existing_obj = $list |
||
238 | ->filterAny($filter) |
||
239 | ->first(); |
||
240 | } |
||
241 | |||
242 | if ($existing_obj) { |
||
243 | $obj = $existing_obj; |
||
244 | } else { |
||
245 | $obj = $objClass::create(); |
||
246 | } |
||
247 | |||
248 | // Is this a valid field |
||
249 | if (!$obj->hasField($dbField)) { |
||
250 | throw new UnexpectedValueException("Invalid field (" . $dbField . ") on " . $obj->ClassName . "."); |
||
251 | } |
||
252 | |||
253 | if ($obj->ID && $obj->canEdit()) { |
||
254 | // An existing record and can edit, update quantity |
||
255 | $curr_qty = ($obj->Quantity) ? $obj->Quantity : 0; |
||
256 | |||
257 | $obj->setCastedField( |
||
258 | "Quantity", |
||
259 | $curr_qty + 1 |
||
260 | ); |
||
261 | |||
262 | $list->add($obj); |
||
263 | } |
||
264 | |||
265 | if (!$obj->ID && $obj->canCreate()) { |
||
266 | // If source item not set, try and get one or get a |
||
267 | // an existing record |
||
268 | if (!$source_item && class_exists($source_class)) { |
||
269 | $source_item = $source_class::get() |
||
270 | ->filterAny($filter) |
||
271 | ->first(); |
||
272 | } |
||
273 | |||
274 | if ($source_item) { |
||
275 | foreach ($this->getSourceFields() as $obj_field => $source_field) { |
||
276 | $obj->setCastedField( |
||
277 | $obj_field, |
||
278 | $source_item->$source_field |
||
279 | ); |
||
280 | } |
||
281 | |||
282 | // Setup the tax |
||
283 | $tax_field = $this->getSourceTaxField(); |
||
284 | $tax = TaxRate::get()->find("Rate", $source_item->$tax_field); |
||
285 | if ($tax) { |
||
286 | $obj->TaxRateID = $tax->ID; |
||
287 | } |
||
288 | } else { |
||
289 | $obj->setCastedField($this->getCreateField(), $string); |
||
290 | } |
||
291 | |||
292 | $obj->setCastedField("Quantity", 1); |
||
293 | $list->add($obj, []); |
||
294 | } |
||
295 | |||
296 | // Finally, issue a redirect to update totals |
||
297 | $controller = Controller::curr(); |
||
298 | |||
299 | $response = $controller->response; |
||
300 | $response->addHeader('X-Pjax', 'Content'); |
||
301 | $response->addHeader('X-Reload', true); |
||
302 | |||
303 | return $controller->redirect($grid->getForm()->controller->Link(), 302); |
||
304 | } |
||
305 | } |
||
306 | |||
307 | /** |
||
308 | * Renders the TextField and add button to the GridField. |
||
309 | * |
||
310 | * @param $girdField GridField |
||
311 | * |
||
312 | * @return string HTML |
||
313 | **/ |
||
314 | public function getHTMLFragments($gridField) |
||
371 | ]; |
||
372 | } |
||
373 | |||
374 | /** |
||
375 | * Returns a json array of a search results that can be used by for |
||
376 | * example Jquery.ui.autosuggestion |
||
377 | * |
||
378 | * @param GridField $gridField |
||
379 | * @param SS_HTTPRequest $request |
||
380 | */ |
||
381 | public function doSearch($gridField, $request) |
||
382 | { |
||
383 | $source_class = $this->getSourceClass(); |
||
384 | $search_filter = $this->getSearchFilter(); |
||
385 | $params = []; |
||
386 | |||
387 | // Do we have filter fields setup? |
||
388 | if ($this->getAutocompleteFields()) { |
||
389 | $search_fields = $this->getAutocompleteFields(); |
||
390 | } else { |
||
391 | $search_fields = $this->scaffoldSearchFields($source_class); |
||
392 | } |
||
393 | |||
394 | if (!$search_fields) { |
||
395 | throw new LogicException( |
||
396 | sprintf( |
||
397 | 'GridFieldAddExistingAutocompleter: No searchable fields could be found for class "%s"', |
||
398 | $source_class |
||
399 | ) |
||
400 | ); |
||
401 | } |
||
402 | |||
403 | foreach ($search_fields as $search_field) { |
||
404 | $name = (strpos($search_field, ':') !== false) ? $search_field : $search_field . ":" . $search_filter; |
||
405 | $params[$name] = $request->getVar('gridfield_relationsearch'); |
||
406 | } |
||
407 | |||
408 | $json = []; |
||
409 | Config::nest(); |
||
410 | |||
411 | if (class_exists($source_class)) { |
||
412 | $results = DataList::create($source_class) |
||
413 | ->filterAny($params) |
||
414 | ->sort(strtok($search_fields[0], ':'), 'ASC') |
||
415 | ->limit($this->getResultsLimit()); |
||
416 | |||
417 | $originalSourceFileComments = SSViewer::config()->get('source_file_comments'); |
||
418 | |||
419 | SSViewer::config()->update('source_file_comments', false); |
||
420 | $viewer = SSViewer::fromString($this->resultsFormat); |
||
421 | |||
422 | foreach ($results as $result) { |
||
423 | $title = Convert::html2raw($viewer->process($result)); |
||
424 | $json[] = [ |
||
425 | 'label' => $title, |
||
426 | 'value' => $title, |
||
427 | 'id' => $title, |
||
428 | ]; |
||
429 | } |
||
430 | |||
431 | SSViewer::config()->update('source_file_comments', $originalSourceFileComments); |
||
432 | } |
||
433 | |||
434 | Config::unnest(); |
||
435 | $response = new HTTPResponse(json_encode($json)); |
||
436 | $response->addHeader('Content-Type', 'application/json'); |
||
437 | return $response; |
||
438 | } |
||
439 | |||
440 | /** |
||
441 | * Returns the database field for which we'll add the new data object. |
||
442 | * |
||
443 | * @return string |
||
444 | **/ |
||
445 | public function getDataObjectField() |
||
448 | } |
||
449 | |||
450 | /** |
||
451 | * Set the database field. |
||
452 | * |
||
453 | * @param $field string |
||
454 | **/ |
||
455 | public function setDataObjectField($field) |
||
458 | } |
||
459 | } |
||
460 |