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 (#57)
by Ross
16:56
created

AbstractIntegrationTest.php$0 ➔ getFileSystem()   A

Complexity

Conditions 3

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
cc 3
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\EntityGenerator;
12
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\Field\EntityFieldSetter;
13
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\Field\FieldGenerator;
14
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\RelationsGenerator;
15
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\NamespaceHelper;
16
use EdmondsCommerce\DoctrineStaticMeta\Schema\Schema;
17
use EdmondsCommerce\PHPQA\Constants;
18
use PHPUnit\Framework\TestCase;
19
use Symfony\Component\Filesystem\Filesystem;
20
21
/**
22
 * Class AbstractTest
23
 *
24
 * @package EdmondsCommerce\DoctrineStaticMeta
25
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
26
 */
27
abstract class AbstractIntegrationTest extends TestCase
28
{
29
    public const TEST_TYPE                   = 'integration';
30
    public const VAR_PATH                    = __DIR__.'/../../var/testOutput/';
31
    public const WORK_DIR                    = 'override me';
32
    public const TEST_PROJECT_ROOT_NAMESPACE = 'My\\Test\\Project';
33
34
    /**
35
     * The absolute path to the Entities folder, eg:
36
     * /var/www/vhosts/doctrine-static-meta/var/{testWorkDir}/Entities
37
     *
38
     * @var string
39
     */
40
    protected $entitiesPath = '';
41
42
    /**
43
     * The absolute path to the EntityRelations folder, eg:
44
     * /var/www/vhosts/doctrine-static-meta/var/{testWorkDir}/Entity/Relations
45
     *
46
     * @var string
47
     */
48
    protected $entityRelationsPath = '';
49
50
    /**
51
     * @var Container
52
     */
53
    protected $container;
54
55
    /**
56
     * @var Filesystem
57
     */
58
    protected $filesystem;
59
60
    /**
61
     * @var string|null
62
     */
63
    protected $copiedWorkDir;
64
    /**
65
     * @var string|null
66
     */
67
    protected $copiedRootNamespace;
68
69
70
    /**
71
     * Prepare working directory, ensure its empty, create entities folder and set up env variables
72
     *
73
     * The order of these actions is critical
74
     */
75
    public function setup()
76
    {
77
        if (false !== stripos(static::WORK_DIR, self::WORK_DIR)) {
78
            throw new \RuntimeException(
79
                "You must set a `public const WORK_DIR=AbstractTest::VAR_PATH.'/'"
80
                .".self::TEST_TYPE.'/folderName/';` in your test class"
81
            );
82
        }
83
        if (false === strpos(static::WORK_DIR, static::TEST_TYPE)) {
84
            throw new \RuntimeException(
85
                'Your WORK_DIR is missing the test type, should look like: '
86
                ."`public const WORK_DIR=AbstractTest::VAR_PATH.'/'"
87
                .".self::TEST_TYPE.'/folderName/';` in your test class"
88
            );
89
        }
90
        $this->copiedWorkDir = null;
91
        $this->entitiesPath  = static::WORK_DIR
92
                               .'/'.AbstractCommand::DEFAULT_SRC_SUBFOLDER
93
                               .'/'.AbstractGenerator::ENTITIES_FOLDER_NAME;
94
        $this->getFileSystem()->mkdir($this->entitiesPath);
95
        $this->entitiesPath        = realpath($this->entitiesPath);
96
        $this->entityRelationsPath = static::WORK_DIR
97
                                     .'/'.AbstractCommand::DEFAULT_SRC_SUBFOLDER
98
                                     .'/'.AbstractGenerator::ENTITY_RELATIONS_FOLDER_NAME;
99
        $this->getFileSystem()->mkdir($this->entityRelationsPath);
100
        $this->entityRelationsPath = realpath($this->entityRelationsPath);
101
        $this->setupContainer($this->entitiesPath);
102
        $this->clearWorkDir();
103
        $this->extendAutoloader(
104
            static::TEST_PROJECT_ROOT_NAMESPACE.'\\',
105
            static::WORK_DIR.'/'.AbstractCommand::DEFAULT_SRC_SUBFOLDER
106
        );
107
    }
108
109
    /**
110
     * If PHP loads any files whilst generating, then subsequent changes to those files will not have any effect
111
     *
112
     * To resolve this, we need to clone the copied code into a new namespace before running it
113
     *
114
     * We only allow copying to a new work dir once per test run, different extras must be used
115
     *
116
     * @return string $copiedWorkDir
117
     * @throws \ReflectionException
118
     */
119
    protected function setupCopiedWorkDir(): string
120
    {
121
        $extra                     = $this->getCopiedExtra();
122
        $this->copiedWorkDir       = rtrim(static::WORK_DIR, '/').'Copies/'.$extra.'/';
123
        $this->copiedRootNamespace = $extra.static::TEST_PROJECT_ROOT_NAMESPACE;
124
        if (is_dir($this->copiedWorkDir)) {
125
            throw new \RuntimeException(
126
                'The Copied WorkDir '.$this->copiedWorkDir
127
                .' Already Exists, please choose a different $extra than '.$extra
128
            );
129
        }
130
        $this->filesystem->mkdir($this->copiedWorkDir);
131
        $this->filesystem->mirror(static::WORK_DIR, $this->copiedWorkDir);
132
        $nsRoot   = rtrim(
133
            str_replace(
134
                '\\\\',
135
                '\\',
136
                \substr(
137
                    static::TEST_PROJECT_ROOT_NAMESPACE,
138
                    0,
139
                    strpos(static::TEST_PROJECT_ROOT_NAMESPACE, '\\')
140
                )
141
            ),
142
            '\\'
143
        );
144
        $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->copiedWorkDir));
145
        foreach ($iterator as $info) {
146
            /**
147
             * @var \SplFileInfo $info
148
             */
149
            if (false === $info->isFile()) {
150
                continue;
151
            }
152
            $contents = file_get_contents($info->getPathname());
153
154
            $updated = \preg_replace(
155
                '%(use|namespace)\s+?'.$nsRoot.'\\\\%',
156
                '$1 '.$extra.$nsRoot.'\\',
157
                $contents
158
            );
159
            file_put_contents($info->getPathname(), $updated);
160
        }
161
        $this->extendAutoloader(
162
            $this->copiedRootNamespace.'\\',
163
            $this->copiedWorkDir.'/'.AbstractCommand::DEFAULT_SRC_SUBFOLDER
164
        );
165
166
        return $this->copiedWorkDir;
167
    }
168
169
    /**
170
     * Get the extra bit we add to the copied work dir, based on the current class name and hte current test name
171
     *
172
     * @return string
173
     * @throws \ReflectionException
174
     */
175
    protected function getCopiedExtra(): string
176
    {
177
        return (new \ReflectionClass($this))->getShortName().'_'.$this->getName().'_';
178
    }
179
180
    /**
181
     * When working with a copied work dir, use this function to translate the FQN of any Entities etc
182
     *
183
     * @param string $fqn
184
     *
185
     * @return string
186
     * @throws Exception\DoctrineStaticMetaException
187
     * @throws \ReflectionException
188
     */
189
    protected function getCopiedFqn(string $fqn): string
190
    {
191
        $extra = $this->getCopiedExtra();
192
193
        return $this->container
194
            ->get(NamespaceHelper::class)
195
            ->tidy('\\'.$extra.ltrim($fqn, '\\'));
196
    }
197
198
    /**
199
     * @return bool
200
     * @SuppressWarnings(PHPMD.Superglobals)
201
     */
202
    protected function isTravis(): bool
203
    {
204
        return isset($_SERVER['TRAVIS']);
205
    }
206
207
    /**
208
     * @param string $entitiesPath
209
     *
210
     * @throws Exception\ConfigException
211
     * @throws Exception\DoctrineStaticMetaException
212
     * @SuppressWarnings(PHPMD.Superglobals)
213
     * @SuppressWarnings(PHPMD.StaticAccess)
214
     */
215
    protected function setupContainer(string $entitiesPath)
216
    {
217
        SimpleEnv::setEnv(Config::getProjectRootDirectory().'/.env');
218
        $testConfig                                       = $_SERVER;
219
        $testConfig[ConfigInterface::PARAM_ENTITIES_PATH] = $entitiesPath;
220
        $testConfig[ConfigInterface::PARAM_DB_NAME]       .= '_test';
221
        $testConfig[ConfigInterface::PARAM_DEVMODE]       = true;
222
        $this->container                                  = new Container();
223
        $this->container->buildSymfonyContainer($testConfig);
224
    }
225
226
227
    /**
228
     * Accesses the standard Composer autoloader
229
     *
230
     * Extends the class and also providing an entry point for xdebugging
231
     *
232
     * Extends this with a PSR4 root for our WORK_DIR
233
     *
234
     * @param string $namespace
235
     * @param string $path
236
     */
237
    protected function extendAutoloader(string $namespace, string $path)
238
    {
239
        $namespace = rtrim($namespace, '\\').'\\';
240
        $loader    = new class($namespace) extends ClassLoader
241
        {
242
            /**
243
             * @var string
244
             */
245
            protected $namespace;
246
247
            public function __construct(string $namespace)
248
            {
249
                $this->namespace = $namespace;
250
            }
251
252
            public function loadClass($class)
253
            {
254
                if (false === strpos($class, $this->namespace)) {
255
                    return false;
256
                }
257
                $found = parent::loadClass($class);
258
                if (\in_array(gettype($found), ['boolean', 'NULL'], true)) {
259
                    //good spot to set a break point ;)
260
                    return false;
261
                }
262
263
                return true;
264
            }
265
        };
266
        $loader->addPsr4($namespace, $path);
267
        $loader->register();
268
    }
269
270
    protected function clearWorkDir()
271
    {
272
        $this->getFileSystem()->mkdir(static::WORK_DIR);
273
        $this->emptyDirectory(static::WORK_DIR);
274
        if (empty($this->entitiesPath)) {
275
            throw new \RuntimeException('$this->entitiesPath path is empty');
276
        }
277
        $this->getFileSystem()->mkdir($this->entitiesPath);
278
    }
279
280
    protected function getFileSystem(): Filesystem
281
    {
282
        if (null === $this->filesystem) {
283
            $this->filesystem = (null !== $this->container)
284
                ? $this->container->get(Filesystem::class)
285
                : new Filesystem();
286
        }
287
288
        return $this->filesystem;
289
    }
290
291
    protected function emptyDirectory(string $path)
292
    {
293
        $fileSystem = $this->getFileSystem();
294
        $fileSystem->remove($path);
295
        $fileSystem->mkdir($path);
296
    }
297
298
    protected function assertNoMissedReplacements(string $createdFile)
299
    {
300
        $createdFile = $this->getCodeHelper()->resolvePath($createdFile);
301
        $this->assertFileExists($createdFile);
302
        $contents = file_get_contents($createdFile);
303
        $this->assertNotContains(
304
            'template',
305
            $contents,
306
            'Found the word "template" (case insensitive) in the created file '.$createdFile,
307
            true
308
        );
309
    }
310
311
    protected function assertFileContains(string $createdFile, string $needle)
312
    {
313
        $createdFile = $this->getCodeHelper()->resolvePath($createdFile);
314
        $this->assertFileExists($createdFile);
315
        $contents = file_get_contents($createdFile);
316
        $this->assertContains(
317
            $needle,
318
            $contents,
319
            "Missing '$needle' in file '$createdFile'"
320
        );
321
    }
322
323
    protected function getEntityGenerator(): EntityGenerator
324
    {
325
        /**
326
         * @var EntityGenerator $entityGenerator
327
         */
328
        $entityGenerator = $this->container->get(EntityGenerator::class);
329
        $entityGenerator->setPathToProjectRoot(static::WORK_DIR)
330
                        ->setProjectRootNamespace(static::TEST_PROJECT_ROOT_NAMESPACE);
331
332
        return $entityGenerator;
333
    }
334
335
    protected function getRelationsGenerator(): RelationsGenerator
336
    {
337
        /**
338
         * @var RelationsGenerator $relationsGenerator
339
         */
340
        $relationsGenerator = $this->container->get(RelationsGenerator::class);
341
        $relationsGenerator->setPathToProjectRoot(static::WORK_DIR)
342
                           ->setProjectRootNamespace(static::TEST_PROJECT_ROOT_NAMESPACE);
343
344
        return $relationsGenerator;
345
    }
346
347
    protected function getFieldGenerator(): FieldGenerator
348
    {
349
        /**
350
         * @var \EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\Field\FieldGenerator $fieldGenerator
351
         */
352
        $fieldGenerator = $this->container->get(FieldGenerator::class);
353
        $fieldGenerator->setPathToProjectRoot(static::WORK_DIR)
354
                       ->setProjectRootNamespace(static::TEST_PROJECT_ROOT_NAMESPACE);
355
356
        return $fieldGenerator;
357
    }
358
359
    protected function getFieldSetter(): EntityFieldSetter
360
    {
361
        static $fieldSetter;
362
        if (null === $fieldSetter) {
363
            $fieldSetter = $this->container->get(EntityFieldSetter::class);
364
        }
365
366
        return $fieldSetter;
367
    }
368
369
    /**
370
     * @return EntityManager
371
     * @throws Exception\DoctrineStaticMetaException
372
     */
373
    protected function getEntityManager(): EntityManager
374
    {
375
        return $this->container->get(EntityManagerInterface::class);
376
    }
377
378
    /**
379
     * Run QA tools against the generated code
380
     *
381
     * Can specify a custom namespace root if required
382
     *
383
     * Will run:
384
     *
385
     * - PHP linting
386
     * - PHPStan
387
     *
388
     * @SuppressWarnings(PHPMD.Superglobals)
389
     * @param null|string $namespaceRoot
390
     *
391
     * @return bool
392
     * @throws Exception\DoctrineStaticMetaException
393
     * @throws \ReflectionException
394
     */
395
    public function qaGeneratedCode(?string $namespaceRoot = null): bool
396
    {
397
        if (isset($_SERVER[Constants::QA_QUICK_TESTS_KEY])
398
            && (int)$_SERVER[Constants::QA_QUICK_TESTS_KEY] === Constants::QA_QUICK_TESTS_ENABLED
399
        ) {
400
            return true;
401
        }
402
        $workDir       = static::WORK_DIR;
403
        $namespaceRoot = ltrim($namespaceRoot ?? static::TEST_PROJECT_ROOT_NAMESPACE, '\\');
404
        if (null !== $this->copiedWorkDir) {
405
            $workDir       = $this->copiedWorkDir;
406
            $namespaceRoot = ltrim($this->getCopiedFqn(static::TEST_PROJECT_ROOT_NAMESPACE), '\\');
407
        }
408
        static $codeValidator;
409
        if (null === $codeValidator) {
410
            $codeValidator = new CodeValidator();
411
        }
412
        $errors = $codeValidator($workDir, $namespaceRoot);
413
        $this->assertNull($errors);
414
415
        return true;
416
    }
417
418
    protected function getSchema(): Schema
419
    {
420
        return $this->container->get(Schema::class);
421
    }
422
423
    protected function getCodeHelper(): CodeHelper
424
    {
425
        return $this->container->get(CodeHelper::class);
426
    }
427
}
428