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 (#127)
by joseph
25:04 queued 08:38
created

AbstractTest.php$0 ➔ getNamespaceHelper()   A

Complexity

Conditions 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 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 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 getNamespaceHelper(): NamespaceHelper
324
    {
325
        return $this->container->get(NamespaceHelper::class);
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
     * This will replace both the raw TestCodeGenerator root namespace and the test level root namespace
411
     *
412
     * @param string $fqn
413
     *
414
     * @return string
415
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
416
     * @throws \ReflectionException
417
     */
418
    protected function getCopiedFqn(string $fqn): string
419
    {
420
        $copiedNamespaceRoot = $this->getCopiedNamespaceRoot();
421
422
        return $this->container
423
            ->get(NamespaceHelper::class)
424
            ->tidy('\\' . $copiedNamespaceRoot . '\\'
425
                   . ltrim(
426
                       \str_replace(
427
                           [
428
                               static::TEST_PROJECT_ROOT_NAMESPACE,
429
                               TestCodeGenerator::TEST_PROJECT_ROOT_NAMESPACE,
430
                           ],
431
                           '',
432
                           $fqn
433
                       ),
434
                       '\\'
435
                   ));
436
    }
437
438
    /**
439
     * @return bool
440
     * @SuppressWarnings(PHPMD.Superglobals)
441
     */
442
    protected function isTravis(): bool
443
    {
444
        return isset($_SERVER['TRAVIS']);
445
    }
446
447
    protected function getEntityEmbeddableSetter(): EntityEmbeddableSetter
448
    {
449
        return $this->container->get(EntityEmbeddableSetter::class);
450
    }
451
452
    /**
453
     * @return ArchetypeEmbeddableGenerator
454
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
455
     */
456
    protected function getArchetypeEmbeddableGenerator(): ArchetypeEmbeddableGenerator
457
    {
458
        /**
459
         * @var ArchetypeEmbeddableGenerator $generator
460
         */
461
        $generator = $this->container->get(ArchetypeEmbeddableGenerator::class);
462
        $generator->setProjectRootNamespace(static::TEST_PROJECT_ROOT_NAMESPACE)
463
                  ->setPathToProjectRoot(static::WORK_DIR);
464
465
        return $generator;
466
    }
467
468
    protected function assertNoMissedReplacements(string $createdFile, array $checkFor = []): void
469
    {
470
        $createdFile = $this->getPathHelper()->resolvePath($createdFile);
471
        self::assertFileExists($createdFile);
472
        $contents   = file_get_contents($createdFile);
473
        $checkFor[] = 'template';
474
        foreach ($checkFor as $check) {
475
            self::assertNotRegExp(
476
                '%[^a-z]' . $check . '[^a-z]%i',
477
                $contents,
478
                'Found the word "' . $check . '" (case insensitive) in the created file ' . $createdFile
479
            );
480
        }
481
    }
482
483
    protected function getPathHelper(): PathHelper
484
    {
485
        return $this->container->get(PathHelper::class);
486
    }
487
488
    protected function getTestEntityGeneratorFactory(): TestEntityGeneratorFactory
489
    {
490
        return $this->container->get(TestEntityGeneratorFactory::class);
491
    }
492
493
    protected function assertFileContains(string $createdFile, string $needle): void
494
    {
495
        $createdFile = $this->getPathHelper()->resolvePath($createdFile);
496
        self::assertFileExists($createdFile);
497
        $contents = file_get_contents($createdFile);
498
        self::assertContains(
499
            $needle,
500
            $contents,
501
            "Missing '$needle' in file '$createdFile'"
502
        );
503
    }
504
505
    protected function getEntityGenerator(): EntityGenerator
506
    {
507
        /**
508
         * @var EntityGenerator $entityGenerator
509
         */
510
        $entityGenerator = $this->container->get(EntityGenerator::class);
511
        $entityGenerator->setPathToProjectRoot($this->copiedWorkDir ?? static::WORK_DIR)
512
                        ->setProjectRootNamespace($this->copiedRootNamespace ?? static::TEST_PROJECT_ROOT_NAMESPACE);
513
514
        return $entityGenerator;
515
    }
516
517
    protected function getRelationsGenerator(): RelationsGenerator
518
    {
519
        /**
520
         * @var RelationsGenerator $relationsGenerator
521
         */
522
        $relationsGenerator = $this->container->get(RelationsGenerator::class);
523
        $relationsGenerator->setPathToProjectRoot($this->copiedWorkDir ?? static::WORK_DIR)
524
                           ->setProjectRootNamespace($this->copiedRootNamespace ?? static::TEST_PROJECT_ROOT_NAMESPACE);
525
526
        return $relationsGenerator;
527
    }
528
529
    protected function getFieldGenerator(): FieldGenerator
530
    {
531
        /**
532
         * @var \EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\Field\FieldGenerator $fieldGenerator
533
         */
534
        $fieldGenerator = $this->container->get(FieldGenerator::class);
535
        $fieldGenerator->setPathToProjectRoot($this->copiedWorkDir ?? static::WORK_DIR)
536
                       ->setProjectRootNamespace($this->copiedRootNamespace ?? static::TEST_PROJECT_ROOT_NAMESPACE);
537
538
        return $fieldGenerator;
539
    }
540
541
    protected function getTestCodeGenerator(): TestCodeGenerator
542
    {
543
        return $this->container->get(TestCodeGenerator::class);
544
    }
545
546
    protected function getFieldSetter(): EntityFieldSetter
547
    {
548
        $fieldSetter = $this->container->get(EntityFieldSetter::class);
549
        $fieldSetter->setPathToProjectRoot($this->copiedWorkDir ?? static::WORK_DIR)
550
                    ->setProjectRootNamespace($this->copiedRootNamespace ?? static::TEST_PROJECT_ROOT_NAMESPACE);
551
552
        return $fieldSetter;
553
    }
554
555
    protected function getSchema(): Schema
556
    {
557
        return $this->container->get(Schema::class);
558
    }
559
560
    protected function getCodeHelper(): CodeHelper
561
    {
562
        return $this->container->get(CodeHelper::class);
563
    }
564
565
    protected function getUnusedRelationsRemover(): UnusedRelationsRemover
566
    {
567
        return $this->container->get(UnusedRelationsRemover::class);
568
    }
569
570
    protected function createEntity(string $entityFqn): EntityInterface
571
    {
572
        return $this->getEntityFactory()->create($entityFqn);
573
    }
574
575
    protected function getEntityFactory(): EntityFactory
576
    {
577
        $factory = $this->container->get(EntityFactory::class);
578
        $factory->setEntityManager($this->getEntityManager());
579
580
        return $factory;
581
    }
582
}
583