Completed
Push — master ( f258b8...883a46 )
by Paweł
81:43 queued 66:40
created

UpdatePage::provideImageUrlForType()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
/*
4
 * This file is part of the Sylius package.
5
 *
6
 * (c) Paweł Jędrzejewski
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Sylius\Behat\Page\Admin\Taxon;
15
16
use Behat\Mink\Driver\Selenium2Driver;
17
use Behat\Mink\Element\NodeElement;
18
use Behat\Mink\Exception\ElementNotFoundException;
19
use Sylius\Behat\Behaviour\ChecksCodeImmutability;
20
use Sylius\Behat\Page\Admin\Crud\UpdatePage as BaseUpdatePage;
21
use Sylius\Behat\Service\AutocompleteHelper;
22
use Sylius\Behat\Service\SlugGenerationHelper;
23
use Sylius\Component\Core\Model\TaxonInterface;
24
use Webmozart\Assert\Assert;
25
26
/**
27
 * @author Arkadiusz Krakowiak <[email protected]>
28
 */
29
class UpdatePage extends BaseUpdatePage implements UpdatePageInterface
30
{
31
    use ChecksCodeImmutability;
32
33
    /** @var array */
34
    private $imageUrls = [];
35
36
    /**
37
     * {@inheritdoc}
38
     */
39
    public function chooseParent(TaxonInterface $taxon)
40
    {
41
        AutocompleteHelper::chooseValue($this->getSession(), $this->getElement('parent')->getParent(), $taxon->getName());
0 ignored issues
show
Bug introduced by
It seems like $this->getElement('parent')->getParent() can be null; however, chooseValue() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
42
    }
43
44
    /**
45
     * {@inheritdoc}
46
     */
47
    public function describeItAs($description, $languageCode)
48
    {
49
        $this->getDocument()->fillField(sprintf('sylius_taxon_translations_%s_description', $languageCode), $description);
50
    }
51
52
    /**
53
     * {@inheritdoc}
54
     */
55
    public function nameIt($name, $languageCode)
56
    {
57
        $this->activateLanguageTab($languageCode);
58
        $this->getDocument()->fillField(sprintf('sylius_taxon_translations_%s_name', $languageCode), $name);
59
60
        if ($this->getDriver() instanceof Selenium2Driver) {
61
            SlugGenerationHelper::waitForSlugGeneration(
62
                $this->getSession(),
63
                $this->getElement('slug', ['%language%' => $languageCode])
64
            );
65
        }
66
    }
67
68
    /**
69
     * {@inheritdoc}
70
     */
71
    public function specifySlug($slug, $languageCode)
72
    {
73
        $this->getDocument()->fillField(sprintf('sylius_taxon_translations_%s_slug', $languageCode), $slug);
74
    }
75
76
    /**
77
     * {@inheritdoc}
78
     */
79
    public function attachImage($path, $type = null)
80
    {
81
        $filesPath = $this->getParameter('files_path');
82
83
        $this->getDocument()->find('css', '[data-form-collection="add"]')->click();
84
85
        $imageForm = $this->getLastImageElement();
86
        if (null !== $type) {
87
            $imageForm->fillField('Type', $type);
88
        }
89
90
        $imageForm->find('css', 'input[type="file"]')->attachFile($filesPath.$path);
91
    }
92
93
    /**
94
     * {@inheritdoc}
95
     */
96
    public function isImageWithTypeDisplayed($type)
97
    {
98
        $imageElement = $this->getImageElementByType($type);
99
100
        $imageUrl = $imageElement ? $imageElement->find('css', 'img')->getAttribute('src') : $this->provideImageUrlForType($type);
101
        if (null === $imageElement && null === $imageUrl) {
102
            return false;
103
        }
104
105
        $this->getDriver()->visit($imageUrl);
106
        $pageText = $this->getDocument()->getText();
107
        $this->getDriver()->back();
108
109
        return false === stripos($pageText, '404 Not Found');
110
    }
111
112
    /**
113
     * {@inheritdoc}
114
     */
115
    public function isSlugReadonly($languageCode = 'en_US')
116
    {
117
        return SlugGenerationHelper::isSlugReadonly(
118
            $this->getSession(),
119
            $this->getElement('slug', ['%language%' => $languageCode])
120
        );
121
    }
122
123
    /**
124
     * {@inheritdoc}
125
     */
126
    public function removeImageWithType($type)
127
    {
128
        $imageElement = $this->getImageElementByType($type);
129
        $imageSourceElement = $imageElement->find('css', 'img');
130
        if (null !== $imageSourceElement) {
131
            $this->saveImageUrlForType($type, $imageSourceElement->getAttribute('src'));
132
        }
133
134
        $imageElement->clickLink('Delete');
135
    }
136
137
    public function removeFirstImage()
138
    {
139
        $imageElement = $this->getFirstImageElement();
140
        $imageTypeElement = $imageElement->find('css', 'input[type=text]');
141
        $imageSourceElement = $imageElement->find('css', 'img');
142
143
        if (null !== $imageTypeElement && null !== $imageSourceElement) {
144
            $this->saveImageUrlForType(
145
                $imageTypeElement->getValue(),
146
                $imageSourceElement->getAttribute('src')
147
            );
148
        }
149
150
        $imageElement->clickLink('Delete');
151
    }
152
153
    /**
154
     * {@inheritdoc}
155
     */
156
    public function enableSlugModification($languageCode = 'en_US')
157
    {
158
        SlugGenerationHelper::enableSlugModification(
159
            $this->getSession(),
160
            $this->getElement('toggle_taxon_slug_modification_button', ['%locale%' => $languageCode])
161
        );
162
    }
163
164
    /**
165
     * {@inheritdoc}
166
     */
167
    public function countImages()
168
    {
169
        $imageElements = $this->getImageElements();
170
171
        return count($imageElements);
172
    }
173
174
    /**
175
     * {@inheritdoc}
176
     */
177
    public function changeImageWithType($type, $path)
178
    {
179
        $filesPath = $this->getParameter('files_path');
180
181
        $imageForm = $this->getImageElementByType($type);
182
        $imageForm->find('css', 'input[type="file"]')->attachFile($filesPath.$path);
183
    }
184
185
    /**
186
     * {@inheritdoc}
187
     */
188
    public function modifyFirstImageType($type)
189
    {
190
        $firstImage = $this->getFirstImageElement();
191
192
        $typeField = $firstImage->findField('Type');
193
        $typeField->setValue($type);
194
    }
195
196
    /**
197
     * {@inheritdoc}
198
     */
199
    public function getParent()
200
    {
201
        return $this->getElement('parent')->getValue();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->getElement('parent')->getValue(); (string|boolean|array) is incompatible with the return type declared by the interface Sylius\Behat\Page\Admin\...ageInterface::getParent of type string.

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:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
202
    }
203
204
    /**
205
     * {@inheritdoc}
206
     */
207
    public function getSlug($languageCode = 'en_US')
208
    {
209
        return $this->getElement('slug', ['%language%' => $languageCode])->getValue();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->getElement...uageCode))->getValue(); (string|boolean|array) is incompatible with the return type declared by the interface Sylius\Behat\Page\Admin\...ePageInterface::getSlug of type string.

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:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
210
    }
211
212
    /**
213
     * {@inheritdoc}
214
     */
215
    public function getValidationMessageForImage()
216
    {
217
        $lastImageElement = $this->getLastImageElement();
218
219
        $foundElement = $lastImageElement->find('css', '.sylius-validation-error');
220
        if (null === $foundElement) {
221
            throw new ElementNotFoundException($this->getSession(), 'Tag', 'css', '.sylius-validation-error');
222
        }
223
224
        return $foundElement->getText();
225
    }
226
227
    /**
228
     * {@inheritdoc}
229
     */
230
    public function getValidationMessageForImageAtPlace($place)
231
    {
232
        $images = $this->getImageElements();
233
234
        $foundElement = $images[$place]->find('css', '.sylius-validation-error');
235
        if (null === $foundElement) {
236
            throw new ElementNotFoundException($this->getSession(), 'Tag', 'css', '.sylius-validation-error');
237
        }
238
239
        return $foundElement->getText();
240
    }
241
242
    /**
243
     * {@inheritdoc}
244
     */
245
    public function activateLanguageTab($locale)
246
    {
247
        if (!$this->getDriver() instanceof Selenium2Driver) {
248
            return;
249
        }
250
251
        $languageTabTitle = $this->getElement('language_tab', ['%locale%' => $locale]);
252
        if (!$languageTabTitle->hasClass('active')) {
253
            $languageTabTitle->click();
254
        }
255
256
        $this->getDocument()->waitFor(10, function () use ($languageTabTitle) {
257
            return $languageTabTitle->hasClass('active');
258
        });
259
    }
260
261
    /**
262
     * {@inheritdoc}
263
     */
264
    protected function getElement($name, array $parameters = [])
265
    {
266
        if (!isset($parameters['%language%'])) {
267
            $parameters['%language%'] = 'en_US';
268
        }
269
270
        return parent::getElement($name, $parameters);
271
    }
272
273
    /**
274
     * @return NodeElement
275
     */
276
    protected function getCodeElement()
277
    {
278
        return $this->getElement('code');
279
    }
280
281
    /**
282
     * {@inheritdoc}
283
     */
284
    protected function getDefinedElements()
285
    {
286
        return array_merge(parent::getDefinedElements(), [
287
            'code' => '#sylius_taxon_code',
288
            'description' => '#sylius_taxon_translations_en_US_description',
289
            'images' => '#sylius_taxon_images',
290
            'language_tab' => '[data-locale="%locale%"] .title',
291
            'name' => '#sylius_taxon_translations_en_US_name',
292
            'parent' => '#sylius_taxon_parent',
293
            'slug' => '#sylius_taxon_translations_%language%_slug',
294
            'toggle_taxon_slug_modification_button' => '[data-locale="%locale%"] .toggle-taxon-slug-modification',
295
        ]);
296
    }
297
298
    /**
299
     * @return NodeElement
300
     */
301
    private function getLastImageElement()
302
    {
303
        $imageElements = $this->getImageElements();
304
305
        Assert::notEmpty($imageElements);
306
307
        return end($imageElements);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression end($imageElements); of type Behat\Mink\Element\NodeElement|false adds false to the return on line 307 which is incompatible with the return type documented by Sylius\Behat\Page\Admin\...ge::getLastImageElement of type Behat\Mink\Element\NodeElement. It seems like you forgot to handle an error condition.
Loading history...
308
    }
309
310
    /**
311
     * @return NodeElement
312
     */
313
    private function getFirstImageElement()
314
    {
315
        $imageElements = $this->getImageElements();
316
317
        Assert::notEmpty($imageElements);
318
319
        return reset($imageElements);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression reset($imageElements); of type Behat\Mink\Element\NodeElement|false adds false to the return on line 319 which is incompatible with the return type documented by Sylius\Behat\Page\Admin\...e::getFirstImageElement of type Behat\Mink\Element\NodeElement. It seems like you forgot to handle an error condition.
Loading history...
320
    }
321
322
    /**
323
     * @return NodeElement[]
324
     */
325
    private function getImageElements()
326
    {
327
        $images = $this->getElement('images');
328
329
        return $images->findAll('css', 'div[data-form-collection="item"]');
330
    }
331
332
    /**
333
     * @param string $type
334
     *
335
     * @return NodeElement
336
     */
337
    private function getImageElementByType($type)
338
    {
339
        $images = $this->getElement('images');
340
        $typeInput = $images->find('css', 'input[value="'.$type.'"]');
341
342
        if (null === $typeInput) {
343
            return null;
344
        }
345
346
        return $typeInput->getParent()->getParent()->getParent();
347
    }
348
349
    private function provideImageUrlForType(string $type): ?string
350
    {
351
        return $this->imageUrls[$type] ?? null;
352
    }
353
354
    private function saveImageUrlForType(string $type, string $imageUrl): void
355
    {
356
        $this->imageUrls[$type] = $imageUrl;
357
    }
358
}
359