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

FullProjectBuildLargeTest::generateFields()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 12
rs 10
c 0
b 0
f 0
cc 4
nc 8
nop 0
1
<?php declare(strict_types=1);
2
3
namespace EdmondsCommerce\DoctrineStaticMeta\Tests\Large;
4
5
use Doctrine\Common\Inflector\Inflector;
6
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Command\GenerateFieldCommand;
7
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\AbstractGenerator;
8
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\Field\FieldGenerator;
9
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\RelationsGenerator;
10
use EdmondsCommerce\DoctrineStaticMeta\Config;
11
use EdmondsCommerce\DoctrineStaticMeta\ConfigInterface;
12
use EdmondsCommerce\DoctrineStaticMeta\Entity\Embeddable\Objects\Financial\MoneyEmbeddable;
13
use EdmondsCommerce\DoctrineStaticMeta\Entity\Embeddable\Objects\Geo\AddressEmbeddable;
14
use EdmondsCommerce\DoctrineStaticMeta\Entity\Embeddable\Objects\Identity\FullNameEmbeddable;
15
use EdmondsCommerce\DoctrineStaticMeta\Entity\Embeddable\Traits\Financial\HasMoneyEmbeddableTrait;
16
use EdmondsCommerce\DoctrineStaticMeta\Entity\Embeddable\Traits\Geo\HasAddressEmbeddableTrait;
17
use EdmondsCommerce\DoctrineStaticMeta\Entity\Embeddable\Traits\Identity\HasFullNameEmbeddableTrait;
18
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\String\BusinessIdentifierCodeFieldTrait;
19
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\String\NullableStringFieldTrait;
20
use EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException;
21
use EdmondsCommerce\DoctrineStaticMeta\MappingHelper;
22
use EdmondsCommerce\DoctrineStaticMeta\Tests\Assets\AbstractLargeTest;
23
use EdmondsCommerce\DoctrineStaticMeta\Tests\Assets\AbstractTest;
24
25
/**
26
 * Class GeneratedCodeTest
27
 *
28
 * @package EdmondsCommerce\DoctrineStaticMeta\GeneratedCode
29
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
30
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
31
 * @coversNothing
32
 */
33
class FullProjectBuildLargeTest extends AbstractLargeTest
34
{
35
    public const TEST_ENTITY_NAMESPACE_BASE = self::TEST_PROJECT_ROOT_NAMESPACE
36
                                              . '\\' . AbstractGenerator::ENTITIES_FOLDER_NAME;
37
38
    public const TEST_FIELD_TRAIT_NAMESPACE = self::TEST_FIELD_NAMESPACE_BASE . '\\Traits\\';
39
40
    public const TEST_ENTITY_PERSON        = self::TEST_ENTITY_NAMESPACE_BASE . '\\Person';
41
    public const TEST_ENTITY_ADDRESS       = self::TEST_ENTITY_NAMESPACE_BASE . '\\Attributes\\Address';
42
    public const TEST_ENTITY_EMAIL         = self::TEST_ENTITY_NAMESPACE_BASE . '\\Attributes\\Email';
43
    public const TEST_ENTITY_COMPANY       = self::TEST_ENTITY_NAMESPACE_BASE . '\\Company';
44
    public const TEST_ENTITY_DIRECTOR      = self::TEST_ENTITY_NAMESPACE_BASE . '\\Company\\Director';
45
    public const TEST_ENTITY_ORDER         = self::TEST_ENTITY_NAMESPACE_BASE . '\\Order';
46
    public const TEST_ENTITY_ORDER_ADDRESS = self::TEST_ENTITY_NAMESPACE_BASE . '\\Order\\Address';
47
48
    public const TEST_ENTITY_NAME_SPACING_SOME_CLIENT    = self::TEST_ENTITY_NAMESPACE_BASE . '\\Some\\Client';
49
    public const TEST_ENTITY_NAME_SPACING_ANOTHER_CLIENT = self::TEST_ENTITY_NAMESPACE_BASE
50
                                                           . '\\Another\\Deeply\\Nested\\Client';
51
52
    public const TEST_ENTITIES = [
53
        self::TEST_ENTITY_PERSON,
54
        self::TEST_ENTITY_ADDRESS,
55
        self::TEST_ENTITY_EMAIL,
56
        self::TEST_ENTITY_COMPANY,
57
        self::TEST_ENTITY_DIRECTOR,
58
        self::TEST_ENTITY_ORDER,
59
        self::TEST_ENTITY_ORDER_ADDRESS,
60
        self::TEST_ENTITY_NAME_SPACING_SOME_CLIENT,
61
        self::TEST_ENTITY_NAME_SPACING_ANOTHER_CLIENT,
62
    ];
63
64
    public const TEST_RELATIONS = [
65
        [self::TEST_ENTITY_PERSON, RelationsGenerator::HAS_UNIDIRECTIONAL_MANY_TO_ONE, self::TEST_ENTITY_ADDRESS],
66
        [self::TEST_ENTITY_PERSON, RelationsGenerator::HAS_ONE_TO_MANY, self::TEST_ENTITY_EMAIL],
67
        [self::TEST_ENTITY_COMPANY, RelationsGenerator::HAS_MANY_TO_MANY, self::TEST_ENTITY_DIRECTOR],
68
        [self::TEST_ENTITY_COMPANY, RelationsGenerator::HAS_ONE_TO_MANY, self::TEST_ENTITY_ADDRESS],
69
        [self::TEST_ENTITY_COMPANY, RelationsGenerator::HAS_UNIDIRECTIONAL_ONE_TO_MANY, self::TEST_ENTITY_EMAIL],
70
        [self::TEST_ENTITY_DIRECTOR, RelationsGenerator::HAS_ONE_TO_ONE, self::TEST_ENTITY_PERSON],
71
        [self::TEST_ENTITY_ORDER, RelationsGenerator::HAS_MANY_TO_ONE, self::TEST_ENTITY_PERSON],
72
        [self::TEST_ENTITY_ORDER, RelationsGenerator::HAS_ONE_TO_MANY, self::TEST_ENTITY_ORDER_ADDRESS],
73
        [self::TEST_ENTITY_ORDER_ADDRESS, RelationsGenerator::HAS_UNIDIRECTIONAL_ONE_TO_ONE, self::TEST_ENTITY_ADDRESS],
74
        [
75
            self::TEST_ENTITY_COMPANY,
76
            RelationsGenerator::HAS_ONE_TO_ONE,
77
            self::TEST_ENTITY_NAME_SPACING_SOME_CLIENT,
78
        ],
79
        [
80
            self::TEST_ENTITY_COMPANY,
81
            RelationsGenerator::HAS_ONE_TO_ONE,
82
            self::TEST_ENTITY_NAME_SPACING_ANOTHER_CLIENT,
83
        ],
84
    ];
85
86
    public const TEST_FIELD_NAMESPACE_BASE = self::TEST_PROJECT_ROOT_NAMESPACE . '\\Entity\\Fields';
87
88
    public const UNIQUEABLE_FIELD_TYPES = [
89
        MappingHelper::TYPE_INTEGER,
90
        MappingHelper::TYPE_STRING,
91
    ];
92
93
    public const DUPLICATE_SHORT_NAME_FIELDS = [
94
        [self::TEST_FIELD_NAMESPACE_BASE . '\\Traits\\Something\\FooFieldTrait', NullableStringFieldTrait::class],
95
        [
96
            self::TEST_FIELD_NAMESPACE_BASE . '\\Traits\\Otherthing\\FooFieldTrait',
97
            BusinessIdentifierCodeFieldTrait::class,
98
        ],
99
    ];
100
101
    public const EMBEDDABLE_TRAIT_BASE = self::TEST_PROJECT_ROOT_NAMESPACE . '\\Entity\\Embeddable\\Traits';
102
103
    public const TEST_EMBEDDABLES          = [
104
        [
105
            MoneyEmbeddable::class,
106
            self::EMBEDDABLE_TRAIT_BASE . '\\Financial\\HasPriceEmbeddableTrait',
107
            'PriceEmbeddable',
108
        ],
109
        [
110
            AddressEmbeddable::class,
111
            self::EMBEDDABLE_TRAIT_BASE . '\\Geo\\HasHeadOfficeEmbeddableTrait',
112
            'HeadOfficeEmbeddable',
113
        ],
114
        [
115
            FullNameEmbeddable::class,
116
            self::EMBEDDABLE_TRAIT_BASE . '\\Identity\\HasPersonEmbeddableTrait',
117
            'PersonEmbeddable',
118
        ],
119
    ];
120
    public const BASH_PHPNOXDEBUG_FUNCTION = <<<'BASH'
121
function phpNoXdebug {
122
    debugMode="off"
123
    if [[ "$-" == *x* ]]
124
    then
125
        debugMode='on'
126
    fi
127
    if [[ "$debugMode" == "on" ]]
128
    then
129
        set +x
130
    fi
131
    local returnCode;
132
    local temporaryPath="$(mktemp -t php.XXXX).ini"
133
    # Using awk to ensure that files ending without newlines do not lead to configuration error
134
    /usr/bin/php -i | grep "\.ini" | grep -o -e '\(/[a-z0-9._-]\+\)\+\.ini' | grep -v xdebug \
135
        | xargs awk 'FNR==1{print ""}1' > "$temporaryPath"
136
    #Run PHP with temp config with no xdebug, display errors on stderr
137
    set +e
138
    /usr/bin/php -n -c "$temporaryPath" "$@"    
139
    returnCode=$?
140
    set -e
141
    rm -f "$temporaryPath"
142
    if [[ "$debugMode" == "on" ]]
143
    then
144
        set -x
145
    fi
146
    return ${returnCode};    
147
}
148
149
BASH;
150
    /**
151
     * @var string
152
     */
153
    private $workDir;
154
155
    /**
156
     * @throws \Exception
157
     * @throws \Psr\Container\ContainerExceptionInterface
158
     * @throws \Psr\Container\NotFoundExceptionInterface
159
     * @SuppressWarnings(PHPMD.Superglobals)
160
     * @SuppressWarnings(PHPMD.StaticAccess)
161
     */
162
    public function setup()
163
    {
164
        if ($this->isQuickTests()) {
165
            return;
166
        }
167
        $this->assertNoUncommitedChanges();
168
        $this->workDir      = $this->isTravis() ?
169
            AbstractTest::VAR_PATH . '/GeneratedCodeTest'
170
            : sys_get_temp_dir() . '/dsm/test-project';
171
        $this->entitiesPath = $this->workDir . '/src/Entities';
172
        $this->getFileSystem()->mkdir($this->workDir);
173
        $this->emptyDirectory($this->workDir . '');
174
        $this->getFileSystem()->mkdir($this->entitiesPath);
175
        $this->setupContainer($this->entitiesPath);
176
        $this->initRebuildFile();
177
        $this->setupGeneratedDb();
178
        $this->initComposerAndInstall();
179
        $fileSystem = $this->getFileSystem();
180
        $fileSystem->mkdir(
181
            [
182
                $this->workDir . '/tests/',
183
                $this->workDir . '/cache/Proxies',
184
                $this->workDir . '/cache/qa',
185
                $this->workDir . '/qaConfig',
186
            ]
187
        );
188
        file_put_contents(
189
            $this->workDir . '/qaConfig/phpunit.xml',
190
            <<<XML
191
<phpunit
192
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
193
        xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/3.7/phpunit.xsd"
194
        cacheTokens="false"
195
        colors="true"
196
        verbose="true"
197
        bootstrap="../tests/bootstrap.php"
198
>
199
    <testsuites>
200
        <testsuite name="tests">
201
            <directory suffix="Test.php">../tests/</directory>
202
        </testsuite>
203
    </testsuites>
204
</phpunit>
205
XML
206
        );
207
        $fileSystem->symlink($this->workDir . '/qaConfig/phpunit.xml', $this->workDir . '/phpunit.xml');
208
209
        $fileSystem->copy(
210
            __DIR__ . '/../../qaConfig/qaConfig.inc.bash',
211
            $this->workDir . '/qaConfig/qaConfig.inc.bash'
212
        );
213
        $fileSystem->copy(__DIR__ . '/../../cli-config.php', $this->workDir . '/cli-config.php');
214
        file_put_contents($this->workDir . '/README.md', '#Generated Code');
215
216
        $entities            = $this->generateEntities();
217
        $standardFieldEntity = $this->generateStandardFieldEntity();
218
        $this->generateRelations();
219
        $this->generateFields();
220
        $this->setFields(
221
            $entities,
222
            $this->getFieldFqns()
223
        );
224
        $this->setTheDuplicateNamedFields($entities);
225
        $this->setFields(
226
            [$standardFieldEntity],
227
            FieldGenerator::STANDARD_FIELDS
228
        );
229
        foreach ($entities as $entityFqn) {
230
            foreach ([
231
                         HasMoneyEmbeddableTrait::class,
232
                         HasFullNameEmbeddableTrait::class,
233
                         HasAddressEmbeddableTrait::class,
234
                     ] as $embeddableTraitFqn) {
235
                $this->setEmbeddable($entityFqn, $embeddableTraitFqn);
236
            }
237
            foreach (self::TEST_EMBEDDABLES as list($archetypeObject, $traitFqn, $className)) {
238
                $this->generateEmbeddable(
239
                    '\\' . $archetypeObject,
240
                    $className
241
                );
242
                $this->setEmbeddable($entityFqn, $traitFqn);
243
            }
244
        }
245
        $this->removeUnusedRelations();
246
        $this->execDoctrine('o:c:metadata');
247
        $this->execDoctrine('o:v');
248
    }
249
250
    /**
251
     * We need to check for uncommited changes in the main project. If there are, then the generated code tests will
252
     * not get them as it works by cloning this repo via the filesystem
253
     */
254
    protected function assertNoUncommitedChanges(): void
255
    {
256
        if ($this->isTravis()) {
257
            return;
258
        }
259
        exec("git status | grep -E 'nothing to commit, working .*? clean' ", $output, $exitCode);
260
        if (0 !== $exitCode) {
261
            $this->markTestSkipped(
262
                'uncommitted changes detected in this project, '
263
                . 'there is no point running the generated code test as it will not have your uncommitted changes.'
264
                . "\n\n" . implode("\n", $output)
265
            );
266
        }
267
    }
268
269
    protected function initRebuildFile(): void
270
    {
271
        $bash =
272
            <<<'BASH'
273
#!/usr/bin/env bash
274
readonly DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )";
275
cd "$DIR";
276
set -e
277
set -u
278
set -o pipefail
279
standardIFS="$IFS"
280
IFS=$'\n\t'
281
echo "
282
===========================================
283
$(hostname) $0 $@
284
===========================================
285
"
286
# Error Handling
287
backTraceExit () {
288
    local err=$?
289
    set +o xtrace
290
    local code="${1:-1}"
291
    printf "\n\nError in ${BASH_SOURCE[1]}:${BASH_LINENO[0]}. '${BASH_COMMAND}'\n\n exited with status: \n\n$err\n\n"
292
    # Print out the stack trace described by $function_stack
293
    if [ ${#FUNCNAME[@]} -gt 2 ]
294
    then
295
        echo "Call tree:"
296
        for ((i=1;i<${#FUNCNAME[@]}-1;i++))
297
        do
298
            echo " $i: ${BASH_SOURCE[$i+1]}:${BASH_LINENO[$i]} ${FUNCNAME[$i]}(...)"
299
        done
300
    fi
301
    echo "Exiting with status ${code}"
302
    exit "${code}"
303
}
304
trap 'backTraceExit' ERR
305
set -o errtrace
306
# Error Handling Ends
307
308
echo "clearing out generated code"
309
rm -rf src/* tests/*
310
311
echo "preparing empty Entities directory"
312
mkdir src/Entities
313
314
echo "making sure we have the latest version of code"
315
(cd vendor/edmondscommerce/doctrine-static-meta && git pull)
316
317
BASH;
318
        if (!$this->isTravis()) {
319
            $bash .= self::BASH_PHPNOXDEBUG_FUNCTION;
320
        }
321
        file_put_contents(
322
            $this->workDir . '/rebuild.bash',
323
            "\n\n" . $bash
324
        );
325
    }
326
327
    /**
328
     * @return string Generated Database Name
329
     * @throws \Exception
330
     * @throws \Psr\Container\ContainerExceptionInterface
331
     * @throws \Psr\Container\NotFoundExceptionInterface
332
     */
333
    protected function setupGeneratedDb(): string
334
    {
335
        $dbHost = $this->container->get(Config::class)->get(ConfigInterface::PARAM_DB_HOST);
336
        $dbUser = $this->container->get(Config::class)->get(ConfigInterface::PARAM_DB_USER);
337
        $dbPass = $this->container->get(Config::class)->get(ConfigInterface::PARAM_DB_PASS);
338
        $dbName = $this->container->get(Config::class)->get(ConfigInterface::PARAM_DB_NAME);
339
        $link   = mysqli_connect($dbHost, $dbUser, $dbPass);
340
        if (!$link) {
0 ignored issues
show
introduced by
$link is of type mysqli, thus it always evaluated to true.
Loading history...
341
            throw new DoctrineStaticMetaException('Failed getting connection in ' . __METHOD__);
342
        }
343
        $generatedDbName = $dbName . '_generated';
344
        mysqli_query($link, "DROP DATABASE IF EXISTS $generatedDbName");
345
        mysqli_query(
346
            $link,
347
            "CREATE DATABASE $generatedDbName 
348
        CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci"
349
        );
350
        mysqli_close($link);
351
352
        $rebuildBash = <<<BASH
353
echo "Dropping and creating the DB $generatedDbName"        
354
mysql -u $dbUser -p$dbPass -h $dbHost -e "DROP DATABASE IF EXISTS $generatedDbName";
355
mysql -u $dbUser -p$dbPass -h $dbHost -e "CREATE DATABASE $generatedDbName 
356
CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci";
357
BASH;
358
        $this->addToRebuildFile($rebuildBash);
359
        file_put_contents(
360
            $this->workDir . '/.env',
361
            <<<EOF
362
export dbUser="{$dbUser}"
363
export dbPass="{$dbPass}"
364
export dbHost="{$dbHost}"
365
export dbName="$generatedDbName"
366
export devMode=0
367
export dbDebug=0
368
EOF
369
        );
370
371
        return $generatedDbName;
372
    }
373
374
    /**
375
     * @param string $bash
376
     *
377
     * @return bool
378
     * @throws \Exception
379
     */
380
    protected function addToRebuildFile(string $bash): bool
381
    {
382
        $result = file_put_contents(
383
            $this->workDir . '/rebuild.bash',
384
            "\n\n" . $bash . "\n\n",
385
            FILE_APPEND
386
        );
387
        if (!$result) {
388
            throw new \RuntimeException('Failed writing to rebuild file');
389
        }
390
391
        return true;
392
    }
393
394
    protected function initComposerAndInstall(): void
395
    {
396
        $vcsPath      = realpath(__DIR__ . '/../../../doctrine-static-meta/');
397
        $namespace    = str_replace('\\', '\\\\', self::TEST_PROJECT_ROOT_NAMESPACE);
398
        $composerJson = <<<JSON
399
{
400
  "require": {
401
    "edmondscommerce/doctrine-static-meta": "dev-%s",
402
    "edmondscommerce/typesafe-functions": "dev-master@dev",
403
    "gossi/php-code-generator": "^0.6@dev",
404
    "gossi/php-code-formatter": "dev-master@dev",
405
    "gossi/php-code-profiles": "dev-master@dev"
406
  },
407
  "repositories": [
408
    {
409
      "type": "vcs",
410
      "url": "%s"
411
    },
412
    {
413
      "type": "vcs",
414
      "url": "https://github.com/edmondscommerce/Faker.git"
415
    }
416
  ],
417
  "minimum-stability": "stable",
418
  "require-dev": {
419
    "fzaninotto/faker": "dev-dsm-patches@dev",
420
    "edmondscommerce/phpqa": "~1"
421
  },
422
  "autoload": {
423
    "psr-4": {
424
      "$namespace\\\\": [
425
        "src/"
426
      ]
427
    }
428
  },
429
  "autoload-dev": {
430
    "psr-4": {
431
      "$namespace\\\\": [
432
        "tests/"
433
      ]
434
    }
435
  },
436
  "config": {
437
    "bin-dir": "bin",
438
    "preferred-install": {
439
      "edmondscommerce/*": "source",
440
      "fzaninotto/faker": "source",
441
      "*": "dist"
442
    },
443
    "optimize-autoloader": true
444
  }
445
}
446
JSON;
447
448
        $gitCurrentBranchName = trim(shell_exec("git branch | grep '*' | cut -d ' ' -f 2-"));
449
        if (\ts\stringContains($gitCurrentBranchName, 'HEAD detached at')) {
450
            $gitCurrentBranchName = trim(str_replace('HEAD detached at', '', $gitCurrentBranchName), " \t\n\r\0\x0B()");
451
        }
452
        file_put_contents(
453
            $this->workDir . '/composer.json',
454
            sprintf($composerJson, $gitCurrentBranchName, $vcsPath)
455
        );
456
457
        $phpCmd   = $this->isTravis() ? 'php' : 'phpNoXdebug';
458
        $bashCmds = <<<BASH
459
           
460
$phpCmd $(which composer) install \
461
    --prefer-dist
462
463
$phpCmd $(which composer) dump-autoload --optimize
464
465
BASH;
466
        $this->execBash($bashCmds);
467
    }
468
469
    /**
470
     * Runs bash with strict error handling and verbose logging
471
     *
472
     * Will ensure the phpNoXdebugFunction is available and will CD into the correct directory before running commands
473
     *
474
     * Asserts that the command returns with an exit code of 0
475
     *
476
     * Appends to the rebuild file allowing easy rerunning of the commmands in the test project
477
     *
478
     * @param string $bashCmds
479
     *
480
     * @throws \Exception
481
     */
482
    protected function execBash(string $bashCmds): void
483
    {
484
        fwrite(STDERR, "\n\t# Executing:\n\t$bashCmds");
485
        $startTime = microtime(true);
486
487
        $fullCmds = '';
488
        if (!$this->isTravis()) {
489
            $fullCmds .= "\n" . self::BASH_PHPNOXDEBUG_FUNCTION . "\n\n";
490
        }
491
        $fullCmds .= "set -xe;\n";
492
        $fullCmds .= "cd {$this->workDir};\n";
493
        #$fullCmds .= "exec 2>&1;\n";
494
        $fullCmds .= "$bashCmds\n";
495
496
        $output   = [];
497
        $exitCode = 0;
498
        exec($fullCmds, $output, $exitCode);
499
500
        if (0 !== $exitCode) {
501
            throw new \RuntimeException(
502
                "Error running bash commands:\n\nOutput:\n----------\n\n"
503
                . implode("\n", $output)
504
                . "\n\nCommands:\n----------\n"
505
                . str_replace(
506
                    "\n",
507
                    "\n\t",
508
                    "\n$bashCmds"
509
                ) . "\n\n"
510
            );
511
        }
512
513
        $seconds = round(microtime(true) - $startTime, 2);
514
        fwrite(STDERR, "\n\t\t#Completed in $seconds seconds\n");
515
    }
516
517
    /**
518
     * Generate all test entities
519
     *
520
     * @return array
521
     */
522
    protected function generateEntities(): array
523
    {
524
        foreach (self::TEST_ENTITIES as $entityFqn) {
525
            $this->generateEntity($entityFqn);
526
        }
527
528
        return self::TEST_ENTITIES;
529
    }
530
531
    protected function generateEntity(string $entityFqn): void
532
    {
533
        $namespace   = self::TEST_PROJECT_ROOT_NAMESPACE;
534
        $doctrineCmd = <<<DOCTRINE
535
 dsm:generate:entity \
536
    --project-root-namespace="{$namespace}" \
537
    --entity-fully-qualified-name="{$entityFqn}"
538
DOCTRINE;
539
        $this->execDoctrine($doctrineCmd);
540
    }
541
542
    protected function execDoctrine(string $doctrineCmd): void
543
    {
544
        $phpCmd  = $this->isTravis() ? 'php' : 'phpNoXdebug';
545
        $bash    = <<<BASH
546
$phpCmd bin/doctrine $doctrineCmd    
547
BASH;
548
        $error   = false;
549
        $message = '';
550
        try {
551
            $this->execBash($bash);
552
        } catch (\RuntimeException $e) {
553
            $this->addToRebuildFile("\n\nexit 0;\n\n#The command below failed...\n\n");
554
            $error   = true;
555
            $message = $e->getMessage();
556
        }
557
        $this->addToRebuildFile($bash);
558
        self::assertFalse($error, $message);
559
    }
560
561
    /**
562
     * @return string
563
     */
564
    protected function generateStandardFieldEntity(): string
565
    {
566
        $entityFqn = self::TEST_ENTITY_NAMESPACE_BASE . '\\Standard\\Field';
567
        $this->generateUuidEntity($entityFqn);
568
569
        return $entityFqn;
570
    }
571
572
    protected function generateUuidEntity(string $entityFqn): void
573
    {
574
        $namespace   = self::TEST_PROJECT_ROOT_NAMESPACE;
575
        $doctrineCmd = <<<DOCTRINE
576
 dsm:generate:entity \
577
    --project-root-namespace="{$namespace}" \
578
    --entity-fully-qualified-name="{$entityFqn}" \
579
    --uuid-primary-key
580
DOCTRINE;
581
        $this->execDoctrine($doctrineCmd);
582
    }
583
584
    /**
585
     * Generate all test relations
586
     *
587
     * @return void
588
     */
589
    protected function generateRelations(): void
590
    {
591
        foreach (self::TEST_RELATIONS as $relation) {
592
            $this->setRelation(...$relation);
0 ignored issues
show
Bug introduced by
The call to EdmondsCommerce\Doctrine...argeTest::setRelation() has too few arguments starting with type. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

592
            $this->/** @scrutinizer ignore-call */ 
593
                   setRelation(...$relation);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
593
        }
594
    }
595
596
    protected function setRelation(string $entity1, string $type, string $entity2): void
597
    {
598
        $namespace = self::TEST_PROJECT_ROOT_NAMESPACE;
599
        $this->execDoctrine(
600
            <<<DOCTRINE
601
dsm:set:relation \
602
    --project-root-path="{$this->workDir}" \
603
    --project-root-namespace="{$namespace}" \
604
    --entity1="{$entity1}" \
605
    --hasType="{$type}" \
606
    --entity2="{$entity2}"    
607
DOCTRINE
608
        );
609
    }
610
611
    /**
612
     * Generate one field per common type
613
     *
614
     * @return void
615
     */
616
    protected function generateFields(): void
617
    {
618
        foreach (MappingHelper::COMMON_TYPES as $type) {
619
            $fieldFqn = self::TEST_FIELD_TRAIT_NAMESPACE . '\\' . $type;
620
            $this->generateField($fieldFqn, $type);
621
        }
622
        foreach (self::UNIQUEABLE_FIELD_TYPES as $uniqueableType) {
623
            $fieldFqn = self::TEST_FIELD_TRAIT_NAMESPACE . '\\Unique' . ucwords($uniqueableType);
624
            $this->generateField($fieldFqn, $uniqueableType, null, true);
625
        }
626
        foreach (self::DUPLICATE_SHORT_NAME_FIELDS as $duplicateShortName) {
627
            $this->generateField(...$duplicateShortName);
0 ignored issues
show
Bug introduced by
The call to EdmondsCommerce\Doctrine...geTest::generateField() has too few arguments starting with type. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

627
            $this->/** @scrutinizer ignore-call */ 
628
                   generateField(...$duplicateShortName);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
628
        }
629
    }
630
631
    /**
632
     * @param string     $propertyName
633
     * @param string     $type
634
     * @param mixed|null $default
635
     * @param bool       $isUnique
636
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
637
     */
638
    protected function generateField(
639
        string $propertyName,
640
        string $type,
641
        $default = null,
642
        bool $isUnique = false
643
    ): void {
644
        $namespace   = self::TEST_PROJECT_ROOT_NAMESPACE;
645
        $doctrineCmd = <<<DOCTRINE
646
 dsm:generate:field \
647
    --project-root-path="{$this->workDir}" \
648
    --project-root-namespace="{$namespace}" \
649
    --field-fully-qualified-name="{$propertyName}" \
650
    --field-property-doctrine-type="{$type}"
651
DOCTRINE;
652
        if (null !== $default) {
653
            $doctrineCmd .= ' --' . GenerateFieldCommand::OPT_DEFAULT_VALUE . '="' . $default . '"' . "\\\n";
654
        }
655
        if (true === $isUnique) {
656
            $doctrineCmd .= ' --' . GenerateFieldCommand::OPT_IS_UNIQUE . "\\\n";
657
        }
658
        $this->execDoctrine($doctrineCmd);
659
    }
660
661
    /**
662
     * Set each field type on each entity type
663
     *
664
     * @param array $entities
665
     * @param array $fields
666
     *
667
     * @return void
668
     */
669
    protected function setFields(array $entities, array $fields): void
670
    {
671
        foreach ($entities as $entityFqn) {
672
            foreach ($fields as $fieldFqn) {
673
                $this->setField($entityFqn, $fieldFqn);
674
            }
675
        }
676
    }
677
678
    protected function setField(string $entityFqn, string $fieldFqn): void
679
    {
680
        $namespace   = self::TEST_PROJECT_ROOT_NAMESPACE;
681
        $doctrineCmd = <<<DOCTRINE
682
 dsm:set:field \
683
    --project-root-path="{$this->workDir}" \
684
    --project-root-namespace="{$namespace}" \
685
    --entity="{$entityFqn}" \
686
    --field="{$fieldFqn}"
687
DOCTRINE;
688
        $this->execDoctrine($doctrineCmd);
689
    }
690
691
    /**
692
     * @return array
693
     * @SuppressWarnings(PHPMD.StaticAccess)
694
     */
695
    protected function getFieldFqns(): array
696
    {
697
        $fieldFqns = [];
698
        foreach (MappingHelper::COMMON_TYPES as $type) {
699
            $fieldFqns[] = self::TEST_FIELD_TRAIT_NAMESPACE . Inflector::classify($type) . 'FieldTrait';
700
        }
701
        foreach (self::UNIQUEABLE_FIELD_TYPES as $type) {
702
            $fieldFqns[] = self::TEST_FIELD_TRAIT_NAMESPACE . Inflector::classify('unique_' . $type) . 'FieldTrait';
703
        }
704
705
        return $fieldFqns;
706
    }
707
708
    protected function setTheDuplicateNamedFields(array $entities)
709
    {
710
        foreach ($entities as $k => $entityFqn) {
711
            $fieldKey = ($k % 2 === 0) ? 0 : 1;
712
            $this->setField($entityFqn, self::DUPLICATE_SHORT_NAME_FIELDS[$fieldKey][0]);
713
        }
714
    }
715
716
    protected function setEmbeddable(string $entityFqn, string $embeddableTraitFqn): void
717
    {
718
        $doctrineCmd = <<<DOCTRINE
719
 dsm:set:embeddable \
720
    --entity="{$entityFqn}" \
721
    --embeddable="{$embeddableTraitFqn}"
722
DOCTRINE;
723
        $this->execDoctrine($doctrineCmd);
724
    }
725
726
    protected function generateEmbeddable(string $archetypeFqn, string $newClassName): void
727
    {
728
        $doctrineCmd = <<<DOCTRINE
729
dsm:generate:embeddable \
730
    --classname="{$newClassName}" \
731
    --archetype="{$archetypeFqn}"
732
DOCTRINE;
733
        $this->execDoctrine($doctrineCmd);
734
    }
735
736
    protected function removeUnusedRelations(): void
737
    {
738
        $namespace   = self::TEST_PROJECT_ROOT_NAMESPACE;
739
        $doctrineCmd = <<<DOCTRINE
740
 dsm:remove:unusedRelations \
741
    --project-root-namespace="{$namespace}"
742
DOCTRINE;
743
        $this->execDoctrine($doctrineCmd);
744
    }
745
746
    /**
747
     * @SuppressWarnings(PHPMD.Superglobals)
748
     * @throws \Exception
749
     */
750
    public function testRunTests(): void
751
    {
752
        $this->assertWeCheckAllPossibleRelationTypes();
753
        if ($this->isQuickTests()) {
754
            $this->markTestSkipped('Quick tests is enabled');
755
        }
756
        /** @lang bash */
757
        $bashCmds = <<<BASH
758
759
set +x
760
761
echo "
762
763
--------------------------------------------------
764
STARTS Running Tests In {$this->workDir}
765
--------------------------------------------------
766
767
"
768
769
#Prevent the retry tool dialogue etc
770
export CI=true
771
772
bash -x bin/qa
773
774
echo "
775
776
--------------------------------------------------
777
DONE Running Tests In {$this->workDir}
778
--------------------------------------------------
779
780
"
781
set -x
782
BASH;
783
        $this->execBash($bashCmds);
784
    }
785
786
    protected function assertWeCheckAllPossibleRelationTypes(): void
787
    {
788
        $included = $toTest = [];
789
        foreach (RelationsGenerator::HAS_TYPES as $hasType) {
790
            if (0 === \strpos($hasType, RelationsGenerator::PREFIX_INVERSE)) {
791
                continue;
792
            }
793
            $toTest[$hasType] = true;
794
        }
795
        \ksort($toTest);
796
        foreach (self::TEST_RELATIONS as $relation) {
797
            $included[$relation[1]] = true;
798
        }
799
        \ksort($included);
800
        $missing = \array_diff(\array_keys($toTest), \array_keys($included));
801
        self::assertEmpty(
802
            $missing,
803
            'We are not testing all relation types - '
804
            . 'these ones have not been included: '
805
            . print_r($missing, true)
806
        );
807
    }
808
}
809