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
Pull Request — master (#120)
by joseph
21:56
created

AbstractTest::qaGeneratedCode()

Size

Total Lines 19
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 13
dl 0
loc 19
c 0
b 0
f 0
nc 5
nop 1
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 getNamespaceHelper(): NamespaceHelper
168
    {
169
        return $this->container->get(NamespaceHelper::class);
170
    }
171
172
    protected function emptyDirectory(string $path): void
173
    {
174
        $fileSystem = $this->getFileSystem();
175
        $fileSystem->remove($path);
176
        $fileSystem->mkdir($path);
177
    }
178
179
    /**
180
     * @param string $entitiesPath
181
     *
182
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\ConfigException
183
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
184
     * @SuppressWarnings(PHPMD.Superglobals)
185
     * @SuppressWarnings(PHPMD.StaticAccess)
186
     */
187
    protected function setupContainer(string $entitiesPath): void
188
    {
189
        SimpleEnv::setEnv(Config::getProjectRootDirectory() . '/.env');
190
        $testConfig                                               = $_SERVER;
191
        $testConfig[ConfigInterface::PARAM_ENTITIES_PATH]         = $entitiesPath;
192
        $testConfig[ConfigInterface::PARAM_DB_NAME]               .= '_test';
193
        $testConfig[ConfigInterface::PARAM_DEVMODE]               = true;
194
        $testConfig[ConfigInterface::PARAM_FILESYSTEM_CACHE_PATH] = static::WORK_DIR . '/cache/dsm';
195
        $this->container                                          = new Container();
196
        $this->container->buildSymfonyContainer($testConfig);
197
    }
198
199
    /**
200
     * Clear the Doctrine Cache
201
     *
202
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
203
     */
204
    protected function clearCache(): void
205
    {
206
        $cache = $this->getEntityManager()
207
                      ->getConfiguration()
208
                      ->getMetadataCacheImpl();
209
        if ($cache instanceof CacheProvider) {
210
            $cache->deleteAll();
211
        }
212
    }
213
214
    /**
215
     * @return EntityManagerInterface
216
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
217
     */
218
    protected function getEntityManager(): EntityManagerInterface
219
    {
220
        return $this->container->get(EntityManagerInterface::class);
221
    }
222
223
    /**
224
     * Accesses the standard Composer autoloader
225
     *
226
     * Extends the class and also providing an entry point for xdebugging
227
     *
228
     * Extends this with a PSR4 root for our WORK_DIR
229
     *
230
     * @param string $namespace
231
     * @param string $path
232
     *
233
     * @throws \ReflectionException
234
     */
235
    protected function extendAutoloader(string $namespace, string $path): void
236
    {
237
        //Unregister any previously set extension first
238
        $registered = \spl_autoload_functions();
239
        foreach ($registered as $loader) {
240
            if ((new  \ts\Reflection\ReflectionClass(\get_class($loader[0])))->isAnonymous()) {
241
                \spl_autoload_unregister($loader);
242
            }
243
        }
244
        //Then build a new extension and register it
245
        $namespace  = rtrim($namespace, '\\') . '\\';
246
        $testLoader = new class($namespace) extends ClassLoader
247
        {
248
            /**
249
             * @var string
250
             */
251
            protected $namespace;
252
253
            public function __construct(string $namespace)
254
            {
255
                $this->namespace = $namespace;
256
            }
257
258
            public function loadClass($class)
259
            {
260
                if (false === strpos($class, $this->namespace)) {
261
                    return false;
262
                }
263
                $found = parent::loadClass($class);
264
                if (false === $found || null === $found) {
0 ignored issues
show
introduced by
The condition null === $found is always false.
Loading history...
265
                    //good point to set a breakpoint
266
                    return $found;
267
                }
268
269
                return $found;
270
            }
271
        };
272
        $testLoader->addPsr4($namespace, $path . '/src', true);
273
        $testLoader->addPsr4($namespace, $path . '/tests', true);
274
        $testLoader->register();
275
    }
276
277
    /**
278
     * Run QA tools against the generated code
279
     *
280
     * Can specify a custom namespace root if required
281
     *
282
     * Will run:
283
     *
284
     * - PHP linting
285
     * - PHPStan
286
     *
287
     * @SuppressWarnings(PHPMD.Superglobals)
288
     * @param null|string $namespaceRoot
289
     *
290
     * @return bool
291
     */
292
    public function qaGeneratedCode(?string $namespaceRoot = null): bool
293
    {
294
        if ($this->isQuickTests()) {
295
            return true;
296
        }
297
        $workDir       = static::WORK_DIR;
298
        $namespaceRoot = trim($namespaceRoot ?? static::TEST_PROJECT_ROOT_NAMESPACE, '\\');
299
        if (null !== $this->copiedRootNamespace) {
300
            $workDir       = $this->copiedWorkDir;
301
            $namespaceRoot = trim($this->copiedRootNamespace, '\\');
302
        }
303
        static $codeValidator;
304
        if (null === $codeValidator) {
305
            $codeValidator = new CodeValidator();
306
        }
307
        $errors = $codeValidator($workDir, $namespaceRoot);
308
        self::assertNull($errors);
309
310
        return true;
311
    }
312
313
    /**
314
     * @return bool
315
     * @SuppressWarnings(PHPMD.Superglobals)
316
     */
317
    protected function isQuickTests(): bool
318
    {
319
        if (isset($_SERVER[Constants::QA_QUICK_TESTS_KEY])
320
            && (int)$_SERVER[Constants::QA_QUICK_TESTS_KEY] === Constants::QA_QUICK_TESTS_ENABLED
321
        ) {
322
            return true;
323
        }
324
325
        return false;
326
    }
327
328
    protected function dump(EntityInterface $entity): string
329
    {
330
        return (new EntityDebugDumper())->dump($entity, $this->getEntityManager());
331
    }
332
333
    /**
334
     * If PHP loads any files whilst generating, then subsequent changes to those files will not have any effect
335
     *
336
     * To resolve this, we need to clone the copied code into a new namespace before running it
337
     *
338
     * We only allow copying to a new work dir once per test run, different extras must be used
339
     *
340
     * @return string $copiedWorkDir
341
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
342
     * @throws \ReflectionException
343
     */
344
    protected function setupCopiedWorkDir(): string
345
    {
346
        $copiedNamespaceRoot       = $this->getCopiedNamespaceRoot();
347
        $this->copiedWorkDir       = rtrim(static::WORK_DIR, '/') . 'Copies/' . $copiedNamespaceRoot . '/';
348
        $this->copiedRootNamespace = $copiedNamespaceRoot;
349
        if (is_dir($this->copiedWorkDir)) {
350
            throw new \RuntimeException(
351
                'The Copied WorkDir ' . $this->copiedWorkDir . ' Already Exists'
352
            );
353
        }
354
        if (is_dir($this->copiedWorkDir)) {
355
            $this->getFileSystem()->remove($this->copiedWorkDir);
356
        }
357
        $this->getFileSystem()->mkdir($this->copiedWorkDir);
358
        $this->getFileSystem()->mirror(static::WORK_DIR, $this->copiedWorkDir);
359
        $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->copiedWorkDir));
360
361
        foreach ($iterator as $info) {
362
            /**
363
             * @var \SplFileInfo $info
364
             */
365
            if (false === $info->isFile()) {
366
                continue;
367
            }
368
            $contents = file_get_contents($info->getPathname());
369
370
            $updated = \preg_replace(
371
                '%(use|namespace)\s+?'
372
                . $this->container->get(FindAndReplaceHelper::class)
373
                                  ->escapeSlashesForRegex(static::TEST_PROJECT_ROOT_NAMESPACE)
374
                . '\\\\%',
375
                '$1 ' . $copiedNamespaceRoot . '\\',
376
                $contents
377
            );
378
            if ('AbstractEntityTest.php' === $info->getBasename()) {
379
                $updated = str_replace(
380
                    "'" . static::TEST_PROJECT_ROOT_NAMESPACE,
381
                    "'" . $copiedNamespaceRoot,
382
                    $updated
383
                );
384
            }
385
            file_put_contents($info->getPathname(), $updated);
386
        }
387
        $this->extendAutoloader(
388
            $this->copiedRootNamespace . '\\',
389
            $this->copiedWorkDir
390
        );
391
        $this->clearCache();
392
393
        return $this->copiedWorkDir;
394
    }
395
396
    /**
397
     * Get the namespace root to use in a copied work dir
398
     *
399
     * @return string
400
     * @throws \ReflectionException
401
     */
402
    protected function getCopiedNamespaceRoot(): string
403
    {
404
        return (new  \ts\Reflection\ReflectionClass(static::class))->getShortName() . '_' . $this->getName() . '_';
405
    }
406
407
    /**
408
     * When working with a copied work dir, use this function to translate the FQN of any Entities etc
409
     *
410
     * @param string $fqn
411
     *
412
     * @return string
413
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
414
     * @throws \ReflectionException
415
     */
416
    protected function getCopiedFqn(string $fqn): string
417
    {
418
        $copiedNamespaceRoot = $this->getCopiedNamespaceRoot();
419
420
        return $this->container
421
            ->get(NamespaceHelper::class)
422
            ->tidy('\\' . $copiedNamespaceRoot . '\\'
423
                   . ltrim(
424
                       \str_replace(static::TEST_PROJECT_ROOT_NAMESPACE, '', $fqn),
425
                       '\\'
426
                   ));
427
    }
428
429
    /**
430
     * @return bool
431
     * @SuppressWarnings(PHPMD.Superglobals)
432
     */
433
    protected function isTravis(): bool
434
    {
435
        return isset($_SERVER['TRAVIS']);
436
    }
437
438
    protected function getEntityEmbeddableSetter(): EntityEmbeddableSetter
439
    {
440
        return $this->container->get(EntityEmbeddableSetter::class);
441
    }
442
443
    /**
444
     * @return ArchetypeEmbeddableGenerator
445
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
446
     */
447
    protected function getArchetypeEmbeddableGenerator(): ArchetypeEmbeddableGenerator
448
    {
449
        /**
450
         * @var ArchetypeEmbeddableGenerator $generator
451
         */
452
        $generator = $this->container->get(ArchetypeEmbeddableGenerator::class);
453
        $generator->setProjectRootNamespace(static::TEST_PROJECT_ROOT_NAMESPACE)
454
                  ->setPathToProjectRoot(static::WORK_DIR);
455
456
        return $generator;
457
    }
458
459
    protected function assertNoMissedReplacements(string $createdFile, array $checkFor = []): void
460
    {
461
        $createdFile = $this->getPathHelper()->resolvePath($createdFile);
462
        self::assertFileExists($createdFile);
463
        $contents   = file_get_contents($createdFile);
464
        $checkFor[] = 'template';
465
        foreach ($checkFor as $check) {
466
            self::assertNotRegExp(
467
                '%[^a-z]' . $check . '[^a-z]%i',
468
                $contents,
469
                'Found the word "' . $check . '" (case insensitive) in the created file ' . $createdFile
470
            );
471
        }
472
    }
473
474
    protected function getPathHelper(): PathHelper
475
    {
476
        return $this->container->get(PathHelper::class);
477
    }
478
479
    protected function getTestEntityGeneratorFactory(): TestEntityGeneratorFactory
480
    {
481
        return $this->container->get(TestEntityGeneratorFactory::class);
482
    }
483
484
    protected function assertFileContains(string $createdFile, string $needle): void
485
    {
486
        $createdFile = $this->getPathHelper()->resolvePath($createdFile);
487
        self::assertFileExists($createdFile);
488
        $contents = file_get_contents($createdFile);
489
        self::assertContains(
490
            $needle,
491
            $contents,
492
            "Missing '$needle' in file '$createdFile'"
493
        );
494
    }
495
496
    protected function getEntityGenerator(): EntityGenerator
497
    {
498
        /**
499
         * @var EntityGenerator $entityGenerator
500
         */
501
        $entityGenerator = $this->container->get(EntityGenerator::class);
502
        $entityGenerator->setPathToProjectRoot($this->copiedWorkDir ?? static::WORK_DIR)
503
                        ->setProjectRootNamespace($this->copiedRootNamespace ?? static::TEST_PROJECT_ROOT_NAMESPACE);
504
505
        return $entityGenerator;
506
    }
507
508
    protected function getRelationsGenerator(): RelationsGenerator
509
    {
510
        /**
511
         * @var RelationsGenerator $relationsGenerator
512
         */
513
        $relationsGenerator = $this->container->get(RelationsGenerator::class);
514
        $relationsGenerator->setPathToProjectRoot($this->copiedWorkDir ?? static::WORK_DIR)
515
                           ->setProjectRootNamespace($this->copiedRootNamespace ?? static::TEST_PROJECT_ROOT_NAMESPACE);
516
517
        return $relationsGenerator;
518
    }
519
520
    protected function getFieldGenerator(): FieldGenerator
521
    {
522
        /**
523
         * @var \EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\Field\FieldGenerator $fieldGenerator
524
         */
525
        $fieldGenerator = $this->container->get(FieldGenerator::class);
526
        $fieldGenerator->setPathToProjectRoot($this->copiedWorkDir ?? static::WORK_DIR)
527
                       ->setProjectRootNamespace($this->copiedRootNamespace ?? static::TEST_PROJECT_ROOT_NAMESPACE);
528
529
        return $fieldGenerator;
530
    }
531
532
    protected function getTestCodeGenerator(): TestCodeGenerator
533
    {
534
        return $this->container->get(TestCodeGenerator::class);
535
    }
536
537
    protected function getFieldSetter(): EntityFieldSetter
538
    {
539
        $fieldSetter = $this->container->get(EntityFieldSetter::class);
540
        $fieldSetter->setPathToProjectRoot($this->copiedWorkDir ?? static::WORK_DIR)
541
                    ->setProjectRootNamespace($this->copiedRootNamespace ?? static::TEST_PROJECT_ROOT_NAMESPACE);
542
543
        return $fieldSetter;
544
    }
545
546
    protected function getSchema(): Schema
547
    {
548
        return $this->container->get(Schema::class);
549
    }
550
551
    protected function getCodeHelper(): CodeHelper
552
    {
553
        return $this->container->get(CodeHelper::class);
554
    }
555
556
    protected function getUnusedRelationsRemover(): UnusedRelationsRemover
557
    {
558
        return $this->container->get(UnusedRelationsRemover::class);
559
    }
560
561
    protected function createEntity(string $entityFqn): EntityInterface
562
    {
563
        return $this->getEntityFactory()->create($entityFqn);
564
    }
565
566
    protected function getEntityFactory(): EntityFactory
567
    {
568
        $factory = $this->container->get(EntityFactory::class);
569
        $factory->setEntityManager($this->getEntityManager());
570
571
        return $factory;
572
    }
573
}
574