Completed
Pull Request — 3.1 (#348)
by Piotr
09:36 queued 07:40
created

DataContext::entityToClassName()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 8.8333
c 0
b 0
f 0
cc 7
nc 7
nop 1
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';
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);
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);
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);
214
215
        expect(count($this->getEntityField($entity, $collectionName)))->toBe($expectedCount);
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);
225
226
        expect($this->getEntityField($entity, $field))->toBe($value);
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);
236
237
        expect($this->getEntityField($entity, $field))->notToBe($value);
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