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.
Passed
Pull Request — master (#152)
by joseph
15:36
created

FullProjectBuildLargeTest::setField()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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

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

708
            $this->/** @scrutinizer ignore-call */ 
709
                   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...
709
        }
710
    }
711
712
    protected function setRelation(string $entity1, string $type, string $entity2, bool $required): void
713
    {
714
        $namespace = self::TEST_PROJECT_ROOT_NAMESPACE;
715
        $this->execDoctrine(
716
            <<<DOCTRINE
717
dsm:set:relation \
718
    --project-root-path="{$this->workDir}" \
719
    --project-root-namespace="{$namespace}" \
720
    --entity1="{$entity1}" \
721
    --hasType="{$type}" \
722
    --entity2="{$entity2}" \
723
    --required-relation="{$required}"
724
DOCTRINE
725
        );
726
    }
727
728
    /**
729
     * Generate one field per common type
730
     *
731
     * @return void
732
     */
733
    protected function generateFields(): void
734
    {
735
        foreach (MappingHelper::COMMON_TYPES as $type) {
736
            $fieldFqn = self::TEST_FIELD_TRAIT_NAMESPACE . '\\' . $type;
737
            $this->generateField($fieldFqn, $type);
738
        }
739
        foreach (self::UNIQUEABLE_FIELD_TYPES as $uniqueableType) {
740
            $fieldFqn = self::TEST_FIELD_TRAIT_NAMESPACE . '\\Unique' . ucwords($uniqueableType);
741
            $this->generateField($fieldFqn, $uniqueableType, null, true);
742
        }
743
        foreach (self::DUPLICATE_SHORT_NAME_FIELDS as $duplicateShortName) {
744
            $this->generateField(...$duplicateShortName);
0 ignored issues
show
Bug introduced by
The call to EdmondsCommerce\Doctrine...geTest::generateField() has too few arguments starting with type. ( Ignorable by Annotation )

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

744
            $this->/** @scrutinizer ignore-call */ 
745
                   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...
745
        }
746
    }
747
748
    /**
749
     * @param string     $propertyName
750
     * @param string     $type
751
     * @param mixed|null $default
752
     * @param bool       $isUnique
753
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
754
     */
755
    protected function generateField(
756
        string $propertyName,
757
        string $type,
758
        $default = null,
759
        bool $isUnique = false
760
    ): void {
761
        $namespace   = self::TEST_PROJECT_ROOT_NAMESPACE;
762
        $doctrineCmd = <<<DOCTRINE
763
 dsm:generate:field \
764
    --project-root-path="{$this->workDir}" \
765
    --project-root-namespace="{$namespace}" \
766
    --field-fully-qualified-name="{$propertyName}" \
767
    --field-property-doctrine-type="{$type}"
768
DOCTRINE;
769
        if (null !== $default) {
770
            $doctrineCmd .= ' --' . GenerateFieldCommand::OPT_DEFAULT_VALUE . '="' . $default . '"' . "\\\n";
771
        }
772
        if (true === $isUnique) {
773
            $doctrineCmd .= ' --' . GenerateFieldCommand::OPT_IS_UNIQUE . "\\\n";
774
        }
775
        $this->execDoctrine($doctrineCmd);
776
    }
777
778
    /**
779
     * Set each field type on each entity type
780
     *
781
     * @param array $entities
782
     * @param array $fields
783
     *
784
     * @return void
785
     */
786
    protected function setFields(array $entities, array $fields): void
787
    {
788
        foreach ($entities as $entityFqn) {
789
            foreach ($fields as $fieldFqn) {
790
                $this->setField($entityFqn, $fieldFqn);
791
            }
792
        }
793
    }
794
795
    protected function setField(string $entityFqn, string $fieldFqn): void
796
    {
797
        $namespace   = self::TEST_PROJECT_ROOT_NAMESPACE;
798
        $doctrineCmd = <<<DOCTRINE
799
 dsm:set:field \
800
    --project-root-path="{$this->workDir}" \
801
    --project-root-namespace="{$namespace}" \
802
    --entity="{$entityFqn}" \
803
    --field="{$fieldFqn}"
804
DOCTRINE;
805
        $this->execDoctrine($doctrineCmd);
806
    }
807
808
    /**
809
     * @return array
810
     * @SuppressWarnings(PHPMD.StaticAccess)
811
     */
812
    protected function getFieldFqns(): array
813
    {
814
        $fieldFqns = [];
815
        foreach (MappingHelper::COMMON_TYPES as $type) {
816
            $fieldFqns[] = self::TEST_FIELD_TRAIT_NAMESPACE . Inflector::classify($type) . 'FieldTrait';
817
        }
818
        foreach (self::UNIQUEABLE_FIELD_TYPES as $type) {
819
            $fieldFqns[] = self::TEST_FIELD_TRAIT_NAMESPACE . Inflector::classify('unique_' . $type) . 'FieldTrait';
820
        }
821
822
        return $fieldFqns;
823
    }
824
825
    protected function setTheDuplicateNamedFields(array $entities): void
826
    {
827
        foreach ($entities as $k => $entityFqn) {
828
            $fieldKey = ($k % 2 === 0) ? 0 : 1;
829
            $this->setField($entityFqn, self::DUPLICATE_SHORT_NAME_FIELDS[$fieldKey][0]);
830
        }
831
    }
832
833
    protected function setEmbeddable(string $entityFqn, string $embeddableTraitFqn): void
834
    {
835
        $doctrineCmd = <<<DOCTRINE
836
 dsm:set:embeddable \
837
    --entity="{$entityFqn}" \
838
    --embeddable="{$embeddableTraitFqn}"
839
DOCTRINE;
840
        $this->execDoctrine($doctrineCmd);
841
    }
842
843
    protected function generateEmbeddable(string $archetypeFqn, string $newClassName): void
844
    {
845
        $doctrineCmd = <<<DOCTRINE
846
dsm:generate:embeddable \
847
    --classname="{$newClassName}" \
848
    --archetype="{$archetypeFqn}"
849
DOCTRINE;
850
        $this->execDoctrine($doctrineCmd);
851
    }
852
853
    protected function removeUnusedRelations(): void
854
    {
855
        $namespace   = self::TEST_PROJECT_ROOT_NAMESPACE;
856
        $doctrineCmd = <<<DOCTRINE
857
 dsm:remove:unusedRelations \
858
    --project-root-namespace="{$namespace}"
859
DOCTRINE;
860
        $this->execDoctrine($doctrineCmd);
861
    }
862
863
    /**
864
     * @SuppressWarnings(PHPMD.Superglobals)
865
     * @throws \Exception
866
     */
867
    public function testRunTests(): void
868
    {
869
        if ($this->isQuickTests()) {
870
            $this->markTestSkipped('Quick tests is enabled');
871
        }
872
        /** @lang bash */
873
        $bashCmds = <<<BASH
874
875
set +x
876
877
echo "
878
879
--------------------------------------------------
880
STARTS Running Tests In {$this->workDir}
881
--------------------------------------------------
882
883
"
884
885
#Prevent the retry tool dialogue etc
886
export CI=true
887
888
bash bin/qa
889
890
echo "
891
892
--------------------------------------------------
893
DONE Running Tests In {$this->workDir}
894
--------------------------------------------------
895
896
"
897
set -x
898
BASH;
899
        $this->execBash($bashCmds);
900
    }
901
}
902