Tools of the Trade

Sebastian Bergmann

Sebastian Bergmann

Helps teams build testable soware and implement ecient tests.

Interessengemeinscha PHP e.V.

Als gemeinnütziger Verein möchten wir die Akzeptanz, Verbreitung und Weiterentwicklung der Programmiersprache PHP und das Wissen um die Programmiersprache PHP in Wissenscha, Forschung und Ausbildung ördern.

 https://igphp.de/  @igphp Code Editing

PhpStorm is the only PHP IDE that deserves to be called IDE, IMO. Everything else is inferior at best and only gets in your way at worst. PhpStorm is the only PHP IDE that deserves to be called IDE, IMO. Everything else is inferior at best and only gets in your way at worst. I am not paid to say that. I simply cannot imagine working productively with something else. Php Inspections (EA Extended) Php Inspections (EA Ultimate) Dependency Management https://github.com/composer/getcomposer.org/issues/36 https://youtu.be/Ci_I0ATr748 "[e]nables you to declare the libraries you depend on" and "[]inds out which versions of which packages can and need to be installed, and installs them (meaning it downloads them into your project)". $ curl -s https://getcomposer.org/installer |

$ php composer.phar --version

$ chmod +x composer.phar

$ mv composer.phar /usr/local/bin/composer

$ composer self-update Do not "cURL and pipe to bash"!

Users are encouraged by documentation to copy/paste commands such as

$ curl -s https://getcomposer.org/installer | php into their shell.

This prevents any verication of authenticity and integrity of the downloaded executable before running it.

Users doing this are literally asking for their system to be compromised! https://thephp.cc/news/2019/02/blast-from-the-past Life is too short to talk about dead technology. $ mkdir project

$ cd project

$ composer require sebastian/diff Using version ^3.0 for sebastian/diff ./composer.json has been created Loading composer repositories with package information Updating dependencies (including require-dev) Package operations: 1 install, 0 updates, 0 removals - Installing sebastian/diff (3.0.0): Loading from cache Writing lock file Generating autoload files $ tree . ├── composer.json ├── composer.lock └── vendor ├── autoload.php ├── composer │ └── ... └── sebastian └── diff └── ...

15 directories, 62 files composer.json

{ {"require": "sebastian/diff": "^3.0" } } composer.json ⇨ Dependency Resolution ⇨ composer.lock composer.lock ⇨ Download ⇨ Installation ⇨ Autoloader Generation composer.lock not under version control composer.lock under version control composer.lock and vendor under version control

PHA Installation and Verication Environment "PHIVE makes installation easy by providing a means to resolve [a] given alias to an actual download location, including the verication of the certicate supplied by the server. Once downloaded, the archive’s SHA1, SHA256 or SHA512 hash is veried as well as its OpenPGP/GnuPG or OpenSSL signature." $ wget -O phive.phar https://phar.io/releases/phive.phar

$ wget -O phive.phar.asc https://phar.io/releases/phive.phar.asc

$ gpg --keyserver hkps.pool.sks-keyservers.net --recv-keys 0x9D8A98B29B2D5D79

$ gpg --verify phive.phar.asc phive.phar

$ php phive.phar --version

$ chmod +x phive.phar

$ mv phive.phar /usr/local/bin/phive

$ phive self-update $ mkdir project

$ cd project

$ phive install phpunit Phive 0.12.4 - Copyright (C) 2015-2019 by Arne Blankerts, Sebastian Heuer and Contributors Linking /home/sb/.phive/phars/phpunit-8.3.5.phar to /home/sb/project/tools/phpunit

$ tree . ├── phive.xml └── tools └── phpunit -> /home/sb/.phive/phars/phpunit-8.3.5.phar

1 directory, 2 files $ mkdir project

$ cd project

$ phive install --copy phpunit Phive 0.12.4 - Copyright (C) 2015-2019 by Arne Blankerts, Sebastian Heuer and Contributors Copying phpunit-8.3.5.phar to /home/sb/project/tools/phpunit

$ tree . ├── phive.xml └── tools └── phpunit

1 directory, 2 files phive.xml

tools not under version control tools under version control So ... should you install tools using Composer or use PHARs? https://twitter.com/s_bergmann/status/999635212723212288 php-scoper https://github.com/sebastianbergmann/phpunit/issues/2015 Static Analysis

$ wget https://github.com/vimeo/psalm/releases/download/3.5.1/psalm.phar

$ chmod +x psalm.phar

$ mv psalm.phar tools/psalm

$ ./tools/psalm --version Psalm 3.5.1@deb36e8b273d4b25601f1991482a69d412e3169f $ phive install --copy psalm

$ ./tools/psalm --version Psalm 3.5.1@deb36e8b273d4b25601f1991482a69d412e3169f

$a = ['hello', 5]; foo($a[1]); foo();

if (rand(0, 1)) $b = 5; echo $b;

$c = rand(0, 5); if ($c) {} elseif ($c) {}

ERROR: InvalidReturnStatement - 3:12 - No return values are expected for foo INFO: UnusedParam - 2:21 - Param $s is never referenced in this method ERROR: InvalidReturnType - 2:26 - The declared return type 'void' for foo is incorrect, got 'string' ERROR: InvalidScalarArgument - 7:5 - Argument 1 of foo expects string, int(5) provided ERROR: TooFewArguments - 8:1 - Too few arguments for method foo - expecting 1 but saw 0 INFO: PossiblyUndefinedGlobalVariable - 11:6 - Possibly undefined global variable $b, first seen on line 10 ERROR: TypeDoesNotContainType - 14:20 - Found a contradiction when evaluating $c and trying to reconcile type 'int(0)' to !falsy

/** @var array> */ private $symbolAnnotations;

// ... }

/** @param * string|string[] $originalClassName * @psalm-template * RealInstanceType of object @psalm-param * class-string|string[] $originalClassName @psalm-return * MockObject&RealInstanceType */ protected function createMock($originalClassName): MockObject { return $this->getMockBuilder($originalClassName) ->disableOriginalConstructor() ->disableOriginalClone() ->disableArgumentCloning() ->disallowMockingUnknownTypes() ->getMock(); }

// ... }

final class Test { // ... public static function getMissingRequirements(string $className, string $methodName): array { // ... if (!empty($required['PHP'])) { $operator = empty($required['PHP']['operator']) ? '>=' : $required['PHP']['operator'];

if (!\version_compare(\PHP_VERSION, $required['PHP']['version'], $operator)) { $missing[] = \sprintf('PHP %s %s is required.', $operator, $required['PHP']['version']); $hint = $hint ?? 'PHP'; } } elseif (!empty($required['PHP_constraint'])) { // ... } // ... }

ERROR: InvalidArgument - src/Util/Test.php:303:78 - Argument 3 of version_compare expects string(\x3c)|string(lt)|string(\x3c=)|string(le)| string(\x3e)|string(gt)|string(\x3e=)|string(ge)|string(==)|string(=)|string(eq)| string(!=)|string(\x3c\x3e)|string(ne), string(>=)|mixed provided --- a/src/Util/Test.php +++ b/src/Util/Test.php @@ -300,6 +301,8 @@ public static function getMissingRequirements(string $className, string $met if (!empty($required['PHP'])) { $operator = empty($required['PHP']['operator']) ? '>=' : $required['PHP']['operator

+ self::ensureOperatorIsValid($operator); + if (!\version_compare(\PHP_VERSION, $required['PHP']['version'], $operator)) { $missing[] = \sprintf('PHP %s %s is required.', $operator, $required['PHP']['ve $hint = $hint ?? 'PHP'; @@ -1279,4 +1286,19 @@ private static function shouldCoversAnnotationBeUsed(array $annotations):

return true; } + + private static function ensureOperatorIsValid(string $operator): void + { + if (!\in_array($operator, ['<', 'lt', '<=', 'le', '>', 'gt', '>=', 'ge', '==', '=', 'eq + throw new Exception( + \sprintf( + '"%s" is not a valid version_compare() operator', + $operator + ) + ); + } + } }

$i += $x;

return $i; }

ERROR: ImpureStaticVariable - src/test.php:6:5 - Cannot use a static variable in a mutation-free context

return $x; }

ERROR: ImpureFunctionCall - src/test.php:5:5 - Cannot call an impure function from a mutation-free context fopen(__FILE__, 'r'); Roave Backward Compatibility Check $ wget https://github.com/Roave/BackwardCompatibilityCheck/releases/download/4.1.0/roave-backward-compatibility-check.phar

$ chmod +x roave-backward-compatibility-check

$ mv roave-backward-compatibility-check tools/roave-backward-compatibility-check

$ ./tools/roave-backward-compatibility-check --version roave/backward-compatibility-check 4.1.0@5092098994e3de84300a71c997494d6f637e839d $ ./tools/roave-backward-compatibility-check --from=7.5.16 --to=8.0.0 [BC] CHANGED: Class PHPUnit\Util\TestDox\XmlResultPrinter became final [BC] CHANGED: PHPUnit\Util\TestDox\XmlResultPrinter was marked "@internal" [BC] CHANGED: Class PHPUnit\Util\TestDox\TextResultPrinter became final [BC] CHANGED: PHPUnit\Util\TestDox\TextResultPrinter was marked "@internal" [BC] REMOVED: Class PHPUnit\Util\TestDox\TestResult has been deleted [BC] CHANGED: PHPUnit\Util\TestDox\ResultPrinter was marked "@internal" [BC] CHANGED: PHPUnit\Util\TestDox\NamePrettifier was marked "@internal" [BC] CHANGED: PHPUnit\Util\TestDox\HtmlResultPrinter was marked "@internal" [BC] CHANGED: The return type of PHPUnit\Framework\TestCase::setUpBeforeClass() changed from no type to void [BC] CHANGED: The return type of PHPUnit\Framework\TestCase::tearDownAfterClass() changed from no type to void [BC] CHANGED: The return type of PHPUnit\Framework\TestCase#setUp() changed from no type to void [BC] CHANGED: The return type of PHPUnit\Framework\TestCase#tearDown() changed from no type to void [BC] CHANGED: The return type of PHPUnit\Framework\TestCase#getExpectedExceptionMessage() changed from ?string to string [BC] CHANGED: The return type of PHPUnit\Framework\TestCase#getExpectedExceptionMessageRegExp() changed from ?string to string [BC] CHANGED: The return type of PHPUnit\Framework\TestCase#expectNotToPerformAssertions() changed from no type to void [BC] CHANGED: The parameter $newValue of PHPUnit\Framework\TestCase#iniSet() changed from no type to a non-contravariant string [BC] CHANGED: The parameter $newValue of PHPUnit\Framework\TestCase#iniSet() changed from no type to string [BC] CHANGED: The return type of PHPUnit\Framework\TestCase#assertPreConditions() changed from no type to void [BC] CHANGED: The return type of PHPUnit\Framework\TestCase#assertPostConditions() changed from no type to void [BC] CHANGED: The return type of PHPUnit\Framework\TestCase#onNotSuccessfulTest() changed from no type to void . . . Coding Guidelines php-cs-xer $ wget https://cs.sensiolabs.org/download/php-cs-fixer-v2.phar

$ chmod +x php-cs-fixer-v2.phar

$ mv php-cs-fixer-v2.phar tools/php-cs-fixer

$ php php-cs-fixer --version PHP CS Fixer 2.15.3 Europe Round by Fabien Potencier and Dariusz Ruminski (705490b) $ phive install --copy php-cs-fixer

$ php php-cs-fixer --version PHP CS Fixer 2.15.3 Europe Round by Fabien Potencier and Dariusz Ruminski (705490b) .php_cs.dist

return PhpCsFixer\Config::create() ->setRiskyAllowed(true) ->setRules( [ // ... 'braces' => true, // ... ] ) ->setFinder( PhpCsFixer\Finder::create() ->files() ->in(__DIR__ . '/src') ->in(__DIR__ . '/tests') ); $ php-cs-fixer fix --dry-run -v --show-progress=dots --diff-format=udiff Loaded config default from "/usr/local/src/phpunit/.php_cs.dist"...... 63 / 499 ( 13%) ...... 126 / 499 ( 25%) ...... 189 / 499 ( 38%) ...... 252 / 499 ( 51%) ...... 315 / 499 ( 63%) ...... 378 / 499 ( 76%) ...... 441 / 499 ( 88%) ...... 499 / 499 (100%) Legend: ?-unknown, I-invalid file syntax, file ignored, S-Skipped, .-no changes, F-fixed, E-error

Checked all files in 9.242 seconds, 26.000 MB memory used $ php-cs-fixer fix --dry-run -v --show-progress=dots --diff-format=udiff Loaded config default from "/usr/local/src/phpunit/.php_cs.dist". Using cache file ".php_cs.cache". SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 63 / 499 ( 13%) SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 126 / 499 ( 25%) SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 189 / 499 ( 38%) SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 252 / 499 ( 51%) SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 315 / 499 ( 63%) SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 378 / 499 ( 76%) SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 441 / 499 ( 88%) SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 499 / 499 (100%) Legend: ?-unknown, I-invalid file syntax, file ignored, S-Skipped, .-no changes, F-fixed, E-error

Checked all files in 0.023 seconds, 14.000 MB memory used $ php-cs-fixer fix --dry-run -v --show-progress=dots --diff-format=udiff Loaded config default from "/usr/local/src/phpunit/.php_cs.dist". Using cache file ".php_cs.cache". SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 63 / 499 ( 13%) SSSSFSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 126 / 499 ( 25%) SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 189 / 499 ( 38%) SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 252 / 499 ( 51%) SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 315 / 499 ( 63%) SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 378 / 499 ( 76%) SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 441 / 499 ( 88%) SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 499 / 499 (100%) Legend: ?-unknown, I-invalid file syntax, file ignored, S-Skipped, .-no changes, F-fixed, E-error 1) src/Framework/Test.php (braces) ------begin diff ------Original +++ New @@ -15,7 +15,8 @@ /** * A Test can be run and collect its results. */ -interface Test extends Countable { +interface Test extends Countable +{ /** * Runs a test and collects its result in a TestResult instance. */ 1) src/Framework/Test.php (braces) ------begin diff ------Original +++ New @@ -15,7 +15,8 @@ /** * A Test can be run and collect its results. */ -interface Test extends Countable { +interface Test extends Countable +{ /** * Runs a test and collects its result in a TestResult instance. */

------end diff ------

Checked all files in 0.030 seconds, 14.000 MB memory used $ php-cs-fixer fix --dry-run --format checkstyle 2>/dev/null $ php-cs-fixer fix -v --show-progress=dots Loaded config default from "/usr/local/src/phpunit/.php_cs.dist". Using cache file ".php_cs.cache". SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 63 / 499 ( 13%) SSSSFSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 126 / 499 ( 25%) SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 189 / 499 ( 38%) SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 252 / 499 ( 51%) SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 315 / 499 ( 63%) SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 378 / 499 ( 76%) SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 441 / 499 ( 88%) SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 499 / 499 (100%) Legend: ?-unknown, I-invalid file syntax, file ignored, S-Skipped, .-no changes, F-fixed, E-error 1) src/Framework/Test.php (braces)

Fixed all files in 0.034 seconds, 14.000 MB memory used Architecture

"[dePHPend] detects aws in your architecture, before they drag you down into the depths of dependency hell ..." $ wget https://phar.dephpend.com/dephpend.phar

$ chmod +x dephpend.phar

$ mv dephpend.phar tools/dephpend

$ ./tools/dephpend --version ______| | | __ \| | | | __ \ | | __| | ___| |__) | |__| | |__) |______| | / _` |/ _ \ ___/| __ | ___/ _ \ '_ \ / _` | | (_| | __/ | | | | | | | __/ | | | (_| | \__,_|\___|_| |_| |_|_| \___|_| |_|\__,_| 0.6.1 $ phive install --copy dephpend

$ ./tools/dephpend --version ______| | | __ \| | | | __ \ | | __| | ___| |__) | |__| | |__) |______| | / _` |/ _ \ ___/| __ | ___/ _ \ '_ \ / _` | | (_| | __/ | | | | | | | __/ | | | (_| | \__,_|\___|_| |_| |_|_| \___|_| |_|\__,_| 0.6.1 $ dephpend metrics neos-4.0.4 +------+------+ | Classes: | 3895 | | Abstract classes: | 205 | | Interfaces: | 244 | | Traits: | 34 | | Abstractness: | 0.110 | +------+------+ +------+------+------+------+ | | Afferent Coupling | Efferent Coupling | Instability | +------+------+------+------+ | Flowpack\Neos\FrontendLogin\Security\NeosRequestPattern | 0 | 3 | 1.00 | | Flowpack\Neos\FrontendLogin\Controller\AuthenticationController | 0 | 7 | 1.00 | | \Transliterator\SyncTool | 0 | 12 | 1.00 | | \Component\Console\Input\InputAwareInterface | 2 | 1 | 0.33 | | Symfony\Component\Console\Input\InputInterface | 69 | 3 | 0.04 | | Symfony\Component\Console\Input\InputArgument | 36 | 2 | 0.05 | | Symfony\Component\Console\Input\ArrayInput | 8 | 4 | 0.33 | | Symfony\Component\Console\Input\ArgvInput | 5 | 3 | 0.38 | | Symfony\Component\Console\Input\Input | 4 | 5 | 0.56 | | Symfony\Component\Console\Input\StreamableInputInterface | 4 | 1 | 0.20 | . . . $ dephpend text neos-4.0.4 Flowpack\Neos\FrontendLogin\Security\NeosRequestPattern --> Neos\Flow\Mvc\ActionRequest Flowpack\Neos\FrontendLogin\Security\NeosRequestPattern --> Neos\Flow\Mvc\RequestInterface Flowpack\Neos\FrontendLogin\Security\NeosRequestPattern --> Neos\Flow\Security\RequestPatternInterface Flowpack\Neos\FrontendLogin\Controller\AuthenticationController --> Neos\Flow\Annotations Flowpack\Neos\FrontendLogin\Controller\AuthenticationController --> Neos\Flow\I18n\Translator Flowpack\Neos\FrontendLogin\Controller\AuthenticationController --> Neos\Flow\Mvc\ActionRequest Flowpack\Neos\FrontendLogin\Controller\AuthenticationController --> Neos\Flow\Security\Authentication\Controller\AbstractAuthentication Flowpack\Neos\FrontendLogin\Controller\AuthenticationController --> Neos\Flow\Security\Exception\AuthenticationRequiredException Flowpack\Neos\FrontendLogin\Controller\AuthenticationController --> Neos\Error\Messages Flowpack\Neos\FrontendLogin\Controller\AuthenticationController --> Neos\Error\Messages\Message . . . dephpend dephpend dsm --no-classes neos-4.0.4 > dsm.html dephpend uml --no-classes --depth=3 --output=depth-3.png neos-4.0.4 dephpend uml --no-classes --depth=2 --output=depth-2.png neos-4.0.4 dephpend uml --no-classes --depth=1 --output=depth-1.png neos-4.0.4 Architecture Timeline Architecture Constraints Testing

$ wget -O phpunit.phar https://phar.phpunit.de/phpunit-8.3.phar

$ php phpunit.phar --version PHPUnit 8.3.5 by Sebastian Bergmann and contributors.

$ chmod +x phpunit.phar

$ mv phpunit.phar tools/phpunit

$ ./tools/phpunit --version PHPUnit 8.3.5 by Sebastian Bergmann and contributors. $ phive install --copy phpunit

$ ./tools/phpunit --version PHPUnit 8.3.5 by Sebastian Bergmann and contributors. assertInstanceOf( Email::class, Email::fromString('[email protected]') ); }

public function testCannotBeCreatedFromInvalidEmailAddress(): void { $this->expectException(InvalidArgumentException::class);

Email::fromString('invalid'); }

public function testCanBeUsedAsString(): void { $this->assertEquals( '[email protected]', Email::fromString('[email protected]') ); } } $ ./tools/phpunit.phar tests/EmailTest PHPUnit 8.3.5 by Sebastian Bergmann and contributors.

... 3 / 3 (100%)

Time: 28 ms, Memory: 4.00MB

OK (3 tests, 3 assertions) $ ./tools/phpunit.phar --testdox --color tests/EmailTest PHPUnit 8.3.5 by Sebastian Bergmann and contributors.

Email ✔ Can be created from valid email address ✔ Cannot be created from invalid email address ✔ Can be used as string

Time: 41 ms, Memory: 4.00 MB

OK (3 tests, 3 assertions) https://talks.thephp.cc/ Infection $ wget https://github.com/infection/infection/releases/download/0.13.6/infection.phar

$ chmod +x infection.phar

$ mv infection.phar tools/infection

$ ./tools/infection --version You are running Infection with Xdebug enabled. ______/ _/___ / __/______/ /_(_)______/ // __ \/ /_/ _ \/ ___/ __/ / __ \/ __ \ _/ // / / / __/ __/ /__/ /_/ / /_/ / / / / /___/_/ /_/_/ \___/\___/\__/_/\____/_/ /_/

Infection - PHP Mutation Testing Framework 0.12.2 $ phive install --copy infection

$ ./tools/infection --version You are running Infection with Xdebug enabled. ______/ _/___ / __/______/ /_(_)______/ // __ \/ /_/ _ \/ ___/ __/ / __ \/ __ \ _/ // / / / __/ __/ /__/ /_/ / /_/ / / / / /___/_/ /_/_/ \___/\___/\__/_/\____/_/ /_/

Infection - PHP Mutation Testing Framework 0.12.2 $ ./infection.phar You are running Infection with xdebug enabled. ______/ _/___ / __/______/ /_(_)______/ // __ \/ /_/ _ \/ ___/ __/ / __ \/ __ \ _/ // / / / __/ __/ /__/ /_/ / /_/ / / / / /___/_/ /_/_/ \___/\___/\__/_/\____/_/ /_/

Running initial test suite...

PHPUnit version: 8.3.5

32 [======] 1 sec

Generate mutants...

Processing source code files: 12/12 Creating mutated files and processes: 71/71 .: killed, M: escaped, S: uncovered, E: fatal error, T: timed out

...... ME.MEEE.E...E....E...E.E.....M.M..M.EMMEE (50 / 71) E.E...... MM..... (71 / 71)

71 mutations were generated: 48 mutants were killed 0 mutants were not covered by tests 9 covered mutants were not detected 14 errors were encountered 0 time outs were encountered

Metrics: Mutation Score Indicator (MSI): 87% Mutation Code Coverage: 100% Covered Code MSI: 87%

Please note that some mutants will inevitably be harmless (i.e. false positives). infection-log.txt

. . 3) /usr/local/src/object-graph/src/node/Node.php:85 [M] DecrementInteger exec /usr/bin/php -c /tmp/infectionQ7N5Uz /usr/local/src/object-graph/vendor/phpunit/phpunit/phpunit --c

--- Original +++ New @@ @@ { - return \count($this->getReferencedNodes()) > 0; + return \count($this->getReferencedNodes()) > -1; } }

PHPUnit 8.3.5 by Sebastian Bergmann and contributors.

Runtime: PHP 7.3.2 Configuration: /tmp/infection/phpunitConfiguration.bcd1ff53a4fbff65c7be37bd91826df9.infection.xml

.... 4 / 4 (100%)

Time: 19 ms, Memory: 4.00MB

OK (4 tests, 5 assertions) . . Understanding untime Aspects object-graph

$cart = new ShoppingCart; $cart->add(new ShoppingCartItem('Foo', new Money(123, new Currency('EUR')), 1)); $cart->add(new ShoppingCartItem('Bar', new Money(456, new Currency('EUR')), 1));

$cart = new ShoppingCart; $cart->add(new ShoppingCartItem('Foo', new Money(123, new Currency('EUR')), 1)); $cart->add(new ShoppingCartItem('Bar', new Money(456, new Currency('EUR')), 1)); object_graph_dump('graph.svg', $cart);

$cart = new ShoppingCart; $cart->add(new ShoppingCartItem('Foo', new Money(123, new Currency('EUR')), 1)); $cart->add(new ShoppingCartItem('Bar', new Money(456, new Currency('EUR')), 1)); object_graph_dump('graph.svg', $cart);

#2 ShoppingCartItem #3 Money description'Foo' #4 Currency amount123 unitPrice#3 isoCode'EUR' #1 ShoppingCart currency#4 quantity1 items [ 0 =>#2 1 =>#5 #5 ShoppingCartItem ] #6 Money description'Bar' #7 Currency amount456 unitPrice#6 isoCode'EUR' currency#7 quantity1 tideways.com "[Tideways is y]our mission control center for PHP application performance.

[It] saves you time by taking the guesswork out of your app's backend performance. Gain detailed insights, spot performance bottlenecks, and get real-time error detection alerts."  tideways/php-xhprof-extension Modern, Open Source PHP 7 proler that is compatible with XHPro's data format  tideways/toolkit Collection of commandline tools to interact with PHP and perform various debugging, proling and introspection jobs  sebastianbergmann/phpunit-tideways-listener Test Listener for PHPUnit that can dump prole information

Links

https://www.jetbrains.com/phpstorm/ https://plugins.jetbrains.com/plugin/7622-php-inspections-ea-extended- https://plugins.jetbrains.com/plugin/10215-php-inspections-ea-ultimate- https://getcomposer.org/ https://phar.io/ https://github.com/humbug/php-scoper https://github.com/FriendsOPHP/PHP-CS-Fixer https://getpsalm.org/ https://github.com/Roave/BackwardCompatibilityCheck https://dephpend.com/ https://phpunit.de/ https://infection.github.io/ https://github.com/sebastianbergmann/object-graph https://tideways.com/ https://xdebug.org/ Contact

 https://thephp.cc  https://talks.thephp.cc  [email protected]  @s_bergmann