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 ( c22d50...0bd9a0 )
by joseph
18s queued 11s
created

AbstractTest::getNamespaceHelper()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
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
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 getNamespaceHelper(): NamespaceHelper
166
    {
167
        return $this->container->get(NamespaceHelper::class);
168
    }
169
170
    protected function emptyDirectory(string $path): void
171
    {
172
        $fileSystem = $this->getFileSystem();
173
        $fileSystem->remove($path);
174
        $fileSystem->mkdir($path);
175
    }
176
177
    /**
178
     * @param string $entitiesPath
179
     *
180
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\ConfigException
181
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
182
     * @SuppressWarnings(PHPMD.Superglobals)
183
     * @SuppressWarnings(PHPMD.StaticAccess)
184
     */
185
    protected function setupContainer(string $entitiesPath): void
186
    {
187
        SimpleEnv::setEnv(Config::getProjectRootDirectory() . '/.env');
188
        $testConfig                                               = $_SERVER;
189
        $testConfig[ConfigInterface::PARAM_ENTITIES_PATH]         = $entitiesPath;
190
        $testConfig[ConfigInterface::PARAM_DB_NAME]               .= '_test';
191
        $testConfig[ConfigInterface::PARAM_DEVMODE]               = true;
192
        $testConfig[ConfigInterface::PARAM_FILESYSTEM_CACHE_PATH] = static::WORK_DIR . '/cache/dsm';
193
        $this->container                                          = new Container();
194
        $this->container->buildSymfonyContainer($testConfig);
195
    }
196
197
    /**
198
     * Clear the Doctrine Cache
199
     *
200
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
201
     */
202
    protected function clearCache(): void
203
    {
204
        $cache = $this->getEntityManager()
205
                      ->getConfiguration()
206
                      ->getMetadataCacheImpl();
207
        if ($cache instanceof CacheProvider) {
208
            $cache->deleteAll();
209
        }
210
    }
211
212
    /**
213
     * @return EntityManagerInterface
214
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
215
     */
216
    protected function getEntityManager(): EntityManagerInterface
217
    {
218
        return $this->container->get(EntityManagerInterface::class);
219
    }
220
221
    /**
222
     * Accesses the standard Composer autoloader
223
     *
224
     * Extends the class and also providing an entry point for xdebugging
225
     *
226
     * Extends this with a PSR4 root for our WORK_DIR
227
     *
228
     * @param string $namespace
229
     * @param string $path
230
     *
231
     * @throws \ReflectionException
232
     */
233
    protected function extendAutoloader(string $namespace, string $path): void
234
    {
235
        //Unregister any previously set extension first
236
        $registered = \spl_autoload_functions();
237
        foreach ($registered as $loader) {
238
            if ((new  \ts\Reflection\ReflectionClass(\get_class($loader[0])))->isAnonymous()) {
239
                \spl_autoload_unregister($loader);
240
            }
241
        }
242
        //Then build a new extension and register it
243
        $namespace  = rtrim($namespace, '\\') . '\\';
244
        $testLoader = new class($namespace) extends ClassLoader
245
        {
246
            /**
247
             * @var string
248
             */
249
            protected $namespace;
250
251
            public function __construct(string $namespace)
252
            {
253
                $this->namespace = $namespace;
254
            }
255
256
            public function loadClass($class)
257
            {
258
                if (false === strpos($class, $this->namespace)) {
259
                    return false;
260
                }
261
                $found = parent::loadClass($class);
262
                if (false === $found || null === $found) {
0 ignored issues
show
introduced by
The condition null === $found is always false.
Loading history...
263
                    //good point to set a breakpoint
264
                    return $found;
265
                }
266
267
                return $found;
268
            }
269
        };
270
        $testLoader->addPsr4($namespace, $path . '/src', true);
271
        $testLoader->addPsr4($namespace, $path . '/tests', true);
272
        $testLoader->register();
273
    }
274
275
    /**
276
     * Run QA tools against the generated code
277
     *
278
     * Can specify a custom namespace root if required
279
     *
280
     * Will run:
281
     *
282
     * - PHP linting
283
     * - PHPStan
284
     *
285
     * @SuppressWarnings(PHPMD.Superglobals)
286
     * @param null|string $namespaceRoot
287
     *
288
     * @return bool
289
     */
290
    public function qaGeneratedCode(?string $namespaceRoot = null): bool
291
    {
292
        if ($this->isQuickTests()) {
293
            return true;
294
        }
295
        $workDir       = static::WORK_DIR;
296
        $namespaceRoot = trim($namespaceRoot ?? static::TEST_PROJECT_ROOT_NAMESPACE, '\\');
297
        if (null !== $this->copiedRootNamespace) {
298
            $workDir       = $this->copiedWorkDir;
299
            $namespaceRoot = trim($this->copiedRootNamespace, '\\');
300
        }
301
        static $codeValidator;
302
        if (null === $codeValidator) {
303
            $codeValidator = new CodeValidator();
304
        }
305
        $errors = $codeValidator($workDir, $namespaceRoot);
306
        self::assertNull($errors);
307
308
        return true;
309
    }
310
311
    /**
312
     * @return bool
313
     * @SuppressWarnings(PHPMD.Superglobals)
314
     */
315
    protected function isQuickTests(): bool
316
    {
317
        if (isset($_SERVER[Constants::QA_QUICK_TESTS_KEY])
318
            && (int)$_SERVER[Constants::QA_QUICK_TESTS_KEY] === Constants::QA_QUICK_TESTS_ENABLED
319
        ) {
320
            return true;
321
        }
322
323
        return false;
324
    }
325
326
    protected function dump(EntityInterface $entity): string
327
    {
328
        return (new EntityDebugDumper())->dump($entity, $this->getEntityManager());
329
    }
330
331
    /**
332
     * If PHP loads any files whilst generating, then subsequent changes to those files will not have any effect
333
     *
334
     * To resolve this, we need to clone the copied code into a new namespace before running it
335
     *
336
     * We only allow copying to a new work dir once per test run, different extras must be used
337
     *
338
     * @return string $copiedWorkDir
339
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
340
     * @throws \ReflectionException
341
     */
342
    protected function setupCopiedWorkDir(): string
343
    {
344
        $copiedNamespaceRoot       = $this->getCopiedNamespaceRoot();
345
        $this->copiedWorkDir       = rtrim(static::WORK_DIR, '/') . 'Copies/' . $copiedNamespaceRoot . '/';
346
        $this->copiedRootNamespace = $copiedNamespaceRoot;
347
        if (is_dir($this->copiedWorkDir)) {
348
            throw new \RuntimeException(
349
                'The Copied WorkDir ' . $this->copiedWorkDir . ' Already Exists'
350
            );
351
        }
352
        if (is_dir($this->copiedWorkDir)) {
353
            $this->getFileSystem()->remove($this->copiedWorkDir);
354
        }
355
        $this->getFileSystem()->mkdir($this->copiedWorkDir);
356
        $this->getFileSystem()->mirror(static::WORK_DIR, $this->copiedWorkDir);
357
        $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->copiedWorkDir));
358
359
        foreach ($iterator as $info) {
360
            /**
361
             * @var \SplFileInfo $info
362
             */
363
            if (false === $info->isFile()) {
364
                continue;
365
            }
366
            $contents = file_get_contents($info->getPathname());
367
368
            $updated = \preg_replace(
369
                '%(use|namespace)\s+?'
370
                . $this->container->get(FindAndReplaceHelper::class)
371
                                  ->escapeSlashesForRegex(static::TEST_PROJECT_ROOT_NAMESPACE)
372
                . '\\\\%',
373
                '$1 ' . $copiedNamespaceRoot . '\\',
374
                $contents
375
            );
376
            file_put_contents($info->getPathname(), $updated);
377
        }
378
        $this->extendAutoloader(
379
            $this->copiedRootNamespace . '\\',
380
            $this->copiedWorkDir
381
        );
382
        $this->clearCache();
383
384
        return $this->copiedWorkDir;
385
    }
386
387
    /**
388
     * Get the namespace root to use in a copied work dir
389
     *
390
     * @return string
391
     * @throws \ReflectionException
392
     */
393
    protected function getCopiedNamespaceRoot(): string
394
    {
395
        return (new  \ts\Reflection\ReflectionClass(static::class))->getShortName() . '_' . $this->getName() . '_';
396
    }
397
398
    /**
399
     * When working with a copied work dir, use this function to translate the FQN of any Entities etc
400
     *
401
     * @param string $fqn
402
     *
403
     * @return string
404
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
405
     * @throws \ReflectionException
406
     */
407
    protected function getCopiedFqn(string $fqn): string
408
    {
409
        $copiedNamespaceRoot = $this->getCopiedNamespaceRoot();
410
411
        return $this->container
412
            ->get(NamespaceHelper::class)
413
            ->tidy('\\' . $copiedNamespaceRoot . '\\'
414
                   . ltrim(
415
                       \str_replace(static::TEST_PROJECT_ROOT_NAMESPACE, '', $fqn),
416
                       '\\'
417
                   ));
418
    }
419
420
    /**
421
     * @return bool
422
     * @SuppressWarnings(PHPMD.Superglobals)
423
     */
424
    protected function isTravis(): bool
425
    {
426
        return isset($_SERVER['TRAVIS']);
427
    }
428
429
    protected function getEntityEmbeddableSetter(): EntityEmbeddableSetter
430
    {
431
        return $this->container->get(EntityEmbeddableSetter::class);
432
    }
433
434
    /**
435
     * @return ArchetypeEmbeddableGenerator
436
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
437
     */
438
    protected function getArchetypeEmbeddableGenerator(): ArchetypeEmbeddableGenerator
439
    {
440
        /**
441
         * @var ArchetypeEmbeddableGenerator $generator
442
         */
443
        $generator = $this->container->get(ArchetypeEmbeddableGenerator::class);
444
        $generator->setProjectRootNamespace(static::TEST_PROJECT_ROOT_NAMESPACE)
445
                  ->setPathToProjectRoot(static::WORK_DIR);
446
447
        return $generator;
448
    }
449
450
    protected function assertNoMissedReplacements(string $createdFile, array $checkFor = []): void
451
    {
452
        $createdFile = $this->getPathHelper()->resolvePath($createdFile);
453
        self::assertFileExists($createdFile);
454
        $contents   = file_get_contents($createdFile);
455
        $checkFor[] = 'template';
456
        foreach ($checkFor as $check) {
457
            self::assertNotRegExp(
458
                '%[^a-z]' . $check . '[^a-z]%i',
459
                $contents,
460
                'Found the word "' . $check . '" (case insensitive) in the created file ' . $createdFile
461
            );
462
        }
463
    }
464
465
    protected function getPathHelper(): PathHelper
466
    {
467
        return $this->container->get(PathHelper::class);
468
    }
469
470
    protected function assertFileContains(string $createdFile, string $needle): void
471
    {
472
        $createdFile = $this->getPathHelper()->resolvePath($createdFile);
473
        self::assertFileExists($createdFile);
474
        $contents = file_get_contents($createdFile);
475
        self::assertContains(
476
            $needle,
477
            $contents,
478
            "Missing '$needle' in file '$createdFile'"
479
        );
480
    }
481
482
    protected function getEntityGenerator(): EntityGenerator
483
    {
484
        /**
485
         * @var EntityGenerator $entityGenerator
486
         */
487
        $entityGenerator = $this->container->get(EntityGenerator::class);
488
        $entityGenerator->setPathToProjectRoot($this->copiedWorkDir ?? static::WORK_DIR)
489
                        ->setProjectRootNamespace($this->copiedRootNamespace ?? static::TEST_PROJECT_ROOT_NAMESPACE);
490
491
        return $entityGenerator;
492
    }
493
494
    protected function getRelationsGenerator(): RelationsGenerator
495
    {
496
        /**
497
         * @var RelationsGenerator $relationsGenerator
498
         */
499
        $relationsGenerator = $this->container->get(RelationsGenerator::class);
500
        $relationsGenerator->setPathToProjectRoot($this->copiedWorkDir ?? static::WORK_DIR)
501
                           ->setProjectRootNamespace($this->copiedRootNamespace ?? static::TEST_PROJECT_ROOT_NAMESPACE);
502
503
        return $relationsGenerator;
504
    }
505
506
    protected function getFieldGenerator(): FieldGenerator
507
    {
508
        /**
509
         * @var \EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\Field\FieldGenerator $fieldGenerator
510
         */
511
        $fieldGenerator = $this->container->get(FieldGenerator::class);
512
        $fieldGenerator->setPathToProjectRoot($this->copiedWorkDir ?? static::WORK_DIR)
513
                       ->setProjectRootNamespace($this->copiedRootNamespace ?? static::TEST_PROJECT_ROOT_NAMESPACE);
514
515
        return $fieldGenerator;
516
    }
517
518
    protected function getFieldSetter(): EntityFieldSetter
519
    {
520
        $fieldSetter = $this->container->get(EntityFieldSetter::class);
521
        $fieldSetter->setPathToProjectRoot($this->copiedWorkDir ?? static::WORK_DIR)
522
                    ->setProjectRootNamespace($this->copiedRootNamespace ?? static::TEST_PROJECT_ROOT_NAMESPACE);
523
524
        return $fieldSetter;
525
    }
526
527
    protected function getSchema(): Schema
528
    {
529
        return $this->container->get(Schema::class);
530
    }
531
532
    protected function getCodeHelper(): CodeHelper
533
    {
534
        return $this->container->get(CodeHelper::class);
535
    }
536
537
    protected function createEntity(string $entityFqn): EntityInterface
538
    {
539
        return $this->getEntityFactory()->create($entityFqn);
540
    }
541
542
    protected function getEntityFactory(): EntityFactory
543
    {
544
        $factory = $this->container->get(EntityFactory::class);
545
        $factory->setEntityManager($this->getEntityManager());
546
547
        return $factory;
548
    }
549
}
550