GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 84892e...749633 )
by joseph
25:37 queued 22:57
created

AbstractTest.php$0 ➔ isQuickTests()   A

Complexity

Conditions 3

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.9666
c 0
b 0
f 0
cc 3
1
<?php declare(strict_types=1);
2
3
namespace EdmondsCommerce\DoctrineStaticMeta\Tests\Assets;
4
5
use Composer\Autoload\ClassLoader;
6
use Doctrine\Common\Cache\CacheProvider;
7
use Doctrine\ORM\EntityManagerInterface;
8
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\CodeHelper;
9
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Command\AbstractCommand;
10
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\AbstractGenerator;
11
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\Embeddable\ArchetypeEmbeddableGenerator;
12
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\Embeddable\EntityEmbeddableSetter;
13
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\EntityGenerator;
14
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\Field\EntityFieldSetter;
15
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\Field\FieldGenerator;
16
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\FindAndReplaceHelper;
17
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\RelationsGenerator;
18
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\NamespaceHelper;
19
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\PathHelper;
20
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\UnusedRelationsRemover;
21
use EdmondsCommerce\DoctrineStaticMeta\Config;
22
use EdmondsCommerce\DoctrineStaticMeta\ConfigInterface;
23
use EdmondsCommerce\DoctrineStaticMeta\Container;
24
use EdmondsCommerce\DoctrineStaticMeta\Entity\Factory\EntityFactory;
25
use EdmondsCommerce\DoctrineStaticMeta\Entity\Interfaces\EntityInterface;
26
use EdmondsCommerce\DoctrineStaticMeta\Entity\Testing\EntityDebugDumper;
27
use EdmondsCommerce\DoctrineStaticMeta\Entity\Testing\EntityGenerator\TestEntityGeneratorFactory;
28
use EdmondsCommerce\DoctrineStaticMeta\Schema\Schema;
29
use EdmondsCommerce\DoctrineStaticMeta\SimpleEnv;
30
use EdmondsCommerce\PHPQA\Constants;
31
use PHPUnit\Framework\TestCase;
32
use Symfony\Component\Filesystem\Filesystem;
33
34
/**
35
 * Class AbstractTest
36
 *
37
 * @package EdmondsCommerce\DoctrineStaticMeta
38
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
39
 * @SuppressWarnings(PHPMD.NumberOfChildren)
40
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
41
 */
42
abstract class AbstractTest extends TestCase
43
{
44
    public const TEST_TYPE_SMALL             = 'Small';
45
    public const TEST_TYPE_MEDIUM            = 'Medium';
46
    public const TEST_TYPE_LARGE             = 'Large';
47
    public const VAR_PATH                    = __DIR__ . '/../../var/testOutput/';
48
    public const WORK_DIR                    = 'override me';
49
    public const TEST_PROJECT_ROOT_NAMESPACE = 'My\\Test\\Project';
50
    protected static $buildOnce = false;
51
    protected static $built     = false;
52
    /**
53
     * The absolute path to the Entities folder, eg:
54
     * /var/www/vhosts/doctrine-static-meta/var/{testWorkDir}/Entities
55
     *
56
     * @var string
57
     */
58
    protected $entitiesPath = '';
59
    /**
60
     * The absolute path to the EntityRelations folder, eg:
61
     * /var/www/vhosts/doctrine-static-meta/var/{testWorkDir}/Entity/Relations
62
     *
63
     * @var string
64
     */
65
    protected $entityRelationsPath = '';
66
    /**
67
     * @var Container
68
     */
69
    protected $container;
70
    /**
71
     * @var Filesystem
72
     */
73
    protected $filesystem;
74
    /**
75
     * @var string|null
76
     */
77
    protected $copiedWorkDir;
78
    /**
79
     * @var string|null
80
     */
81
    protected $copiedRootNamespace;
82
83
    /**
84
     * Ensure built steps are set to false when test class is instantiated
85
     */
86
    public static function setUpBeforeClass()
87
    {
88
        self::$built   = false;
89
        static::$built = false;
90
    }
91
92
    /**
93
     * Prepare working directory, ensure its empty, create entities folder and set up env variables
94
     *
95
     * The order of these actions is critical
96
     */
97
    public function setUp()
98
    {
99
        if (false !== stripos(static::WORK_DIR, self::WORK_DIR)) {
100
            throw new \RuntimeException(
101
                "You must set a `public const WORK_DIR=AbstractTest::VAR_PATH.'/'"
102
                . ".self::TEST_TYPE.'/folderName/';` in your test class"
103
            );
104
        }
105
        if (false === strpos(static::WORK_DIR, '/' . static::TEST_TYPE_SMALL)
106
            && false === strpos(static::WORK_DIR, '/' . static::TEST_TYPE_MEDIUM)
107
            && false === strpos(static::WORK_DIR, '/' . static::TEST_TYPE_LARGE)
108
        ) {
109
            throw new \RuntimeException(
110
                'Your WORK_DIR is missing the test type, should look like: '
111
                . "`public const WORK_DIR=AbstractTest::VAR_PATH.'/'"
112
                . ".self::TEST_TYPE_(SMALL|MEDIUM|LARGE).'/folderName/';` in your test class"
113
            );
114
        }
115
        $this->copiedWorkDir       = null;
116
        $this->copiedRootNamespace = null;
117
        $this->entitiesPath        = static::WORK_DIR
118
                                     . '/' . AbstractCommand::DEFAULT_SRC_SUBFOLDER
119
                                     . '/' . AbstractGenerator::ENTITIES_FOLDER_NAME;
120
        $this->entityRelationsPath = static::WORK_DIR
121
                                     . '/' . AbstractCommand::DEFAULT_SRC_SUBFOLDER
122
                                     . '/' . AbstractGenerator::ENTITY_RELATIONS_FOLDER_NAME;
123
        $this->clearWorkDir();
124
        $this->setupContainer($this->entitiesPath);
125
        $this->clearCache();
126
        $this->extendAutoloader(
127
            static::TEST_PROJECT_ROOT_NAMESPACE . '\\',
128
            static::WORK_DIR
129
        );
130
    }
131
132
    protected function clearWorkDir(): void
133
    {
134
        if (true === static::$buildOnce && true === static::$built) {
135
            $this->entitiesPath = $this->getRealPath($this->entitiesPath);
136
137
            return;
138
        }
139
        $this->getFileSystem()->mkdir(static::WORK_DIR);
140
        $this->emptyDirectory(static::WORK_DIR);
141
        $this->getFileSystem()->mkdir($this->entitiesPath);
142
        $this->entitiesPath = $this->getRealPath($this->entitiesPath);
143
144
        $this->getFileSystem()->mkdir($this->entityRelationsPath);
145
        $this->entityRelationsPath = realpath($this->entityRelationsPath);
146
    }
147
148
    protected function getRealPath(string $path)
149
    {
150
        $realpath = realpath($path);
151
        if (false === $realpath) {
152
            throw new \RuntimeException('Failed getting realpath for path: ' . $path);
153
        }
154
155
        return $realpath;
156
    }
157
158
    protected function getFileSystem(): Filesystem
159
    {
160
        if (null === $this->filesystem) {
161
            $this->filesystem = new Filesystem();
162
        }
163
164
        return $this->filesystem;
165
    }
166
167
    protected function emptyDirectory(string $path): void
168
    {
169
        $fileSystem = $this->getFileSystem();
170
        $fileSystem->remove($path);
171
        $fileSystem->mkdir($path);
172
    }
173
174
    /**
175
     * @param string $entitiesPath
176
     *
177
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\ConfigException
178
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
179
     * @SuppressWarnings(PHPMD.Superglobals)
180
     * @SuppressWarnings(PHPMD.StaticAccess)
181
     */
182
    protected function setupContainer(string $entitiesPath): void
183
    {
184
        SimpleEnv::setEnv(Config::getProjectRootDirectory() . '/.env');
185
        $testConfig                                               = $_SERVER;
186
        $testConfig[ConfigInterface::PARAM_ENTITIES_PATH]         = $entitiesPath;
187
        $testConfig[ConfigInterface::PARAM_DB_NAME]               .= '_test';
188
        $testConfig[ConfigInterface::PARAM_DEVMODE]               = true;
189
        $testConfig[ConfigInterface::PARAM_FILESYSTEM_CACHE_PATH] = static::WORK_DIR . '/cache/dsm';
190
        $this->container                                          = new Container();
191
        $this->container->buildSymfonyContainer($testConfig);
192
    }
193
194
    /**
195
     * Clear the Doctrine Cache
196
     *
197
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
198
     */
199
    protected function clearCache(): void
200
    {
201
        $cache = $this->getEntityManager()
202
                      ->getConfiguration()
203
                      ->getMetadataCacheImpl();
204
        if ($cache instanceof CacheProvider) {
205
            $cache->deleteAll();
206
        }
207
    }
208
209
    /**
210
     * @return EntityManagerInterface
211
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
212
     */
213
    protected function getEntityManager(): EntityManagerInterface
214
    {
215
        return $this->container->get(EntityManagerInterface::class);
216
    }
217
218
    /**
219
     * Accesses the standard Composer autoloader
220
     *
221
     * Extends the class and also providing an entry point for xdebugging
222
     *
223
     * Extends this with a PSR4 root for our WORK_DIR
224
     *
225
     * @param string $namespace
226
     * @param string $path
227
     *
228
     * @throws \ReflectionException
229
     */
230
    protected function extendAutoloader(string $namespace, string $path): void
231
    {
232
        //Unregister any previously set extension first
233
        $registered = \spl_autoload_functions();
234
        foreach ($registered as $loader) {
235
            if ((new  \ts\Reflection\ReflectionClass(\get_class($loader[0])))->isAnonymous()) {
236
                \spl_autoload_unregister($loader);
237
            }
238
        }
239
        //Then build a new extension and register it
240
        $namespace  = rtrim($namespace, '\\') . '\\';
241
        $testLoader = new class($namespace) extends ClassLoader
242
        {
243
            /**
244
             * @var string
245
             */
246
            protected $namespace;
247
248
            public function __construct(string $namespace)
249
            {
250
                $this->namespace = $namespace;
251
            }
252
253
            public function loadClass($class)
254
            {
255
                if (false === strpos($class, $this->namespace)) {
256
                    return false;
257
                }
258
                $found = parent::loadClass($class);
259
                if (false === $found || null === $found) {
0 ignored issues
show
introduced by
The condition null === $found is always false.
Loading history...
260
                    //good point to set a breakpoint
261
                    return $found;
262
                }
263
264
                return $found;
265
            }
266
        };
267
        $testLoader->addPsr4($namespace, $path . '/src', true);
268
        $testLoader->addPsr4($namespace, $path . '/tests', true);
269
        $testLoader->register();
270
    }
271
272
    /**
273
     * Run QA tools against the generated code
274
     *
275
     * Can specify a custom namespace root if required
276
     *
277
     * Will run:
278
     *
279
     * - PHP linting
280
     * - PHPStan
281
     *
282
     * @SuppressWarnings(PHPMD.Superglobals)
283
     * @param null|string $namespaceRoot
284
     *
285
     * @return bool
286
     */
287
    public function qaGeneratedCode(?string $namespaceRoot = null): bool
288
    {
289
        if ($this->isQuickTests()) {
290
            return true;
291
        }
292
        $workDir       = static::WORK_DIR;
293
        $namespaceRoot = trim($namespaceRoot ?? static::TEST_PROJECT_ROOT_NAMESPACE, '\\');
294
        if (null !== $this->copiedRootNamespace) {
295
            $workDir       = $this->copiedWorkDir;
296
            $namespaceRoot = trim($this->copiedRootNamespace, '\\');
297
        }
298
        static $codeValidator;
299
        if (null === $codeValidator) {
300
            $codeValidator = new CodeValidator();
301
        }
302
        $errors = $codeValidator($workDir, $namespaceRoot);
303
        self::assertNull($errors);
304
305
        return true;
306
    }
307
308
    /**
309
     * @return bool
310
     * @SuppressWarnings(PHPMD.Superglobals)
311
     */
312
    protected function isQuickTests(): bool
313
    {
314
        if (isset($_SERVER[Constants::QA_QUICK_TESTS_KEY])
315
            && (int)$_SERVER[Constants::QA_QUICK_TESTS_KEY] === Constants::QA_QUICK_TESTS_ENABLED
316
        ) {
317
            return true;
318
        }
319
320
        return false;
321
    }
322
323
    protected function tearDown()
324
    {
325
        $entityManager = $this->getEntityManager();
326
        $connection    = $entityManager->getConnection();
327
328
        $entityManager->close();
329
        $connection->close();
330
    }
331
332
    protected function getNamespaceHelper(): NamespaceHelper
333
    {
334
        return $this->container->get(NamespaceHelper::class);
335
    }
336
337
    protected function dump(EntityInterface $entity): string
338
    {
339
        return (new EntityDebugDumper())->dump($entity, $this->getEntityManager());
340
    }
341
342
    /**
343
     * If PHP loads any files whilst generating, then subsequent changes to those files will not have any effect
344
     *
345
     * To resolve this, we need to clone the copied code into a new namespace before running it
346
     *
347
     * We only allow copying to a new work dir once per test run, different extras must be used
348
     *
349
     * @return string $copiedWorkDir
350
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
351
     * @throws \ReflectionException
352
     */
353
    protected function setupCopiedWorkDir(): string
354
    {
355
        $copiedNamespaceRoot       = $this->getCopiedNamespaceRoot();
356
        $this->copiedWorkDir       = rtrim(static::WORK_DIR, '/') . 'Copies/' . $copiedNamespaceRoot . '/';
357
        $this->copiedRootNamespace = $copiedNamespaceRoot;
358
        if (is_dir($this->copiedWorkDir)) {
359
            throw new \RuntimeException(
360
                'The Copied WorkDir ' . $this->copiedWorkDir . ' Already Exists'
361
            );
362
        }
363
        if (is_dir($this->copiedWorkDir)) {
364
            $this->getFileSystem()->remove($this->copiedWorkDir);
365
        }
366
        $this->getFileSystem()->mkdir($this->copiedWorkDir);
367
        $this->getFileSystem()->mirror(static::WORK_DIR, $this->copiedWorkDir);
368
        $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->copiedWorkDir));
369
370
        foreach ($iterator as $info) {
371
            /**
372
             * @var \SplFileInfo $info
373
             */
374
            if (false === $info->isFile()) {
375
                continue;
376
            }
377
            $contents = file_get_contents($info->getPathname());
378
379
            $updated = \preg_replace(
380
                '%(use|namespace)\s+?'
381
                . $this->container->get(FindAndReplaceHelper::class)
382
                                  ->escapeSlashesForRegex(static::TEST_PROJECT_ROOT_NAMESPACE)
383
                . '\\\\%',
384
                '$1 ' . $copiedNamespaceRoot . '\\',
385
                $contents
386
            );
387
            if ('AbstractEntityTest.php' === $info->getBasename()) {
388
                $updated = str_replace(
389
                    "'" . static::TEST_PROJECT_ROOT_NAMESPACE,
390
                    "'" . $copiedNamespaceRoot,
391
                    $updated
392
                );
393
            }
394
            file_put_contents($info->getPathname(), $updated);
395
        }
396
        $this->extendAutoloader(
397
            $this->copiedRootNamespace . '\\',
398
            $this->copiedWorkDir
399
        );
400
        $this->clearCache();
401
402
        return $this->copiedWorkDir;
403
    }
404
405
    /**
406
     * Get the namespace root to use in a copied work dir
407
     *
408
     * @return string
409
     * @throws \ReflectionException
410
     */
411
    protected function getCopiedNamespaceRoot(): string
412
    {
413
        return (new  \ts\Reflection\ReflectionClass(static::class))->getShortName() . '_' . $this->getName() . '_';
414
    }
415
416
    /**
417
     * When working with a copied work dir, use this function to translate the FQN of any Entities etc
418
     *
419
     * This will replace both the raw TestCodeGenerator root namespace and the test level root namespace
420
     *
421
     * @param string $fqn
422
     *
423
     * @return string
424
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
425
     * @throws \ReflectionException
426
     */
427
    protected function getCopiedFqn(string $fqn): string
428
    {
429
        $copiedNamespaceRoot = $this->getCopiedNamespaceRoot();
430
431
        return $this->container
432
            ->get(NamespaceHelper::class)
433
            ->tidy('\\' . $copiedNamespaceRoot . '\\'
434
                   . ltrim(
435
                       \str_replace(
436
                           [
437
                               static::TEST_PROJECT_ROOT_NAMESPACE,
438
                               TestCodeGenerator::TEST_PROJECT_ROOT_NAMESPACE,
439
                           ],
440
                           '',
441
                           $fqn
442
                       ),
443
                       '\\'
444
                   ));
445
    }
446
447
    /**
448
     * @return bool
449
     * @SuppressWarnings(PHPMD.Superglobals)
450
     */
451
    protected function isTravis(): bool
452
    {
453
        return isset($_SERVER['TRAVIS']);
454
    }
455
456
    protected function getEntityEmbeddableSetter(): EntityEmbeddableSetter
457
    {
458
        return $this->container->get(EntityEmbeddableSetter::class);
459
    }
460
461
    /**
462
     * @return ArchetypeEmbeddableGenerator
463
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
464
     */
465
    protected function getArchetypeEmbeddableGenerator(): ArchetypeEmbeddableGenerator
466
    {
467
        /**
468
         * @var ArchetypeEmbeddableGenerator $generator
469
         */
470
        $generator = $this->container->get(ArchetypeEmbeddableGenerator::class);
471
        $generator->setProjectRootNamespace(static::TEST_PROJECT_ROOT_NAMESPACE)
472
                  ->setPathToProjectRoot(static::WORK_DIR);
473
474
        return $generator;
475
    }
476
477
    protected function assertNoMissedReplacements(string $createdFile, array $checkFor = []): void
478
    {
479
        $createdFile = $this->getPathHelper()->resolvePath($createdFile);
480
        self::assertFileExists($createdFile);
481
        $contents   = file_get_contents($createdFile);
482
        $checkFor[] = 'template';
483
        foreach ($checkFor as $check) {
484
            self::assertNotRegExp(
485
                '%[^a-z]' . $check . '[^a-z]%i',
486
                $contents,
487
                'Found the word "' . $check . '" (case insensitive) in the created file ' . $createdFile
488
            );
489
        }
490
    }
491
492
    protected function getPathHelper(): PathHelper
493
    {
494
        return $this->container->get(PathHelper::class);
495
    }
496
497
    protected function getTestEntityGeneratorFactory(): TestEntityGeneratorFactory
498
    {
499
        return $this->container->get(TestEntityGeneratorFactory::class);
500
    }
501
502
    protected function assertFileContains(string $createdFile, string $needle): void
503
    {
504
        $createdFile = $this->getPathHelper()->resolvePath($createdFile);
505
        self::assertFileExists($createdFile);
506
        $contents = file_get_contents($createdFile);
507
        self::assertContains(
508
            $needle,
509
            $contents,
510
            "Missing '$needle' in file '$createdFile'"
511
        );
512
    }
513
514
    protected function getEntityGenerator(): EntityGenerator
515
    {
516
        /**
517
         * @var EntityGenerator $entityGenerator
518
         */
519
        $entityGenerator = $this->container->get(EntityGenerator::class);
520
        $entityGenerator->setPathToProjectRoot($this->copiedWorkDir ?? static::WORK_DIR)
521
                        ->setProjectRootNamespace($this->copiedRootNamespace ?? static::TEST_PROJECT_ROOT_NAMESPACE);
522
523
        return $entityGenerator;
524
    }
525
526
    protected function getRelationsGenerator(): RelationsGenerator
527
    {
528
        /**
529
         * @var RelationsGenerator $relationsGenerator
530
         */
531
        $relationsGenerator = $this->container->get(RelationsGenerator::class);
532
        $relationsGenerator->setPathToProjectRoot($this->copiedWorkDir ?? static::WORK_DIR)
533
                           ->setProjectRootNamespace($this->copiedRootNamespace ?? static::TEST_PROJECT_ROOT_NAMESPACE);
534
535
        return $relationsGenerator;
536
    }
537
538
    protected function getFieldGenerator(): FieldGenerator
539
    {
540
        /**
541
         * @var \EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\Field\FieldGenerator $fieldGenerator
542
         */
543
        $fieldGenerator = $this->container->get(FieldGenerator::class);
544
        $fieldGenerator->setPathToProjectRoot($this->copiedWorkDir ?? static::WORK_DIR)
545
                       ->setProjectRootNamespace($this->copiedRootNamespace ?? static::TEST_PROJECT_ROOT_NAMESPACE);
546
547
        return $fieldGenerator;
548
    }
549
550
    protected function getTestCodeGenerator(): TestCodeGenerator
551
    {
552
        return $this->container->get(TestCodeGenerator::class);
553
    }
554
555
    protected function getFieldSetter(): EntityFieldSetter
556
    {
557
        $fieldSetter = $this->container->get(EntityFieldSetter::class);
558
        $fieldSetter->setPathToProjectRoot($this->copiedWorkDir ?? static::WORK_DIR)
559
                    ->setProjectRootNamespace($this->copiedRootNamespace ?? static::TEST_PROJECT_ROOT_NAMESPACE);
560
561
        return $fieldSetter;
562
    }
563
564
    protected function getSchema(): Schema
565
    {
566
        return $this->container->get(Schema::class);
567
    }
568
569
    protected function getCodeHelper(): CodeHelper
570
    {
571
        return $this->container->get(CodeHelper::class);
572
    }
573
574
    protected function getUnusedRelationsRemover(): UnusedRelationsRemover
575
    {
576
        return $this->container->get(UnusedRelationsRemover::class);
577
    }
578
579
    protected function createEntity(string $entityFqn): EntityInterface
580
    {
581
        return $this->getEntityFactory()->create($entityFqn);
582
    }
583
584
    protected function getEntityFactory(): EntityFactory
585
    {
586
        $factory = $this->container->get(EntityFactory::class);
587
        $factory->setEntityManager($this->getEntityManager());
588
589
        return $factory;
590
    }
591
}
592