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

AbstractTest.php$0 ➔ getFieldGenerator()   A

Complexity

Conditions 1

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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