GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 1bdb55...40a31e )
by joseph
18:06 queued 15:36
created

generateStandardFieldEntity()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 1
nc 1
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_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
        if (\ts\stringContains($gitCurrentBranchName, 'HEAD detached at')) {
450
            $gitCurrentBranchName = trim(str_replace('HEAD detached at', '', $gitCurrentBranchName), " \t\n\r\0\x0B()");
451
        }
452
        file_put_contents(
453
            $this->workDir . '/composer.json',
454
            sprintf($composerJson, $gitCurrentBranchName, $vcsPath)
455
        );
456
457
        $phpCmd   = $this->isTravis() ? 'php' : 'phpNoXdebug';
458
        $bashCmds = <<<BASH
459
           
460
$phpCmd $(which composer) install \
461
    --prefer-dist
462
463
$phpCmd $(which composer) dump-autoload --optimize
464
465
BASH;
466
        $this->execBash($bashCmds);
467
    }
468
469
    /**
470
     * Runs bash with strict error handling and verbose logging
471
     *
472
     * Will ensure the phpNoXdebugFunction is available and will CD into the correct directory before running commands
473
     *
474
     * Asserts that the command returns with an exit code of 0
475
     *
476
     * Appends to the rebuild file allowing easy rerunning of the commmands in the test project
477
     *
478
     * @param string $bashCmds
479
     *
480
     * @throws \Exception
481
     */
482
    protected function execBash(string $bashCmds): void
483
    {
484
        fwrite(STDERR, "\n\t# Executing:\n\t$bashCmds");
485
        $startTime = microtime(true);
486
487
        $fullCmds = '';
488
        if (!$this->isTravis()) {
489
            $fullCmds .= "\n" . self::BASH_PHPNOXDEBUG_FUNCTION . "\n\n";
490
        }
491
        $fullCmds .= "set -xe;\n";
492
        $fullCmds .= "cd {$this->workDir};\n";
493
        #$fullCmds .= "exec 2>&1;\n";
494
        $fullCmds .= "$bashCmds\n";
495
496
        $output   = [];
497
        $exitCode = 0;
498
        exec($fullCmds, $output, $exitCode);
499
500
        if (0 !== $exitCode) {
501
            throw new \RuntimeException(
502
                "Error running bash commands:\n\nOutput:\n----------\n\n"
503
                . implode("\n", $output)
504
                . "\n\nCommands:\n----------\n"
505
                . str_replace(
506
                    "\n",
507
                    "\n\t",
508
                    "\n$bashCmds"
509
                ) . "\n\n"
510
            );
511
        }
512
513
        $seconds = round(microtime(true) - $startTime, 2);
514
        fwrite(STDERR, "\n\t\t#Completed in $seconds seconds\n");
515
    }
516
517
    /**
518
     * Generate all test entities
519
     *
520
     * @return array
521
     */
522
    protected function generateEntities(): array
523
    {
524
        foreach (self::TEST_ENTITIES as $entityFqn) {
525
            $this->generateEntity($entityFqn);
526
        }
527
528
        return self::TEST_ENTITIES;
529
    }
530
531
    protected function generateEntity(string $entityFqn): void
532
    {
533
        $namespace   = self::TEST_PROJECT_ROOT_NAMESPACE;
534
        $doctrineCmd = <<<DOCTRINE
535
 dsm:generate:entity \
536
    --project-root-namespace="{$namespace}" \
537
    --entity-fully-qualified-name="{$entityFqn}"
538
DOCTRINE;
539
        $this->execDoctrine($doctrineCmd);
540
    }
541
542
    protected function execDoctrine(string $doctrineCmd): void
543
    {
544
        $phpCmd  = $this->isTravis() ? 'php' : 'phpNoXdebug';
545
        $bash    = <<<BASH
546
$phpCmd bin/doctrine $doctrineCmd    
547
BASH;
548
        $error   = false;
549
        $message = '';
550
        try {
551
            $this->execBash($bash);
552
        } catch (\RuntimeException $e) {
553
            $this->addToRebuildFile("\n\nexit 0;\n\n#The command below failed...\n\n");
554
            $error   = true;
555
            $message = $e->getMessage();
556
        }
557
        $this->addToRebuildFile($bash);
558
        self::assertFalse($error, $message);
559
    }
560
561
    /**
562
     * @return string
563
     */
564
    protected function generateStandardFieldEntity(): string
565
    {
566
        $entityFqn = self::TEST_ENTITY_NAMESPACE_BASE . '\\Standard\\Field';
567
        $this->generateUuidEntity($entityFqn);
568
569
        return $entityFqn;
570
    }
571
572
    protected function generateUuidEntity(string $entityFqn): void
573
    {
574
        $namespace   = self::TEST_PROJECT_ROOT_NAMESPACE;
575
        $doctrineCmd = <<<DOCTRINE
576
 dsm:generate:entity \
577
    --project-root-namespace="{$namespace}" \
578
    --entity-fully-qualified-name="{$entityFqn}" \
579
    --uuid-primary-key
580
DOCTRINE;
581
        $this->execDoctrine($doctrineCmd);
582
    }
583
584
    /**
585
     * Generate all test relations
586
     *
587
     * @return void
588
     */
589
    protected function generateRelations(): void
590
    {
591
        foreach (self::TEST_RELATIONS as $relation) {
592
            $this->setRelation(...$relation);
0 ignored issues
show
Bug introduced by
The call to EdmondsCommerce\Doctrine...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

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

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

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

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

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

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

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

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