Complex classes like UpdateSimpleProductPage 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 UpdateSimpleProductPage, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
30 | class UpdateSimpleProductPage extends BaseUpdatePage implements UpdateSimpleProductPageInterface |
||
31 | { |
||
32 | use ChecksCodeImmutability; |
||
33 | |||
34 | /** |
||
35 | * {@inheritdoc} |
||
36 | */ |
||
37 | public function nameItIn($name, $localeCode) |
||
38 | { |
||
39 | $this->activateLanguageTab($localeCode); |
||
40 | $this->getElement('name', ['%locale%' => $localeCode])->setValue($name); |
||
41 | |||
42 | if ($this->getDriver() instanceof Selenium2Driver) { |
||
43 | SlugGenerationHelper::waitForSlugGeneration( |
||
44 | $this->getSession(), |
||
45 | $this->getElement('slug', ['%locale%' => $localeCode]) |
||
46 | ); |
||
47 | } |
||
48 | } |
||
49 | |||
50 | /** |
||
51 | * {@inheritdoc} |
||
52 | */ |
||
53 | public function specifyPrice($channelName, $price) |
||
54 | { |
||
55 | $this->getElement('price', ['%channelName%' => $channelName])->setValue($price); |
||
56 | } |
||
57 | |||
58 | /** |
||
59 | * {@inheritdoc} |
||
60 | */ |
||
61 | public function specifyOriginalPrice($channelName, $originalPrice) |
||
62 | { |
||
63 | $this->getElement('original_price', ['%channelName%' => $channelName])->setValue($originalPrice); |
||
64 | } |
||
65 | |||
66 | public function addSelectedAttributes() |
||
67 | { |
||
68 | $this->clickTabIfItsNotActive('attributes'); |
||
69 | $this->getDocument()->pressButton('Add attributes'); |
||
70 | |||
71 | $form = $this->getDocument()->find('css', 'form'); |
||
72 | |||
73 | $this->getDocument()->waitFor(1, function () use ($form) { |
||
74 | return $form->hasClass('loading'); |
||
75 | }); |
||
76 | } |
||
77 | |||
78 | /** |
||
79 | * {@inheritdoc} |
||
80 | */ |
||
81 | public function removeAttribute($attributeName, $localeCode) |
||
82 | { |
||
83 | $this->clickTabIfItsNotActive('attributes'); |
||
84 | |||
85 | $this->getElement('attribute_delete_button', ['%attributeName%' => $attributeName, '$localeCode%' => $localeCode])->press(); |
||
86 | } |
||
87 | |||
88 | /** |
||
89 | * {@inheritdoc} |
||
90 | */ |
||
91 | public function getAttributeValue($attribute, $localeCode) |
||
92 | { |
||
93 | $this->clickTabIfItsNotActive('attributes'); |
||
94 | $this->clickLocaleTabIfItsNotActive($localeCode); |
||
95 | |||
96 | return $this->getElement('attribute', ['%attributeName%' => $attribute, '%localeCode%' => $localeCode])->getValue(); |
||
|
|||
97 | } |
||
98 | |||
99 | /** |
||
100 | * {@inheritdoc} |
||
101 | */ |
||
102 | public function getNumberOfAttributes() |
||
103 | { |
||
104 | return count($this->getDocument()->findAll('css', '.attribute')); |
||
105 | } |
||
106 | |||
107 | /** |
||
108 | * {@inheritdoc} |
||
109 | */ |
||
110 | public function hasAttribute($attributeName) |
||
111 | { |
||
112 | return null !== $this->getDocument()->find('css', sprintf('.attribute .label:contains("%s")', $attributeName)); |
||
113 | } |
||
114 | |||
115 | /** |
||
116 | * {@inheritdoc} |
||
117 | */ |
||
118 | public function selectMainTaxon(TaxonInterface $taxon) |
||
119 | { |
||
120 | $this->openTaxonBookmarks(); |
||
121 | |||
122 | $mainTaxonElement = $this->getElement('main_taxon')->getParent(); |
||
123 | |||
124 | AutocompleteHelper::chooseValue($this->getSession(), $mainTaxonElement, $taxon->getName()); |
||
125 | } |
||
126 | |||
127 | /** |
||
128 | * {@inheritdoc} |
||
129 | */ |
||
130 | public function isMainTaxonChosen($taxonName) |
||
131 | { |
||
132 | $this->openTaxonBookmarks(); |
||
133 | |||
134 | return $taxonName === $this->getDocument()->find('css', '.search > .text')->getText(); |
||
135 | } |
||
136 | |||
137 | public function disableTracking() |
||
138 | { |
||
139 | $this->getElement('tracked')->uncheck(); |
||
140 | } |
||
141 | |||
142 | public function enableTracking() |
||
143 | { |
||
144 | $this->getElement('tracked')->check(); |
||
145 | } |
||
146 | |||
147 | /** |
||
148 | * {@inheritdoc} |
||
149 | */ |
||
150 | public function isTracked() |
||
151 | { |
||
152 | return $this->getElement('tracked')->isChecked(); |
||
153 | } |
||
154 | |||
155 | /** |
||
156 | * {@inheritdoc} |
||
157 | */ |
||
158 | public function enableSlugModification($locale) |
||
159 | { |
||
160 | SlugGenerationHelper::enableSlugModification( |
||
161 | $this->getSession(), |
||
162 | $this->getElement('toggle_slug_modification_button', ['%locale%' => $locale]) |
||
163 | ); |
||
164 | } |
||
165 | |||
166 | /** |
||
167 | * {@inheritdoc} |
||
168 | */ |
||
169 | public function isImageWithTypeDisplayed($type) |
||
170 | { |
||
171 | $imageElement = $this->getImageElementByType($type); |
||
172 | |||
173 | if (null === $imageElement) { |
||
174 | return false; |
||
175 | } |
||
176 | |||
177 | $imageUrl = $imageElement->find('css', 'img')->getAttribute('src'); |
||
178 | $this->getDriver()->visit($imageUrl); |
||
179 | $pageText = $this->getDocument()->getText(); |
||
180 | $this->getDriver()->back(); |
||
181 | |||
182 | return false === stripos($pageText, '404 Not Found'); |
||
183 | } |
||
184 | |||
185 | /** |
||
186 | * {@inheritdoc} |
||
187 | */ |
||
188 | public function attachImage($path, $type = null) |
||
189 | { |
||
190 | $this->clickTabIfItsNotActive('media'); |
||
191 | |||
192 | $filesPath = $this->getParameter('files_path'); |
||
193 | |||
194 | $this->getDocument()->clickLink('Add'); |
||
195 | |||
196 | $imageForm = $this->getLastImageElement(); |
||
197 | if (null !== $type) { |
||
198 | $imageForm->fillField('Type', $type); |
||
199 | } |
||
200 | |||
201 | $imageForm->find('css', 'input[type="file"]')->attachFile($filesPath.$path); |
||
202 | } |
||
203 | |||
204 | /** |
||
205 | * {@inheritdoc} |
||
206 | */ |
||
207 | public function changeImageWithType($type, $path) |
||
208 | { |
||
209 | $filesPath = $this->getParameter('files_path'); |
||
210 | |||
211 | $imageForm = $this->getImageElementByType($type); |
||
212 | $imageForm->find('css', 'input[type="file"]')->attachFile($filesPath.$path); |
||
213 | } |
||
214 | |||
215 | /** |
||
216 | * {@inheritdoc} |
||
217 | */ |
||
218 | public function removeImageWithType($type) |
||
219 | { |
||
220 | $this->clickTabIfItsNotActive('media'); |
||
221 | |||
222 | $imageElement = $this->getImageElementByType($type); |
||
223 | $imageElement->clickLink('Delete'); |
||
224 | } |
||
225 | |||
226 | public function removeFirstImage() |
||
227 | { |
||
228 | $this->clickTabIfItsNotActive('media'); |
||
229 | |||
230 | $imageElement = $this->getFirstImageElement(); |
||
231 | $imageElement->clickLink('Delete'); |
||
232 | } |
||
233 | |||
234 | /** |
||
235 | * {@inheritdoc} |
||
236 | */ |
||
237 | public function modifyFirstImageType($type) |
||
238 | { |
||
239 | $this->clickTabIfItsNotActive('media'); |
||
240 | |||
241 | $firstImage = $this->getFirstImageElement(); |
||
242 | $this->setImageType($firstImage, $type); |
||
243 | } |
||
244 | |||
245 | /** |
||
246 | * {@inheritdoc} |
||
247 | */ |
||
248 | public function countImages() |
||
249 | { |
||
250 | $imageElements = $this->getImageElements(); |
||
251 | |||
252 | return count($imageElements); |
||
253 | } |
||
254 | |||
255 | /** |
||
256 | * {@inheritdoc} |
||
257 | */ |
||
258 | public function isSlugReadonlyIn($locale) |
||
259 | { |
||
260 | return SlugGenerationHelper::isSlugReadonly( |
||
261 | $this->getSession(), |
||
262 | $this->getElement('slug', ['%locale%' => $locale]) |
||
263 | ); |
||
264 | } |
||
265 | |||
266 | /** |
||
267 | * {@inheritdoc} |
||
268 | */ |
||
269 | public function associateProducts(ProductAssociationTypeInterface $productAssociationType, array $productsNames) |
||
295 | |||
296 | /** |
||
297 | * {@inheritdoc} |
||
298 | */ |
||
299 | public function hasAssociatedProduct($productName, ProductAssociationTypeInterface $productAssociationType) |
||
308 | |||
309 | /** |
||
310 | * {@inheritdoc} |
||
311 | */ |
||
312 | public function removeAssociatedProduct($productName, ProductAssociationTypeInterface $productAssociationType) |
||
325 | |||
326 | /** |
||
327 | * {@inheritdoc} |
||
328 | */ |
||
329 | public function getPricingConfigurationForChannelAndCurrencyCalculator(ChannelInterface $channel, CurrencyInterface $currency) |
||
337 | |||
338 | /** |
||
339 | * {@inheritdoc} |
||
340 | */ |
||
341 | public function getSlug($locale) |
||
347 | |||
348 | /** |
||
349 | * {@inheritdoc} |
||
350 | */ |
||
351 | public function specifySlugIn($slug, $locale) |
||
357 | |||
358 | /** |
||
359 | * {@inheritdoc} |
||
360 | */ |
||
361 | public function activateLanguageTab($locale) |
||
372 | |||
373 | public function getPriceForChannel($channelName) |
||
374 | { |
||
375 | return $this->getElement('price', ['%channelName%' => $channelName])->getValue(); |
||
376 | } |
||
377 | |||
378 | /** |
||
379 | * {@inheritdoc} |
||
380 | */ |
||
381 | public function getOriginalPriceForChannel($channelName) |
||
382 | { |
||
383 | return $this->getElement('original_price', ['%channelName%' => $channelName])->getValue(); |
||
384 | } |
||
385 | |||
386 | /** |
||
387 | * {@inheritdoc} |
||
388 | */ |
||
389 | public function isShippingRequired() |
||
390 | { |
||
391 | return $this->getElement('shipping_required')->isChecked(); |
||
392 | } |
||
393 | |||
394 | /** |
||
395 | * {@inheritdoc} |
||
396 | */ |
||
397 | protected function getCodeElement() |
||
401 | |||
402 | /** |
||
403 | * {@inheritdoc} |
||
404 | */ |
||
405 | protected function getElement($name, array $parameters = []) |
||
413 | |||
414 | /** |
||
415 | * {@inheritdoc} |
||
416 | */ |
||
417 | protected function getDefinedElements() |
||
442 | |||
443 | private function openTaxonBookmarks() |
||
447 | |||
448 | /** |
||
449 | * @param string $tabName |
||
450 | */ |
||
451 | private function clickTabIfItsNotActive($tabName) |
||
458 | |||
459 | /** |
||
460 | * @param string $tabName |
||
461 | */ |
||
462 | private function clickTab($tabName) |
||
467 | |||
468 | /** |
||
469 | * @param string $localeCode |
||
470 | */ |
||
471 | private function clickLocaleTabIfItsNotActive($localeCode) |
||
478 | |||
479 | /** |
||
480 | * @param string $type |
||
481 | * |
||
482 | * @return NodeElement |
||
483 | */ |
||
484 | private function getImageElementByType($type) |
||
495 | |||
496 | /** |
||
497 | * @return NodeElement[] |
||
498 | */ |
||
499 | private function getImageElements() |
||
505 | |||
506 | /** |
||
507 | * @return NodeElement |
||
508 | */ |
||
509 | private function getLastImageElement() |
||
517 | |||
518 | /** |
||
519 | * @return NodeElement |
||
520 | */ |
||
521 | private function getFirstImageElement() |
||
529 | |||
530 | /** |
||
531 | * @param NodeElement $imageElement |
||
532 | * @param string $type |
||
533 | */ |
||
534 | private function setImageType(NodeElement $imageElement, $type) |
||
539 | } |
||
540 |
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.