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 (#118)
by joseph
21:38
created

AbstractTest.php$0 ➔ getTestCodeGenerator()   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 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
            file_put_contents($info->getPathname(), $updated);
379
        }
380
        $this->extendAutoloader(
381
            $this->copiedRootNamespace . '\\',
382
            $this->copiedWorkDir
383
        );
384
        $this->clearCache();
385
386
        return $this->copiedWorkDir;
387
    }
388
389
    /**
390
     * Get the namespace root to use in a copied work dir
391
     *
392
     * @return string
393
     * @throws \ReflectionException
394
     */
395
    protected function getCopiedNamespaceRoot(): string
396
    {
397
        return (new  \ts\Reflection\ReflectionClass(static::class))->getShortName() . '_' . $this->getName() . '_';
398
    }
399
400
    /**
401
     * When working with a copied work dir, use this function to translate the FQN of any Entities etc
402
     *
403
     * @param string $fqn
404
     *
405
     * @return string
406
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
407
     * @throws \ReflectionException
408
     */
409
    protected function getCopiedFqn(string $fqn): string
410
    {
411
        $copiedNamespaceRoot = $this->getCopiedNamespaceRoot();
412
413
        return $this->container
414
            ->get(NamespaceHelper::class)
415
            ->tidy('\\' . $copiedNamespaceRoot . '\\'
416
                   . ltrim(
417
                       \str_replace(static::TEST_PROJECT_ROOT_NAMESPACE, '', $fqn),
418
                       '\\'
419
                   ));
420
    }
421
422
    /**
423
     * @return bool
424
     * @SuppressWarnings(PHPMD.Superglobals)
425
     */
426
    protected function isTravis(): bool
427
    {
428
        return isset($_SERVER['TRAVIS']);
429
    }
430
431
    protected function getEntityEmbeddableSetter(): EntityEmbeddableSetter
432
    {
433
        return $this->container->get(EntityEmbeddableSetter::class);
434
    }
435
436
    /**
437
     * @return ArchetypeEmbeddableGenerator
438
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
439
     */
440
    protected function getArchetypeEmbeddableGenerator(): ArchetypeEmbeddableGenerator
441
    {
442
        /**
443
         * @var ArchetypeEmbeddableGenerator $generator
444
         */
445
        $generator = $this->container->get(ArchetypeEmbeddableGenerator::class);
446
        $generator->setProjectRootNamespace(static::TEST_PROJECT_ROOT_NAMESPACE)
447
                  ->setPathToProjectRoot(static::WORK_DIR);
448
449
        return $generator;
450
    }
451
452
    protected function assertNoMissedReplacements(string $createdFile, array $checkFor = []): void
453
    {
454
        $createdFile = $this->getPathHelper()->resolvePath($createdFile);
455
        self::assertFileExists($createdFile);
456
        $contents   = file_get_contents($createdFile);
457
        $checkFor[] = 'template';
458
        foreach ($checkFor as $check) {
459
            self::assertNotRegExp(
460
                '%[^a-z]' . $check . '[^a-z]%i',
461
                $contents,
462
                'Found the word "' . $check . '" (case insensitive) in the created file ' . $createdFile
463
            );
464
        }
465
    }
466
467
    protected function getPathHelper(): PathHelper
468
    {
469
        return $this->container->get(PathHelper::class);
470
    }
471
472
    protected function getTestEntityGeneratorFactory(): TestEntityGeneratorFactory
473
    {
474
        return $this->container->get(TestEntityGeneratorFactory::class);
475
    }
476
477
    protected function assertFileContains(string $createdFile, string $needle): void
478
    {
479
        $createdFile = $this->getPathHelper()->resolvePath($createdFile);
480
        self::assertFileExists($createdFile);
481
        $contents = file_get_contents($createdFile);
482
        self::assertContains(
483
            $needle,
484
            $contents,
485
            "Missing '$needle' in file '$createdFile'"
486
        );
487
    }
488
489
    protected function getEntityGenerator(): EntityGenerator
490
    {
491
        /**
492
         * @var EntityGenerator $entityGenerator
493
         */
494
        $entityGenerator = $this->container->get(EntityGenerator::class);
495
        $entityGenerator->setPathToProjectRoot($this->copiedWorkDir ?? static::WORK_DIR)
496
                        ->setProjectRootNamespace($this->copiedRootNamespace ?? static::TEST_PROJECT_ROOT_NAMESPACE);
497
498
        return $entityGenerator;
499
    }
500
501
    protected function getRelationsGenerator(): RelationsGenerator
502
    {
503
        /**
504
         * @var RelationsGenerator $relationsGenerator
505
         */
506
        $relationsGenerator = $this->container->get(RelationsGenerator::class);
507
        $relationsGenerator->setPathToProjectRoot($this->copiedWorkDir ?? static::WORK_DIR)
508
                           ->setProjectRootNamespace($this->copiedRootNamespace ?? static::TEST_PROJECT_ROOT_NAMESPACE);
509
510
        return $relationsGenerator;
511
    }
512
513
    protected function getFieldGenerator(): FieldGenerator
514
    {
515
        /**
516
         * @var \EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\Field\FieldGenerator $fieldGenerator
517
         */
518
        $fieldGenerator = $this->container->get(FieldGenerator::class);
519
        $fieldGenerator->setPathToProjectRoot($this->copiedWorkDir ?? static::WORK_DIR)
520
                       ->setProjectRootNamespace($this->copiedRootNamespace ?? static::TEST_PROJECT_ROOT_NAMESPACE);
521
522
        return $fieldGenerator;
523
    }
524
525
    protected function getTestCodeGenerator(): TestCodeGenerator
526
    {
527
        return $this->container->get(TestCodeGenerator::class);
528
    }
529
530
    protected function getFieldSetter(): EntityFieldSetter
531
    {
532
        $fieldSetter = $this->container->get(EntityFieldSetter::class);
533
        $fieldSetter->setPathToProjectRoot($this->copiedWorkDir ?? static::WORK_DIR)
534
                    ->setProjectRootNamespace($this->copiedRootNamespace ?? static::TEST_PROJECT_ROOT_NAMESPACE);
535
536
        return $fieldSetter;
537
    }
538
539
    protected function getSchema(): Schema
540
    {
541
        return $this->container->get(Schema::class);
542
    }
543
544
    protected function getCodeHelper(): CodeHelper
545
    {
546
        return $this->container->get(CodeHelper::class);
547
    }
548
549
    protected function getUnusedRelationsRemover(): UnusedRelationsRemover
550
    {
551
        return $this->container->get(UnusedRelationsRemover::class);
552
    }
553
554
    protected function createEntity(string $entityFqn): EntityInterface
555
    {
556
        return $this->getEntityFactory()->create($entityFqn);
557
    }
558
559
    protected function getEntityFactory(): EntityFactory
560
    {
561
        $factory = $this->container->get(EntityFactory::class);
562
        $factory->setEntityManager($this->getEntityManager());
563
564
        return $factory;
565
    }
566
}
567