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 (#100)
by joseph
18:26
created

AbstractIntegrationTest::getFieldGenerator()

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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