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 (#100)
by joseph
18:26
created

FullProjectBuildFunctionalTest::generateEntity()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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