Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like RawDrupalContext 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 RawDrupalContext, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
26 | class RawDrupalContext extends RawMinkContext implements DrupalAwareInterface { |
||
27 | |||
28 | /** |
||
29 | * Drupal driver manager. |
||
30 | * |
||
31 | * @var \Drupal\DrupalDriverManager |
||
32 | */ |
||
33 | private $drupal; |
||
34 | |||
35 | /** |
||
36 | * Test parameters. |
||
37 | * |
||
38 | * @var array |
||
39 | */ |
||
40 | private $drupalParameters; |
||
41 | |||
42 | /** |
||
43 | * Event dispatcher object. |
||
44 | * |
||
45 | * @var \Behat\Testwork\Hook\HookDispatcher |
||
46 | */ |
||
47 | protected $dispatcher; |
||
48 | |||
49 | /** |
||
50 | * Drupal user manager. |
||
51 | * |
||
52 | * @var \Drupal\DrupalUserManagerInterface |
||
53 | */ |
||
54 | protected $userManager; |
||
55 | |||
56 | /** |
||
57 | * Keep track of nodes so they can be cleaned up. |
||
58 | * |
||
59 | * @var array |
||
60 | */ |
||
61 | protected $nodes = array(); |
||
62 | |||
63 | /** |
||
64 | * Keep track of all terms that are created so they can easily be removed. |
||
65 | * |
||
66 | * @var array |
||
67 | */ |
||
68 | protected $terms = array(); |
||
69 | |||
70 | /** |
||
71 | * Keep track of any roles that are created so they can easily be removed. |
||
72 | * |
||
73 | * @var array |
||
74 | */ |
||
75 | protected $roles = array(); |
||
76 | |||
77 | /** |
||
78 | * Keep track of any languages that are created so they can easily be removed. |
||
79 | * |
||
80 | * @var array |
||
81 | */ |
||
82 | protected $languages = array(); |
||
83 | |||
84 | /** |
||
85 | * {@inheritDoc} |
||
86 | */ |
||
87 | public function setDrupal(DrupalDriverManager $drupal) { |
||
88 | $this->drupal = $drupal; |
||
89 | } |
||
90 | |||
91 | /** |
||
92 | * {@inheritDoc} |
||
93 | */ |
||
94 | public function getDrupal() { |
||
97 | |||
98 | /** |
||
99 | * {@inheritDoc} |
||
100 | */ |
||
101 | public function setUserManager(DrupalUserManagerInterface $userManager) { |
||
102 | $this->userManager = $userManager; |
||
103 | } |
||
104 | |||
105 | /** |
||
106 | * {@inheritdoc} |
||
107 | */ |
||
108 | public function getUserManager() { |
||
109 | return $this->userManager; |
||
110 | } |
||
111 | |||
112 | /** |
||
113 | * Magic setter. |
||
114 | */ |
||
115 | public function __set($name, $value) { |
||
116 | switch ($name) { |
||
117 | case 'user': |
||
118 | trigger_error('Interacting directly with the RawDrupalContext::$user property has been deprecated. Use RawDrupalContext::getUserManager->setCurrentUser() instead.', E_USER_DEPRECATED); |
||
119 | // Set the user on the user manager service, so it is shared between all |
||
120 | // contexts. |
||
121 | $this->getUserManager()->setCurrentUser($value); |
||
122 | break; |
||
123 | |||
124 | case 'users': |
||
125 | trigger_error('Interacting directly with the RawDrupalContext::$users property has been deprecated. Use RawDrupalContext::getUserManager->addUser() instead.', E_USER_DEPRECATED); |
||
126 | // Set the user on the user manager service, so it is shared between all |
||
127 | // contexts. |
||
128 | if (empty($value)) { |
||
129 | $this->getUserManager()->clearUsers(); |
||
130 | } |
||
131 | else { |
||
132 | foreach ($value as $user) { |
||
133 | $this->getUserManager()->addUser($user); |
||
134 | } |
||
135 | } |
||
136 | break; |
||
137 | } |
||
138 | } |
||
139 | |||
140 | /** |
||
141 | * Magic getter. |
||
142 | */ |
||
143 | public function __get($name) { |
||
144 | switch ($name) { |
||
145 | case 'user': |
||
146 | trigger_error('Interacting directly with the RawDrupalContext::$user property has been deprecated. Use RawDrupalContext::getUserManager->getCurrentUser() instead.', E_USER_DEPRECATED); |
||
147 | // Returns the current user from the user manager service. This is shared |
||
148 | // between all contexts. |
||
149 | return $this->getUserManager()->getCurrentUser(); |
||
150 | |||
151 | case 'users': |
||
152 | trigger_error('Interacting directly with the RawDrupalContext::$users property has been deprecated. Use RawDrupalContext::getUserManager->getUsers() instead.', E_USER_DEPRECATED); |
||
153 | // Returns the current user from the user manager service. This is shared |
||
154 | // between all contexts. |
||
155 | return $this->getUserManager()->getUsers(); |
||
156 | } |
||
157 | } |
||
158 | |||
159 | /** |
||
160 | * {@inheritdoc} |
||
161 | */ |
||
162 | public function setDispatcher(HookDispatcher $dispatcher) { |
||
165 | |||
166 | /** |
||
167 | * Set parameters provided for Drupal. |
||
168 | */ |
||
169 | public function setDrupalParameters(array $parameters) { |
||
172 | |||
173 | /** |
||
174 | * Returns a specific Drupal parameter. |
||
175 | * |
||
176 | * @param string $name |
||
177 | * Parameter name. |
||
178 | * |
||
179 | * @return mixed |
||
180 | */ |
||
181 | public function getDrupalParameter($name) { |
||
184 | |||
185 | /** |
||
186 | * Returns a specific Drupal text value. |
||
187 | * |
||
188 | * @param string $name |
||
189 | * Text value name, such as 'log_out', which corresponds to the default 'Log |
||
190 | * out' link text. |
||
191 | * @throws \Exception |
||
192 | * @return |
||
193 | */ |
||
194 | View Code Duplication | public function getDrupalText($name) { |
|
201 | |||
202 | /** |
||
203 | * Returns a specific css selector. |
||
204 | * |
||
205 | * @param $name |
||
206 | * string CSS selector name |
||
207 | */ |
||
208 | View Code Duplication | public function getDrupalSelector($name) { |
|
209 | $text = $this->getDrupalParameter('selectors'); |
||
210 | if (!isset($text[$name])) { |
||
211 | throw new \Exception(sprintf('No such selector configured: %s', $name)); |
||
212 | } |
||
213 | return $text[$name]; |
||
214 | } |
||
215 | |||
216 | /** |
||
217 | * Get active Drupal Driver. |
||
218 | * |
||
219 | * @return \Drupal\Driver\DrupalDriver |
||
220 | */ |
||
221 | public function getDriver($name = NULL) { |
||
224 | |||
225 | /** |
||
226 | * Get driver's random generator. |
||
227 | */ |
||
228 | public function getRandom() { |
||
231 | |||
232 | /** |
||
233 | * Massage node values to match the expectations on different Drupal versions. |
||
234 | * |
||
235 | * @beforeNodeCreate |
||
236 | */ |
||
237 | public static function alterNodeParameters(BeforeNodeCreateScope $scope) { |
||
238 | $node = $scope->getEntity(); |
||
239 | |||
240 | // Get the Drupal API version if available. This is not available when |
||
241 | // using e.g. the BlackBoxDriver or DrushDriver. |
||
242 | $api_version = NULL; |
||
243 | $driver = $scope->getContext()->getDrupal()->getDriver(); |
||
244 | if ($driver instanceof \Drupal\Driver\DrupalDriver) { |
||
245 | $api_version = $scope->getContext()->getDrupal()->getDriver()->version; |
||
246 | } |
||
247 | |||
248 | // On Drupal 8 the timestamps should be in UNIX time. |
||
249 | switch ($api_version) { |
||
250 | case 8: |
||
251 | foreach (array('changed', 'created', 'revision_timestamp') as $field) { |
||
252 | if (!empty($node->$field) && !is_numeric($node->$field)) { |
||
253 | $node->$field = strtotime($node->$field); |
||
254 | } |
||
255 | } |
||
256 | break; |
||
257 | } |
||
258 | } |
||
259 | |||
260 | /** |
||
261 | * Remove any created nodes. |
||
262 | * |
||
263 | * @AfterScenario |
||
264 | */ |
||
265 | public function cleanNodes() { |
||
272 | |||
273 | /** |
||
274 | * Remove any created users. |
||
275 | * |
||
276 | * @AfterScenario |
||
277 | */ |
||
278 | public function cleanUsers() { |
||
279 | // Remove any users that were created. |
||
280 | if ($this->userManager->hasUsers()) { |
||
281 | foreach ($this->userManager->getUsers() as $user) { |
||
282 | $this->getDriver()->userDelete($user); |
||
283 | } |
||
284 | $this->getDriver()->processBatch(); |
||
285 | $this->userManager->clearUsers(); |
||
286 | if ($this->loggedIn()) { |
||
287 | $this->logout(); |
||
288 | } |
||
289 | } |
||
290 | } |
||
291 | |||
292 | /** |
||
293 | * Remove any created terms. |
||
294 | * |
||
295 | * @AfterScenario |
||
296 | */ |
||
297 | public function cleanTerms() { |
||
304 | |||
305 | /** |
||
306 | * Remove any created roles. |
||
307 | * |
||
308 | * @AfterScenario |
||
309 | */ |
||
310 | public function cleanRoles() { |
||
317 | |||
318 | /** |
||
319 | * Remove any created languages. |
||
320 | * |
||
321 | * @AfterScenario |
||
322 | */ |
||
323 | public function cleanLanguages() { |
||
330 | |||
331 | /** |
||
332 | * Clear static caches. |
||
333 | * |
||
334 | * @AfterScenario @api |
||
335 | */ |
||
336 | public function clearStaticCaches() { |
||
337 | $this->getDriver()->clearStaticCaches(); |
||
338 | } |
||
339 | |||
340 | /** |
||
341 | * Dispatch scope hooks. |
||
342 | * |
||
343 | * @param string $scope |
||
344 | * The entity scope to dispatch. |
||
345 | * @param \stdClass $entity |
||
346 | * The entity. |
||
347 | */ |
||
348 | protected function dispatchHooks($scopeType, \stdClass $entity) { |
||
349 | $fullScopeClass = 'Drupal\\DrupalExtension\\Hook\\Scope\\' . $scopeType; |
||
350 | $scope = new $fullScopeClass($this->getDrupal()->getEnvironment(), $this, $entity); |
||
351 | $callResults = $this->dispatcher->dispatchScopeHooks($scope); |
||
352 | |||
353 | // The dispatcher suppresses exceptions, throw them here if there are any. |
||
354 | foreach ($callResults as $result) { |
||
355 | if ($result->hasException()) { |
||
356 | $exception = $result->getException(); |
||
357 | throw $exception; |
||
358 | } |
||
359 | } |
||
360 | } |
||
361 | |||
362 | /** |
||
363 | * Create a node. |
||
364 | * |
||
365 | * @return object |
||
366 | * The created node. |
||
367 | */ |
||
368 | View Code Duplication | public function nodeCreate($node) { |
|
376 | |||
377 | /** |
||
378 | * Parses the field values and turns them into the format expected by Drupal. |
||
379 | * |
||
380 | * Multiple values in a single field must be separated by commas. Wrap the |
||
381 | * field value in double quotes in case it should contain a comma. |
||
382 | * |
||
383 | * Compound field properties are identified using a ':' operator, either in |
||
384 | * the column heading or in the cell. If multiple properties are present in a |
||
385 | * single cell, they must be separated using ' - ', and values should not |
||
386 | * contain ':' or ' - '. |
||
387 | * |
||
388 | * Possible formats for the values: |
||
389 | * A |
||
390 | * A, B, "a value, containing a comma" |
||
391 | * A - B |
||
392 | * x: A - y: B |
||
393 | * A - B, C - D, "E - F" |
||
394 | * x: A - y: B, x: C - y: D, "x: E - y: F" |
||
395 | * |
||
396 | * See field_handlers.feature for examples of usage. |
||
397 | * |
||
398 | * @param string $entity_type |
||
399 | * The entity type. |
||
400 | * @param \stdClass $entity |
||
401 | * An object containing the entity properties and fields as properties. |
||
402 | * |
||
403 | * @throws \Exception |
||
404 | * Thrown when a field name is invalid. |
||
405 | */ |
||
406 | public function parseEntityFields($entity_type, \stdClass $entity) { |
||
483 | |||
484 | /** |
||
485 | * Create a user. |
||
486 | * |
||
487 | * @return object |
||
488 | * The created user. |
||
489 | */ |
||
490 | public function userCreate($user) { |
||
498 | |||
499 | /** |
||
500 | * Create a term. |
||
501 | * |
||
502 | * @return object |
||
503 | * The created term. |
||
504 | */ |
||
505 | View Code Duplication | public function termCreate($term) { |
|
513 | |||
514 | /** |
||
515 | * Creates a language. |
||
516 | * |
||
517 | * @param \stdClass $language |
||
518 | * An object with the following properties: |
||
519 | * - langcode: the langcode of the language to create. |
||
520 | * |
||
521 | * @return object|FALSE |
||
522 | * The created language, or FALSE if the language was already created. |
||
523 | */ |
||
524 | public function languageCreate(\stdClass $language) { |
||
533 | |||
534 | /** |
||
535 | * Log-in the given user. |
||
536 | * |
||
537 | * @param \stdClass $user |
||
538 | * The user to log in. |
||
539 | */ |
||
540 | public function login(\stdClass $user) { |
||
571 | |||
572 | /** |
||
573 | * Logs the current user out. |
||
574 | */ |
||
575 | public function logout() { |
||
579 | |||
580 | /** |
||
581 | * Determine if the a user is already logged in. |
||
582 | * |
||
583 | * @return boolean |
||
584 | * Returns TRUE if a user is logged in for this session. |
||
585 | */ |
||
586 | public function loggedIn() { |
||
627 | |||
628 | /** |
||
629 | * User with a given role is already logged in. |
||
630 | * |
||
631 | * @param string $role |
||
632 | * A single role, or multiple comma-separated roles in a single string. |
||
633 | * |
||
634 | * @return boolean |
||
635 | * Returns TRUE if the current logged in user has this role (or roles). |
||
636 | */ |
||
637 | public function loggedInWithRole($role) { |
||
640 | |||
641 | } |
||
642 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.