Completed
Pull Request — 3.1 (#348)
by Piotr
07:35
created

DataContext::applyFieldFormatters()   B

Complexity

Conditions 6
Paths 10

Size

Total Lines 68

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 68
rs 8.0759
c 0
b 0
f 0
cc 6
nc 10
nop 2

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * (c) FSi sp. z o.o. <[email protected]>
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
declare(strict_types=1);
11
12
namespace FSi\Bundle\AdminBundle\Behat\Context;
13
14
use Behat\Gherkin\Node\TableNode;
15
use DateTime;
16
use Doctrine\ORM\Tools\SchemaTool;
17
use FSi\FixturesBundle\Entity\Category;
18
use FSi\FixturesBundle\Entity\News;
19
use FSi\FixturesBundle\Entity\Person;
20
use FSi\FixturesBundle\Entity\Subscriber;
21
use FSi\FixturesBundle\Entity\Tag;
22
use InvalidArgumentException;
23
use RuntimeException;
24
use Symfony\Component\PropertyAccess\PropertyAccess;
25
use Symfony\Component\PropertyAccess\PropertyAccessor;
26
use function expect;
27
use function file_exists;
28
29
class DataContext extends AbstractContext
30
{
31
    /**
32
     * @var PropertyAccessor|null
33
     */
34
    private $propertyAccessor = null;
35
36
    /**
37
     * @BeforeScenario
38
     */
39
    public function createDatabase(): void
40
    {
41
        $this->deleteDatabaseIfExist();
42
43
        $entityManager = $this->getEntityManager();
44
        $metadata = $entityManager->getMetadataFactory()->getAllMetadata();
45
46
        $tool = new SchemaTool($entityManager);
47
        $tool->createSchema($metadata);
48
    }
49
50
    /**
51
     * @AfterScenario
52
     */
53
    public function deleteDatabaseIfExist(): void
54
    {
55
        $dbFilePath = $this->getKernel()->getRootDir() . '/data.sqlite';
0 ignored issues
show
Deprecated Code introduced by
The method Symfony\Component\HttpKe...Interface::getRootDir() has been deprecated with message: since Symfony 4.2

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
56
        if (true === file_exists($dbFilePath)) {
57
            unlink($dbFilePath);
58
        }
59
    }
60
61
    /**
62
     * @Transform :className
63
     */
64
    public function entityToClassName(string $entityName): string
65
    {
66
        switch ($entityName) {
67
            case 'news':
68
                return News::class;
69
            case 'category':
70
            case 'categories':
71
                return Category::class;
72
            case 'subscriber':
73
            case 'subscribers':
74
                return Subscriber::class;
75
            case 'person':
76
                return Person::class;
77
        }
78
    }
79
80
    /**
81
     * @Given there are :count :className
82
     * @Given there is :count :className
83
     */
84
    public function thereIsNumberOfEntities(int $count, string $className)
85
    {
86
        $entityManager = $this->getEntityManager();
87
        for ($i = 0; $i < $count; $i++) {
88
            $instance = new $className();
89
            $this->applyFieldFormatters($instance);
90
            $this->applyEntityModifiers($instance);
91
            $entityManager->persist($instance);
92
        }
93
94
        $entityManager->flush();
95
96
        expect(count($this->getRepository($className)->findAll()))->toBe($count);
97
    }
98
99
    /**
100
     * @Given there is a :className with :field :value present in the database
101
     */
102
    public function thereIsAnEntityWithField(string $className, string $field, $value)
103
    {
104
        $instance = new $className();
105
        $formatters = $this->getFieldFormatters($className);
106
        $formatters[$field] = function () use ($value) {
107
            return $this->parseScenarioValue((string) $value);
108
        };
109
        $this->applyEntityModifiers($instance);
110
        $this->applyFieldFormatters($instance, $formatters);
111
112
        $entityManager = $this->getEntityManager();
113
        $entityManager->persist($instance);
114
        $entityManager->flush();
115
116
        expect($this->getRepository($className)->findOneBy([$field => $value]))->toBeAnInstanceOf($className);
0 ignored issues
show
Bug introduced by
The method toBeAnInstanceOf() does not exist on Bossa\PhpSpec\Expect\Subject. Did you maybe mean beAnInstanceOf()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
117
    }
118
119
    /**
120
     * @Then there should be a :className with :field :value present in the database
121
     */
122
    public function entityWithFieldShouldExist(string $className, string $field, $value)
123
    {
124
        expect($this->getRepository($className)->findOneBy([$field => $value]))->toBeAnInstanceOf($className);
0 ignored issues
show
Bug introduced by
The method toBeAnInstanceOf() does not exist on Bossa\PhpSpec\Expect\Subject. Did you maybe mean beAnInstanceOf()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
125
    }
126
127
    /**
128
     * @Given :className with :field :value should not exist in database anymore
129
     */
130
    public function entityShouldNotExistInDatabaseAnymore(string $className, string $field, $value)
131
    {
132
        expect($this->getRepository($className)->findOneBy([$field => $value]))->toBe(null);
133
    }
134
135
    /**
136
     * @Then new :className should be created
137
     */
138
    public function newEntityShouldBeCreated(string $className)
139
    {
140
        $this->thereShouldExistsNumberOfEntities(1, $className);
141
    }
142
143
    /**
144
     * @Then there should not be any :className present in the database
145
     */
146
    public function thereShouldNotBeAnyEntities(string $className)
147
    {
148
        $this->thereShouldExistsNumberOfEntities(0, $className);
149
    }
150
151
    /**
152
     * @Then there should be :count :className present in the database
153
     */
154
    public function thereShouldExistsNumberOfEntities($count, string $className)
155
    {
156
        expect(count($this->getRepository($className)->findAll()))->toBe($count);
157
    }
158
159
    /**
160
     * @Given /^the following news exist in database$/
161
     */
162
    public function followingNewsExistInDatabase(TableNode $table)
163
    {
164
        $manager = $this->getEntityManager();
165
        $faker = $this->getFaker();
166
        $newsRepository = $this->getRepository(News::class);
167
        $categoryRepository = $this->getRepository(Category::class);
168
169
        foreach ($table->getHash() as $newsNode) {
170
            $news = $newsRepository->findOneByTitle($newsNode['Title']);
171
            if (!isset($news)) {
172
                $news = new News();
173
            }
174
175
            $news->setTitle($newsNode['Title']);
176
            if (isset($newsNode['Date']) && $newsNode['Date']) {
177
                $news->setDate(DateTime::createFromFormat('Y-m-d', $newsNode['Date']));
178
            }
179
            if (isset($newsNode['Category']) && $newsNode['Category']) {
180
                /** @var Category|null $category */
181
                $category = $categoryRepository->findOneBy(['title' => $newsNode['Category']]);
182
183
                if ($category === null) {
184
                    throw new InvalidArgumentException(sprintf(
185
                        'Can\'t find category by title "%s"',
186
                        $newsNode['Category']
187
                    ));
188
                }
189
190
                $news->addCategory($category);
191
            }
192
            $news->setCreatedAt($faker->dateTime());
193
            $news->setVisible($faker->boolean());
194
            $news->setCreatorEmail($faker->email());
195
196
            $manager->persist($news);
197
        }
198
199
        $manager->flush();
200
    }
201
202
    /**
203
     * @Then :className with :field :value should have :expectedCount elements in collection :collectionName
204
     */
205
    public function entityShouldHaveElementsInCollection(
206
        string $className,
207
        string $field,
208
        $value,
209
        $expectedCount,
210
        string $collectionName
211
    ) {
212
        $entity = $this->getRepository($className)->findOneBy([$field => $value]);
213
        $this->getEntityManager()->refresh($entity);
0 ignored issues
show
Bug introduced by
It seems like $entity defined by $this->getRepository($cl...rray($field => $value)) on line 212 can also be of type null; however, Doctrine\Persistence\ObjectManager::refresh() does only seem to accept object, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
214
215
        expect(count($this->getEntityField($entity, $collectionName)))->toBe($expectedCount);
0 ignored issues
show
Bug introduced by
It seems like $entity defined by $this->getRepository($cl...rray($field => $value)) on line 212 can also be of type null; however, FSi\Bundle\AdminBundle\B...ntext::getEntityField() does only seem to accept object, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
216
    }
217
218
    /**
219
     * @Then :className with id :id should have changed :field to :value
220
     */
221
    public function entityWithIdShouldHaveChangedField(string $className, $id, string $field, $value)
222
    {
223
        $entity = $this->getRepository($className)->find($id);
224
        $this->getEntityManager()->refresh($entity);
0 ignored issues
show
Bug introduced by
It seems like $entity defined by $this->getRepository($className)->find($id) on line 223 can also be of type null; however, Doctrine\Persistence\ObjectManager::refresh() does only seem to accept object, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
225
226
        expect($this->getEntityField($entity, $field))->toBe($value);
0 ignored issues
show
Bug introduced by
It seems like $entity defined by $this->getRepository($className)->find($id) on line 223 can also be of type null; however, FSi\Bundle\AdminBundle\B...ntext::getEntityField() does only seem to accept object, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
227
    }
228
229
    /**
230
     * @Then :className with id :id should not have his :field changed to :value
231
     */
232
    public function entityWithIdShouldNotHaveChangedFieldValue(string $className, $id, string $field, $value)
233
    {
234
        $entity = $this->getRepository($className)->find($id);
235
        $this->getEntityManager()->refresh($entity);
0 ignored issues
show
Bug introduced by
It seems like $entity defined by $this->getRepository($className)->find($id) on line 234 can also be of type null; however, Doctrine\Persistence\ObjectManager::refresh() does only seem to accept object, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
236
237
        expect($this->getEntityField($entity, $field))->notToBe($value);
0 ignored issues
show
Bug introduced by
It seems like $entity defined by $this->getRepository($className)->find($id) on line 234 can also be of type null; however, FSi\Bundle\AdminBundle\B...ntext::getEntityField() does only seem to accept object, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
238
    }
239
240
    /**
241
     * @param object $instance
242
     * @return void
243
     * @throws InvalidArgumentException
244
     */
245
    private function applyFieldFormatters($instance, ?array $formatters = null)
246
    {
247
        $className = get_class($instance);
248
        if (null === $formatters) {
249
            $formatters = $this->getFieldFormatters($className);
250
        }
251
252
        switch (true) {
253
            case $instance instanceof News:
254
                /** @var News $instance */
255
256
                /** @var string title */
257
                $title = $formatters['title']();
258
                $instance->setTitle($title);
259
260
                /** @var string $creatorEmail */
261
                $creatorEmail = $formatters['creatorEmail']();
262
                $instance->setCreatorEmail($creatorEmail);
263
264
                /** @var Category|null $categories */
265
                $categories = $formatters['categories']();
266
                array_walk(
267
                    $categories,
268
                    static function (Category $category, int $key, News $news): void {
269
                        $news->addCategory($category);
270
                    },
271
                    $instance
272
                );
273
274
                /** @var bool $visible */
275
                $visible = $formatters['visible']();
276
                $instance->setVisible($visible);
277
278
                /** @var string|null $photoKey */
279
                $photoKey = $formatters['photoKey']();
280
                $instance->setPhotoKey($photoKey);
281
                break;
282
            case $instance instanceof Person:
283
                /** @var Person $instance */
284
285
                /** @var string $email */
286
                $email = $formatters['email']();
287
                $instance->setEmail($email);
288
                break;
289
            case $instance instanceof Subscriber;
290
                /** @var Subscriber $instance */
291
292
                /** @var string $email */
293
                $email = $formatters['email']();
294
                $instance->setEmail($email);
295
296
                /** @var bool $active */
297
                $active = $formatters['active']();
298
                $instance->setActive($active);
299
                break;
300
            case $instance instanceof Category:
301
302
                /** @var string title */
303
                $title = $formatters['title']();
304
                $instance->setTitle($title);
305
                break;
306
            default:
307
                throw new InvalidArgumentException(sprintf(
308
                    'Cannot find any column formatters for class "%s',
309
                    $className
310
                ));
311
        }
312
    }
313
314
    private function getFieldFormatters(string $className): array
315
    {
316
        $faker = $this->getFaker();
317
        $formatters = [
318
            News::class => [
319
                'title' => function () use ($faker) {
320
                    return $faker->title;
321
                },
322
                'creatorEmail' => function() use ($faker): string {
323
                    return $faker->email();
324
                },
325
                'categories' => function() use ($faker): array {
326
                    /** @var array<Category> $categories */
327
                    $categories = $this->getRepository(Category::class)->findAll();
328
                    if (0 !== count($categories)) {
329
                        /** @var Category $randomCategory */
330
                        $randomCategory = $faker->randomElement($categories);
331
                        $categories = [$randomCategory];
332
                    }
333
334
                    return $categories;
335
336
                },
337
                'photoKey' => function (): ?string {
338
                    return null;
339
                },
340
                'visible' => function (): bool {
341
                    return true;
342
                }
343
            ],
344
            Person::class => [
345
                'email' => function() use ($faker): string {
346
                    return $faker->email();
347
                }
348
            ],
349
            Subscriber::class => [
350
                'email' => function() use ($faker): string {
351
                    return $faker->email();
352
                },
353
                'active' => function (): bool {
354
                    return true;
355
                }
356
            ],
357
            Category::class => [
358
                'title' => function () use ($faker): string {
359
                    return $faker->title;
360
                }
361
            ]
362
        ];
363
364
        if (false === array_key_exists($className, $formatters)) {
365
            throw new RuntimeException("No formatters for class \"{$className}\"");
366
        }
367
368
        return $formatters[$className];
369
    }
370
371
    /**
372
     * @param object $instance
373
     * @return void
374
     * @throws InvalidArgumentException
375
     */
376
    private function applyEntityModifiers($instance): void
377
    {
378
        $faker = $this->getFaker();
379
        switch (true) {
380
            case $instance instanceof News:
381
                $instance->setTitle($faker->title);
382
                $instance->setCreatedAt(new DateTime());
383
                $tag = new Tag();
384
                $tag->setName($faker->sentence());
385
                $tag->setNews($instance);
386
                $instance->setTags([$tag]);
387
                break;
388
            case $instance instanceof Subscriber:
389
                $instance->setCreatedAt(new DateTime());
390
                break;
391
            case $instance instanceof Person:
392
            case $instance instanceof Category:
393
                break;
394
            default:
395
                throw new InvalidArgumentException(sprintf(
396
                    'Cannot find any modifiers for class "%s',
397
                    get_class($instance)
398
                ));
399
        }
400
    }
401
402
    /**
403
     * @param object $entity
404
     * @param string $field
405
     * @return mixed
406
     */
407
    private function getEntityField($entity, string $field)
408
    {
409
        if (null === $this->propertyAccessor) {
410
            $this->propertyAccessor = PropertyAccess::createPropertyAccessor();
411
        }
412
413
        return $this->propertyAccessor->getValue($entity, strtolower($field));
414
    }
415
}
416