Total Complexity | 55 |
Total Lines | 470 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like GetPaid_REST_CRUD_Controller 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 GetPaid_REST_CRUD_Controller, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
18 | class GetPaid_REST_CRUD_Controller extends GetPaid_REST_Controller { |
||
19 | |||
20 | /** |
||
21 | * Contains this controller's class name. |
||
22 | * |
||
23 | * @var string |
||
24 | */ |
||
25 | public $crud_class; |
||
26 | |||
27 | /** |
||
28 | * Contains the current CRUD object. |
||
29 | * |
||
30 | * @var GetPaid_Data |
||
31 | */ |
||
32 | protected $data_object; |
||
33 | |||
34 | /** |
||
35 | * Registers the routes for the objects of the controller. |
||
36 | * |
||
37 | * @since 1.0.19 |
||
38 | * |
||
39 | * @see register_rest_route() |
||
40 | */ |
||
41 | public function register_namespace_routes( $namespace ) { |
||
42 | |||
43 | register_rest_route( |
||
44 | $namespace, |
||
45 | '/' . $this->rest_base, |
||
46 | array( |
||
47 | array( |
||
48 | 'methods' => WP_REST_Server::READABLE, |
||
49 | 'callback' => array( $this, 'get_items' ), |
||
50 | 'permission_callback' => array( $this, 'get_items_permissions_check' ), |
||
51 | 'args' => $this->get_collection_params(), |
||
52 | ), |
||
53 | array( |
||
54 | 'methods' => WP_REST_Server::CREATABLE, |
||
55 | 'callback' => array( $this, 'create_item' ), |
||
56 | 'permission_callback' => array( $this, 'create_item_permissions_check' ), |
||
57 | 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), |
||
58 | ), |
||
59 | 'schema' => array( $this, 'get_public_item_schema' ), |
||
60 | ) |
||
61 | ); |
||
62 | |||
63 | $get_item_args = array( |
||
64 | 'context' => $this->get_context_param( array( 'default' => 'view' ) ), |
||
65 | ); |
||
66 | |||
67 | register_rest_route( |
||
68 | $namespace, |
||
69 | '/' . $this->rest_base . '/(?P<id>[\d]+)', |
||
70 | array( |
||
71 | 'args' => array( |
||
72 | 'id' => array( |
||
73 | 'description' => __( 'Unique identifier for the object.', 'invoicing' ), |
||
74 | 'type' => 'integer', |
||
75 | ), |
||
76 | ), |
||
77 | array( |
||
78 | 'methods' => WP_REST_Server::READABLE, |
||
79 | 'callback' => array( $this, 'get_item' ), |
||
80 | 'permission_callback' => array( $this, 'get_item_permissions_check' ), |
||
81 | 'args' => $get_item_args, |
||
82 | ), |
||
83 | array( |
||
84 | 'methods' => WP_REST_Server::EDITABLE, |
||
85 | 'callback' => array( $this, 'update_item' ), |
||
86 | 'permission_callback' => array( $this, 'update_item_permissions_check' ), |
||
87 | 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), |
||
88 | ), |
||
89 | array( |
||
90 | 'methods' => WP_REST_Server::DELETABLE, |
||
91 | 'callback' => array( $this, 'delete_item' ), |
||
92 | 'permission_callback' => array( $this, 'delete_item_permissions_check' ), |
||
93 | 'args' => array( |
||
94 | 'force' => array( |
||
95 | 'type' => 'boolean', |
||
96 | 'default' => false, |
||
97 | 'description' => __( 'Whether to bypass Trash and force deletion.', 'invoicing' ), |
||
98 | ), |
||
99 | ), |
||
100 | ), |
||
101 | 'schema' => array( $this, 'get_public_item_schema' ), |
||
102 | ) |
||
103 | ); |
||
104 | |||
105 | } |
||
106 | |||
107 | /** |
||
108 | * Saves a single object. |
||
109 | * |
||
110 | * @param GetPaid_Data $object Object to save. |
||
111 | * @return WP_Error|GetPaid_Data |
||
112 | */ |
||
113 | protected function save_object( $object ) { |
||
114 | $object->save(); |
||
115 | |||
116 | if ( ! empty( $object->last_error ) ) { |
||
117 | return new WP_Error( 'rest_cannot_save', $object->last_error, array( 'status' => 400 ) ); |
||
118 | } |
||
119 | |||
120 | return new $this->crud_class( $object->get_id() ); |
||
121 | } |
||
122 | |||
123 | /** |
||
124 | * Retrieves a single object. |
||
125 | * |
||
126 | * @since 1.0.13 |
||
127 | * |
||
128 | * @param int|WP_Post $object_id Supplied ID. |
||
129 | * @return GetPaid_Data|WP_Error GetPaid_Data object if ID is valid, WP_Error otherwise. |
||
130 | */ |
||
131 | protected function get_object( $object_id ) { |
||
132 | |||
133 | // Do we have an object? |
||
134 | if ( empty( $this->crud_class ) || ! class_exists( $this->crud_class ) ) { |
||
135 | return new WP_Error( 'no_crud_class', __( 'You need to specify a CRUD class for this controller', 'invoicing' ) ); |
||
136 | } |
||
137 | |||
138 | // Fetch the object. |
||
139 | $object = new $this->crud_class( $object_id ); |
||
140 | if ( ! empty( $object->last_error ) ) { |
||
141 | return new WP_Error( 'rest_object_invalid_id', $object->last_error, array( 'status' => 404 ) ); |
||
142 | } |
||
143 | |||
144 | $this->data_object = $object; |
||
145 | return $object->get_id() ? $object : new WP_Error( 'rest_object_invalid_id', __( 'Invalid ID.', 'invoicing' ), array( 'status' => 404 ) ); |
||
146 | |||
147 | } |
||
148 | |||
149 | /** |
||
150 | * Get a single object. |
||
151 | * |
||
152 | * @param WP_REST_Request $request Full details about the request. |
||
153 | * @return WP_Error|WP_REST_Response |
||
154 | */ |
||
155 | public function get_item( $request ) { |
||
156 | |||
157 | // Fetch the item. |
||
158 | $object = $this->get_object( $request['id'] ); |
||
159 | |||
160 | if ( is_wp_error( $object ) ) { |
||
161 | return $object; |
||
162 | } |
||
163 | |||
164 | // Generate a response. |
||
165 | return rest_ensure_response( $this->prepare_item_for_response( $object, $request ) ); |
||
|
|||
166 | |||
167 | } |
||
168 | |||
169 | /** |
||
170 | * Create a single object. |
||
171 | * |
||
172 | * @param WP_REST_Request $request Full details about the request. |
||
173 | * @return WP_Error|WP_REST_Response |
||
174 | */ |
||
175 | public function create_item( $request ) { |
||
176 | |||
177 | // Can not create an existing item. |
||
178 | if ( ! empty( $request['id'] ) ) { |
||
179 | /* translators: %s: post type */ |
||
180 | return new WP_Error( "getpaid_rest_{$this->rest_base}_exists", __( 'Cannot create existing resource.', 'invoicing' ), array( 'status' => 400 ) ); |
||
181 | } |
||
182 | |||
183 | // Generate a GetPaid_Data object from the request. |
||
184 | $object = $this->prepare_item_for_database( $request ); |
||
185 | if ( is_wp_error( $object ) ) { |
||
186 | return $object; |
||
187 | } |
||
188 | |||
189 | // Save the object. |
||
190 | $object = $this->save_object( $object ); |
||
191 | if ( is_wp_error( $object ) ) { |
||
192 | return $object; |
||
193 | } |
||
194 | |||
195 | // Save special fields. |
||
196 | $save_special = $this->update_additional_fields_for_object( $object, $request ); |
||
197 | if ( is_wp_error( $save_special ) ) { |
||
198 | $object->delete( true ); |
||
199 | return $save_special; |
||
200 | } |
||
201 | |||
202 | $request->set_param( 'context', 'edit' ); |
||
203 | $response = $this->prepare_item_for_response( $object, $request ); |
||
204 | $response = rest_ensure_response( $response ); |
||
205 | $response->set_status( 201 ); |
||
206 | $response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $object->get_id() ) ) ); |
||
207 | |||
208 | return $response; |
||
209 | } |
||
210 | |||
211 | /** |
||
212 | * Update a single object. |
||
213 | * |
||
214 | * @param WP_REST_Request $request Full details about the request. |
||
215 | * @return WP_Error|WP_REST_Response |
||
216 | */ |
||
217 | public function update_item( $request ) { |
||
218 | |||
219 | // Fetch the item. |
||
220 | $object = $this->get_object( $request['id'] ); |
||
221 | if ( is_wp_error( $object ) ) { |
||
222 | return $object; |
||
223 | } |
||
224 | |||
225 | // Prepare the item for saving. |
||
226 | $object = $this->prepare_item_for_database( $request ); |
||
227 | if ( is_wp_error( $object ) ) { |
||
228 | return $object; |
||
229 | } |
||
230 | |||
231 | // Save the item. |
||
232 | $object = $this->save_object( $object ); |
||
233 | if ( is_wp_error( $object ) ) { |
||
234 | return $object; |
||
235 | } |
||
236 | |||
237 | // Save special fields (those added via hooks). |
||
238 | $save_special = $this->update_additional_fields_for_object( $object, $request ); |
||
239 | if ( is_wp_error( $save_special ) ) { |
||
240 | return $save_special; |
||
241 | } |
||
242 | |||
243 | $request->set_param( 'context', 'edit' ); |
||
244 | $response = $this->prepare_item_for_response( $object, $request ); |
||
245 | return rest_ensure_response( $response ); |
||
246 | } |
||
247 | |||
248 | /** |
||
249 | * Prepare links for the request. |
||
250 | * |
||
251 | * @param GetPaid_Data $object GetPaid_Data object. |
||
252 | * @return array Links for the given object. |
||
253 | */ |
||
254 | protected function prepare_links( $object ) { |
||
255 | |||
256 | $links = array( |
||
257 | 'self' => array( |
||
258 | 'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $object->get_id() ) ), |
||
259 | ), |
||
260 | 'collection' => array( |
||
261 | 'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ), |
||
262 | ), |
||
263 | ); |
||
264 | |||
265 | return $links; |
||
266 | } |
||
267 | |||
268 | /** |
||
269 | * Get the query params for collections of attachments. |
||
270 | * |
||
271 | * @return array |
||
272 | */ |
||
273 | public function get_collection_params() { |
||
277 | } |
||
278 | |||
279 | /** |
||
280 | * Only return writable props from schema. |
||
281 | * |
||
282 | * @param array $schema Schema. |
||
283 | * @return bool |
||
284 | */ |
||
285 | public function filter_writable_props( $schema ) { |
||
287 | } |
||
288 | |||
289 | /** |
||
290 | * Prepare a single object for create or update. |
||
291 | * |
||
292 | * @since 1.0.19 |
||
293 | * @param WP_REST_Request $request Request object. |
||
294 | * @return GetPaid_Data|WP_Error Data object or WP_Error. |
||
295 | */ |
||
296 | protected function prepare_item_for_database( $request ) { |
||
297 | |||
298 | // Do we have an object? |
||
299 | if ( empty( $this->crud_class ) || ! class_exists( $this->crud_class ) ) { |
||
300 | return new WP_Error( 'no_crud_class', __( 'You need to specify a CRUD class for this controller', 'invoicing' ) ); |
||
301 | } |
||
302 | |||
303 | // Prepare the object. |
||
304 | $id = isset( $request['id'] ) ? absint( $request['id'] ) : 0; |
||
305 | $object = new $this->crud_class( $id ); |
||
306 | |||
307 | // Abort if an error exists. |
||
308 | if ( ! empty( $object->last_error ) ) { |
||
309 | return new WP_Error( 'invalid_item', $object->last_error ); |
||
310 | } |
||
311 | |||
312 | $schema = $this->get_item_schema(); |
||
313 | $data_keys = array_keys( array_filter( $schema['properties'], array( $this, 'filter_writable_props' ) ) ); |
||
314 | |||
315 | // Handle all writable props. |
||
316 | foreach ( $data_keys as $key ) { |
||
317 | $value = $request[ $key ]; |
||
318 | |||
319 | if ( ! is_null( $value ) ) { |
||
320 | switch ( $key ) { |
||
321 | |||
322 | case 'meta_data': |
||
323 | if ( is_array( $value ) ) { |
||
324 | foreach ( $value as $meta ) { |
||
325 | $object->update_meta_data( $meta['key'], $meta['value'], isset( $meta['id'] ) ? $meta['id'] : '' ); |
||
326 | } |
||
327 | } |
||
328 | break; |
||
329 | |||
330 | default: |
||
331 | if ( is_callable( array( $object, "set_{$key}" ) ) ) { |
||
332 | $object->{"set_{$key}"}( $value ); |
||
333 | } |
||
334 | break; |
||
335 | } |
||
336 | } |
||
337 | } |
||
338 | |||
339 | // Filters an object before it is inserted via the REST API.. |
||
340 | return apply_filters( "getpaid_rest_pre_insert_{$this->rest_base}_object", $object, $request ); |
||
341 | } |
||
342 | |||
343 | /** |
||
344 | * Retrieves data from a GetPaid class. |
||
345 | * |
||
346 | * @since 1.0.19 |
||
347 | * @param GetPaid_Meta_Data[] $meta_data meta data objects. |
||
348 | * @return array |
||
349 | */ |
||
350 | protected function prepare_object_meta_data( $meta_data ) { |
||
351 | $meta = array(); |
||
352 | |||
353 | foreach ( $meta_data as $object ) { |
||
354 | $meta[] = $object->get_data(); |
||
355 | } |
||
356 | |||
357 | return $meta; |
||
358 | } |
||
359 | |||
360 | /** |
||
361 | * Retrieves invoice items. |
||
362 | * |
||
363 | * @since 1.0.19 |
||
364 | * @param WPInv_Invoice $invoice Invoice items. |
||
365 | * @param array $fields Fields to include. |
||
366 | * @return array |
||
367 | */ |
||
368 | protected function prepare_invoice_items( $invoice ) { |
||
369 | $items = array(); |
||
370 | |||
371 | foreach ( $invoice->get_items() as $item ) { |
||
372 | |||
373 | $item_data = $item->prepare_data_for_saving(); |
||
374 | |||
375 | if ( 'amount' == $invoice->get_template() ) { |
||
376 | $item_data['quantity'] = 1; |
||
377 | } |
||
378 | |||
379 | $items[] = $item_data; |
||
380 | } |
||
381 | |||
382 | return $items; |
||
383 | } |
||
384 | |||
385 | /** |
||
386 | * Retrieves data from a GetPaid class. |
||
387 | * |
||
388 | * @since 1.0.19 |
||
389 | * @param GetPaid_Data $object Data object. |
||
390 | * @param array $fields Fields to include. |
||
391 | * @param string $context either view or edit. |
||
392 | * @return array |
||
393 | */ |
||
394 | protected function prepare_object_data( $object, $fields, $context = 'view' ) { |
||
395 | |||
396 | $data = array(); |
||
397 | |||
398 | // Handle all writable props. |
||
399 | foreach ( array_keys( $this->get_schema_properties() ) as $key ) { |
||
400 | |||
401 | // Abort if it is not included. |
||
402 | if ( ! empty( $fields ) && ! $this->is_field_included( $key, $fields ) ) { |
||
403 | continue; |
||
404 | } |
||
405 | |||
406 | // Or this current object does not support the field. |
||
407 | if ( ! $this->object_supports_field( $object, $key ) ) { |
||
408 | continue; |
||
409 | } |
||
410 | |||
411 | // Handle meta data. |
||
412 | if ( $key == 'meta_data' ) { |
||
413 | $data['meta_data'] = $this->prepare_object_meta_data( $object->get_meta_data() ); |
||
414 | continue; |
||
415 | } |
||
416 | |||
417 | // Handle items. |
||
418 | if ( $key == 'items' && is_a( $object, 'WPInv_Invoice' ) ) { |
||
419 | $data['items'] = $this->prepare_invoice_items( $object ); |
||
420 | continue; |
||
421 | } |
||
422 | |||
423 | // Booleans. |
||
424 | if ( is_callable( array( $object, $key ) ) ) { |
||
425 | $data[ $key ] = $object->$key( $context ); |
||
426 | continue; |
||
427 | } |
||
428 | |||
429 | // Get object value. |
||
430 | if ( is_callable( array( $object, "get_{$key}" ) ) ) { |
||
431 | $value = $object->{"get_{$key}"}( $context ); |
||
432 | |||
433 | // If the value is an instance of GetPaid_Data... |
||
434 | if ( is_a( $value, 'GetPaid_Data' ) ) { |
||
435 | $value = $value->get_data( $context ); |
||
436 | } |
||
437 | |||
438 | // For objects, retrieves it's properties. |
||
439 | $data[ $key ] = is_object( $value ) ? get_object_vars( $value ) : $value; |
||
440 | continue; |
||
441 | } |
||
442 | } |
||
443 | |||
444 | return $data; |
||
445 | } |
||
446 | |||
447 | /** |
||
448 | * Checks if a key should be included in a response. |
||
449 | * |
||
450 | * @since 1.0.19 |
||
451 | * @param GetPaid_Data $object Data object. |
||
452 | * @param string $field_key The key to check for. |
||
453 | * @return bool |
||
454 | */ |
||
455 | public function object_supports_field( $object, $field_key ) { |
||
456 | return apply_filters( 'getpaid_rest_object_supports_key', true, $object, $field_key ); |
||
457 | } |
||
458 | |||
459 | /** |
||
460 | * Prepare a single object output for response. |
||
461 | * |
||
462 | * @since 1.0.19 |
||
463 | * @param GetPaid_Data $object Data object. |
||
464 | * @param WP_REST_Request $request Request object. |
||
465 | * @return WP_REST_Response |
||
466 | */ |
||
467 | public function prepare_item_for_response( $object, $request ) { |
||
488 | } |
||
489 | |||
490 | } |
||
491 |