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 ( 149ff2...b9f286 )
by Ross
13s
created

AbstractTest::getCopiedFqn()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 2
1
<?php declare(strict_types=1);
2
3
namespace EdmondsCommerce\DoctrineStaticMeta;
4
5
use Composer\Autoload\ClassLoader;
6
use Doctrine\ORM\EntityManagerInterface;
7
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\CodeHelper;
8
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Command\AbstractCommand;
9
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\AbstractGenerator;
10
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\EntityGenerator;
11
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\FieldGenerator;
12
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\RelationsGenerator;
13
use EdmondsCommerce\DoctrineStaticMeta\GeneratedCode\GeneratedCodeTest;
14
use EdmondsCommerce\DoctrineStaticMeta\Schema\Database;
15
use EdmondsCommerce\DoctrineStaticMeta\Schema\Schema;
16
use EdmondsCommerce\PHPQA\Constants;
17
use Overtrue\PHPLint\Linter;
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 AbstractTest extends TestCase
28
{
29
    public const VAR_PATH                    = __DIR__.'/../var/testOutput';
30
    public const WORK_DIR                    = 'override me';
31
    public const TEST_PROJECT_ROOT_NAMESPACE = 'My\\Test\\Project';
32
33
    /**
34
     * The absolute path to the Entities folder, eg:
35
     * /var/www/vhosts/doctrine-static-meta/var/{testWorkDir}/Entities
36
     *
37
     * @var string
38
     */
39
    protected $entitiesPath = '';
40
41
    /**
42
     * The absolute path to the EntityRelations folder, eg:
43
     * /var/www/vhosts/doctrine-static-meta/var/{testWorkDir}/Entity/Relations
44
     *
45
     * @var string
46
     */
47
    protected $entityRelationsPath = '';
48
49
    /**
50
     * @var Container
51
     */
52
    protected $container;
53
54
    /**
55
     * @var Filesystem
56
     */
57
    protected $filesystem;
58
59
60
    /**
61
     * Prepare working directory, ensure its empty, create entities folder and set up env variables
62
     *
63
     * The order of these actions is critical
64
     */
65
    public function setup()
66
    {
67
        $this->entitiesPath = static::WORK_DIR
68
                              .'/'.AbstractCommand::DEFAULT_SRC_SUBFOLDER
69
                              .'/'.AbstractGenerator::ENTITIES_FOLDER_NAME;
70
        $this->getFileSystem()->mkdir($this->entitiesPath);
71
        $this->entitiesPath        = realpath($this->entitiesPath);
72
        $this->entityRelationsPath = static::WORK_DIR
73
                                     .'/'.AbstractCommand::DEFAULT_SRC_SUBFOLDER
74
                                     .'/'.AbstractGenerator::ENTITY_RELATIONS_FOLDER_NAME;
75
        $this->getFileSystem()->mkdir($this->entityRelationsPath);
76
        $this->entityRelationsPath = realpath($this->entityRelationsPath);
77
        $this->setupContainer($this->entitiesPath);
78
        $this->clearWorkDir();
79
        $this->extendAutoloader(
80
            static::TEST_PROJECT_ROOT_NAMESPACE.'\\',
81
            static::WORK_DIR.'/'.AbstractCommand::DEFAULT_SRC_SUBFOLDER
82
        );
83
    }
84
85
    /**
86
     * If PHP loads any files whilst generating, then subsequent changes to those files will not have any effect
87
     *
88
     * To resolve this, we need to clone the copied code into a new namespace before running it
89
     *
90
     * @param string $extra
91
     */
92
    protected function setupCopiedWorkDir(string $extra = 'Copied'): void
93
    {
94
        $copiedWorkDir = rtrim(static::WORK_DIR, '/').$extra.'/';
95
        if (is_dir($copiedWorkDir)) {
96
            exec('rm -rf '.$copiedWorkDir);
97
        }
98
        $this->filesystem->mkdir($copiedWorkDir);
99
        $this->filesystem->mirror(static::WORK_DIR, $copiedWorkDir);
100
        $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($copiedWorkDir));
101
        foreach ($iterator as $info) {
102
            /**
103
             * @var \SplFileInfo $info
104
             */
105
            if (false === $info->isFile()) {
106
                continue;
107
            }
108
            $contents        = file_get_contents($info->getPathname());
109
            $copiedNameSpace = $extra.static::TEST_PROJECT_ROOT_NAMESPACE;
110
            $updated         = \str_replace(
111
                [
112
                    'namespace '.static::TEST_PROJECT_ROOT_NAMESPACE,
113
                    'use '.static::TEST_PROJECT_ROOT_NAMESPACE,
114
                ],
115
                [
116
                    'namespace '.$copiedNameSpace,
117
                    'use '.$copiedNameSpace,
118
                ],
119
                $contents
120
            );
121
            file_put_contents($info->getPathname(), $updated);
122
        }
123
        $this->extendAutoloader(
124
            $extra.static::TEST_PROJECT_ROOT_NAMESPACE.'\\',
125
            $copiedWorkDir.'/'.AbstractCommand::DEFAULT_SRC_SUBFOLDER
126
        );
127
    }
128
129
    /**
130
     * When working with a copied work dir, use this function to translate the FQN of any Entities etc
131
     *
132
     * @param string $fqn
133
     * @param string $extra
134
     *
135
     * @return string
136
     */
137
    protected function getCopiedFqn(string $fqn, string $extra = 'Copied'): string
138
    {
139
        return '\\'.$extra.ltrim($fqn, '\\');
140
    }
141
142
    /**
143
     * @return bool
144
     * @SuppressWarnings(PHPMD.Superglobals)
145
     */
146
    protected function isTravis(): bool
147
    {
148
        return isset($_SERVER['TRAVIS']);
149
    }
150
151
    /**
152
     * @throws Exception\ConfigException
153
     * @throws Exception\DoctrineStaticMetaException
154
     * @SuppressWarnings(PHPMD.Superglobals)
155
     * @SuppressWarnings(PHPMD.StaticAccess)
156
     */
157
    protected function setupContainer(string $entitiesPath)
158
    {
159
        SimpleEnv::setEnv(Config::getProjectRootDirectory().'/.env');
160
        $testConfig                                       = $_SERVER;
161
        $testConfig[ConfigInterface::PARAM_ENTITIES_PATH] = $entitiesPath;
162
        $testConfig[ConfigInterface::PARAM_DB_NAME]       .= '_test';
163
        $testConfig[ConfigInterface::PARAM_DEVMODE]       = true;
164
        $this->container                                  = new Container();
165
        $this->container->buildSymfonyContainer($testConfig);
166
        $this->container->get(Database::class)->drop(true)->create(true);
167
    }
168
169
170
    /**
171
     * Accesses the standard Composer autoloader
172
     *
173
     * Extends the class and also providing an entry point for xdebugging
174
     *
175
     * Extends this with a PSR4 root for our WORK_DIR
176
     *
177
     * @param string $namespace
178
     * @param string $path
179
     */
180
    protected function extendAutoloader(string $namespace, string $path)
181
    {
182
        $loader = new class($namespace) extends ClassLoader
183
        {
184
            /**
185
             * @var string
186
             */
187
            protected $namespace;
188
189
            public function __construct(string $namespace)
190
            {
191
                $this->namespace = $namespace;
192
            }
193
194
            public function loadClass($class)
195
            {
196
                if (false === strpos($class, $this->namespace)) {
197
                    return false;
198
                }
199
                $found = parent::loadClass($class);
200
                if (\in_array(gettype($found), ['boolean', 'NULL'], true)) {
201
                    //good spot to set a break point ;)
202
                    return false;
203
                }
204
205
                return true;
206
            }
207
        };
208
        $loader->addPsr4($namespace, $path);
209
        $loader->register();
210
    }
211
212
    protected function clearWorkDir()
213
    {
214
        if (static::WORK_DIR === self::WORK_DIR) {
0 ignored issues
show
introduced by
The condition static::WORK_DIR === self::WORK_DIR is always true.
Loading history...
215
            throw new \RuntimeException(
216
                "You must set a `const WORK_DIR=AbstractTest::VAR_PATH.'/folderName/';` in your test class"
217
            );
218
        }
219
        $this->getFileSystem()->mkdir(static::WORK_DIR);
220
        $this->emptyDirectory(static::WORK_DIR);
221
        if (empty($this->entitiesPath)) {
222
            throw new \RuntimeException('$this->entitiesPath path is empty');
223
        }
224
        $this->getFileSystem()->mkdir($this->entitiesPath);
225
    }
226
227
    protected function getFileSystem(): Filesystem
228
    {
229
        if (null === $this->filesystem) {
230
            $this->filesystem = (null !== $this->container)
231
                ? $this->container->get(Filesystem::class)
232
                : new Filesystem();
233
        }
234
235
        return $this->filesystem;
236
    }
237
238
    protected function emptyDirectory(string $path)
239
    {
240
        $fileSystem = $this->getFileSystem();
241
        $fileSystem->remove($path);
242
        $fileSystem->mkdir($path);
243
    }
244
245
    protected function assertNoMissedReplacements(string $createdFile)
246
    {
247
        $createdFile = $this->container->get(CodeHelper::class)->resolvePath($createdFile);
248
        $this->assertFileExists($createdFile);
249
        $contents = file_get_contents($createdFile);
250
        $this->assertNotContains(
251
            'template',
252
            $contents,
253
            'Found the word "template" (case insensitive) in the created file '.$createdFile,
254
            true
255
        );
256
    }
257
258
    protected function assertFileContains(string $createdFile, string $needle)
259
    {
260
        $createdFile = $this->container->get(CodeHelper::class)->resolvePath($createdFile);
261
        $this->assertFileExists($createdFile);
262
        $contents = file_get_contents($createdFile);
263
        $this->assertContains(
264
            $needle,
265
            $contents,
266
            "Missing '$needle' in file '$createdFile'"
267
        );
268
    }
269
270
    protected function getEntityGenerator(): EntityGenerator
271
    {
272
        /**
273
         * @var EntityGenerator $entityGenerator
274
         */
275
        $entityGenerator = $this->container->get(EntityGenerator::class);
276
        $entityGenerator->setPathToProjectRoot(static::WORK_DIR)
277
                        ->setProjectRootNamespace(static::TEST_PROJECT_ROOT_NAMESPACE);
278
279
        return $entityGenerator;
280
    }
281
282
    protected function getRelationsGenerator(): RelationsGenerator
283
    {
284
        /**
285
         * @var RelationsGenerator $relationsGenerator
286
         */
287
        $relationsGenerator = $this->container->get(RelationsGenerator::class);
288
        $relationsGenerator->setPathToProjectRoot(static::WORK_DIR)
289
                           ->setProjectRootNamespace(static::TEST_PROJECT_ROOT_NAMESPACE);
290
291
        return $relationsGenerator;
292
    }
293
294
    protected function getFieldGenerator(): FieldGenerator
295
    {
296
        /**
297
         * @var FieldGenerator $fieldGenerator
298
         */
299
        $fieldGenerator = $this->container->get(FieldGenerator::class);
300
        $fieldGenerator->setPathToProjectRoot(static::WORK_DIR)
301
                       ->setProjectRootNamespace(static::TEST_PROJECT_ROOT_NAMESPACE);
302
303
        return $fieldGenerator;
304
    }
305
306
    /**
307
     * @return EntityManagerInterface
308
     * @throws Exception\DoctrineStaticMetaException
309
     */
310
    protected function getEntityManager(): EntityManagerInterface
311
    {
312
        return $this->container->get(EntityManagerInterface::class);
313
    }
314
315
    protected function getSchema(): Schema
316
    {
317
        return $this->container->get(Schema::class);
318
    }
319
320
    /**
321
     * @SuppressWarnings(PHPMD.Superglobals)
322
     */
323
    public function qaGeneratedCode(): bool
324
    {
325
        if (isset($_SERVER[Constants::QA_QUICK_TESTS_KEY])
326
            && (int)$_SERVER[Constants::QA_QUICK_TESTS_KEY] === Constants::QA_QUICK_TESTS_ENABLED
327
        ) {
328
            return true;
329
        }
330
        //lint
331
        $path       = static::WORK_DIR;
332
        $exclude    = ['vendor'];
333
        $extensions = ['php'];
334
335
        $linter  = new Linter($path, $exclude, $extensions);
336
        $lint    = $linter->lint([], false);
337
        $message = str_replace($path, '', print_r($lint, true));
338
        $this->assertEmpty($lint, "\n\nPHP Syntax Errors in $path\n\n$message\n\n");
339
340
        $phpstanNamespace  = static::TEST_PROJECT_ROOT_NAMESPACE.'\\\\';
341
        $phpstanFolder     = static::WORK_DIR.'/'.AbstractCommand::DEFAULT_SRC_SUBFOLDER;
342
        $phpstanAutoLoader = '<?php declare(strict_types=1);
343
require __DIR__."/../../../vendor/autoload.php";
344
345
use Composer\Autoload\ClassLoader;
346
347
$loader = new class extends ClassLoader
348
        {
349
            public function loadClass($class)
350
            {
351
                if (false === strpos($class, "'.AbstractTest::TEST_PROJECT_ROOT_NAMESPACE.'")) {
352
                    return false;
353
                }
354
                $found = parent::loadClass($class);
355
                if (\in_array(gettype($found), [\'boolean\', \'NULL\'], true)) {
356
                    //good spot to set a break point ;)
357
                    return false;
358
                }
359
360
                return true;
361
            }
362
        };
363
        $loader->addPsr4(
364
            "'.$phpstanNamespace.'","'.$phpstanFolder.'"
365
        );
366
        $loader->register();
367
';
368
        file_put_contents(static::WORK_DIR.'/phpstan-autoloader.php', $phpstanAutoLoader);
369
        // A hunch that travis is not liking the no xdebug command
370
        $phpstanCommand = GeneratedCodeTest::BASH_PHPNOXDEBUG_FUNCTION
371
                          ."\n\nphpNoXdebug bin/phpstan.phar analyse $path/src -l7 -a "
372
                          .static::WORK_DIR.'/phpstan-autoloader.php 2>&1';
373
        if ($this->isTravis()) {
374
            $phpstanCommand = "bin/phpstan.phar analyse $path/src -l7 -a "
375
                              .static::WORK_DIR.'/phpstan-autoloader.php 2>&1';
376
        }
377
        exec(
378
            $phpstanCommand,
379
            $output,
380
            $exitCode
381
        );
382
        if (0 !== $exitCode) {
383
            $this->fail('PHPStan errors found in generated code at '.$path
384
                        .':'."\n\n".implode("\n", $output));
385
        }
386
387
        return true;
388
    }
389
}
390