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 ( 0bd9a0...71ef0e )
by joseph
24:07 queued 20:47
created

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