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
17:40
created

FullProjectBuildFunctionalTest::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;
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_COMPANY        = self::TEST_ENTITY_NAMESPACE_BASE . '\\Company';
44
    public const TEST_ENTITY_NAME_SPACING_SOME_CLIENT    = self::TEST_ENTITY_NAMESPACE_BASE . '\\Some\\Client';
45
    public const TEST_ENTITY_NAME_SPACING_ANOTHER_CLIENT = self::TEST_ENTITY_NAMESPACE_BASE
46
                                                           . '\\Another\\Deeply\\Nested\\Client';
47
48
    public const TEST_ENTITIES = [
49
        self::TEST_ENTITY_PERSON,
50
        self::TEST_ENTITY_ADDRESS,
51
        self::TEST_ENTITY_EMAIL,
52
        self::TEST_ENTITY_COMPANY,
53
        self::TEST_ENTITY_DIRECTOR,
54
        self::TEST_ENTITY_ORDER,
55
        self::TEST_ENTITY_ORDER_ADDRESS,
56
        self::TEST_ENTITY_NAME_SPACING_COMPANY,
57
        self::TEST_ENTITY_NAME_SPACING_SOME_CLIENT,
58
        self::TEST_ENTITY_NAME_SPACING_ANOTHER_CLIENT,
59
    ];
60
61
    public const TEST_RELATIONS = [
62
        [self::TEST_ENTITY_PERSON, RelationsGenerator::HAS_UNIDIRECTIONAL_MANY_TO_ONE, self::TEST_ENTITY_ADDRESS],
63
        [self::TEST_ENTITY_PERSON, RelationsGenerator::HAS_ONE_TO_MANY, self::TEST_ENTITY_EMAIL],
64
        [self::TEST_ENTITY_COMPANY, RelationsGenerator::HAS_MANY_TO_MANY, self::TEST_ENTITY_DIRECTOR],
65
        [self::TEST_ENTITY_COMPANY, RelationsGenerator::HAS_ONE_TO_MANY, self::TEST_ENTITY_ADDRESS],
66
        [self::TEST_ENTITY_COMPANY, RelationsGenerator::HAS_UNIDIRECTIONAL_ONE_TO_MANY, self::TEST_ENTITY_EMAIL],
67
        [self::TEST_ENTITY_DIRECTOR, RelationsGenerator::HAS_ONE_TO_ONE, self::TEST_ENTITY_PERSON],
68
        [self::TEST_ENTITY_ORDER, RelationsGenerator::HAS_MANY_TO_ONE, self::TEST_ENTITY_PERSON],
69
        [self::TEST_ENTITY_ORDER, RelationsGenerator::HAS_ONE_TO_MANY, self::TEST_ENTITY_ORDER_ADDRESS],
70
        [self::TEST_ENTITY_ORDER_ADDRESS, RelationsGenerator::HAS_UNIDIRECTIONAL_ONE_TO_ONE, self::TEST_ENTITY_ADDRESS],
71
        [
72
            self::TEST_ENTITY_NAME_SPACING_COMPANY,
73
            RelationsGenerator::HAS_ONE_TO_ONE,
74
            self::TEST_ENTITY_NAME_SPACING_SOME_CLIENT,
75
        ],
76
        [
77
            self::TEST_ENTITY_NAME_SPACING_COMPANY,
78
            RelationsGenerator::HAS_ONE_TO_ONE,
79
            self::TEST_ENTITY_NAME_SPACING_ANOTHER_CLIENT,
80
        ],
81
    ];
82
83
    public const TEST_FIELD_NAMESPACE_BASE = self::TEST_PROJECT_ROOT_NAMESPACE . '\\Entity\\Fields';
84
85
    public const UNIQUEABLE_FIELD_TYPES = [
86
        MappingHelper::TYPE_INTEGER,
87
        MappingHelper::TYPE_STRING,
88
    ];
89
90
    public const DUPLICATE_SHORT_NAME_FIELDS = [
91
        [self::TEST_FIELD_NAMESPACE_BASE . '\\Traits\\Something\\Foo', NullableStringFieldTrait::class],
92
        [self::TEST_FIELD_NAMESPACE_BASE . '\\Traits\\Otherthing\\Foo', BusinessIdentifierCodeFieldTrait::class],
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 (isset($_SERVER[Constants::QA_QUICK_TESTS_KEY])
159
            && (int)$_SERVER[Constants::QA_QUICK_TESTS_KEY] === Constants::QA_QUICK_TESTS_ENABLED
160
        ) {
161
            return;
162
        }
163
        $this->assertNoUncommitedChanges();
164
        $this->workDir      = $this->isTravis() ?
165
            AbstractIntegrationTest::VAR_PATH . '/GeneratedCodeTest'
166
            : sys_get_temp_dir() . '/dsm/test-project';
167
        $this->entitiesPath = $this->workDir . '/src/Entities';
168
        $this->getFileSystem()->mkdir($this->workDir);
169
        $this->emptyDirectory($this->workDir . '');
170
        $this->getFileSystem()->mkdir($this->entitiesPath);
171
        $this->setupContainer($this->entitiesPath);
172
        $this->initRebuildFile();
173
        $this->setupGeneratedDb();
174
        $this->initComposerAndInstall();
175
        $fileSystem = $this->getFileSystem();
176
        $fileSystem->mkdir(
177
            [
178
                $this->workDir . '/tests/',
179
                $this->workDir . '/cache/Proxies',
180
                $this->workDir . '/cache/qa',
181
                $this->workDir . '/qaConfig',
182
            ]
183
        );
184
        file_put_contents(
185
            $this->workDir . '/qaConfig/phpunit.xml',
186
            <<<XML
187
<phpunit
188
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
189
        xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/3.7/phpunit.xsd"
190
        cacheTokens="false"
191
        colors="true"
192
        verbose="true"
193
        bootstrap="../tests/bootstrap.php"
194
>
195
    <testsuites>
196
        <testsuite name="tests">
197
            <directory suffix="Test.php">../tests/</directory>
198
        </testsuite>
199
    </testsuites>
200
</phpunit>
201
XML
202
        );
203
        $fileSystem->symlink($this->workDir . '/qaConfig/phpunit.xml', $this->workDir . '/phpunit.xml');
204
205
        $fileSystem->copy(
206
            __DIR__ . '/../../qaConfig/qaConfig.inc.bash',
207
            $this->workDir . '/qaConfig/qaConfig.inc.bash'
208
        );
209
        $fileSystem->copy(__DIR__ . '/../../cli-config.php', $this->workDir . '/cli-config.php');
210
        file_put_contents($this->workDir . '/README.md', '#Generated Code');
211
212
        $this->addToRebuildFile(self::BASH_PHPNOXDEBUG_FUNCTION);
213
214
        $entities            = $this->generateEntities();
215
        $standardFieldEntity = $this->generateStandardFieldEntity();
216
        $this->generateRelations();
217
        $this->generateFields();
218
        $this->setFields(
219
            $entities,
220
            $this->getFieldFqns()
221
        );
222
        $this->setTheDuplicateNamedFields($entities);
223
        $this->setFields(
224
            [$standardFieldEntity],
225
            FieldGenerator::STANDARD_FIELDS
226
        );
227
        foreach ($entities as $entityFqn) {
228
            foreach ([
229
                         HasMoneyEmbeddableTrait::class,
230
                         HasFullNameEmbeddableTrait::class,
231
                         HasAddressEmbeddableTrait::class,
232
                     ] as $embeddableTraitFqn) {
233
                $this->setEmbeddable($entityFqn, $embeddableTraitFqn);
234
            }
235
            foreach (self::TEST_EMBEDDABLES as list($archetypeObject, $traitFqn, $className)) {
236
                $this->generateEmbeddable(
237
                    '\\' . $archetypeObject,
238
                    $className
239
                );
240
                $this->setEmbeddable($entityFqn, $traitFqn);
241
            }
242
        }
243
        $this->removeUnusedRelations();
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
EOF
371
        );
372
373
        return $generatedDbName;
374
    }
375
376
    /**
377
     * @param string $bash
378
     *
379
     * @return bool
380
     * @throws \Exception
381
     */
382
    protected function addToRebuildFile(string $bash): bool
383
    {
384
        $result = file_put_contents(
385
            $this->workDir . '/rebuild.bash',
386
            "\n\n" . $bash . "\n\n",
387
            FILE_APPEND
388
        );
389
        if (!$result) {
390
            throw new \RuntimeException('Failed writing to rebuild file');
391
        }
392
393
        return true;
394
    }
395
396
    protected function initComposerAndInstall(): void
397
    {
398
        $vcsPath      = realpath(__DIR__ . '/../../../doctrine-static-meta/');
399
        $namespace    = str_replace('\\', '\\\\', self::TEST_PROJECT_ROOT_NAMESPACE);
400
        $composerJson = <<<JSON
401
{
402
  "require": {
403
    "edmondscommerce/doctrine-static-meta": "dev-%s",
404
    "edmondscommerce/typesafe-functions": "dev-master@dev"
405
  },
406
  "repositories": [
407
    {
408
      "type": "vcs",
409
      "url": "%s"
410
    },
411
    {
412
      "type": "vcs",
413
      "url": "https://github.com/edmondscommerce/Faker.git"
414
    }
415
  ],
416
  "minimum-stability": "stable",
417
  "require-dev": {
418
    "fzaninotto/faker": "dev-dsm-patches@dev",
419
    "edmondscommerce/phpqa": "1.0.1"
420
  },
421
  "autoload": {
422
    "psr-4": {
423
      "$namespace\\\\": [
424
        "src/"
425
      ]
426
    }
427
  },
428
  "autoload-dev": {
429
    "psr-4": {
430
      "$namespace\\\\": [
431
        "tests/"
432
      ]
433
    }
434
  },
435
  "config": {
436
    "bin-dir": "bin",
437
    "preferred-install": {
438
      "edmondscommerce/*": "source",
439
      "fzaninotto/faker": "source",
440
      "*": "dist"
441
    },
442
    "optimize-autoloader": true
443
  }
444
}
445
JSON;
446
447
        $gitCurrentBranchName = trim(shell_exec("git branch | grep '*' | cut -d ' ' -f 2"));
448
        file_put_contents(
449
            $this->workDir . '/composer.json',
450
            sprintf($composerJson, $gitCurrentBranchName, $vcsPath)
451
        );
452
453
        $phpCmd   = $this->isTravis() ? 'php' : 'phpNoXdebug';
454
        $bashCmds = <<<BASH
455
           
456
$phpCmd $(which composer) install \
457
    --prefer-dist
458
459
$phpCmd $(which composer) dump-autoload --optimize
460
461
BASH;
462
        $this->execBash($bashCmds);
463
    }
464
465
    /**
466
     * Runs bash with strict error handling and verbose logging
467
     *
468
     * Will ensure the phpNoXdebugFunction is available and will CD into the correct directory before running commands
469
     *
470
     * Asserts that the command returns with an exit code of 0
471
     *
472
     * Appends to the rebuild file allowing easy rerunning of the commmands in the test project
473
     *
474
     * @param string $bashCmds
475
     *
476
     * @throws \Exception
477
     */
478
    protected function execBash(string $bashCmds): void
479
    {
480
        fwrite(STDERR, "\n\t# Executing:\n\t$bashCmds");
481
        $startTime = microtime(true);
482
483
        $fullCmds = '';
484
        if (!$this->isTravis()) {
485
            $fullCmds .= "\n" . self::BASH_PHPNOXDEBUG_FUNCTION . "\n\n";
486
        }
487
        $fullCmds .= "set -xe;\n";
488
        $fullCmds .= "cd {$this->workDir};\n";
489
        #$fullCmds .= "exec 2>&1;\n";
490
        $fullCmds .= "$bashCmds\n";
491
492
        $output   = [];
493
        $exitCode = 0;
494
        exec($fullCmds, $output, $exitCode);
495
496
        if (0 !== $exitCode) {
497
            throw new \RuntimeException(
498
                "Error running bash commands:\n\nOutput:\n----------\n\n"
499
                . implode("\n", $output)
500
                . "\n\nCommands:\n----------\n"
501
                . str_replace(
502
                    "\n",
503
                    "\n\t",
504
                    "\n$bashCmds"
505
                ) . "\n\n"
506
            );
507
        }
508
509
        $seconds = round(microtime(true) - $startTime, 2);
510
        fwrite(STDERR, "\n\t\t#Completed in $seconds seconds\n");
511
    }
512
513
    /**
514
     * Generate all test entities
515
     *
516
     * @return array
517
     */
518
    protected function generateEntities(): array
519
    {
520
        foreach (self::TEST_ENTITIES as $entityFqn) {
521
            $this->generateEntity($entityFqn);
522
        }
523
524
        return self::TEST_ENTITIES;
525
    }
526
527
    protected function generateEntity(string $entityFqn): void
528
    {
529
        $namespace   = self::TEST_PROJECT_ROOT_NAMESPACE;
530
        $doctrineCmd = <<<DOCTRINE
531
 dsm:generate:entity \
532
    --project-root-namespace="{$namespace}" \
533
    --entity-fully-qualified-name="{$entityFqn}"
534
DOCTRINE;
535
        $this->execDoctrine($doctrineCmd);
536
    }
537
538
    protected function execDoctrine(string $doctrineCmd): void
539
    {
540
        $phpCmd  = $this->isTravis() ? 'php' : 'phpNoXdebug';
541
        $bash    = <<<BASH
542
$phpCmd bin/doctrine $doctrineCmd    
543
BASH;
544
        $error   = false;
545
        $message = '';
546
        try {
547
            $this->execBash($bash);
548
        } catch (\RuntimeException $e) {
549
            $this->addToRebuildFile("\n\nexit 0;\n\n#The command below failed...\n\n");
550
            $error   = true;
551
            $message = $e->getMessage();
552
        }
553
        $this->addToRebuildFile($bash);
554
        self::assertFalse($error, $message);
555
    }
556
557
    /**
558
     * @return string
559
     */
560
    protected function generateStandardFieldEntity(): string
561
    {
562
        $entityFqn = self::TEST_ENTITY_NAMESPACE_BASE . '\\Standard\\Field';
563
        $this->generateUuidEntity($entityFqn);
564
565
        return $entityFqn;
566
    }
567
568
    protected function generateUuidEntity(string $entityFqn): void
569
    {
570
        $namespace   = self::TEST_PROJECT_ROOT_NAMESPACE;
571
        $doctrineCmd = <<<DOCTRINE
572
 dsm:generate:entity \
573
    --project-root-namespace="{$namespace}" \
574
    --entity-fully-qualified-name="{$entityFqn}" \
575
    --uuid-primary-key
576
DOCTRINE;
577
        $this->execDoctrine($doctrineCmd);
578
    }
579
580
    /**
581
     * Generate all test relations
582
     *
583
     * @return void
584
     */
585
    protected function generateRelations(): void
586
    {
587
        foreach (self::TEST_RELATIONS as $relation) {
588
            $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

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

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