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 (#93)
by joseph
71:13 queued 68:42
created

setTheDuplicateNamedFields()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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