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 ( fe6969...c3c1f4 )
by joseph
18s queued 12s
created

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