Complex classes like CommentComponent 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 CommentComponent, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
53 | class CommentComponent extends Component |
||
54 | { |
||
55 | |||
56 | /** |
||
57 | * The controller this component is attached to. |
||
58 | * |
||
59 | * @var \Cake\Controller\Controller |
||
60 | */ |
||
61 | protected $_controller; |
||
62 | |||
63 | /** |
||
64 | * Default configuration. |
||
65 | * |
||
66 | * - redirectOnSuccess: Set to true to redirect to `referer` page on success. |
||
67 | * Set to false for no redirection, or set to an array|string compatible with |
||
68 | * `Controller::redirect()` method. |
||
69 | * |
||
70 | * - successMessage: Custom success alert-message. Or a callable method which |
||
71 | * must return a customized message. |
||
72 | * |
||
73 | * - errorMessage: Custom error alert-message. Or a callable method which must |
||
74 | * return a customized message. |
||
75 | * |
||
76 | * - arrayContext: Information for the ArrayContext provider used by FormHelper |
||
77 | * when rendering comments form. |
||
78 | * |
||
79 | * - validator: A custom validator object, if not provided it automatically |
||
80 | * creates one for you using the information below: |
||
81 | * |
||
82 | * - settings: Array of additional settings parameters, will be merged with |
||
83 | * those coming from Comment Plugin's configuration panel (at backend). |
||
84 | * |
||
85 | * When defining `successMessage` or `errorMessage` as callable functions you |
||
86 | * should expect two arguments. A comment entity as first argument and the |
||
87 | * controller instance this component is attached to as second argument: |
||
88 | * |
||
89 | * ```php |
||
90 | * $options['successMessage'] = function ($comment, $controller) { |
||
91 | * return 'My customized success message'; |
||
92 | * } |
||
93 | * |
||
94 | * $options['errorMessage'] = function ($comment, $controller) { |
||
95 | * return 'My customized error message'; |
||
96 | * } |
||
97 | * ``` |
||
98 | * |
||
99 | * @var array |
||
100 | */ |
||
101 | protected $_defaultConfig = [ |
||
102 | 'redirectOnSuccess' => true, |
||
103 | 'successMessage' => 'Comment saved!', |
||
104 | 'errorMessage' => 'Your comment could not be saved, please check your information.', |
||
105 | 'arrayContext' => [ |
||
106 | 'schema' => [ |
||
107 | 'comment' => [ |
||
108 | 'parent_id' => ['type' => 'integer'], |
||
109 | 'author_name' => ['type' => 'string'], |
||
110 | 'author_email' => ['type' => 'string'], |
||
111 | 'author_web' => ['type' => 'string'], |
||
112 | 'subject' => ['type' => 'string'], |
||
113 | 'body' => ['type' => 'string'], |
||
114 | ] |
||
115 | ], |
||
116 | 'defaults' => [ |
||
117 | 'comment' => [ |
||
118 | 'parent_id' => null, |
||
119 | 'author_name' => null, |
||
120 | 'author_email' => null, |
||
121 | 'author_web' => null, |
||
122 | 'subject' => null, |
||
123 | 'body' => null, |
||
124 | ] |
||
125 | ], |
||
126 | 'errors' => [ |
||
127 | 'comment' => [] |
||
128 | ] |
||
129 | ], |
||
130 | 'validator' => false, |
||
131 | 'settings' => [], // auto-filled with Comment plugin's settings |
||
132 | ]; |
||
133 | |||
134 | /** |
||
135 | * Constructor. |
||
136 | * |
||
137 | * @param \Cake\Controller\ComponentRegistry $collection A ComponentRegistry |
||
138 | * for this component |
||
139 | * @param array $config Array of configuration options to merge with defaults |
||
140 | */ |
||
141 | public function __construct(ComponentRegistry $collection, array $config = []) |
||
142 | { |
||
143 | $this->_defaultConfig['settings'] = plugin('Comment')->settings(); |
||
144 | $this->_defaultConfig['settings']['visibility'] = 0; |
||
145 | $this->_defaultConfig['errorMessage'] = __d('comment', 'Your comment could not be saved, please check your information.'); |
||
146 | $this->_defaultConfig['successMessage'] = function () { |
||
147 | if ($this->config('settings.auto_approve') || |
||
148 | $this->_controller->request->is('userAdmin') |
||
149 | ) { |
||
150 | return __d('comment', 'Comment saved!'); |
||
151 | } |
||
152 | |||
153 | return __d('comment', 'Your comment is awaiting moderation.'); |
||
154 | }; |
||
155 | parent::__construct($collection, $config); |
||
156 | $this->_controller = $this->_registry->getController(); |
||
157 | $this->_loadSettings(); |
||
158 | } |
||
159 | |||
160 | /** |
||
161 | * Called before the controller's beforeFilter method. |
||
162 | * |
||
163 | * @param Event $event The event that was triggered |
||
164 | * @return void |
||
165 | */ |
||
166 | public function beforeFilter(Event $event) |
||
171 | |||
172 | /** |
||
173 | * Called after the controller executes the requested action. |
||
174 | * |
||
175 | * @param Event $event The event that was triggered |
||
176 | * @return void |
||
177 | */ |
||
178 | public function beforeRender(Event $event) |
||
182 | |||
183 | /** |
||
184 | * Reads/writes settings for this component or for CommentHelper class. |
||
185 | * |
||
186 | * @param string|array|null $key The key to get/set, or a complete array of configs. |
||
187 | * @param mixed|null $value The value to set. |
||
188 | * @param bool $merge Whether to merge or overwrite existing config, defaults to true. |
||
189 | * @return mixed Config value being read, or the object itself on write operations. |
||
190 | * @throws \Cake\Core\Exception\Exception When trying to set a key that is invalid. |
||
191 | */ |
||
192 | public function config($key = null, $value = null, $merge = true) |
||
193 | { |
||
194 | if ($key !== null && in_array($key, array_keys($this->_defaultConfig['settings']))) { |
||
195 | $key = "settings.{$key}"; |
||
196 | } |
||
197 | |||
198 | if (!$this->_configInitialized) { |
||
199 | $this->_config = $this->_defaultConfig; |
||
200 | $this->_configInitialized = true; |
||
201 | } |
||
202 | |||
203 | if (is_array($key) || func_num_args() >= 2) { |
||
204 | $this->_configWrite($key, $value, $merge); |
||
205 | |||
206 | return $this; |
||
207 | } |
||
208 | |||
209 | return $this->_configRead($key); |
||
210 | } |
||
211 | |||
212 | /** |
||
213 | * Adds a new comment for the given entity. |
||
214 | * |
||
215 | * @param \Cake\Datasource\EntityInterface $entity The entity where to attach new comment |
||
216 | * @return bool True on success, false otherwise |
||
217 | */ |
||
218 | public function post(EntityInterface $entity) |
||
219 | { |
||
220 | $pk = (string)TableRegistry::get($entity->source())->primaryKey(); |
||
221 | if (empty($this->_controller->request->data['comment']) || |
||
222 | $this->config('settings.visibility') !== 1 || |
||
223 | !$entity->has($pk) |
||
224 | ) { |
||
225 | return false; |
||
226 | } |
||
227 | |||
228 | $this->_controller->loadModel('Comment.Comments'); |
||
229 | $data = $this->_getRequestData($entity); |
||
230 | $this->_controller->Comments->validator('commentValidation', $this->_createValidator()); |
||
231 | $comment = $this->_controller->Comments->newEntity($data, ['validate' => 'commentValidation']); |
||
232 | $errors = $comment->errors(); |
||
233 | $errors = !empty($errors); |
||
234 | |||
235 | if (!$errors) { |
||
236 | $persist = true; |
||
237 | $saved = true; |
||
238 | $this->_controller->Comments->addBehavior('Tree', [ |
||
239 | 'scope' => [ |
||
240 | 'entity_id' => $data['entity_id'], |
||
241 | 'table_alias' => $data['table_alias'], |
||
242 | ] |
||
243 | ]); |
||
244 | |||
245 | if ($this->config('settings.use_akismet')) { |
||
246 | $newStatus = $this->_akismetStatus($data); |
||
247 | $comment->set('status', $newStatus); |
||
248 | |||
249 | if ($newStatus == 'spam' && |
||
250 | $this->config('settings.akismet_action') != 'mark' |
||
251 | ) { |
||
252 | $persist = false; |
||
253 | } |
||
254 | } |
||
255 | |||
256 | if ($persist) { |
||
257 | $saved = $this->_controller->Comments->save($comment); |
||
258 | } |
||
259 | |||
260 | if ($saved) { |
||
261 | $this->_afterSave($comment); |
||
262 | |||
263 | return true; // all OK |
||
264 | } else { |
||
265 | $errors = true; |
||
266 | } |
||
267 | } |
||
268 | |||
269 | if ($errors) { |
||
270 | $this->_setErrors($comment); |
||
271 | $errorMessage = $this->config('errorMessage'); |
||
272 | if (is_callable($errorMessage)) { |
||
273 | $errorMessage = $errorMessage($comment, $this->_controller); |
||
274 | } |
||
275 | $this->_controller->Flash->danger($errorMessage, ['key' => 'commentsForm']); |
||
276 | } |
||
277 | |||
278 | return false; |
||
279 | } |
||
280 | |||
281 | /** |
||
282 | * Calculates comment's status using akismet. |
||
283 | * |
||
284 | * @param array $data Comment's data to be validated by Akismet |
||
285 | * @return string Filtered comment's status |
||
286 | */ |
||
287 | protected function _akismetStatus($data) |
||
319 | |||
320 | /** |
||
321 | * Logic triggered after comment was successfully saved. |
||
322 | * |
||
323 | * @param \Cake\Datasource\EntityInterface $comment Comment that was just saved |
||
324 | * @return void |
||
325 | */ |
||
326 | protected function _afterSave(EntityInterface $comment) |
||
339 | |||
340 | /** |
||
341 | * Extract data from request and prepares for inserting a new comment for |
||
342 | * the given entity. |
||
343 | * |
||
344 | * @param \Cake\Datasource\EntityInterface $entity Entity used to guess table name |
||
345 | * @return array |
||
346 | */ |
||
347 | protected function _getRequestData(EntityInterface $entity) |
||
348 | { |
||
349 | $pk = (string)TableRegistry::get($entity->source())->primaryKey(); |
||
350 | $data = $this->_controller->request->data('comment'); |
||
351 | $return = [ |
||
352 | 'parent_id' => null, |
||
353 | 'subject' => '', |
||
354 | 'body' => '', |
||
355 | 'status' => 'pending', |
||
356 | 'author_name' => null, |
||
357 | 'author_email' => null, |
||
358 | 'author_web' => null, |
||
359 | 'author_ip' => $this->_controller->request->clientIp(), |
||
360 | 'table_alias' => $this->_getTableAlias($entity), |
||
361 | 'entity_id' => $entity->get($pk), |
||
362 | ]; |
||
363 | |||
364 | if (!empty($this->_controller->request->data['comment'])) { |
||
365 | $data = $this->_controller->request->data['comment']; |
||
366 | } |
||
367 | |||
368 | if ($this->_controller->request->is('userLoggedIn')) { |
||
369 | $return['user_id'] = user()->id; |
||
370 | $return['author_name'] = null; |
||
371 | $return['author_email'] = null; |
||
372 | $return['author_web'] = null; |
||
373 | } else { |
||
374 | $return['author_name'] = !empty($data['author_name']) ? h($data['author_name']) : null; |
||
375 | $return['author_email'] = !empty($data['author_email']) ? h($data['author_email']) : null; |
||
376 | $return['author_web'] = !empty($data['author_web']) ? h($data['author_web']) : null; |
||
377 | } |
||
378 | |||
379 | if (!empty($data['subject'])) { |
||
380 | $return['subject'] = h($data['subject']); |
||
381 | } |
||
382 | |||
383 | if (!empty($data['parent_id'])) { |
||
384 | // this is validated at Model side |
||
385 | $return['parent_id'] = $data['parent_id']; |
||
386 | } |
||
387 | |||
388 | if (!empty($data['body'])) { |
||
389 | $return['body'] = TextToolbox::process($data['body'], $this->config('settings.text_processing')); |
||
390 | } |
||
391 | |||
392 | if ($this->config('settings.auto_approve') || |
||
393 | $this->_controller->request->is('userAdmin') |
||
394 | ) { |
||
395 | $return['status'] = 'approved'; |
||
396 | } |
||
397 | |||
398 | return $return; |
||
399 | } |
||
400 | |||
401 | /** |
||
402 | * Get table alias for the given entity. |
||
403 | * |
||
404 | * @param \Cake\Datasource\EntityInterface $entity The entity |
||
405 | * @return string Table alias |
||
406 | */ |
||
407 | protected function _getTableAlias(EntityInterface $entity) |
||
417 | |||
418 | /** |
||
419 | * Prepares error messages for FormHelper. |
||
420 | * |
||
421 | * @param \Comment\Model\Entity\Comment $comment The invalidated comment entity |
||
422 | * to extract error messages |
||
423 | * @return void |
||
424 | */ |
||
425 | protected function _setErrors(Comment $comment) |
||
434 | |||
435 | /** |
||
436 | * Fetch settings from data base and merges |
||
437 | * with this component's configuration. |
||
438 | * |
||
439 | * @return array |
||
440 | */ |
||
441 | protected function _loadSettings() |
||
448 | |||
449 | /** |
||
450 | * Creates a validation object on the fly. |
||
451 | * |
||
452 | * @return \Cake\Validation\Validator |
||
453 | */ |
||
454 | protected function _createValidator() |
||
504 | } |
||
505 |