diff --git a/.github/workflows/Deploy.yml b/.github/workflows/Deploy.yml index 1b7cb84..604a101 100644 --- a/.github/workflows/Deploy.yml +++ b/.github/workflows/Deploy.yml @@ -3,28 +3,36 @@ name: Deploy on: push: branches: - - master + - '*' + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + + - name: Log in to the Container registry + uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - - name: Cache Composer packages - id: composer-cache - uses: actions/cache@v2 - with: - path: vendor - key: ${{ runner.os }}-node-${{ hashFiles('**/composer.lock') }} - restore-keys: | - ${{ runner.os }}-node- + - name: Extract metadata for Docker + id: metadata + uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - - name: Build and push Docker images - uses: docker/build-push-action@v1 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - repository: pmsipilot/docker-compose-viz - tag_with_ref: true + - name: Build and push Docker image + uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.gitignore b/.gitignore index 13b7173..cf2ac55 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,9 @@ -vendor/ -docker.lock bin/ +coverage/ +vendor/ + !bin/dcv !bin/entrypoint.sh -.cache/ -.php_cs.cache +.php-cs-fixer.cache +.phpunit.result.cache +docker.lock diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php new file mode 100644 index 0000000..2009b85 --- /dev/null +++ b/.php-cs-fixer.php @@ -0,0 +1,16 @@ +in(__DIR__ . '/src') + ->in(__DIR__ . '/tests') +; + +$rules = [ + '@PSR12' => true, + 'strict_param' => true, + 'array_syntax' => ['syntax' => 'short'], +]; + +return (new PhpCsFixer\Config())->setRules($rules) + ->setFinder($finder) +; \ No newline at end of file diff --git a/.php_cs b/.php_cs deleted file mode 100644 index b92f2ea..0000000 --- a/.php_cs +++ /dev/null @@ -1,18 +0,0 @@ -in(__DIR__.DIRECTORY_SEPARATOR.'src') - ->in(__DIR__.DIRECTORY_SEPARATOR.'spec') -; - -return (new PhpCsFixer\Config()) - ->setRules([ - '@PSR2' => true, - '@Symfony' => true, - 'array_syntax' => ['syntax' => 'short'], - 'no_useless_else' => true, - 'no_useless_return' => true, - 'ordered_class_elements' => true, - ]) - ->setFinder($finder) -; diff --git a/Dockerfile b/Dockerfile index 0762663..6584b51 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM php:7.4-alpine as builder +FROM php:8.1-alpine as builder COPY composer.json /dcv/composer.json COPY composer.lock /dcv/composer.lock @@ -10,7 +10,7 @@ RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" && php -r "unlink('composer-setup.php');" && \ php composer.phar install --prefer-dist -FROM php:7.4-alpine +FROM php:8.1-alpine RUN apk update && \ apk add graphviz ttf-dejavu && \ @@ -18,8 +18,8 @@ RUN apk update && \ /var/cache/apk/* \ /tmp/* -COPY bin/ /dcv/bin -COPY src/ /dcv/src +COPY bin/ /dcv/bin/ +COPY src/ /dcv/src/ COPY --from=builder /dcv/vendor /dcv/vendor RUN chmod +x /dcv/bin/dcv diff --git a/Makefile b/Makefile index 43e5d69..8bf3a98 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,8 @@ docker: docker.lock test: vendor unit cs +vendor: vendor/composer/installed.json + unit: vendor $(COMPOSER) run ut @@ -24,10 +26,10 @@ clean: rm -rf vendor/ docker.lock: Dockerfile bin/entrypoint.sh vendor src/application.php src/functions.php - $(DOCKER) build -t $(DCV_IMAGE_NAME) . + $(DOCKER) build -t $(DCV_IMAGE_NAME) --no-cache . touch docker.lock -vendor: composer.lock +vendor/composer/installed.json: composer.lock $(COMPOSER) install --prefer-dist composer.lock: composer.json diff --git a/bin/dcv b/bin/dcv index 21e20f0..678f68c 100755 --- a/bin/dcv +++ b/bin/dcv @@ -14,7 +14,7 @@ require_once resolve( ] ); -require_once resolve( +$application = require_once resolve( [ __DIR__, '..', @@ -22,4 +22,6 @@ require_once resolve( 'application.php', ] ); + +$application->run(); ?> diff --git a/composer.json b/composer.json index 7f5a992..46ae6de 100644 --- a/composer.json +++ b/composer.json @@ -2,15 +2,16 @@ "name": "pmsipilot/docker-compose-viz", "description": "Docker compose graph visualization", "require": { - "php": "^7.2", - "symfony/yaml": "^3.1 || ^4", - "symfony/console": "^3.1", + "php": "^8.1", + "symfony/yaml": "^5.4", + "symfony/console": "^5.4", "clue/graph": "^0.9", "graphp/graphviz": "^0.2" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2", - "kahlan/kahlan": "^4.7" + "friendsofphp/php-cs-fixer": "^v3.12.0", + "kahlan/kahlan": "*", + "phpunit/phpunit": "^9.5" }, "license": "MIT", "authors": [ @@ -20,14 +21,27 @@ } ], "autoload": { - "files": ["src/functions.php"], + "files": [ + "src/config.php", + "src/functions.php", + "src/network.php", + "src/port.php", + "src/secret.php", + "src/service.php", + "src/volume.php" + ], "psr-4": { "PMSIpilot\\DockerComposeViz\\": "src/" } }, + "autoload-dev": { + "psr-4": { + "PMSIpilot\\DockerComposeViz\\Tests\\": "tests/" + } + }, "scripts": { - "cs": "php-cs-fixer fix", - "cst": "php-cs-fixer fix --dry-run", - "ut": "kahlan --grep='*.php' --reporter=verbose --persistent=false" + "cs": "php-cs-fixer fix --allow-risky=yes", + "cst": "php-cs-fixer fix --dry-run --allow-risky=yes", + "ut": "phpunit tests --testdox --color" } } diff --git a/composer.lock b/composer.lock index 3f1ad57..91cc970 100644 --- a/composer.lock +++ b/composer.lock @@ -4,27 +4,27 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "883183cc51537213776e61c66e969e5a", + "content-hash": "fd2f8848bec593d31804276cd059005c", "packages": [ { "name": "clue/graph", - "version": "v0.9.1", + "version": "v0.9.3", "source": { "type": "git", "url": "https://github.com/graphp/graph.git", - "reference": "07ce1e2f6d5be2ff600ce13660b25f25cf928c66" + "reference": "d1661c0a0e011a8550fa60ae5354f230d1555909" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/graphp/graph/zipball/07ce1e2f6d5be2ff600ce13660b25f25cf928c66", - "reference": "07ce1e2f6d5be2ff600ce13660b25f25cf928c66", + "url": "https://api.github.com/repos/graphp/graph/zipball/d1661c0a0e011a8550fa60ae5354f230d1555909", + "reference": "d1661c0a0e011a8550fa60ae5354f230d1555909", "shasum": "" }, "require": { - "php": "^7.0 || ^5.3" + "php": ">=5.3" }, "require-dev": { - "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35" + "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35" }, "suggest": { "graphp/algorithms": "Common graph algorithms, such as Dijkstra and Moore-Bellman-Ford (shortest path), minimum spanning tree (MST), Kruskal, Prim and many more..", @@ -40,6 +40,12 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + } + ], "description": "GraPHP is the mathematical graph/network library written in PHP.", "homepage": "https://github.com/graphp/graph", "keywords": [ @@ -49,7 +55,21 @@ "network", "vertex" ], - "time": "2019-10-02T09:10:26+00:00" + "support": { + "issues": "https://github.com/graphp/graph/issues", + "source": "https://github.com/graphp/graph/tree/v0.9.3" + }, + "funding": [ + { + "url": "https://clue.engineering/support", + "type": "custom" + }, + { + "url": "https://github.com/clue", + "type": "github" + } + ], + "time": "2021-12-30T09:22:01+00:00" }, { "name": "graphp/graphviz", @@ -91,34 +111,38 @@ "graphp", "graphviz" ], + "support": { + "issues": "https://github.com/graphp/graphviz/issues", + "source": "https://github.com/graphp/graphviz/tree/v0.2.2" + }, "time": "2019-10-04T13:30:55+00:00" }, { - "name": "psr/log", - "version": "1.1.3", + "name": "psr/container", + "version": "2.0.2", "source": { "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", - "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=7.4.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { "psr-4": { - "Psr\\Log\\": "Psr/Log/" + "Psr\\Container\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -128,51 +152,66 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", "keywords": [ - "log", - "psr", - "psr-3" + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" ], - "time": "2020-03-23T09:12:05+00:00" + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" }, { "name": "symfony/console", - "version": "v3.4.42", + "version": "v5.4.14", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "bfe29ead7e7b1cc9ce74c6a40d06ad1f96fced13" + "reference": "984ea2c0f45f42dfed01d2f3987b187467c4b16d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/bfe29ead7e7b1cc9ce74c6a40d06ad1f96fced13", - "reference": "bfe29ead7e7b1cc9ce74c6a40d06ad1f96fced13", + "url": "https://api.github.com/repos/symfony/console/zipball/984ea2c0f45f42dfed01d2f3987b187467c4b16d", + "reference": "984ea2c0f45f42dfed01d2f3987b187467c4b16d", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/debug": "~2.8|~3.0|~4.0", - "symfony/polyfill-mbstring": "~1.0" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/string": "^5.1|^6.0" }, "conflict": { - "symfony/dependency-injection": "<3.4", - "symfony/process": "<3.3" + "psr/log": ">=3", + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" }, "provide": { - "psr/log-implementation": "1.0" + "psr/log-implementation": "1.0|2.0" }, "require-dev": { - "psr/log": "~1.0", - "symfony/config": "~3.3|~4.0", - "symfony/dependency-injection": "~3.4|~4.0", - "symfony/event-dispatcher": "~2.8|~3.0|~4.0", - "symfony/lock": "~3.4|~4.0", - "symfony/process": "~3.3|~4.0" + "psr/log": "^1|^2", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/event-dispatcher": "^4.4|^5.0|^6.0", + "symfony/lock": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/var-dumper": "^4.4|^5.0|^6.0" }, "suggest": { "psr/log": "For using the console logger", @@ -181,11 +220,6 @@ "symfony/process": "" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.4-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\Console\\": "" @@ -208,47 +242,63 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Console Component", + "description": "Eases the creation of beautiful and testable command line interfaces", "homepage": "https://symfony.com", - "time": "2020-05-30T18:58:05+00:00" + "keywords": [ + "cli", + "command line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v5.4.14" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-10-07T08:01:20+00:00" }, { - "name": "symfony/debug", - "version": "v4.4.10", + "name": "symfony/deprecation-contracts", + "version": "v3.1.1", "source": { "type": "git", - "url": "https://github.com/symfony/debug.git", - "reference": "28f92d08bb6d1fddf8158e02c194ad43870007e6" + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/28f92d08bb6d1fddf8158e02c194ad43870007e6", - "reference": "28f92d08bb6d1fddf8158e02c194ad43870007e6", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918", + "reference": "07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918", "shasum": "" }, "require": { - "php": ">=7.1.3", - "psr/log": "~1.0", - "symfony/polyfill-php80": "^1.15" - }, - "conflict": { - "symfony/http-kernel": "<3.4" - }, - "require-dev": { - "symfony/http-kernel": "^3.4|^4.0|^5.0" + "php": ">=8.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.4-dev" + "dev-main": "3.1-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" } }, "autoload": { - "psr-4": { - "Symfony\\Component\\Debug\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "files": [ + "function.php" ] }, "notification-url": "https://packagist.org/downloads/", @@ -257,34 +307,54 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Debug Component", + "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", - "time": "2020-05-24T08:33:35+00:00" + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-02-25T11:15:52+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.18.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "1c302646f6efc070cd46856e600e5e0684d6b454" + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/1c302646f6efc070cd46856e600e5e0684d6b454", - "reference": "1c302646f6efc070cd46856e600e5e0684d6b454", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" }, "suggest": { "ext-ctype": "For best performance" @@ -292,7 +362,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.18-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -300,12 +370,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -329,32 +399,49 @@ "polyfill", "portable" ], - "time": "2020-07-14T12:35:20+00:00" + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" }, { - "name": "symfony/polyfill-mbstring", - "version": "v1.18.0", + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.26.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a" + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "433d05519ce6990bf3530fba6957499d327395c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/a6977d63bf9a0ad4c65cd352709e230876f9904a", - "reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/433d05519ce6990bf3530fba6957499d327395c2", + "reference": "433d05519ce6990bf3530fba6957499d327395c2", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.1" }, "suggest": { - "ext-mbstring": "For best performance" + "ext-intl": "For best performance" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.18-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -362,12 +449,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -383,38 +470,59 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for the Mbstring extension", + "description": "Symfony polyfill for intl's grapheme_* functions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "mbstring", + "grapheme", + "intl", "polyfill", "portable", "shim" ], - "time": "2020-07-14T12:35:20+00:00" + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" }, { - "name": "symfony/polyfill-php80", - "version": "v1.18.0", + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.26.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "d87d5766cbf48d72388a9f6b85f280c8ad51f981" + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "219aa369ceff116e673852dce47c3a41794c14bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/d87d5766cbf48d72388a9f6b85f280c8ad51f981", - "reference": "d87d5766cbf48d72388a9f6b85f280c8ad51f981", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd", + "reference": "219aa369ceff116e673852dce47c3a41794c14bd", "shasum": "" }, "require": { - "php": ">=7.0.8" + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.18-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -422,12 +530,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, "files": [ "bootstrap.php" ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, "classmap": [ "Resources/stubs" ] @@ -437,10 +545,6 @@ "MIT" ], "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, { "name": "Nicolas Grekas", "email": "p@tchwork.com" @@ -450,56 +554,75 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "description": "Symfony polyfill for intl's Normalizer class and related functions", "homepage": "https://symfony.com", "keywords": [ "compatibility", + "intl", + "normalizer", "polyfill", "portable", "shim" ], - "time": "2020-07-14T12:35:20+00:00" + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" }, { - "name": "symfony/yaml", - "version": "v4.4.10", + "name": "symfony/polyfill-mbstring", + "version": "v1.26.0", "source": { "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "c2d2cc66e892322cfcc03f8f12f8340dbd7a3f8a" + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/c2d2cc66e892322cfcc03f8f12f8340dbd7a3f8a", - "reference": "c2d2cc66e892322cfcc03f8f12f8340dbd7a3f8a", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", "shasum": "" }, "require": { - "php": ">=7.1.3", - "symfony/polyfill-ctype": "~1.8" - }, - "conflict": { - "symfony/console": "<3.4" + "php": ">=7.1" }, - "require-dev": { - "symfony/console": "^3.4|^4.0|^5.0" + "provide": { + "ext-mbstring": "*" }, "suggest": { - "symfony/console": "For validating YAML files using the lint command" + "ext-mbstring": "For best performance" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.4-dev" + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Symfony\\Polyfill\\Mbstring\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -507,50 +630,79 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Yaml Component", + "description": "Symfony polyfill for the Mbstring extension", "homepage": "https://symfony.com", - "time": "2020-05-20T08:37:50+00:00" - } - ], - "packages-dev": [ + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, { - "name": "composer/semver", - "version": "1.5.1", + "name": "symfony/polyfill-php73", + "version": "v1.26.0", "source": { "type": "git", - "url": "https://github.com/composer/semver.git", - "reference": "c6bea70230ef4dd483e6bbcab6005f682ed3a8de" + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/c6bea70230ef4dd483e6bbcab6005f682ed3a8de", - "reference": "c6bea70230ef4dd483e6bbcab6005f682ed3a8de", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/e440d35fa0286f77fb45b79a03fedbeda9307e85", + "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85", "shasum": "" }, "require": { - "php": "^5.3.2 || ^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.5 || ^5.0.5" + "php": ">=7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.x-dev" + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Composer\\Semver\\": "src" - } + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -558,417 +710,2513 @@ ], "authors": [ { - "name": "Nils Adermann", - "email": "naderman@naderman.de", - "homepage": "http://www.naderman.de" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { - "name": "Rob Bast", - "email": "rob.bast@gmail.com", - "homepage": "http://robbast.nl" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Semver library that offers utilities, version constraint parsing and validation.", + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", "keywords": [ - "semantic", - "semver", - "validation", - "versioning" + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } ], - "time": "2020-01-13T12:06:48+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { - "name": "composer/xdebug-handler", - "version": "1.4.2", + "name": "symfony/polyfill-php80", + "version": "v1.26.0", "source": { "type": "git", - "url": "https://github.com/composer/xdebug-handler.git", - "reference": "fa2aaf99e2087f013a14f7432c1cd2dd7d8f1f51" + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/fa2aaf99e2087f013a14f7432c1cd2dd7d8f1f51", - "reference": "fa2aaf99e2087f013a14f7432c1cd2dd7d8f1f51", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace", "shasum": "" }, "require": { - "php": "^5.3.2 || ^7.0 || ^8.0", - "psr/log": "^1.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8" + "php": ">=7.1" }, "type": "library", - "autoload": { - "psr-4": { - "Composer\\XdebugHandler\\": "src" + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-10T07:21:04+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "925e713fe8fcacf6bc05e936edd8dd5441a21239" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/925e713fe8fcacf6bc05e936edd8dd5441a21239", + "reference": "925e713fe8fcacf6bc05e936edd8dd5441a21239", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^2.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.1-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-30T19:18:58+00:00" + }, + { + "name": "symfony/string", + "version": "v6.1.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "7e7e0ff180d4c5a6636eaad57b65092014b61864" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/7e7e0ff180d4c5a6636eaad57b65092014b61864", + "reference": "7e7e0ff180d4c5a6636eaad57b65092014b61864", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.0" + }, + "require-dev": { + "symfony/error-handler": "^5.4|^6.0", + "symfony/http-client": "^5.4|^6.0", + "symfony/translation-contracts": "^2.0|^3.0", + "symfony/var-exporter": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v6.1.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-10-10T09:34:31+00:00" + }, + { + "name": "symfony/yaml", + "version": "v5.4.14", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "e83fe9a72011f07c662da46a05603d66deeeb487" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/e83fe9a72011f07c662da46a05603d66deeeb487", + "reference": "e83fe9a72011f07c662da46a05603d66deeeb487", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<5.3" + }, + "require-dev": { + "symfony/console": "^5.3|^6.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v5.4.14" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-10-03T15:15:50+00:00" + } + ], + "packages-dev": [ + { + "name": "composer/pcre", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "e300eb6c535192decd27a85bc72a9290f0d6b3bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/e300eb6c535192decd27a85bc72a9290f0d6b3bd", + "reference": "e300eb6c535192decd27a85bc72a9290f0d6b3bd", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.3", + "phpstan/phpstan-strict-rules": "^1.1", + "symfony/phpunit-bridge": "^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.0.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-02-25T20:21:48+00:00" + }, + { + "name": "composer/semver", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/3953f23262f2bff1919fc82183ad9acb13ff62c9", + "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-04-01T19:23:25+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "ced299686f41dce890debac69273b47ffe98a40c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ced299686f41dce890debac69273b47ffe98a40c", + "reference": "ced299686f41dce890debac69273b47ffe98a40c", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "symfony/phpunit-bridge": "^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.3" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-02-25T21:32:43+00:00" + }, + { + "name": "doctrine/annotations", + "version": "1.13.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "648b0343343565c4a056bfc8392201385e8d89f0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/648b0343343565c4a056bfc8392201385e8d89f0", + "reference": "648b0343343565c4a056bfc8392201385e8d89f0", + "shasum": "" + }, + "require": { + "doctrine/lexer": "1.*", + "ext-tokenizer": "*", + "php": "^7.1 || ^8.0", + "psr/cache": "^1 || ^2 || ^3" + }, + "require-dev": { + "doctrine/cache": "^1.11 || ^2.0", + "doctrine/coding-standard": "^6.0 || ^8.1", + "phpstan/phpstan": "^1.4.10 || ^1.8.0", + "phpunit/phpunit": "^7.5 || ^8.0 || ^9.1.5", + "symfony/cache": "^4.4 || ^5.2", + "vimeo/psalm": "^4.10" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "https://www.doctrine-project.org/projects/annotations.html", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "support": { + "issues": "https://github.com/doctrine/annotations/issues", + "source": "https://github.com/doctrine/annotations/tree/1.13.3" + }, + "time": "2022-07-02T10:48:51+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.22" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.4.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2022-03-03T08:28:38+00:00" + }, + { + "name": "doctrine/lexer", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/c268e882d4dbdd85e36e4ad69e02dc284f89d229", + "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9.0", + "phpstan/phpstan": "^1.3", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/1.2.3" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2022-02-28T11:07:21+00:00" + }, + { + "name": "friendsofphp/php-cs-fixer", + "version": "v3.12.0", + "source": { + "type": "git", + "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", + "reference": "eae11d945e2885d86e1c080eec1bb30a2aa27998" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/eae11d945e2885d86e1c080eec1bb30a2aa27998", + "reference": "eae11d945e2885d86e1c080eec1bb30a2aa27998", + "shasum": "" + }, + "require": { + "composer/semver": "^3.2", + "composer/xdebug-handler": "^3.0.3", + "doctrine/annotations": "^1.13", + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0", + "sebastian/diff": "^4.0", + "symfony/console": "^5.4 || ^6.0", + "symfony/event-dispatcher": "^5.4 || ^6.0", + "symfony/filesystem": "^5.4 || ^6.0", + "symfony/finder": "^5.4 || ^6.0", + "symfony/options-resolver": "^5.4 || ^6.0", + "symfony/polyfill-mbstring": "^1.23", + "symfony/polyfill-php80": "^1.25", + "symfony/polyfill-php81": "^1.25", + "symfony/process": "^5.4 || ^6.0", + "symfony/stopwatch": "^5.4 || ^6.0" + }, + "require-dev": { + "justinrainbow/json-schema": "^5.2", + "keradus/cli-executor": "^1.5", + "mikey179/vfsstream": "^1.6.10", + "php-coveralls/php-coveralls": "^2.5.2", + "php-cs-fixer/accessible-object": "^1.1", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.2", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.2.1", + "phpspec/prophecy": "^1.15", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "phpunitgoodpractices/polyfill": "^1.6", + "phpunitgoodpractices/traits": "^1.9.2", + "symfony/phpunit-bridge": "^6.0", + "symfony/yaml": "^5.4 || ^6.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "autoload": { + "psr-4": { + "PhpCsFixer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "issues": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues", + "source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v3.12.0" + }, + "funding": [ + { + "url": "https://github.com/keradus", + "type": "github" + } + ], + "time": "2022-10-12T14:20:51+00:00" + }, + { + "name": "kahlan/kahlan", + "version": "5.2.1", + "source": { + "type": "git", + "url": "https://github.com/kahlan/kahlan.git", + "reference": "9edb4daeba5494bcd38a125c5d960447998902f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kahlan/kahlan/zipball/9edb4daeba5494bcd38a125c5d960447998902f4", + "reference": "9edb4daeba5494bcd38a125c5d960447998902f4", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "squizlabs/php_codesniffer": "^3.4" + }, + "bin": [ + "bin/kahlan" + ], + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Kahlan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "CrysaLEAD" + } + ], + "description": "The PHP Test Framework for Freedom, Truth and Justice.", + "keywords": [ + "BDD", + "Behavior-Driven Development", + "Monkey Patching", + "TDD", + "mock", + "stub", + "testing", + "unit test" + ], + "support": { + "issues": "https://github.com/kahlan/kahlan/issues", + "source": "https://github.com/kahlan/kahlan/tree/5.2.1" + }, + "time": "2022-06-18T14:06:54+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3,<3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2022-03-03T13:19:32+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.15.1", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/0ef6c55a3f47f89d7a374e6f835197a0b5fcf900", + "reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.0" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.1" + }, + "time": "2022-09-04T07:30:47+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.3" + }, + "time": "2021-07-20T11:28:43+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.18", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "12fddc491826940cf9b7e88ad9664cf51f0f6d0a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/12fddc491826940cf9b7e88ad9664cf51f0f6d0a", + "reference": "12fddc491826940cf9b7e88ad9664cf51f0f6d0a", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.14", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.3", + "phpunit/php-text-template": "^2.0.2", + "sebastian/code-unit-reverse-lookup": "^2.0.2", + "sebastian/complexity": "^2.0", + "sebastian/environment": "^5.1.2", + "sebastian/lines-of-code": "^1.0.3", + "sebastian/version": "^3.0.1", + "theseer/tokenizer": "^1.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcov": "*", + "ext-xdebug": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.18" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-10-27T13:35:33+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-12-02T12:48:52+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.5.26", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "851867efcbb6a1b992ec515c71cdcf20d895e9d2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/851867efcbb6a1b992ec515c71cdcf20d895e9d2", + "reference": "851867efcbb6a1b992ec515c71cdcf20d895e9d2", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.3.1", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "php": ">=7.3", + "phpunit/php-code-coverage": "^9.2.13", + "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.3", + "phpunit/php-timer": "^5.0.2", + "sebastian/cli-parser": "^1.0.1", + "sebastian/code-unit": "^1.0.6", + "sebastian/comparator": "^4.0.8", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.5", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^3.2", + "sebastian/version": "^3.0.2" + }, + "suggest": { + "ext-soap": "*", + "ext-xdebug": "*" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.26" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2022-10-28T06:00:21+00:00" + }, + { + "name": "psr/cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "time": "2021-02-03T23:26:27+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/log", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/ef29f6d262798707a9edd554e2b82517ef3a9376", + "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/2.0.0" + }, + "time": "2021-07-14T16:41:46+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:08:49+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:08:54+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:30:19+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-09-14T12:41:17+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.7", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:52:27+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "John Stevenson", - "email": "john-stevenson@blueyonder.co.uk" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" } ], - "description": "Restarts a process without Xdebug.", + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", "keywords": [ - "Xdebug", - "performance" + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } ], - "time": "2020-06-04T11:16:35+00:00" + "time": "2020-10-26T13:10:38+00:00" }, { - "name": "doctrine/annotations", - "version": "1.10.3", + "name": "sebastian/environment", + "version": "5.1.4", "source": { "type": "git", - "url": "https://github.com/doctrine/annotations.git", - "reference": "5db60a4969eba0e0c197a19c077780aadbc43c5d" + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/5db60a4969eba0e0c197a19c077780aadbc43c5d", - "reference": "5db60a4969eba0e0c197a19c077780aadbc43c5d", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/1b5dff7bb151a4db11d49d90e5408e4e938270f7", + "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7", "shasum": "" }, "require": { - "doctrine/lexer": "1.*", - "ext-tokenizer": "*", - "php": "^7.1 || ^8.0" + "php": ">=7.3" }, "require-dev": { - "doctrine/cache": "1.*", - "phpunit/phpunit": "^7.5" + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9.x-dev" + "dev-master": "5.1-dev" } }, "autoload": { - "psr-4": { - "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-04-03T09:37:03+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" } }, + "autoload": { + "classmap": [ + "src/" + ] + }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" }, { - "name": "Roman Borschel", - "email": "roman@code-factory.org" + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" }, { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" + "name": "Volker Dusch", + "email": "github@wallbash.com" }, { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" + "name": "Adam Harvey", + "email": "aharvey@php.net" }, { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" } ], - "description": "Docblock Annotations Parser", - "homepage": "http://www.doctrine-project.org", + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", "keywords": [ - "annotations", - "docblock", - "parser" + "export", + "exporter" ], - "time": "2020-05-25T17:24:27+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-09-14T06:03:37+00:00" }, { - "name": "doctrine/lexer", - "version": "1.2.1", + "name": "sebastian/global-state", + "version": "5.0.5", "source": { "type": "git", - "url": "https://github.com/doctrine/lexer.git", - "reference": "e864bbf5904cb8f5bb334f99209b48018522f042" + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/e864bbf5904cb8f5bb334f99209b48018522f042", - "reference": "e864bbf5904cb8f5bb334f99209b48018522f042", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" }, "require-dev": { - "doctrine/coding-standard": "^6.0", - "phpstan/phpstan": "^0.11.8", - "phpunit/phpunit": "^8.2" + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2.x-dev" + "dev-master": "5.0-dev" } }, "autoload": { - "psr-4": { - "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" } ], - "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", - "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", "keywords": [ - "annotations", - "docblock", - "lexer", - "parser", - "php" + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } ], - "time": "2020-05-25T17:44:05+00:00" + "time": "2022-02-14T08:28:10+00:00" }, { - "name": "friendsofphp/php-cs-fixer", - "version": "v2.16.4", + "name": "sebastian/lines-of-code", + "version": "1.0.3", "source": { "type": "git", - "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "1023c3458137ab052f6ff1e09621a721bfdeca13" + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/1023c3458137ab052f6ff1e09621a721bfdeca13", - "reference": "1023c3458137ab052f6ff1e09621a721bfdeca13", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", "shasum": "" }, "require": { - "composer/semver": "^1.4", - "composer/xdebug-handler": "^1.2", - "doctrine/annotations": "^1.2", - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^5.6 || ^7.0", - "php-cs-fixer/diff": "^1.3", - "symfony/console": "^3.4.17 || ^4.1.6 || ^5.0", - "symfony/event-dispatcher": "^3.0 || ^4.0 || ^5.0", - "symfony/filesystem": "^3.0 || ^4.0 || ^5.0", - "symfony/finder": "^3.0 || ^4.0 || ^5.0", - "symfony/options-resolver": "^3.0 || ^4.0 || ^5.0", - "symfony/polyfill-php70": "^1.0", - "symfony/polyfill-php72": "^1.4", - "symfony/process": "^3.0 || ^4.0 || ^5.0", - "symfony/stopwatch": "^3.0 || ^4.0 || ^5.0" + "nikic/php-parser": "^4.6", + "php": ">=7.3" }, "require-dev": { - "johnkary/phpunit-speedtrap": "^1.1 || ^2.0 || ^3.0", - "justinrainbow/json-schema": "^5.0", - "keradus/cli-executor": "^1.2", - "mikey179/vfsstream": "^1.6", - "php-coveralls/php-coveralls": "^2.1", - "php-cs-fixer/accessible-object": "^1.0", - "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.1", - "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.1", - "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.1", - "phpunitgoodpractices/traits": "^1.8", - "symfony/phpunit-bridge": "^5.1", - "symfony/yaml": "^3.0 || ^4.0 || ^5.0" + "phpunit/phpunit": "^9.3" }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters.", - "php-cs-fixer/phpunit-constraint-isidenticalstring": "For IsIdenticalString constraint.", - "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "For XmlMatchesXsd constraint.", - "symfony/polyfill-mbstring": "When enabling `ext-mbstring` is not possible." + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", "autoload": { - "psr-4": { - "PhpCsFixer\\": "src/" - }, "classmap": [ - "tests/Test/AbstractFixerTestCase.php", - "tests/Test/AbstractIntegrationCaseFactory.php", - "tests/Test/AbstractIntegrationTestCase.php", - "tests/Test/Assert/AssertTokensTrait.php", - "tests/Test/IntegrationCase.php", - "tests/Test/IntegrationCaseFactory.php", - "tests/Test/IntegrationCaseFactoryInterface.php", - "tests/Test/InternalIntegrationCaseFactory.php", - "tests/Test/IsIdenticalConstraint.php", - "tests/TestCase.php" + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + }, + "funding": [ { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" + "url": "https://github.com/sebastianbergmann", + "type": "github" } ], - "description": "A tool to automatically fix PHP code style", - "time": "2020-06-27T23:57:46+00:00" + "time": "2020-11-28T06:42:11+00:00" }, { - "name": "kahlan/kahlan", - "version": "4.7.5", + "name": "sebastian/object-enumerator", + "version": "4.0.4", "source": { "type": "git", - "url": "https://github.com/kahlan/kahlan.git", - "reference": "c529ef24201053ba76d3c8c3531acd76b629ce87" + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/kahlan/kahlan/zipball/c529ef24201053ba76d3c8c3531acd76b629ce87", - "reference": "c529ef24201053ba76d3c8c3531acd76b629ce87", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", "shasum": "" }, "require": { - "php": ">=5.5" + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" }, "require-dev": { - "squizlabs/php_codesniffer": "^3.4" + "phpunit/phpunit": "^9.3" }, - "bin": [ - "bin/kahlan" - ], "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, "autoload": { - "psr-4": { - "Kahlan\\": "src/" - }, - "files": [ - "src/functions.php" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "CrysaLEAD" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" } ], - "description": "The PHP Test Framework for Freedom, Truth and Justice.", - "keywords": [ - "BDD", - "Behavior-Driven Development", - "Monkey Patching", - "TDD", - "mock", - "stub", - "testing", - "unit test" + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } ], - "time": "2020-04-25T21:27:19+00:00" + "time": "2020-10-26T13:12:34+00:00" }, { - "name": "paragonie/random_compat", - "version": "v9.99.99", + "name": "sebastian/object-reflector", + "version": "2.0.4", "source": { "type": "git", - "url": "https://github.com/paragonie/random_compat.git", - "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95" + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", - "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", "shasum": "" }, "require": { - "php": "^7" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "4.*|5.*", - "vimeo/psalm": "^1" - }, - "suggest": { - "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + "phpunit/phpunit": "^9.3" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Paragon Initiative Enterprises", - "email": "security@paragonie.com", - "homepage": "https://paragonie.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" } ], - "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", - "keywords": [ - "csprng", - "polyfill", - "pseudorandom", - "random" + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } ], - "time": "2018-07-02T15:55:56+00:00" + "time": "2020-10-26T13:14:26+00:00" }, { - "name": "php-cs-fixer/diff", - "version": "v1.3.0", + "name": "sebastian/recursion-context", + "version": "4.0.4", "source": { "type": "git", - "url": "https://github.com/PHP-CS-Fixer/diff.git", - "reference": "78bb099e9c16361126c86ce82ec4405ebab8e756" + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/diff/zipball/78bb099e9c16361126c86ce82ec4405ebab8e756", - "reference": "78bb099e9c16361126c86ce82ec4405ebab8e756", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^5.7.23 || ^6.4.3", - "symfony/process": "^3.3" + "phpunit/phpunit": "^9.3" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, "autoload": { "classmap": [ "src/" @@ -979,216 +3227,237 @@ "BSD-3-Clause" ], "authors": [ - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - }, { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de" }, { - "name": "SpacePossum" + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" } ], - "description": "sebastian/diff v2 backport support for PHP5.6", - "homepage": "https://github.com/PHP-CS-Fixer", - "keywords": [ - "diff" + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } ], - "time": "2018-02-15T16:58:55+00:00" + "time": "2020-10-26T13:17:30+00:00" }, { - "name": "psr/container", - "version": "1.0.0", + "name": "sebastian/resource-operations", + "version": "3.0.3", "source": { "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "3.0-dev" } }, "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" } ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "time": "2017-02-14T16:28:37+00:00" + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "issues": "https://github.com/sebastianbergmann/resource-operations/issues", + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:45:17+00:00" }, { - "name": "psr/event-dispatcher", - "version": "1.0.0", + "name": "sebastian/type", + "version": "3.2.0", "source": { "type": "git", - "url": "https://github.com/php-fig/event-dispatcher.git", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", + "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", "shasum": "" }, "require": { - "php": ">=7.2.0" + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "3.2-dev" } }, "autoload": { - "psr-4": { - "Psr\\EventDispatcher\\": "src/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Standard interfaces for event handling.", - "keywords": [ - "events", - "psr", - "psr-14" + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/3.2.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } ], - "time": "2019-01-08T18:20:26+00:00" + "time": "2022-09-12T14:47:03+00:00" }, { - "name": "symfony/deprecation-contracts", - "version": "v2.1.3", + "name": "sebastian/version", + "version": "3.0.2", "source": { "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "5e20b83385a77593259c9f8beb2c43cd03b2ac14" + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5e20b83385a77593259c9f8beb2c43cd03b2ac14", - "reference": "5e20b83385a77593259c9f8beb2c43cd03b2ac14", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "dev-master": "3.0-dev" } }, "autoload": { - "files": [ - "function.php" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "url": "https://github.com/sebastianbergmann", + "type": "github" } ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "time": "2020-06-06T08:49:21+00:00" + "time": "2020-09-28T06:39:44+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v5.1.2", + "version": "v6.1.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "cc0d059e2e997e79ca34125a52f3e33de4424ac7" + "reference": "a0449a7ad7daa0f7c0acd508259f80544ab5a347" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/cc0d059e2e997e79ca34125a52f3e33de4424ac7", - "reference": "cc0d059e2e997e79ca34125a52f3e33de4424ac7", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a0449a7ad7daa0f7c0acd508259f80544ab5a347", + "reference": "a0449a7ad7daa0f7c0acd508259f80544ab5a347", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", - "symfony/event-dispatcher-contracts": "^2", - "symfony/polyfill-php80": "^1.15" + "php": ">=8.1", + "symfony/event-dispatcher-contracts": "^2|^3" }, "conflict": { - "symfony/dependency-injection": "<4.4" + "symfony/dependency-injection": "<5.4" }, "provide": { "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "2.0" + "symfony/event-dispatcher-implementation": "2.0|3.0" }, "require-dev": { - "psr/log": "~1.0", - "symfony/config": "^4.4|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/expression-language": "^4.4|^5.0", - "symfony/http-foundation": "^4.4|^5.0", - "symfony/service-contracts": "^1.1|^2", - "symfony/stopwatch": "^4.4|^5.0" + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/error-handler": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/stopwatch": "^5.4|^6.0" }, "suggest": { "symfony/dependency-injection": "", "symfony/http-kernel": "" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\EventDispatcher\\": "" @@ -1211,26 +3480,43 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony EventDispatcher Component", + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", - "time": "2020-05-20T17:43:50+00:00" + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v6.1.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-05T16:51:07+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v2.1.3", + "version": "v3.1.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "f6f613d74cfc5a623fc36294d3451eb7fa5a042b" + "reference": "02ff5eea2f453731cfbc6bc215e456b781480448" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/f6f613d74cfc5a623fc36294d3451eb7fa5a042b", - "reference": "f6f613d74cfc5a623fc36294d3451eb7fa5a042b", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/02ff5eea2f453731cfbc6bc215e456b781480448", + "reference": "02ff5eea2f453731cfbc6bc215e456b781480448", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.1", "psr/event-dispatcher": "^1" }, "suggest": { @@ -1239,7 +3525,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1-dev" + "dev-main": "3.1-dev" }, "thanks": { "name": "symfony/contracts", @@ -1275,32 +3561,45 @@ "interoperability", "standards" ], - "time": "2020-07-06T13:23:11+00:00" + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-02-25T11:15:52+00:00" }, { "name": "symfony/filesystem", - "version": "v5.1.2", + "version": "v6.1.5", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "6e4320f06d5f2cce0d96530162491f4465179157" + "reference": "4d216a2beef096edf040a070117c39ca2abce307" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/6e4320f06d5f2cce0d96530162491f4465179157", - "reference": "6e4320f06d5f2cce0d96530162491f4465179157", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/4d216a2beef096edf040a070117c39ca2abce307", + "reference": "4d216a2beef096edf040a070117c39ca2abce307", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-ctype": "~1.8" + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\Filesystem\\": "" @@ -1323,33 +3622,48 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Filesystem Component", + "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", - "time": "2020-05-30T20:35:19+00:00" + "support": { + "source": "https://github.com/symfony/filesystem/tree/v6.1.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-09-21T20:29:40+00:00" }, { "name": "symfony/finder", - "version": "v5.1.2", + "version": "v6.1.3", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "4298870062bfc667cb78d2b379be4bf5dec5f187" + "reference": "39696bff2c2970b3779a5cac7bf9f0b88fc2b709" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/4298870062bfc667cb78d2b379be4bf5dec5f187", - "reference": "4298870062bfc667cb78d2b379be4bf5dec5f187", + "url": "https://api.github.com/repos/symfony/finder/zipball/39696bff2c2970b3779a5cac7bf9f0b88fc2b709", + "reference": "39696bff2c2970b3779a5cac7bf9f0b88fc2b709", "shasum": "" }, "require": { - "php": ">=7.2.5" + "php": ">=8.1" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } + "require-dev": { + "symfony/filesystem": "^6.0" }, + "type": "library", "autoload": { "psr-4": { "Symfony\\Component\\Finder\\": "" @@ -1372,35 +3686,46 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Finder Component", + "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", - "time": "2020-05-20T17:43:50+00:00" + "support": { + "source": "https://github.com/symfony/finder/tree/v6.1.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-07-29T07:42:06+00:00" }, { "name": "symfony/options-resolver", - "version": "v5.1.2", + "version": "v6.1.0", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "663f5dd5e14057d1954fe721f9709d35837f2447" + "reference": "a3016f5442e28386ded73c43a32a5b68586dd1c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/663f5dd5e14057d1954fe721f9709d35837f2447", - "reference": "663f5dd5e14057d1954fe721f9709d35837f2447", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/a3016f5442e28386ded73c43a32a5b68586dd1c4", + "reference": "a3016f5442e28386ded73c43a32a5b68586dd1c4", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", - "symfony/polyfill-php80": "^1.15" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.1|^3" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\OptionsResolver\\": "" @@ -1423,37 +3748,53 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony OptionsResolver Component", + "description": "Provides an improved replacement for the array_replace PHP function", "homepage": "https://symfony.com", "keywords": [ "config", "configuration", "options" ], - "time": "2020-05-23T13:08:13+00:00" + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v6.1.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-02-25T11:15:52+00:00" }, { - "name": "symfony/polyfill-php70", - "version": "v1.18.0", + "name": "symfony/polyfill-php81", + "version": "v1.26.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "0dd93f2c578bdc9c72697eaa5f1dd25644e618d3" + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/0dd93f2c578bdc9c72697eaa5f1dd25644e618d3", - "reference": "0dd93f2c578bdc9c72697eaa5f1dd25644e618d3", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/13f6d1271c663dc5ae9fb843a8f16521db7687a1", + "reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1", "shasum": "" }, "require": { - "paragonie/random_compat": "~1.0|~2.0|~9.99", - "php": ">=5.3.3" + "php": ">=7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.18-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1461,12 +3802,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php70\\": "" - }, "files": [ "bootstrap.php" ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, "classmap": [ "Resources/stubs" ] @@ -1485,7 +3826,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", @@ -1493,91 +3834,43 @@ "portable", "shim" ], - "time": "2020-07-14T12:35:20+00:00" - }, - { - "name": "symfony/polyfill-php72", - "version": "v1.18.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "639447d008615574653fb3bc60d1986d7172eaae" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/639447d008615574653fb3bc60d1986d7172eaae", - "reference": "639447d008615574653fb3bc60d1986d7172eaae", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.18-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.26.0" }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php72\\": "" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" }, - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "url": "https://github.com/fabpot", + "type": "github" }, { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "time": "2020-07-14T12:35:20+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/process", - "version": "v5.1.2", + "version": "v6.1.3", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "7f6378c1fa2147eeb1b4c385856ce9de0d46ebd1" + "reference": "a6506e99cfad7059b1ab5cab395854a0a0c21292" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/7f6378c1fa2147eeb1b4c385856ce9de0d46ebd1", - "reference": "7f6378c1fa2147eeb1b4c385856ce9de0d46ebd1", + "url": "https://api.github.com/repos/symfony/process/zipball/a6506e99cfad7059b1ab5cab395854a0a0c21292", + "reference": "a6506e99cfad7059b1ab5cab395854a0a0c21292", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.15" + "php": ">=8.1" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\Process\\": "" @@ -1600,45 +3893,53 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Process Component", + "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", - "time": "2020-05-30T20:35:19+00:00" + "support": { + "source": "https://github.com/symfony/process/tree/v6.1.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-06-27T17:24:16+00:00" }, { - "name": "symfony/service-contracts", - "version": "v2.1.3", + "name": "symfony/stopwatch", + "version": "v6.1.5", "source": { "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "58c7475e5457c5492c26cc740cc0ad7464be9442" + "url": "https://github.com/symfony/stopwatch.git", + "reference": "266636bb8f3fbdccc302491df7b3a1b9a8c238a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/58c7475e5457c5492c26cc740cc0ad7464be9442", - "reference": "58c7475e5457c5492c26cc740cc0ad7464be9442", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/266636bb8f3fbdccc302491df7b3a1b9a8c238a7", + "reference": "266636bb8f3fbdccc302491df7b3a1b9a8c238a7", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/container": "^1.0" - }, - "suggest": { - "symfony/service-implementation": "" + "php": ">=8.1", + "symfony/service-contracts": "^1|^2|^3" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.1-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, "autoload": { "psr-4": { - "Symfony\\Contracts\\Service\\": "" - } + "Symfony\\Component\\Stopwatch\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1646,75 +3947,84 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Generic abstractions related to writing services", + "description": "Provides a way to profile code", "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" + "support": { + "source": "https://github.com/symfony/stopwatch/tree/v6.1.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } ], - "time": "2020-07-06T13:23:11+00:00" + "time": "2022-09-28T16:00:52+00:00" }, { - "name": "symfony/stopwatch", - "version": "v5.1.2", + "name": "theseer/tokenizer", + "version": "1.2.1", "source": { "type": "git", - "url": "https://github.com/symfony/stopwatch.git", - "reference": "0f7c58cf81dbb5dd67d423a89d577524a2ec0323" + "url": "https://github.com/theseer/tokenizer.git", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/0f7c58cf81dbb5dd67d423a89d577524a2ec0323", - "reference": "0f7c58cf81dbb5dd67d423a89d577524a2ec0323", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/service-contracts": "^1.0|^2" + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } - }, "autoload": { - "psr-4": { - "Symfony\\Component\\Stopwatch\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + }, + "funding": [ { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "url": "https://github.com/theseer", + "type": "github" } ], - "description": "Symfony Stopwatch Component", - "homepage": "https://symfony.com", - "time": "2020-05-20T17:43:50+00:00" + "time": "2021-07-28T10:34:58+00:00" } ], "aliases": [], @@ -1723,7 +4033,8 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "^7.2" + "php": "^8.1" }, - "platform-dev": [] + "platform-dev": [], + "plugin-api-version": "2.3.0" } diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..e723099 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,17 @@ + + + + + src + + + + + + + tests + + diff --git a/spec/fetch-networks.php b/spec/fetch-networks.php deleted file mode 100644 index 485135e..0000000 --- a/spec/fetch-networks.php +++ /dev/null @@ -1,23 +0,0 @@ - ['image' => 'bar']]; - - expect(fetchNetworks($configuration))->toBe([]); - }); - }); - - describe('from a version 2 configuration', function () { - it('should fetch networks from the dedicated section', function () { - $configuration = ['version' => 2, 'networks' => ['foo' => [], 'bar' => []]]; - - expect(fetchNetworks($configuration))->toBe($configuration['networks']); - }); - }); -}); diff --git a/spec/fetch-services.php b/spec/fetch-services.php deleted file mode 100644 index d7ac077..0000000 --- a/spec/fetch-services.php +++ /dev/null @@ -1,23 +0,0 @@ - ['image' => 'bar'], 'baz' => ['build' => '.']]; - - expect(fetchServices($configuration))->toBe($configuration); - }); - }); - - describe('from a version 2 configuration', function () { - it('should fetch services from the dedicated section', function () { - $configuration = ['version' => 2, 'services' => ['foo' => ['image' => 'bar'], 'baz' => ['build' => '.']]]; - - expect(fetchServices($configuration))->toBe($configuration['services']); - }); - }); -}); diff --git a/spec/fetch-volumes.php b/spec/fetch-volumes.php deleted file mode 100644 index ff1fc16..0000000 --- a/spec/fetch-volumes.php +++ /dev/null @@ -1,23 +0,0 @@ - ['image' => 'bar']]; - - expect(fetchVolumes($configuration))->toBe([]); - }); - }); - - describe('from a version 2 configuration', function () { - it('should fetch volumes from the dedicated section', function () { - $configuration = ['version' => 2, 'volumes' => ['foo' => [], 'bar' => []]]; - - expect(fetchVolumes($configuration))->toBe($configuration['volumes']); - }); - }); -}); diff --git a/spec/fixtures/read-configuration/invalid.yml b/spec/fixtures/read-configuration/invalid.yml deleted file mode 100644 index 2873f33..0000000 --- a/spec/fixtures/read-configuration/invalid.yml +++ /dev/null @@ -1,4 +0,0 @@ -version -services: - foo: - image: bar diff --git a/spec/fixtures/read-configuration/valid.yml b/spec/fixtures/read-configuration/valid.yml deleted file mode 100644 index d7341a6..0000000 --- a/spec/fixtures/read-configuration/valid.yml +++ /dev/null @@ -1,4 +0,0 @@ -version: 2 -services: - foo: - image: bar diff --git a/spec/read-configuratoin.php b/spec/read-configuratoin.php deleted file mode 100644 index 2262345..0000000 --- a/spec/read-configuratoin.php +++ /dev/null @@ -1,26 +0,0 @@ -toThrow(new InvalidArgumentException()); - }); - - it('should parse YAML and return an array', function () { - expect(readConfiguration(__DIR__.'/fixtures/read-configuration/valid.yml')) - ->toBe(['version' => 2, 'services' => ['foo' => ['image' => 'bar']]]); - }); - - it('should report if YAML is invalid', function () { - expect(function () { - readConfiguration(__DIR__.'/fixtures/read-configuration/invalid.yml'); - }) - ->toThrow(new InvalidArgumentException()); - }); -}); diff --git a/src/application.php b/src/application.php index 75c1016..32aecf8 100644 --- a/src/application.php +++ b/src/application.php @@ -1,5 +1,7 @@ register('render') - ->addArgument('input-file', Console\Input\InputArgument::OPTIONAL, 'Path to a docker compose file', getcwd().DIRECTORY_SEPARATOR.'docker-compose.yml') - - ->addOption('override', null, Console\Input\InputOption::VALUE_REQUIRED, 'Tag of the override file to use', 'override') + ->addArgument('input-file', Console\Input\InputArgument::OPTIONAL | Console\Input\InputArgument::IS_ARRAY, 'Path to a docker compose file', [getcwd() . DIRECTORY_SEPARATOR . 'docker-compose.yml']) ->addOption('output-file', 'o', Console\Input\InputOption::VALUE_REQUIRED, 'Path to a output file (Only for "dot" and "image" output format)') ->addOption('output-format', 'm', Console\Input\InputOption::VALUE_REQUIRED, 'Output format (one of: "dot", "image", "display", "graphviz")', 'display') ->addOption('graphviz-output-format', null, Console\Input\InputOption::VALUE_REQUIRED, 'GraphViz Output format (see `man dot` for details)', 'svg') - ->addOption('only', null, Console\Input\InputOption::VALUE_IS_ARRAY | Console\Input\InputOption::VALUE_REQUIRED, 'Display a graph only for a given services') - + ->addOption('include', null, Console\Input\InputOption::VALUE_IS_ARRAY | Console\Input\InputOption::VALUE_REQUIRED, 'Display a graph only for given services') + ->addOption('exclude', null, Console\Input\InputOption::VALUE_IS_ARRAY | Console\Input\InputOption::VALUE_REQUIRED, 'Display a graph without the given services') ->addOption('force', 'f', Console\Input\InputOption::VALUE_NONE, 'Overwrites output file if it already exists') ->addOption('no-volumes', null, Console\Input\InputOption::VALUE_NONE, 'Do not display volumes') ->addOption('no-networks', null, Console\Input\InputOption::VALUE_NONE, 'Do not display networks') ->addOption('no-ports', null, Console\Input\InputOption::VALUE_NONE, 'Do not display ports') + ->addOption('no-secrets', null, Console\Input\InputOption::VALUE_NONE, 'Do not display secrets') + ->addOption('no-configs', null, Console\Input\InputOption::VALUE_NONE, 'Do not display configs') ->addOption('horizontal', 'r', Console\Input\InputOption::VALUE_NONE, 'Display a horizontal graph') ->addOption('ignore-override', null, Console\Input\InputOption::VALUE_NONE, 'Ignore override file') ->addOption('background', null, Console\Input\InputOption::VALUE_REQUIRED, 'Set the graph background color', '#ffffff') - ->setCode(function (Console\Input\InputInterface $input, Console\Output\OutputInterface $output) { $backgroundColor = $input->getOption('background'); @@ -32,15 +33,15 @@ } $logger = logger($output); - $inputFile = $input->getArgument('input-file'); - $inputFileExtension = pathinfo($inputFile, PATHINFO_EXTENSION); - $overrideFile = dirname($inputFile).DIRECTORY_SEPARATOR.basename($inputFile, '.'.$inputFileExtension).'.'.$input->getOption('override').'.'.$inputFileExtension; + $inputFiles = $input->getArgument('input-file'); + $outputFormat = $input->getOption('output-format'); - $outputFile = $input->getOption('output-file') ?: getcwd().DIRECTORY_SEPARATOR.'docker-compose.'.('dot' === $outputFormat ? $outputFormat : 'png'); - $onlyServices = $input->getOption('only'); + $outputFile = $input->getOption('output-file') ?: getcwd() . DIRECTORY_SEPARATOR . 'docker-compose.' . ('dot' === $outputFormat ? $outputFormat : 'png'); + $includeServices = $input->getOption('include'); + $excludeServices = $input->getOption('exclude'); - if (false === in_array($outputFormat, ['dot', 'image', 'display', 'graphviz'])) { + if (false === in_array($outputFormat, ['dot', 'image', 'display', 'graphviz'], true)) { throw new Console\Exception\InvalidArgumentException(sprintf('Invalid output format "%s". It must be one of "dot", "image" or "display".', $outputFormat)); } @@ -54,24 +55,9 @@ } } - $logger(sprintf('Reading configuration from "%s"', $inputFile)); - $configuration = readConfiguration($inputFile); - $configurationVersion = (string) ($configuration['version'] ?? 1); - - if (!$input->getOption('ignore-override') && file_exists($overrideFile)) { - $logger(sprintf('Reading override from "%s"', $overrideFile)); - $override = readConfiguration($overrideFile); - $overrideVersion = (string) ($override['version'] ?? 1); - - if ($configurationVersion !== $overrideVersion) { - throw new Console\Exception\LogicException(sprintf('Version mismatch: file "%s" specifies version "%s" but file "%s" uses version "%s"', $inputFile, $configurationVersion, $overrideFile, $overrideVersion)); - } - - $configuration = array_merge_recursive($configuration, $override); - - $logger(sprintf('Configuration version is "%s"', $configurationVersion), Console\Output\OutputInterface::VERBOSITY_VERY_VERBOSE); - $configuration['version'] = $configurationVersion; - } + $files = findConfigurationFiles($input->getOption('ignore-override'), ...$inputFiles); + $logger(sprintf('Reading configuration from "%s"', implode(', ', $files))); + $configuration = readConfigurations(...$files); $logger('Fetching services'); $services = fetchServices($configuration); @@ -85,22 +71,20 @@ $networks = fetchNetworks($configuration); $logger(sprintf('Found %d networks', count($networks)), Console\Output\OutputInterface::VERBOSITY_VERY_VERBOSE); - if ([] !== $onlyServices) { - $logger(sprintf('Only %s services will be displayed', implode(', ', $onlyServices))); + $logger('Fetching configs'); + $configs = fetchConfigs($configuration); + $logger(sprintf('Found %d configs', count($configs)), Console\Output\OutputInterface::VERBOSITY_VERY_VERBOSE); - $intersect = array_intersect($onlyServices, array_keys($services)); + $logger('Fetching secrets'); + $secrets = fetchSecrets($configuration); + $logger(sprintf('Found %d secrets', count($secrets)), Console\Output\OutputInterface::VERBOSITY_VERY_VERBOSE); - if ($intersect !== $onlyServices) { - throw new Console\Exception\InvalidArgumentException(sprintf('The following services do not exist: "%s"', implode('", "', array_diff($onlyServices, $intersect)))); - } + if ([] !== $includeServices) { + $logger(sprintf('Only %s services will be displayed', implode(', ', $includeServices))); + } - $services = array_filter( - $services, - function ($service) use ($onlyServices) { - return in_array($service, $onlyServices); - }, - ARRAY_FILTER_USE_KEY - ); + if ([] !== $excludeServices) { + $logger(sprintf('Services %s will not be displayed', implode(', ', $excludeServices))); } $flags = 0; @@ -122,17 +106,53 @@ function ($service) use ($onlyServices) { $flags |= WITHOUT_PORTS; } + if (true === $input->getOption('no-secrets')) { + $logger('Secrets will not be displayed'); + + $flags |= WITHOUT_SECRETS; + } + + if (true === $input->getOption('no-configs')) { + $logger('Configs will not be displayed'); + + $flags |= WITHOUT_CONFIGS; + } + $logger('Rendering graph'); $graph = applyGraphvizStyle( - createGraph($services, $volumes, $networks, $inputFile, $flags), + createGraph($services, $volumes, $networks, $configs, $secrets, $flags, $inputFiles[0]), $input->getOption('horizontal'), $input->getOption('background') ); + if ([] !== $includeServices) { + foreach ($graph->getVertices() as $vertex) { + if ($vertex->getAttribute('docker_compose_viz.type') !== 'service') { + continue; + } + + if (!in_array($vertex->getId(), $includeServices, true)) { + $vertex->destroy(); + } + } + } + + if ([] !== $excludeServices) { + foreach ($graph->getVertices() as $vertex) { + if ($vertex->getAttribute('docker_compose_viz.type') !== 'service') { + continue; + } + + if (in_array($vertex->getId(), $includeServices, true)) { + $vertex->destroy(); + } + } + } + switch ($outputFormat) { case 'dot': case 'image': - $rendererClass = 'Graphp\GraphViz\\'.ucfirst($outputFormat); + $rendererClass = 'Graphp\GraphViz\\' . ucfirst($outputFormat); $renderer = new $rendererClass(); file_put_contents($outputFile, $renderer->getOutput($graph)); @@ -152,4 +172,4 @@ function ($service) use ($onlyServices) { } }); -$application->run(); +return $application; diff --git a/src/config.php b/src/config.php new file mode 100644 index 0000000..e8e4790 --- /dev/null +++ b/src/config.php @@ -0,0 +1,75 @@ +hasVertex($id)) { + return $graph->getVertex($id); + } + + $vertex = $graph->createVertex($id); + $vertex->setAttribute('docker_compose_viz.type', 'config'); + $vertex->setAttribute('graphviz.label', $label); + $vertex->setAttribute('graphviz.shape', 'note'); + + return $vertex; +} + +function findConfigVertex(Graph $graph, string $config): Vertex +{ + return $graph->getVertex(getConfigVertexId($config)); +} + +/** + * @see https://github.com/compose-spec/compose-spec/blob/master/spec.md#configs + */ +function addConfigRelation(Graph $graph, string $service, string $config, ?array $mapping = null): void +{ + $serviceVertex = $graph->getVertex(getServiceVertexId($service)); + $configVertex = findConfigVertex($graph, $config); + $edge = null; + + if ($serviceVertex->hasEdgeTo($configVertex)) { + $edges = $serviceVertex->getEdgesTo($configVertex); + + foreach ($edges as $edge) { + if ($edge->getAttribute('docker_compose_viz.type') === 'config') { + break; + } + + $edge = null; + } + } + + if (null === $edge) { + $edge = $serviceVertex->createEdgeTo($configVertex); + } + + $edge->setAttribute('docker_compose_viz.type', 'config'); + + if (isset($mapping['target'])) { + $edge->setAttribute('graphviz.label', $mapping['target']); + } +} diff --git a/src/functions.php b/src/functions.php index 72a7128..2f53a2a 100644 --- a/src/functions.php +++ b/src/functions.php @@ -1,11 +1,12 @@ getCode(), $exception); + if (file_exists($overrideFile)) { + return $overrideFile; } + + return null; } -/** - * @public - * - * @param array $configuration Docker compose (version 1 or 2) configuration - * - * @return array List of service definitions exctracted from the configuration - */ -function fetchServices(array $configuration): array +function findConfigurationFiles(bool $ignoreOverride = false, string ...$paths): array { - if (false === isset($configuration['version']) || 1 === (int) $configuration['version']) { - return $configuration; - } + $files = []; - return $configuration['services'] ?? []; -} + foreach ($paths as $path) { + if (false === file_exists($path)) { + throw new InvalidArgumentException(sprintf('File "%s" does not exist', $path)); + } -/** - * @public - * - * @param array $configuration Docker compose (version 1 or 2) configuration - * - * @return array List of service definitions exctracted from the configuration - */ -function fetchVolumes(array $configuration): array -{ - if (false === isset($configuration['version']) || 1 === (int) $configuration['version']) { - return []; + $files[] = $path; + + if (false === $ignoreOverride) { + $override = findOverride($path); + + if (null !== $override) { + $files[] = $override; + } + } } - return $configuration['volumes'] ?? []; + return $files; } -/** - * @public - * - * @param array $configuration Docker compose (version 1 or 2) configuration - * - * @return array List of service definitions exctracted from the configuration - */ -function fetchNetworks(array $configuration): array +function readConfigurations(string ...$paths): array { - if (false === isset($configuration['version']) || 1 === (int) $configuration['version']) { - return []; + $configuration = []; + + foreach ($paths as $path) { + if (false === file_exists($path)) { + throw new InvalidArgumentException(sprintf('File "%s" does not exist', $path)); + } + + try { + $configuration = array_merge_recursive($configuration, Yaml::parse(file_get_contents($path))); + } catch (ParseException $exception) { + throw new InvalidArgumentException(sprintf('File "%s" does not contain valid YAML', $path), $exception->getCode(), $exception); + } } - return $configuration['networks'] ?? []; + return $configuration; } -/** - * @public - * - * @param array $services Docker compose service definitions - * @param array $volumes Docker compose volume definitions - * @param array $networks Docker compose network definitions - * @param bool $withVolumes Create vertices and edges for volumes - * @param string $path Path of the current docker-compose configuration file - * - * @return Graph The complete graph for the given list of services - */ -function createGraph(array $services, array $volumes, array $networks, string $path, int $flags): Graph +function createGraph(array $services, array $volumes, array $networks, array $configs, array $secrets, int $flags, string $path): Graph { - return makeVerticesAndEdges(new Graph(), $services, $volumes, $networks, $path, $flags); + return makeVerticesAndEdges(new Graph(), $services, $volumes, $networks, $configs, $secrets, $flags, $path); } -/** - * @public - * - * @param Graph $graph Input graph - * @param bool $horizontal Display a horizontal graph - * @param string $horizontal Background color (any hex color or 'transparent') - * - * @return Graph A copy of the input graph with style attributes - */ -function applyGraphvizStyle(Graph $graph, bool $horizontal, string $background): Graph +function makeVerticesAndEdges(Graph $graph, array $services, array $volumes, array $networks, array $configs, array $secrets, int $flags, string $path): Graph { - $graph = $graph->createGraphClone(); - $graph->setAttribute('graphviz.graph.bgcolor', $background); - $graph->setAttribute('graphviz.graph.pad', '0.5'); - $graph->setAttribute('graphviz.graph.ratio', 'fill'); - - if (true === $horizontal) { - $graph->setAttribute('graphviz.graph.rankdir', 'LR'); + if (false === ((bool)($flags & WITHOUT_VOLUMES))) { + iterator_apply($it = new \ArrayIterator($volumes), fn (Iterator $it) => addVolume( + $graph, + $it->key(), + VolumeKind::VOLUME + ), [$it]); } - foreach ($graph->getVertices() as $vertex) { - switch ($vertex->getAttribute('docker_compose.type')) { - case 'service': - $vertex->setAttribute('graphviz.shape', 'component'); - break; - - case 'external_service': - $vertex->setAttribute('graphviz.shape', 'component'); - $vertex->setAttribute('graphviz.color', 'gray'); - break; - - case 'volume': - $vertex->setAttribute('graphviz.shape', 'folder'); - break; - - case 'network': - $vertex->setAttribute('graphviz.shape', 'pentagon'); - break; - - case 'external_network': - $vertex->setAttribute('graphviz.shape', 'pentagon'); - $vertex->setAttribute('graphviz.color', 'gray'); - break; - - case 'port': - $vertex->setAttribute('graphviz.shape', 'circle'); - - if ('udp' === ($proto = $vertex->getAttribute('docker_compose.proto'))) { - $vertex->setAttribute('graphviz.style', 'dashed'); - } - break; - } + if (false === ((bool)($flags & WITHOUT_NETWORKS))) { + iterator_apply($it = new \ArrayIterator($networks), fn (Iterator $it) => addNetwork( + $graph, + $it->key(), + $it->current() + ), [$it]); } - foreach ($graph->getEdges() as $edge) { - switch ($edge->getAttribute('docker_compose.type')) { - case 'ports': - case 'links': - $edge->setAttribute('graphviz.style', 'solid'); - break; - - case 'external_links': - $edge->setAttribute('graphviz.style', 'solid'); - $edge->setAttribute('graphviz.color', 'gray'); - break; - - case 'volumes_from': - case 'volumes': - $edge->setAttribute('graphviz.style', 'dashed'); - break; - - case 'depends_on': - $edge->setAttribute('graphviz.style', 'dotted'); - break; - - case 'extends': - $edge->setAttribute('graphviz.dir', 'both'); - $edge->setAttribute('graphviz.arrowhead', 'inv'); - $edge->setAttribute('graphviz.arrowtail', 'dot'); - break; - } - - if (null !== ($alias = $edge->getAttribute('docker_compose.alias'))) { - $edge->setAttribute('graphviz.label', $alias); - - if (null !== $edge->getAttribute('docker_compose.condition')) { - $edge->setAttribute('graphviz.fontsize', '10'); - } - } - - if ($edge->getAttribute('docker_compose.bidir')) { - $edge->setAttribute('graphviz.dir', 'both'); - } + if (false === ((bool)($flags & WITHOUT_CONFIGS))) { + iterator_apply($it = new \ArrayIterator($configs), fn (Iterator $it) => addConfig($graph, $it->key()), [$it]); } - return $graph; -} - -/** - * @internal - * - * @param Graph $graph Input graph - * @param array $services Docker compose service definitions - * @param array $volumes Docker compose volume definitions - * @param array $networks Docker compose network definitions - * @param bool $withVolumes Create vertices and edges for volumes - * - * @return Graph A copy of the input graph with vertices and edges for services - */ -function makeVerticesAndEdges(Graph $graph, array $services, array $volumes, array $networks, string $path, int $flags): Graph -{ - if (false === ((bool) ($flags & WITHOUT_VOLUMES))) { - foreach (array_keys($volumes) as $volume) { - addVolume($graph, 'named: '.$volume); - } + if (false === ((bool)($flags & WITHOUT_SECRETS))) { + iterator_apply($it = new \ArrayIterator($secrets), fn (Iterator $it) => addSecret($graph, $it->key()), [$it]); } - if (false === ((bool) ($flags & WITHOUT_NETWORKS))) { - foreach ($networks as $network => $definition) { - addNetwork( - $graph, - 'net: '.$network, - isset($definition['external']) && true === $definition['external'] ? 'external_network' : 'network' - ); - } - } + iterator_apply($it = new \ArrayIterator($services), fn (Iterator $it) => addService($graph, $it->key()), [$it]); foreach ($services as $service => $definition) { - addService($graph, $service); - if (isset($definition['extends'])) { if (isset($definition['extends']['file'])) { - $configuration = readConfiguration(dirname($path).DIRECTORY_SEPARATOR.$definition['extends']['file']); + $extendedFile = dirname($path) . DIRECTORY_SEPARATOR . $definition['extends']['file']; + $configuration = readConfigurations($extendedFile); $extendedServices = fetchServices($configuration); $extendedVolumes = fetchVolumes($configuration); $extendedNetworks = fetchNetworks($configuration); - - $graph = makeVerticesAndEdges($graph, $extendedServices, $extendedVolumes, $extendedNetworks, dirname($path).DIRECTORY_SEPARATOR.$definition['extends']['file'], $flags); + $extendedConfigs = fetchNetworks($configuration); + $extendedSecrets = fetchNetworks($configuration); + + $graph = makeVerticesAndEdges( + $graph, + $extendedServices, + $extendedVolumes, + $extendedNetworks, + $extendedConfigs, + $extendedSecrets, + $flags, + $path + ); } - addRelation( - addService($graph, $definition['extends']['service']), - $graph->getVertex($service), - 'extends' - ); - } - - $serviceLinks = []; - - foreach ($definition['links'] ?? [] as $link) { - list($target, $alias) = explodeMapping($link); - - $serviceLinks[$alias] = $target; + addExtendsRelation($graph, $service, $definition['extends']['service']); } - foreach ($serviceLinks as $alias => $target) { - addRelation( - addService($graph, $target), - $graph->getVertex($service), - 'links', - $alias !== $target ? $alias : null - ); - } + iterator_apply($it = new \ArrayIterator($definition['links'] ?? []), fn (Iterator $it) => addLinkRelation( + $graph, + $service, + ...normalizeLinkMapping($it->current()) + ), [$it]); - foreach ($definition['external_links'] ?? [] as $link) { - list($target, $alias) = explodeMapping($link); + iterator_apply($it = new \ArrayIterator($definition['external_links'] ?? []), fn (Iterator $it) => addExternalLinkRelation( + $graph, + $service, + ...normalizeLinkMapping($it->current()) + ), [$it]); - addRelation( - addService($graph, $target, 'external_service'), - $graph->getVertex($service), - 'external_links', - $alias !== $target ? $alias : null - ); - } + iterator_apply($it = new \ArrayIterator($definition['depends_on'] ?? []), fn (Iterator $it) => addDependsRelation( + $graph, + $service, + is_array($it->current()) ? $it->key() : $it->current(), + $it->current()['condition'] ?? null + ), [$it]); - foreach ($definition['depends_on'] ?? [] as $key => $dependency) { - addRelation( - $graph->getVertex($service), - addService($graph, is_array($dependency) ? $key : $dependency), - 'depends_on', - is_array($dependency) && isset($dependency['condition']) ? $dependency['condition'] : null, - false, - is_array($dependency) && isset($dependency['condition']) - ); - } - - foreach ($definition['volumes_from'] ?? [] as $source) { - addRelation( - addService($graph, $source), - $graph->getVertex($service), - 'volumes_from' - ); - } - - if (false === ((bool) ($flags & WITHOUT_VOLUMES))) { - $serviceVolumes = []; + if (false === ((bool)($flags & WITHOUT_VOLUMES))) { + iterator_apply(new \ArrayIterator($definition['volumes_from'] ?? []), fn (Iterator $it) => addVolumesFromRelation( + $graph, + $it->current(), + $service + ), [$it]); foreach ($definition['volumes'] ?? [] as $volume) { - if (is_array($volume)) { - $host = $volume['source']; - $container = $volume['target']; - $attr = !empty($volume['read-only']) ? 'ro' : ''; - } else { - list($host, $container, $attr) = explodeVolumeMapping($volume); - } - - $serviceVolumes[$container] = [$host, $attr]; - } - - foreach ($serviceVolumes as $container => $volume) { - list($host, $attr) = $volume; - - if ('.' !== $host[0] && DIRECTORY_SEPARATOR !== $host[0]) { - $host = 'named: '.$host; - } - - addRelation( - addVolume($graph, $host), - $graph->getVertex($service), - 'volumes', - $host !== $container ? $container : null, - 'ro' !== $attr - ); + $volume = normalizeVolumeMapping($volume, $volumes); + addVolume($graph, $volume['source'], VolumeKind::BIND); + addVolumeRelation($graph, $service, $volume); } } - if (false === ((bool) ($flags & WITHOUT_PORTS))) { - foreach ($definition['ports'] ?? [] as $port) { - list($target, $host, $container, $proto) = explodePortMapping($port); - - addRelation( - addPort($graph, (int) $host, $proto, $target), - $graph->getVertex($service), - 'ports', - $host !== $container ? $container : null - ); - } + if (false === ((bool)($flags & WITHOUT_PORTS))) { + iterator_apply($it = new \ArrayIterator($definition['ports'] ?? []), fn (Iterator $it) => addPortRelation( + $graph, + $service, + normalizePortMapping($it->current()) + ), [$it]); } - if (false === ((bool) ($flags & WITHOUT_NETWORKS))) { - foreach ($definition['networks'] ?? [] as $network => $config) { - $network = is_int($network) ? $config : $network; - $config = is_int($network) ? [] : $config; - $aliases = $config['aliases'] ?? []; - - addRelation( - $graph->getVertex($service), - addNetwork($graph, 'net: '.$network), - 'networks', - count($aliases) > 0 ? implode(', ', $aliases) : null - ); - } + if (false === ((bool)($flags & WITHOUT_NETWORKS))) { + iterator_apply($it = new \ArrayIterator($definition['networks'] ?? []), fn (Iterator $it) => addNetworkRelation( + $graph, + $service, + is_int($it->key()) ? $it->current() : $it->key(), + is_int($it->key()) ? [] : $it->current() + ), [$it]); } - } - - return $graph; -} - -/** - * @internal - * - * @param Graph $graph Input graph - * @param string $service Service name - * @param string $type Service type - * - * @return Vertex - */ -function addService(Graph $graph, string $service, string $type = null) -{ - if (true === $graph->hasVertex($service)) { - return $graph->getVertex($service); - } - - $vertex = $graph->createVertex($service); - $vertex->setAttribute('docker_compose.type', $type ?: 'service'); - - return $vertex; -} - -/** - * @internal - * - * @param Graph $graph Input graph - * @param int $port Port number - * @param string|null $proto Protocol - * - * @return Vertex - */ -function addPort(Graph $graph, int $port, string $proto = null, string $target = null) -{ - $target = $target ? $target.':' : null; - - if (true === $graph->hasVertex($target.$port)) { - return $graph->getVertex($target.$port); - } - - $vertex = $graph->createVertex($target.$port); - $vertex->setAttribute('docker_compose.type', 'port'); - $vertex->setAttribute('docker_compose.proto', $proto ?: 'tcp'); - - return $vertex; -} - -/** - * @internal - * - * @param Graph $graph Input graph - * @param string $path Path - * - * @return Vertex - */ -function addVolume(Graph $graph, string $path) -{ - if (true === $graph->hasVertex($path)) { - return $graph->getVertex($path); - } - - $vertex = $graph->createVertex($path); - $vertex->setAttribute('docker_compose.type', 'volume'); - - return $vertex; -} - -/** - * @internal - * - * @param Graph $graph Input graph - * @param string $name Name of the network - * @param string $type Network type - * - * @return Vertex - */ -function addNetwork(Graph $graph, string $name, string $type = null) -{ - if (true === $graph->hasVertex($name)) { - return $graph->getVertex($name); - } - - $vertex = $graph->createVertex($name); - $vertex->setAttribute('docker_compose.type', $type ?: 'network'); - - return $vertex; -} -/** - * @internal - * - * @param Vertex $from Source vertex - * @param Vertex $to Destination vertex - * @param string $type Type of the relation (one of "links", "volumes_from", "depends_on", "ports"); - * @param string|null $alias Alias associated to the linked element - * @param bool|null $bidirectional Biderectional or not - * @param bool|null $condition Wether the alias represents a condition or not - */ -function addRelation(Vertex $from, Vertex $to, string $type, string $alias = null, bool $bidirectional = false, bool $condition = false): Edge\Directed -{ - $edge = null; - - if ($from->hasEdgeTo($to)) { - $edges = $from->getEdgesTo($to); - - foreach ($edges as $edge) { - if ($edge->getAttribute('docker_compose.type') === $type) { - break; - } + if (false === ((bool)($flags & WITHOUT_CONFIGS))) { + iterator_apply($it = new \ArrayIterator($definition['configs'] ?? []), fn (Iterator $it) => addConfigRelation( + $graph, + $service, + is_array($it->current()) ? $it->current()['source'] : $it->current(), + is_array($it->current()) ? $it->current() : [] + ), [$it]); } - } - - if (null === $edge) { - $edge = $from->createEdgeTo($to); - } - $edge->setAttribute('docker_compose.type', $type); - - if (null !== $alias) { - $edge->setAttribute('docker_compose.alias', $alias); - } - - if (true === $condition) { - $edge->setAttribute('docker_compose.condition', true); + if (false === ((bool)($flags & WITHOUT_SECRETS))) { + iterator_apply($it = new \ArrayIterator($definition['secrets'] ?? []), fn (Iterator $it) => addSecretRelation( + $graph, + $service, + $it->current() + ), [$it]); + } } - $edge->setAttribute('docker_compose.bidir', $bidirectional); - - return $edge; -} - -/** - * @internal - * - * @param string $mapping A docker mapping ([:]) - * - * @return array An 2 or 3 items array containing the parts of the mapping. - * If the mapping does not specify a second part, the first one will be repeated - */ -function explodeMapping($mapping): array -{ - $parts = explode(':', $mapping); - $parts[1] = $parts[1] ?? $parts[0]; - - return [$parts[0], $parts[1]]; + return $graph; } -/** - * @internal - * - * @param string $mapping A docker mapping ([:]) - * - * @return array An 2 or 3 items array containing the parts of the mapping. - * If the mapping does not specify a second part, the first one will be repeated - */ -function explodeVolumeMapping($mapping): array +function applyGraphvizStyle(Graph $graph, bool $horizontal, string $background): Graph { - $parts = explode(':', $mapping); - $parts[1] = $parts[1] ?? $parts[0]; - - return [$parts[0], $parts[1], $parts[2] ?? null]; -} + $graph = $graph->createGraphClone(); + $graph->setAttribute('graphviz.graph.bgcolor', $background); + $graph->setAttribute('graphviz.graph.pad', '0.5'); + $graph->setAttribute('graphviz.graph.ratio', 'fill'); + $graph->setAttribute('graphviz.graph.splines', 'true'); + $graph->setAttribute('graphviz.graph.overlap', 'false'); -/** - * @internal - * - * @param string $mapping A docker mapping ([:]) - * - * @return array An 2 or 3 items array containing the parts of the mapping. - * If the mapping does not specify a second part, the first one will be repeated - */ -function explodePortMapping($mapping): array -{ - $parts = explode(':', $mapping); - - if (count($parts) < 3) { - $target = null; - $host = $parts[0]; - $container = $parts[1] ?? $parts[0]; - } else { - $target = $parts[0]; - $host = $parts[1]; - $container = $parts[2]; + if (true === $horizontal) { + $graph->setAttribute('graphviz.graph.rankdir', 'LR'); } - $subparts = array_values(array_filter(explode('/', $container))); - - return [$target, $host, $subparts[0], $subparts[1] ?? null]; + return $graph; } diff --git a/src/network.php b/src/network.php new file mode 100644 index 0000000..1db53a6 --- /dev/null +++ b/src/network.php @@ -0,0 +1,76 @@ +hasVertex($id)) { + return $graph->getVertex($id); + } + + $vertex = $graph->createVertex($id); + $vertex->setAttribute('docker_compose_viz.type', 'network'); + $vertex->setAttribute('graphviz.label', $label); + $vertex->setAttribute('graphviz.shape', 'pentagon'); + + if (isset($definition['external'])) { + $vertex->setAttribute('graphviz.color', 'gray'); + } + + return $vertex; +} + +/** + * @see https://github.com/compose-spec/compose-spec/blob/master/spec.md#networks + */ +function addNetworkRelation(Graph $graph, string $service, string $network, ?array $mapping = null): void +{ + $serviceVertex = $graph->getVertex(getServiceVertexId($service)); + $networkVertex = $graph->getVertex(getNetworkVertexId($network)); + $edge = null; + + if ($serviceVertex->hasEdgeTo($networkVertex)) { + $edges = $serviceVertex->getEdgesTo($networkVertex); + + foreach ($edges as $edge) { + if ($edge->getAttribute('docker_compose_viz.type') === 'network') { + break; + } + + $edge = null; + } + } + + if (null === $edge) { + $edge = $serviceVertex->createEdgeTo($networkVertex); + } + + $edge->setAttribute('docker_compose_viz.type', 'network'); + + $aliases = $mapping['aliases'] ?? []; + + if (count($aliases) > 0) { + $edge->setAttribute('graphviz.label', implode(', ', $aliases)); + } +} diff --git a/src/port.php b/src/port.php new file mode 100644 index 0000000..c0db7bc --- /dev/null +++ b/src/port.php @@ -0,0 +1,109 @@ +hasVertex($id)) { + return $graph->getVertex($id); + } + + $vertex = $graph->createVertex($id); + $vertex->setAttribute('docker_compose_viz.type', 'port'); + $vertex->setAttribute('graphviz.label', $label); + $vertex->setAttribute('graphviz.shape', 'circle'); + + if ('udp' === $definition['proto']) { + $vertex->setAttribute('graphviz.style', 'dashed'); + } + + return $vertex; +} + +function addPortRelation(Graph $graph, string $service, array $definition): void +{ + $serviceVertex = $graph->getVertex(getServiceVertexId($service)); + $configVertex = addPort($graph, $definition); + $edge = null; + + if ($serviceVertex->hasEdgeTo($configVertex)) { + $edges = $serviceVertex->getEdgesTo($configVertex); + + foreach ($edges as $edge) { + if ($edge->getAttribute('docker_compose_viz.type') === 'port') { + break; + } + + $edge = null; + } + } + + if (null === $edge) { + $edge = $serviceVertex->createEdgeTo($configVertex); + } + + $edge->setAttribute('docker_compose_viz.type', 'port'); + $edge->setAttribute('graphviz.style', 'solid'); + + if (isset($definition['target'])) { + $edge->setAttribute('graphviz.label', $definition['target']); + } +} + +/** + * @see https://github.com/compose-spec/compose-spec/blob/master/spec.md#networks + */ +function normalizePortMapping(string|array $mapping): array +{ + if (is_array($mapping)) { + return $mapping; + } + + $parts = explode(':', $mapping); + $ip = null; + $published = null; + $target = null; + $proto = null; + + if (count($parts) === 1) { + $target = $parts[0]; + $published = $target; + } elseif (count($parts) === 2) { + $published = $parts[0]; + $target = $parts[1]; + } elseif (count($parts) === 3) { + $ip = $parts[0]; + $published = $parts[1]; + $target = $parts[2]; + } + + $subparts = array_values(array_filter(explode('/', $target))); + + if (isset($subparts[1])) { + $proto = $subparts[1]; + } + + return [ + 'host_ip' => $ip, + 'published' => $published, + 'target' => $target, + 'proto' => $proto, + ]; +} diff --git a/src/secret.php b/src/secret.php new file mode 100644 index 0000000..532fe70 --- /dev/null +++ b/src/secret.php @@ -0,0 +1,74 @@ +hasVertex($id)) { + return $graph->getVertex($id); + } + + $vertex = $graph->createVertex($id); + $vertex->setAttribute('docker_compose_viz.type', 'secret'); + $vertex->setAttribute('graphviz.shape', 'hexagon'); + + return $vertex; +} + +/** + * @see https://github.com/compose-spec/compose-spec/blob/master/spec.md#secrets + */ +function addSecretRelation(Graph $graph, string $service, string|array $mapping): void +{ + $serviceVertex = $graph->getVertex(getServiceVertexId($service)); + + if (is_string($mapping)) { + $configVertex = $graph->getVertex(getSecretVertexId($mapping)); + } else { + $configVertex = $graph->getVertex(getSecretVertexId($mapping['source'])); + } + + $edge = null; + + if ($serviceVertex->hasEdgeTo($configVertex)) { + $edges = $serviceVertex->getEdgesTo($configVertex); + + foreach ($edges as $edge) { + if ($edge->getAttribute('docker_compose_viz.type') === 'secret') { + break; + } + + $edge = null; + } + } + + if (null === $edge) { + $edge = $serviceVertex->createEdgeTo($configVertex); + } + + $edge->setAttribute('docker_compose_viz.type', 'secret'); + + if (isset($mapping['target'])) { + $edge->setAttribute('graphviz.label', $mapping['target']); + } +} diff --git a/src/service.php b/src/service.php new file mode 100644 index 0000000..1083d0a --- /dev/null +++ b/src/service.php @@ -0,0 +1,225 @@ +hasVertex($id)) { + return $graph->getVertex($id); + } + + $vertex = $graph->createVertex($id); + $vertex->setAttribute('docker_compose_viz.type', 'service'); + $vertex->setAttribute('graphviz.shape', 'component'); + + if ('external_service' === $type) { + $vertex->setAttribute('graphviz.color', 'gray'); + } + + return $vertex; +} + +function findServiceVertex(Graph $graph, string $service): Vertex +{ + return $graph->getVertex(getServiceVertexId($service)); +} + +/** + * @see https://github.com/compose-spec/compose-spec/blob/master/spec.md#extends + */ +function addExtendsRelation(Graph $graph, string $service, string $extended): void +{ + $serviceVertex = findServiceVertex($graph, $service); + $extendedVertex = findServiceVertex($graph, $extended); + + $edge = null; + + if ($serviceVertex->hasEdgeTo($extendedVertex)) { + $edges = $serviceVertex->getEdgesTo($extendedVertex); + + foreach ($edges as $edge) { + if ($edge->getAttribute('docker_compose_viz.type') === 'extends') { + break; + } + + $edge = null; + } + } + + if (null === $edge) { + $edge = $serviceVertex->createEdgeTo($extendedVertex); + } + + $edge->setAttribute('docker_compose_viz.type', 'extends'); + $edge->setAttribute('graphviz.dir', 'both'); + $edge->setAttribute('graphviz.arrowhead', 'inv'); + $edge->setAttribute('graphviz.arrowtail', 'dot'); +} + +/** + * @see https://github.com/compose-spec/compose-spec/blob/master/spec.md#links + */ +function addLinkRelation(Graph $graph, string $service, string $linked, ?string $alias = null): void +{ + $serviceVertex = findServiceVertex($graph, $service); + $linkedVertex = findServiceVertex($graph, $linked); + + $edge = null; + + if ($serviceVertex->hasEdgeTo($linkedVertex)) { + $edges = $serviceVertex->getEdgesTo($linkedVertex); + + foreach ($edges as $edge) { + if ($edge->getAttribute('docker_compose_viz.type') === 'link') { + break; + } + + $edge = null; + } + } + + if (null === $edge) { + $edge = $serviceVertex->createEdgeTo($linkedVertex); + } + + $edge->setAttribute('docker_compose_viz.type', 'link'); + $edge->setAttribute('graphviz.style', 'solid'); + + if (null !== $alias) { + $edge->setAttribute('graphviz.label', $alias); + } +} + +/** + * @see https://github.com/compose-spec/compose-spec/blob/master/spec.md#external_links + */ +function addExternalLinkRelation(Graph $graph, string $service, string $linked, ?string $alias = null): void +{ + $serviceVertex = findServiceVertex($graph, $service); + $linkedVertex = findServiceVertex($graph, $linked); + + $edge = null; + + if ($serviceVertex->hasEdgeTo($linkedVertex)) { + $edges = $serviceVertex->getEdgesTo($linkedVertex); + + foreach ($edges as $edge) { + if ($edge->getAttribute('docker_compose_viz.type') === 'external_link') { + break; + } + + $edge = null; + } + } + + if (null === $edge) { + $edge = $serviceVertex->createEdgeTo($linkedVertex); + } + + $edge->setAttribute('docker_compose_viz.type', 'external_link'); + $edge->setAttribute('graphviz.style', 'solid'); + $edge->setAttribute('graphviz.color', 'gray'); + + if (null !== $alias) { + $edge->setAttribute('graphviz.label', $alias); + } +} + +/** + * @see https://github.com/compose-spec/compose-spec/blob/master/spec.md#depends_on + */ +function addDependsRelation(Graph $graph, string $service, string $dependency, ?string $condition = null): void +{ + $serviceVertex = findServiceVertex($graph, $service); + $dependencyVertex = findServiceVertex($graph, $dependency); + + $edge = null; + + if ($serviceVertex->hasEdgeTo($dependencyVertex)) { + $edges = $serviceVertex->getEdgesTo($dependencyVertex); + + foreach ($edges as $edge) { + if ($edge->getAttribute('docker_compose_viz.type') === 'depends') { + break; + } + + $edge = null; + } + } + + if (null === $edge) { + $edge = $serviceVertex->createEdgeTo($dependencyVertex); + } + + $edge->setAttribute('docker_compose_viz.type', 'depends'); + $edge->setAttribute('graphviz.style', 'dotted'); + + if (null !== $condition) { + if (null !== $edge->getAttribute('graphviz.label')) { + $label = $edge->getAttribute('graphviz.label'); + $edge->setAttribute('graphviz.label', $label . ' (' . $condition . ')'); + } else { + $edge->setAttribute('graphviz.label', $condition); + } + } +} + +/** + * @see https://github.com/compose-spec/compose-spec/blob/master/spec.md#volumes_from + */ +function addVolumesFromRelation(Graph $graph, string $service, string $dependency): void +{ + $serviceVertex = findServiceVertex($graph, $service); + $dependencyVertex = findServiceVertex($graph, $dependency); + + $edge = null; + + if ($serviceVertex->hasEdgeTo($dependencyVertex)) { + $edges = $serviceVertex->getEdgesTo($dependencyVertex); + + foreach ($edges as $edge) { + if ($edge->getAttribute('docker_compose_viz.type') === 'volumes_from') { + break; + } + + $edge = null; + } + } + + if (null === $edge) { + $edge = $serviceVertex->createEdgeTo($dependencyVertex); + } + + $edge->setAttribute('docker_compose_viz.type', 'volumes_from'); + $edge->setAttribute('graphviz.style', 'dashed'); +} + +/** + * @see https://github.com/compose-spec/compose-spec/blob/master/spec.md#links + */ +function normalizeLinkMapping(string $mapping): array +{ + $parts = explode(':', $mapping); + + return [$parts[0], $parts[1] ?? $parts[0]]; +} diff --git a/src/volume.php b/src/volume.php new file mode 100644 index 0000000..6c626aa --- /dev/null +++ b/src/volume.php @@ -0,0 +1,105 @@ +hasVertex($id)) { + return $graph->getVertex($id); + } + + $vertex = $graph->createVertex($id); + $vertex->setAttribute('docker_compose_viz.type', 'volume'); + $vertex->setAttribute('graphviz.label', $label); + $vertex->setAttribute('graphviz.shape', 'pentagon'); + + if (VolumeKind::VOLUME === $type) { + $vertex->setAttribute('graphviz.color', 'blue'); + } + + return $vertex; +} + +function addVolumeRelation(Graph $graph, string $service, array $definition): void +{ + $serviceVertex = $graph->getVertex(getServiceVertexId($service)); + $volumeVertex = $graph->getVertex(getVolumeVertexId($definition['source'])); + $edge = null; + + if ($serviceVertex->hasEdgeTo($volumeVertex)) { + $edges = $serviceVertex->getEdgesTo($volumeVertex); + + foreach ($edges as $edge) { + if ($edge->getAttribute('docker_compose_viz.type') === 'volume') { + break; + } + + $edge = null; + } + } + + if (null === $edge) { + $edge = $serviceVertex->createEdgeTo($volumeVertex); + } + + $edge->setAttribute('docker_compose_viz.type', 'volume'); + $edge->setAttribute('graphviz.style', 'dashed'); + + if (false === ($definition['read_only'] ?? false)) { + $edge->setAttribute('graphviz.dir', 'both'); + } + + if (isset($definition['target'])) { + $edge->setAttribute('graphviz.label', $definition['target']); + } +} + +/** + * @see https://github.com/compose-spec/compose-spec/blob/master/spec.md#volumes + */ +function normalizeVolumeMapping(string|array $mapping, array $volumes): array +{ + if (is_array($mapping)) { + return $mapping; + } + + $parts = explode(':', $mapping); + $parts[1] = $parts[1] ?? $parts[0]; + $parts[2] = explode(', ', $parts[2] ?? 'rw'); + + $type = in_array($parts[0], $volumes, true) ? 'volume' : 'bind'; + + return [ + 'type' => $type, + 'source' => $parts[0], + 'target' => $parts[1], + 'read_only' => in_array('ro', $parts[2], true), + ]; +} diff --git a/tests/ConfigTest.php b/tests/ConfigTest.php new file mode 100644 index 0000000..1edab40 --- /dev/null +++ b/tests/ConfigTest.php @@ -0,0 +1,341 @@ +assertEquals([], fetchConfigs([])); + } + + /** + * @test + */ + public function returnConfigurationsFromDockerComposeConfiguration(): void + { + $configuration = [ + 'configs' => [ + 'test-config' => ['external' => true] + ] + ]; + + $this->assertEquals($configuration['configs'], fetchConfigs($configuration)); + } + + /** + * @test + */ + public function generateVertexId(): void + { + $config = 'test-config'; + + $this->assertEquals('config:'.$config, getConfigVertexId($config)); + } + + /** + * @test + */ + public function checkIfVertexAlreadyExistsInGraph(): void + { + $config = 'test-config'; + $graph = $this->createMock(Graph::class); + $graph->expects($this->once()) + ->method('hasVertex') + ->with(getConfigVertexId($config)) + ->will($this->returnValue(true)); + $graph->expects($this->once()) + ->method('getVertex') + ->with(getConfigVertexId($config)) + ->will($this->returnValue(new Vertex($graph, getConfigVertexId($config)))); + + addConfig($graph, $config); + } + + /** + * @test + */ + public function addVertexIfNotExistInGraph(): void + { + $config = 'test-config'; + $graph = $this->createMock(Graph::class); + $graph->expects($this->once()) + ->method('hasVertex') + ->with(getConfigVertexId($config)) + ->will($this->returnValue(false)); + $graph->expects($this->once()) + ->method('createVertex') + ->with(getConfigVertexId($config)) + ->will($this->returnValue(new Vertex($graph, getConfigVertexId($config)))); + + addConfig($graph, $config); + } + + /** + * @test + */ + public function setVertexAttributesWhenAdding(): void + { + $config = 'test-config'; + $graph = $this->createMock(Graph::class); + $vertex = $this->createMock(Vertex::class); + $vertex->expects($this->exactly(3)) + ->method('setAttribute') + ->withConsecutive( + [$this->equalTo('docker_compose_viz.type'), $this->equalTo('config')], + [$this->equalTo('graphviz.label'), $this->equalTo($config)], + [$this->equalTo('graphviz.shape'), $this->equalTo('note')], + ); + $graph->expects($this->once()) + ->method('hasVertex') + ->with(getConfigVertexId($config)) + ->will($this->returnValue(false)); + $graph->expects($this->once()) + ->method('createVertex') + ->with(getConfigVertexId($config)) + ->will($this->returnValue($vertex)); + + addConfig($graph, $config); + } + + /** + * @test + */ + public function addRelationWithUnknownService(): void + { + $this->expectException(OutOfBoundsException::class); + + addConfigRelation(new Graph(), 'unknown', 'test-config'); + } + + /** + * @test + */ + public function addRelationWithUnknownConfiguration(): void + { + $this->expectException(OutOfBoundsException::class); + + $service = 'test-service'; + $graph = new Graph(); + $graph->createVertex($service); + + addConfigRelation(new Graph(), $service, 'unknown'); + } + + /** + * @test + */ + public function checkIfEdgeOfExpectedTypeAlreadyExistsInGraph(): void + { + $config = 'test-config'; + $service = 'test-service'; + $graph = $this->createMock(Graph::class); + $configVertex = $this->createMock(Vertex::class); + $serviceVertex = $this->createMock(Vertex::class); + $relation = $this->createMock(Directed::class); + $graph->expects($this->exactly(2)) + ->method('getVertex') + ->withConsecutive( + [$this->equalTo(getServiceVertexId($service))], + [$this->equalTo(getConfigVertexId($config))], + ) + ->will( + $this->onConsecutiveCalls( + $this->returnValue($serviceVertex), + $this->returnValue($configVertex), + ), + ); + $serviceVertex->expects($this->once()) + ->method('hasEdgeTo') + ->with($this->identicalTo($configVertex)) + ->will($this->returnValue(true)); + $serviceVertex->expects($this->once()) + ->method('getEdgesTo') + ->with($this->equalTo($configVertex)) + ->will($this->returnValue([$relation])); + $serviceVertex->expects(($this->never())) + ->method('createEdgeTo') + ->with($this->identicalTo($configVertex)) + ->will($this->returnValue($this->createMock(Directed::class))); + $relation->expects($this->once()) + ->method('getAttribute') + ->with($this->equalTo('docker_compose_viz.type')) + ->will($this->returnValue('config')); + + addConfigRelation($graph, $service, $config); + } + + /** + * @test + */ + public function addEdgeIfNotExistWithExpectedTypeInGraph(): void + { + $config = 'test-config'; + $service = 'test-service'; + $graph = $this->createMock(Graph::class); + $configVertex = $this->createMock(Vertex::class); + $serviceVertex = $this->createMock(Vertex::class); + $relation = $this->createMock(Directed::class); + $graph->expects($this->exactly(2)) + ->method('getVertex') + ->withConsecutive( + [$this->equalTo(getServiceVertexId($service))], + [$this->equalTo(getConfigVertexId($config))], + ) + ->will( + $this->onConsecutiveCalls( + $this->returnValue($serviceVertex), + $this->returnValue($configVertex), + ), + ); + $serviceVertex->expects($this->once()) + ->method('hasEdgeTo') + ->with($this->identicalTo($configVertex)) + ->will($this->returnValue(true)); + $serviceVertex->expects($this->once()) + ->method('getEdgesTo') + ->with($this->equalTo($configVertex)) + ->will($this->returnValue([$relation])); + $serviceVertex->expects(($this->once())) + ->method('createEdgeTo') + ->with($this->identicalTo($configVertex)) + ->will($this->returnValue($this->createMock(Directed::class))); + $relation->expects($this->once()) + ->method('getAttribute') + ->with($this->equalTo('docker_compose_viz.type')) + ->will($this->returnValue('unknown')); + + addConfigRelation($graph, $service, $config); + } + + /** + * @test + */ + public function addEdgeIfNotExistInGraph(): void + { + $config = 'test-config'; + $service = 'test-service'; + $graph = $this->createMock(Graph::class); + $configVertex = $this->createMock(Vertex::class); + $serviceVertex = $this->createMock(Vertex::class); + $graph->expects($this->exactly(2)) + ->method('getVertex') + ->withConsecutive( + [$this->equalTo(getServiceVertexId($service))], + [$this->equalTo(getConfigVertexId($config))], + ) + ->will( + $this->onConsecutiveCalls( + $this->returnValue($serviceVertex), + $this->returnValue($configVertex), + ), + ); + $serviceVertex->expects($this->once()) + ->method('hasEdgeTo') + ->with($this->identicalTo($configVertex)) + ->will($this->returnValue(false)); + $serviceVertex->expects(($this->once())) + ->method('createEdgeTo') + ->with($this->identicalTo($configVertex)) + ->will($this->returnValue($this->createMock(Directed::class))); + + addConfigRelation($graph, $service, $config); + } + + /** + * @test + */ + public function setEdgeAttributesWhenAdding(): void + { + $config = 'test-config'; + $service = 'test-service'; + $graph = $this->createMock(Graph::class); + $configVertex = $this->createMock(Vertex::class); + $serviceVertex = $this->createMock(Vertex::class); + $relation = $this->createMock(Directed::class); + $graph->expects($this->exactly(2)) + ->method('getVertex') + ->withConsecutive( + [$this->equalTo(getServiceVertexId($service))], + [$this->equalTo(getConfigVertexId($config))] + ) + ->will( + $this->onConsecutiveCalls( + $this->returnValue($serviceVertex), + $this->returnValue($configVertex), + ) + ); + $serviceVertex->expects($this->once()) + ->method('hasEdgeTo') + ->with($this->identicalTo($configVertex)) + ->will($this->returnValue(false)); + $serviceVertex->expects(($this->once())) + ->method('createEdgeTo') + ->with($this->identicalTo($configVertex)) + ->will($this->returnValue($relation)); + $relation->expects($this->once()) + ->method('setAttribute') + ->with($this->equalTo('docker_compose_viz.type'), $this->equalTo('config')); + + addConfigRelation($graph, $service, $config); + } + + /** + * @test + */ + public function setEdgeAttributesWhenAddingWithMapping(): void + { + $config = 'test-config'; + $service = 'test-service'; + $mapping = ['target' => 'config-target']; + $graph = $this->createMock(Graph::class); + $configVertex = $this->createMock(Vertex::class); + $serviceVertex = $this->createMock(Vertex::class); + $relation = $this->createMock(Directed::class); + $graph->expects($this->exactly(2)) + ->method('getVertex') + ->withConsecutive( + [$this->equalTo(getServiceVertexId($service))], + [$this->equalTo(getConfigVertexId($config))], + ) + ->will( + $this->onConsecutiveCalls( + $this->returnValue($serviceVertex), + $this->returnValue($configVertex), + ), + ); + $serviceVertex->expects($this->once()) + ->method('hasEdgeTo') + ->with($this->identicalTo($configVertex)) + ->will($this->returnValue(false)); + $serviceVertex->expects(($this->once())) + ->method('createEdgeTo') + ->with($this->identicalTo($configVertex)) + ->will($this->returnValue($relation)); + $relation->expects($this->exactly(2)) + ->method('setAttribute') + ->withConsecutive( + [$this->equalTo('docker_compose_viz.type'), $this->equalTo('config')], + [$this->equalTo('graphviz.label'), $this->equalTo($mapping['target'])], + ); + + addConfigRelation($graph, $service, $config, $mapping); + } +} diff --git a/tests/NetworkTest.php b/tests/NetworkTest.php new file mode 100644 index 0000000..3b3388a --- /dev/null +++ b/tests/NetworkTest.php @@ -0,0 +1,369 @@ +assertEquals([], fetchNetworks([])); + } + + /** + * @test + */ + public function returnNetworksFromDockerComposeConfiguration(): void + { + $configuration = [ + 'networks' => [ + 'test-net' => ['external' => true] + ] + ]; + + $this->assertEquals($configuration['networks'], fetchNetworks($configuration)); + } + + /** + * @test + */ + public function generateVertexId(): void + { + $network = 'test-net'; + + $this->assertEquals('net:'.$network, getNetworkVertexId($network)); + } + + /** + * @test + */ + public function checkIfVertexAlreadyExistsInGraph(): void + { + $network = 'test-net'; + $graph = $this->createMock(Graph::class); + $graph->expects($this->once()) + ->method('hasVertex') + ->with(getNetworkVertexId($network)) + ->will($this->returnValue(true)); + $graph->expects($this->once()) + ->method('getVertex') + ->with(getNetworkVertexId($network)) + ->will($this->returnValue(new Vertex($graph, getNetworkVertexId($network)))); + + addNetwork($graph, $network); + } + + /** + * @test + */ + public function addVertexIfNotExistInGraph(): void + { + $network = 'test-net'; + $graph = $this->createMock(Graph::class); + $graph->expects($this->once()) + ->method('hasVertex') + ->with(getNetworkVertexId($network)) + ->will($this->returnValue(false)); + $graph->expects($this->once()) + ->method('createVertex') + ->with(getNetworkVertexId($network)) + ->will($this->returnValue(new Vertex($graph, getNetworkVertexId($network)))); + + addNetwork($graph, $network); + } + + /** + * @test + */ + public function setVertexAttributesWhenAdding(): void + { + $network = 'test-net'; + $graph = $this->createMock(Graph::class); + $vertex = $this->createMock(Vertex::class); + $vertex->expects($this->exactly(3)) + ->method('setAttribute') + ->withConsecutive( + [$this->equalTo('docker_compose_viz.type'), $this->equalTo('network')], + [$this->equalTo('graphviz.label'), $this->equalTo($network)], + [$this->equalTo('graphviz.shape'), $this->equalTo('pentagon')], + ); + $graph->expects($this->once()) + ->method('hasVertex') + ->with(getNetworkVertexId($network)) + ->will($this->returnValue(false)); + $graph->expects($this->once()) + ->method('createVertex') + ->with(getNetworkVertexId($network)) + ->will($this->returnValue($vertex)); + + addNetwork($graph, $network); + } + + /** + * @test + */ + public function setVertexLabelAttributeFromName(): void + { + $network = 'test-net'; + $definition = ['name' => 'test-net-name']; + $graph = $this->createMock(Graph::class); + $vertex = $this->createMock(Vertex::class); + $vertex->expects($this->exactly(3)) + ->method('setAttribute') + ->withConsecutive( + [$this->equalTo('docker_compose_viz.type'), $this->equalTo('network')], + [$this->equalTo('graphviz.label'), $this->equalTo($definition['name'])], + [$this->equalTo('graphviz.shape'), $this->equalTo('pentagon')], + ); + $graph->expects($this->once()) + ->method('hasVertex') + ->with(getNetworkVertexId($network)) + ->will($this->returnValue(false)); + $graph->expects($this->once()) + ->method('createVertex') + ->with(getNetworkVertexId($network)) + ->will($this->returnValue($vertex)); + + addNetwork($graph, $network, $definition); + } + + /** + * @test + */ + public function addRelationWithUnknownService(): void + { + $this->expectException(OutOfBoundsException::class); + + addNetworkRelation(new Graph(), 'unknown', 'test-net'); + } + + /** + * @test + */ + public function addRelationWithUnknownNetwork(): void + { + $this->expectException(OutOfBoundsException::class); + + $service = 'test-service'; + $graph = new Graph(); + $graph->createVertex($service); + + addNetworkRelation(new Graph(), $service, 'unknown'); + } + + /** + * @test + */ + public function checkIfEdgeOfExpectedTypeAlreadyExistsInGraph(): void + { + $network = 'test-net'; + $service = 'test-service'; + $graph = $this->createMock(Graph::class); + $networkVertex = $this->createMock(Vertex::class); + $serviceVertex = $this->createMock(Vertex::class); + $relation = $this->createMock(Directed::class); + $graph->expects($this->exactly(2)) + ->method('getVertex') + ->withConsecutive( + [$this->equalTo(getServiceVertexId($service))], + [$this->equalTo(getNetworkVertexId($network))], + ) + ->will( + $this->onConsecutiveCalls( + $this->returnValue($serviceVertex), + $this->returnValue($networkVertex), + ), + ); + $serviceVertex->expects($this->once()) + ->method('hasEdgeTo') + ->with($this->identicalTo($networkVertex)) + ->will($this->returnValue(true)); + $serviceVertex->expects($this->once()) + ->method('getEdgesTo') + ->with($this->equalTo($networkVertex)) + ->will($this->returnValue([$relation])); + $serviceVertex->expects(($this->never())) + ->method('createEdgeTo') + ->with($this->identicalTo($networkVertex)) + ->will($this->returnValue($this->createMock(Directed::class))); + $relation->expects($this->once()) + ->method('getAttribute') + ->with($this->equalTo('docker_compose_viz.type')) + ->will($this->returnValue('network')); + + addNetworkRelation($graph, $service, $network); + } + + /** + * @test + */ + public function addEdgeIfNotExistWithExpectedTypeInGraph(): void + { + $network = 'test-net'; + $service = 'test-service'; + $graph = $this->createMock(Graph::class); + $networkVertex = $this->createMock(Vertex::class); + $serviceVertex = $this->createMock(Vertex::class); + $relation = $this->createMock(Directed::class); + $graph->expects($this->exactly(2)) + ->method('getVertex') + ->withConsecutive( + [$this->equalTo(getServiceVertexId($service))], + [$this->equalTo(getNetworkVertexId($network))], + ) + ->will( + $this->onConsecutiveCalls( + $this->returnValue($serviceVertex), + $this->returnValue($networkVertex), + ), + ); + $serviceVertex->expects($this->once()) + ->method('hasEdgeTo') + ->with($this->identicalTo($networkVertex)) + ->will($this->returnValue(true)); + $serviceVertex->expects($this->once()) + ->method('getEdgesTo') + ->with($this->equalTo($networkVertex)) + ->will($this->returnValue([$relation])); + $serviceVertex->expects(($this->once())) + ->method('createEdgeTo') + ->with($this->identicalTo($networkVertex)) + ->will($this->returnValue($this->createMock(Directed::class))); + $relation->expects($this->once()) + ->method('getAttribute') + ->with($this->equalTo('docker_compose_viz.type')) + ->will($this->returnValue('unknown')); + + addNetworkRelation($graph, $service, $network); + } + + /** + * @test + */ + public function addEdgeIfNotExistInGraph(): void + { + $network = 'test-net'; + $service = 'test-service'; + $graph = $this->createMock(Graph::class); + $networkVertex = $this->createMock(Vertex::class); + $serviceVertex = $this->createMock(Vertex::class); + $graph->expects($this->exactly(2)) + ->method('getVertex') + ->withConsecutive( + [$this->equalTo(getServiceVertexId($service))], + [$this->equalTo(getNetworkVertexId($network))], + ) + ->will( + $this->onConsecutiveCalls( + $this->returnValue($serviceVertex), + $this->returnValue($networkVertex), + ), + ); + $serviceVertex->expects($this->once()) + ->method('hasEdgeTo') + ->with($this->identicalTo($networkVertex)) + ->will($this->returnValue(false)); + $serviceVertex->expects(($this->once())) + ->method('createEdgeTo') + ->with($this->identicalTo($networkVertex)) + ->will($this->returnValue($this->createMock(Directed::class))); + + addNetworkRelation($graph, $service, $network); + } + + /** + * @test + */ + public function setEdgeAttributesWhenAdding(): void + { + $network = 'test-net'; + $service = 'test-service'; + $graph = $this->createMock(Graph::class); + $networkVertex = $this->createMock(Vertex::class); + $serviceVertex = $this->createMock(Vertex::class); + $relation = $this->createMock(Directed::class); + $graph->expects($this->exactly(2)) + ->method('getVertex') + ->withConsecutive( + [$this->equalTo(getServiceVertexId($service))], + [$this->equalTo(getNetworkVertexId($network))] + ) + ->will( + $this->onConsecutiveCalls( + $this->returnValue($serviceVertex), + $this->returnValue($networkVertex), + ) + ); + $serviceVertex->expects($this->once()) + ->method('hasEdgeTo') + ->with($this->identicalTo($networkVertex)) + ->will($this->returnValue(false)); + $serviceVertex->expects(($this->once())) + ->method('createEdgeTo') + ->with($this->identicalTo($networkVertex)) + ->will($this->returnValue($relation)); + $relation->expects($this->once()) + ->method('setAttribute') + ->with($this->equalTo('docker_compose_viz.type'), $this->equalTo('network')); + + addNetworkRelation($graph, $service, $network); + } + + /** + * @test + */ + public function setEdgeAttributesWhenAddingWithMapping(): void + { + $network = 'test-net'; + $service = 'test-service'; + $mapping = ['aliases' => ['foo', 'bar']]; + $graph = $this->createMock(Graph::class); + $networkVertex = $this->createMock(Vertex::class); + $serviceVertex = $this->createMock(Vertex::class); + $relation = $this->createMock(Directed::class); + $graph->expects($this->exactly(2)) + ->method('getVertex') + ->withConsecutive( + [$this->equalTo(getServiceVertexId($service))], + [$this->equalTo(getNetworkVertexId($network))], + ) + ->will( + $this->onConsecutiveCalls( + $this->returnValue($serviceVertex), + $this->returnValue($networkVertex), + ), + ); + $serviceVertex->expects($this->once()) + ->method('hasEdgeTo') + ->with($this->identicalTo($networkVertex)) + ->will($this->returnValue(false)); + $serviceVertex->expects(($this->once())) + ->method('createEdgeTo') + ->with($this->identicalTo($networkVertex)) + ->will($this->returnValue($relation)); + $relation->expects($this->exactly(2)) + ->method('setAttribute') + ->withConsecutive( + [$this->equalTo('docker_compose_viz.type'), $this->equalTo('network')], + [$this->equalTo('graphviz.label'), $this->equalTo(implode(', ', $mapping['aliases']))], + ); + + addNetworkRelation($graph, $service, $network, $mapping); + } +} diff --git a/tests/PortTest.php b/tests/PortTest.php new file mode 100644 index 0000000..359ad06 --- /dev/null +++ b/tests/PortTest.php @@ -0,0 +1,298 @@ +assertEquals('port:'.$port, getPortVertexId($port)); + } + + /** + * @test + */ + public function checkIfVertexAlreadyExistsInGraph(): void + { + $port = '443'; + $graph = $this->createMock(Graph::class); + $graph->expects($this->once()) + ->method('hasVertex') + ->with(getPortVertexId($port)) + ->will($this->returnValue(true)); + $graph->expects($this->once()) + ->method('getVertex') + ->with(getPortVertexId($port)) + ->will($this->returnValue(new Vertex($graph, getPortVertexId($port)))); + + addPort($graph, normalizePortMapping($port)); + } + + /** + * @test + */ + public function addVertexIfNotExistInGraph(): void + { + $port = '443'; + $graph = $this->createMock(Graph::class); + $graph->expects($this->once()) + ->method('hasVertex') + ->with(getPortVertexId($port)) + ->will($this->returnValue(false)); + $graph->expects($this->once()) + ->method('createVertex') + ->with(getPortVertexId($port)) + ->will($this->returnValue(new Vertex($graph, getPortVertexId($port)))); + + addPort($graph, normalizePortMapping($port)); + } + + /** + * @test + */ + public function setVertexAttributesWhenAdding(): void + { + $port = '443'; + $graph = $this->createMock(Graph::class); + $vertex = $this->createMock(Vertex::class); + $vertex->expects($this->exactly(3)) + ->method('setAttribute') + ->withConsecutive( + [$this->equalTo('docker_compose_viz.type'), $this->equalTo('port')], + [$this->equalTo('graphviz.label'), $this->equalTo($port)], + [$this->equalTo('graphviz.shape'), $this->equalTo('circle')], + ); + $graph->expects($this->once()) + ->method('hasVertex') + ->with(getPortVertexId($port)) + ->will($this->returnValue(false)); + $graph->expects($this->once()) + ->method('createVertex') + ->with(getPortVertexId($port)) + ->will($this->returnValue($vertex)); + + addPort($graph, normalizePortMapping($port)); + } + + /** + * @test + */ + public function addRelationWithUnknownService(): void + { + $this->expectException(OutOfBoundsException::class); + + addPortRelation(new Graph(), 'unknown', normalizePortMapping('443')); + } + + /** + * @test + */ + public function addRelationWithUnknownPort(): void + { + $this->expectException(OutOfBoundsException::class); + + $service = 'test-service'; + $graph = new Graph(); + $graph->createVertex($service); + + addPortRelation(new Graph(), $service, normalizePortMapping('443')); + } + + /** + * @test + */ + public function checkIfEdgeOfExpectedTypeAlreadyExistsInGraph(): void + { + $port = '443'; + $service = 'test-service'; + $graph = $this->createMock(Graph::class); + $portVertex = $this->createMock(Vertex::class); + $serviceVertex = $this->createMock(Vertex::class); + $relation = $this->createMock(Directed::class); + $graph->expects($this->once()) + ->method('hasVertex') + ->with($this->equalTo(getPortVertexId($port))) + ->will($this->returnValue(true)); + $graph->expects($this->exactly(2)) + ->method('getVertex') + ->withConsecutive( + [$this->equalTo(getServiceVertexId($service))], + [$this->equalTo(getPortVertexId($port))], + ) + ->will( + $this->onConsecutiveCalls( + $this->returnValue($serviceVertex), + $this->returnValue($portVertex), + ), + ); + $serviceVertex->expects($this->once()) + ->method('hasEdgeTo') + ->with($this->identicalTo($portVertex)) + ->will($this->returnValue(true)); + $serviceVertex->expects($this->once()) + ->method('getEdgesTo') + ->with($this->equalTo($portVertex)) + ->will($this->returnValue([$relation])); + $serviceVertex->expects(($this->never())) + ->method('createEdgeTo') + ->with($this->identicalTo($portVertex)) + ->will($this->returnValue($this->createMock(Directed::class))); + $relation->expects($this->once()) + ->method('getAttribute') + ->with($this->equalTo('docker_compose_viz.type')) + ->will($this->returnValue('port')); + + addPortRelation($graph, $service, normalizePortMapping($port)); + } + + /** + * @test + */ + public function addEdgeIfNotExistWithExpectedTypeInGraph(): void + { + $port = '443'; + $service = 'test-service'; + $graph = $this->createMock(Graph::class); + $portVertex = $this->createMock(Vertex::class); + $serviceVertex = $this->createMock(Vertex::class); + $relation = $this->createMock(Directed::class); + $graph->expects($this->once()) + ->method('hasVertex') + ->with($this->equalTo(getPortVertexId($port))) + ->will($this->returnValue(true)); + $graph->expects($this->exactly(2)) + ->method('getVertex') + ->withConsecutive( + [$this->equalTo(getServiceVertexId($service))], + [$this->equalTo(getPortVertexId($port))], + ) + ->will( + $this->onConsecutiveCalls( + $this->returnValue($serviceVertex), + $this->returnValue($portVertex), + ), + ); + $serviceVertex->expects($this->once()) + ->method('hasEdgeTo') + ->with($this->identicalTo($portVertex)) + ->will($this->returnValue(true)); + $serviceVertex->expects($this->once()) + ->method('getEdgesTo') + ->with($this->equalTo($portVertex)) + ->will($this->returnValue([$relation])); + $serviceVertex->expects(($this->once())) + ->method('createEdgeTo') + ->with($this->identicalTo($portVertex)) + ->will($this->returnValue($this->createMock(Directed::class))); + $relation->expects($this->once()) + ->method('getAttribute') + ->with($this->equalTo('docker_compose_viz.type')) + ->will($this->returnValue('unknown')); + + addPortRelation($graph, $service, normalizePortMapping($port)); + } + + /** + * @test + */ + public function addEdgeIfNotExistInGraph(): void + { + $port = '443'; + $service = 'test-service'; + $graph = $this->createMock(Graph::class); + $portVertex = $this->createMock(Vertex::class); + $serviceVertex = $this->createMock(Vertex::class); + $graph->expects($this->once()) + ->method('hasVertex') + ->with($this->equalTo(getPortVertexId($port))) + ->will($this->returnValue(true)); + $graph->expects($this->exactly(2)) + ->method('getVertex') + ->withConsecutive( + [$this->equalTo(getServiceVertexId($service))], + [$this->equalTo(getPortVertexId($port))], + ) + ->will( + $this->onConsecutiveCalls( + $this->returnValue($serviceVertex), + $this->returnValue($portVertex), + ), + ); + $serviceVertex->expects($this->once()) + ->method('hasEdgeTo') + ->with($this->identicalTo($portVertex)) + ->will($this->returnValue(false)); + $serviceVertex->expects(($this->once())) + ->method('createEdgeTo') + ->with($this->identicalTo($portVertex)) + ->will($this->returnValue($this->createMock(Directed::class))); + + addPortRelation($graph, $service, normalizePortMapping($port)); + } + + /** + * @test + */ + public function setEdgeAttributesWhenAdding(): void + { + $port = '443'; + $service = 'test-service'; + $graph = $this->createMock(Graph::class); + $portVertex = $this->createMock(Vertex::class); + $serviceVertex = $this->createMock(Vertex::class); + $relation = $this->createMock(Directed::class); + $graph->expects($this->once()) + ->method('hasVertex') + ->with($this->equalTo(getPortVertexId($port))) + ->will($this->returnValue(true)); + $graph->expects($this->exactly(2)) + ->method('getVertex') + ->withConsecutive( + [$this->equalTo(getServiceVertexId($service))], + [$this->equalTo(getPortVertexId($port))] + ) + ->will( + $this->onConsecutiveCalls( + $this->returnValue($serviceVertex), + $this->returnValue($portVertex), + ) + ); + $serviceVertex->expects($this->once()) + ->method('hasEdgeTo') + ->with($this->identicalTo($portVertex)) + ->will($this->returnValue(false)); + $serviceVertex->expects(($this->once())) + ->method('createEdgeTo') + ->with($this->identicalTo($portVertex)) + ->will($this->returnValue($relation)); + $relation->expects($this->exactly(3)) + ->method('setAttribute') + ->withConsecutive( + [$this->equalTo('docker_compose_viz.type'), $this->equalTo('port')], + [$this->equalTo('graphviz.style'), $this->equalTo('solid')], + [$this->equalTo('graphviz.label'), $this->equalTo($port)], + ); + + addPortRelation($graph, $service, normalizePortMapping($port)); + } +} diff --git a/tests/SecretTest.php b/tests/SecretTest.php new file mode 100644 index 0000000..4e7618d --- /dev/null +++ b/tests/SecretTest.php @@ -0,0 +1,340 @@ +assertEquals([], fetchSecrets([])); + } + + /** + * @test + */ + public function returnSecreturationsFromDockerComposeSecreturation(): void + { + $secreturation = [ + 'secrets' => [ + 'test-secret' => ['external' => true] + ] + ]; + + $this->assertEquals($secreturation['secrets'], fetchSecrets($secreturation)); + } + + /** + * @test + */ + public function generateVertexId(): void + { + $secret = 'test-secret'; + + $this->assertEquals('secret:'.$secret, getSecretVertexId($secret)); + } + + /** + * @test + */ + public function checkIfVertexAlreadyExistsInGraph(): void + { + $secret = 'test-secret'; + $graph = $this->createMock(Graph::class); + $graph->expects($this->once()) + ->method('hasVertex') + ->with(getSecretVertexId($secret)) + ->will($this->returnValue(true)); + $graph->expects($this->once()) + ->method('getVertex') + ->with(getSecretVertexId($secret)) + ->will($this->returnValue(new Vertex($graph, getSecretVertexId($secret)))); + + addSecret($graph, $secret); + } + + /** + * @test + */ + public function addVertexIfNotExistInGraph(): void + { + $secret = 'test-secret'; + $graph = $this->createMock(Graph::class); + $graph->expects($this->once()) + ->method('hasVertex') + ->with(getSecretVertexId($secret)) + ->will($this->returnValue(false)); + $graph->expects($this->once()) + ->method('createVertex') + ->with(getSecretVertexId($secret)) + ->will($this->returnValue(new Vertex($graph, getSecretVertexId($secret)))); + + addSecret($graph, $secret); + } + + /** + * @test + */ + public function setVertexAttributesWhenAdding(): void + { + $secret = 'test-secret'; + $graph = $this->createMock(Graph::class); + $vertex = $this->createMock(Vertex::class); + $vertex->expects($this->exactly(2)) + ->method('setAttribute') + ->withConsecutive( + [$this->equalTo('docker_compose_viz.type'), $this->equalTo('secret')], + [$this->equalTo('graphviz.shape'), $this->equalTo('hexagon')], + ); + $graph->expects($this->once()) + ->method('hasVertex') + ->with(getSecretVertexId($secret)) + ->will($this->returnValue(false)); + $graph->expects($this->once()) + ->method('createVertex') + ->with(getSecretVertexId($secret)) + ->will($this->returnValue($vertex)); + + addSecret($graph, $secret); + } + + /** + * @test + */ + public function addRelationWithUnknownService(): void + { + $this->expectException(OutOfBoundsException::class); + + addSecretRelation(new Graph(), 'unknown', 'test-secret'); + } + + /** + * @test + */ + public function addRelationWithUnknownSecreturation(): void + { + $this->expectException(OutOfBoundsException::class); + + $service = 'test-service'; + $graph = new Graph(); + $graph->createVertex($service); + + addSecretRelation(new Graph(), $service, 'unknown'); + } + + /** + * @test + */ + public function checkIfEdgeOfExpectedTypeAlreadyExistsInGraph(): void + { + $secret = 'test-secret'; + $service = 'test-service'; + $graph = $this->createMock(Graph::class); + $secretVertex = $this->createMock(Vertex::class); + $serviceVertex = $this->createMock(Vertex::class); + $relation = $this->createMock(Directed::class); + $graph->expects($this->exactly(2)) + ->method('getVertex') + ->withConsecutive( + [$this->equalTo(getServiceVertexId($service))], + [$this->equalTo(getSecretVertexId($secret))], + ) + ->will( + $this->onConsecutiveCalls( + $this->returnValue($serviceVertex), + $this->returnValue($secretVertex), + ), + ); + $serviceVertex->expects($this->once()) + ->method('hasEdgeTo') + ->with($this->identicalTo($secretVertex)) + ->will($this->returnValue(true)); + $serviceVertex->expects($this->once()) + ->method('getEdgesTo') + ->with($this->equalTo($secretVertex)) + ->will($this->returnValue([$relation])); + $serviceVertex->expects(($this->never())) + ->method('createEdgeTo') + ->with($this->identicalTo($secretVertex)) + ->will($this->returnValue($this->createMock(Directed::class))); + $relation->expects($this->once()) + ->method('getAttribute') + ->with($this->equalTo('docker_compose_viz.type')) + ->will($this->returnValue('secret')); + + addSecretRelation($graph, $service, $secret); + } + + /** + * @test + */ + public function addEdgeIfNotExistWithExpectedTypeInGraph(): void + { + $secret = 'test-secret'; + $service = 'test-service'; + $graph = $this->createMock(Graph::class); + $secretVertex = $this->createMock(Vertex::class); + $serviceVertex = $this->createMock(Vertex::class); + $relation = $this->createMock(Directed::class); + $graph->expects($this->exactly(2)) + ->method('getVertex') + ->withConsecutive( + [$this->equalTo(getServiceVertexId($service))], + [$this->equalTo(getSecretVertexId($secret))], + ) + ->will( + $this->onConsecutiveCalls( + $this->returnValue($serviceVertex), + $this->returnValue($secretVertex), + ), + ); + $serviceVertex->expects($this->once()) + ->method('hasEdgeTo') + ->with($this->identicalTo($secretVertex)) + ->will($this->returnValue(true)); + $serviceVertex->expects($this->once()) + ->method('getEdgesTo') + ->with($this->equalTo($secretVertex)) + ->will($this->returnValue([$relation])); + $serviceVertex->expects(($this->once())) + ->method('createEdgeTo') + ->with($this->identicalTo($secretVertex)) + ->will($this->returnValue($this->createMock(Directed::class))); + $relation->expects($this->once()) + ->method('getAttribute') + ->with($this->equalTo('docker_compose_viz.type')) + ->will($this->returnValue('unknown')); + + addSecretRelation($graph, $service, $secret); + } + + /** + * @test + */ + public function addEdgeIfNotExistInGraph(): void + { + $secret = 'test-secret'; + $service = 'test-service'; + $graph = $this->createMock(Graph::class); + $secretVertex = $this->createMock(Vertex::class); + $serviceVertex = $this->createMock(Vertex::class); + $graph->expects($this->exactly(2)) + ->method('getVertex') + ->withConsecutive( + [$this->equalTo(getServiceVertexId($service))], + [$this->equalTo(getSecretVertexId($secret))], + ) + ->will( + $this->onConsecutiveCalls( + $this->returnValue($serviceVertex), + $this->returnValue($secretVertex), + ), + ); + $serviceVertex->expects($this->once()) + ->method('hasEdgeTo') + ->with($this->identicalTo($secretVertex)) + ->will($this->returnValue(false)); + $serviceVertex->expects(($this->once())) + ->method('createEdgeTo') + ->with($this->identicalTo($secretVertex)) + ->will($this->returnValue($this->createMock(Directed::class))); + + addSecretRelation($graph, $service, $secret); + } + + /** + * @test + */ + public function setEdgeAttributesWhenAdding(): void + { + $secret = 'test-secret'; + $service = 'test-service'; + $graph = $this->createMock(Graph::class); + $secretVertex = $this->createMock(Vertex::class); + $serviceVertex = $this->createMock(Vertex::class); + $relation = $this->createMock(Directed::class); + $graph->expects($this->exactly(2)) + ->method('getVertex') + ->withConsecutive( + [$this->equalTo(getServiceVertexId($service))], + [$this->equalTo(getSecretVertexId($secret))] + ) + ->will( + $this->onConsecutiveCalls( + $this->returnValue($serviceVertex), + $this->returnValue($secretVertex), + ) + ); + $serviceVertex->expects($this->once()) + ->method('hasEdgeTo') + ->with($this->identicalTo($secretVertex)) + ->will($this->returnValue(false)); + $serviceVertex->expects(($this->once())) + ->method('createEdgeTo') + ->with($this->identicalTo($secretVertex)) + ->will($this->returnValue($relation)); + $relation->expects($this->once()) + ->method('setAttribute') + ->with($this->equalTo('docker_compose_viz.type'), $this->equalTo('secret')); + + addSecretRelation($graph, $service, $secret); + } + + /** + * @test + */ + public function setEdgeAttributesWhenAddingWithMapping(): void + { + $secret = 'test-secret'; + $service = 'test-service'; + $mapping = ['source' => $secret, 'target' => 'secret-target']; + $graph = $this->createMock(Graph::class); + $secretVertex = $this->createMock(Vertex::class); + $serviceVertex = $this->createMock(Vertex::class); + $relation = $this->createMock(Directed::class); + $graph->expects($this->exactly(2)) + ->method('getVertex') + ->withConsecutive( + [$this->equalTo(getServiceVertexId($service))], + [$this->equalTo(getSecretVertexId($secret))], + ) + ->will( + $this->onConsecutiveCalls( + $this->returnValue($serviceVertex), + $this->returnValue($secretVertex), + ), + ); + $serviceVertex->expects($this->once()) + ->method('hasEdgeTo') + ->with($this->identicalTo($secretVertex)) + ->will($this->returnValue(false)); + $serviceVertex->expects(($this->once())) + ->method('createEdgeTo') + ->with($this->identicalTo($secretVertex)) + ->will($this->returnValue($relation)); + $relation->expects($this->exactly(2)) + ->method('setAttribute') + ->withConsecutive( + [$this->equalTo('docker_compose_viz.type'), $this->equalTo('secret')], + [$this->equalTo('graphviz.label'), $this->equalTo($mapping['target'])], + ); + + addSecretRelation($graph, $service, $mapping); + } +} diff --git a/tests/ServiceTest.php b/tests/ServiceTest.php new file mode 100644 index 0000000..fc737f9 --- /dev/null +++ b/tests/ServiceTest.php @@ -0,0 +1,478 @@ +assertEquals([], fetchServices([])); + } + + /** + * @test + */ + public function returnServicesFromDockerComposeConfiguration(): void + { + $configuration = [ + 'services' => [ + 'test-service' => ['external' => true] + ] + ]; + + $this->assertEquals($configuration['services'], fetchServices($configuration)); + } + + /** + * @test + */ + public function generateVertexId(): void + { + $service = 'test-service'; + + $this->assertEquals('service:'.$service, getServiceVertexId($service)); + } + + /** + * @test + */ + public function checkIfVertexAlreadyExistsInGraph(): void + { + $service = 'test-service'; + $graph = $this->createMock(Graph::class); + $graph->expects($this->once()) + ->method('hasVertex') + ->with(getServiceVertexId($service)) + ->will($this->returnValue(true)); + $graph->expects($this->once()) + ->method('getVertex') + ->with(getServiceVertexId($service)) + ->will($this->returnValue(new Vertex($graph, getServiceVertexId($service)))); + + addService($graph, $service); + } + + /** + * @test + */ + public function addVertexIfNotExistInGraph(): void + { + $service = 'test-service'; + $graph = $this->createMock(Graph::class); + $graph->expects($this->once()) + ->method('hasVertex') + ->with(getServiceVertexId($service)) + ->will($this->returnValue(false)); + $graph->expects($this->once()) + ->method('createVertex') + ->with(getServiceVertexId($service)) + ->will($this->returnValue(new Vertex($graph, getServiceVertexId($service)))); + + addService($graph, $service); + } + + /** + * @test + */ + public function setVertexAttributesWhenAdding(): void + { + $service = 'test-service'; + $graph = $this->createMock(Graph::class); + $vertex = $this->createMock(Vertex::class); + $vertex->expects($this->exactly(2)) + ->method('setAttribute') + ->withConsecutive( + [$this->equalTo('docker_compose_viz.type'), $this->equalTo('service')], + [$this->equalTo('graphviz.shape'), $this->equalTo('component')], + ); + $graph->expects($this->once()) + ->method('hasVertex') + ->with(getServiceVertexId($service)) + ->will($this->returnValue(false)); + $graph->expects($this->once()) + ->method('createVertex') + ->with(getServiceVertexId($service)) + ->will($this->returnValue($vertex)); + + addService($graph, $service); + } + + /** + * @test + */ + public function addExtendsRelationWithUnknownSourceService(): void + { + $this->expectException(OutOfBoundsException::class); + + $service = 'test-service'; + $graph = new Graph(); + $graph->createVertex($service); + + addExtendsRelation(new Graph(), 'unknown', $service); + } + + /** + * @test + */ + public function addExtendsRelationWithUnknownTargetService(): void + { + $this->expectException(OutOfBoundsException::class); + + $service = 'test-service'; + $graph = new Graph(); + $graph->createVertex($service); + + addExtendsRelation(new Graph(), $service, 'unknown'); + } + + /** + * @test + */ + public function addLinkRelationWithUnknownSourceService(): void + { + $this->expectException(OutOfBoundsException::class); + + $service = 'test-service'; + $graph = new Graph(); + $graph->createVertex($service); + + addLinkRelation(new Graph(), 'unknown', $service); + } + + /** + * @test + */ + public function addLinkRelationWithUnknownTargetService(): void + { + $this->expectException(OutOfBoundsException::class); + + $service = 'test-service'; + $graph = new Graph(); + $graph->createVertex($service); + + addLinkRelation(new Graph(), $service, 'unknown'); + } + + /** + * @test + */ + public function addExternalLinkRelationWithUnknownSourceService(): void + { + $this->expectException(OutOfBoundsException::class); + + $service = 'test-service'; + $graph = new Graph(); + $graph->createVertex($service); + + addExternalLinkRelation(new Graph(), 'unknown', $service); + } + + /** + * @test + */ + public function addExternalLinkRelationWithUnknownTargetService(): void + { + $this->expectException(OutOfBoundsException::class); + + $service = 'test-service'; + $graph = new Graph(); + $graph->createVertex($service); + + addExternalLinkRelation(new Graph(), $service, 'unknown'); + } + + /** + * @test + */ + public function addDependsRelationWithUnknownSourceService(): void + { + $this->expectException(OutOfBoundsException::class); + + $service = 'test-service'; + $graph = new Graph(); + $graph->createVertex($service); + + addDependsRelation(new Graph(), 'unknown', $service); + } + + /** + * @test + */ + public function addDependsRelationWithUnknownTargetService(): void + { + $this->expectException(OutOfBoundsException::class); + + $service = 'test-service'; + $graph = new Graph(); + $graph->createVertex($service); + + addDependsRelation(new Graph(), $service, 'unknown'); + } + + /** + * @test + */ + public function addVolumesFromRelationWithUnknownSourceService(): void + { + $this->expectException(OutOfBoundsException::class); + + $service = 'test-service'; + $graph = new Graph(); + $graph->createVertex($service); + + addVolumesFromRelation(new Graph(), 'unknown', $service); + } + + /** + * @test + */ + public function addVolumesFromRelationWithUnknownTargetService(): void + { + $this->expectException(OutOfBoundsException::class); + + $service = 'test-service'; + $graph = new Graph(); + $graph->createVertex($service); + + addVolumesFromRelation(new Graph(), $service, 'unknown'); + } + + /** + * @test + * @dataProvider checkIfEdgeOfExpectedTypeAlreadyExistsInGraphProvider + */ + public function checkIfEdgeOfExpectedTypeAlreadyExistsInGraph(string $type, callable $function): void + { + $source = 'source-service'; + $target = 'target-service'; + $graph = $this->createMock(Graph::class); + $sourceVertex = $this->createMock(Vertex::class); + $targetVertex = $this->createMock(Vertex::class); + $relation = $this->createMock(Directed::class); + $graph->expects($this->exactly(2)) + ->method('getVertex') + ->withConsecutive( + [$this->equalTo(getServiceVertexId($source))], + [$this->equalTo(getServiceVertexId($target))], + ) + ->will( + $this->onConsecutiveCalls( + $this->returnValue($sourceVertex), + $this->returnValue($targetVertex), + ), + ); + $sourceVertex->expects($this->once()) + ->method('hasEdgeTo') + ->with($this->identicalTo($targetVertex)) + ->will($this->returnValue(true)); + $sourceVertex->expects($this->once()) + ->method('getEdgesTo') + ->with($this->equalTo($targetVertex)) + ->will($this->returnValue([$relation])); + $sourceVertex->expects(($this->never())) + ->method('createEdgeTo') + ->with($this->identicalTo($targetVertex)) + ->will($this->returnValue($this->createMock(Directed::class))); + $relation->expects($this->once()) + ->method('getAttribute') + ->with($this->equalTo('docker_compose_viz.type')) + ->will($this->returnValue($type)); + + $function($graph, $source, $target); + } + + private function checkIfEdgeOfExpectedTypeAlreadyExistsInGraphProvider(): array + { + return [ + 'extends' => ['extends', 'PMSIpilot\DockerComposeViz\addExtendsRelation'], + 'link' => ['link', 'PMSIpilot\DockerComposeViz\addLinkRelation'], + 'external_link' => ['external_link', 'PMSIpilot\DockerComposeViz\addExternalLinkRelation'], + 'depends' => ['depends', 'PMSIpilot\DockerComposeViz\addDependsRelation'], + 'volumes_from' => ['volumes_from', 'PMSIpilot\DockerComposeViz\addVolumesFromRelation'], + ]; + } + + /** + * @test + * @dataProvider addEdgeIfNotExistsProvider + */ + public function addEdgeIfNotExistWithExpectedTypeInGraph(callable $function): void + { + $source = 'source-service'; + $target = 'target-service'; + $graph = $this->createMock(Graph::class); + $sourceVertex = $this->createMock(Vertex::class); + $targetVertex = $this->createMock(Vertex::class); + $relation = $this->createMock(Directed::class); + $graph->expects($this->exactly(2)) + ->method('getVertex') + ->withConsecutive( + [$this->equalTo(getServiceVertexId($source))], + [$this->equalTo(getServiceVertexId($target))], + ) + ->will( + $this->onConsecutiveCalls( + $this->returnValue($sourceVertex), + $this->returnValue($targetVertex), + ), + ); + $sourceVertex->expects($this->once()) + ->method('hasEdgeTo') + ->with($this->identicalTo($targetVertex)) + ->will($this->returnValue(true)); + $sourceVertex->expects($this->once()) + ->method('getEdgesTo') + ->with($this->equalTo($targetVertex)) + ->will($this->returnValue([$relation])); + $sourceVertex->expects(($this->once())) + ->method('createEdgeTo') + ->with($this->identicalTo($targetVertex)) + ->will($this->returnValue($this->createMock(Directed::class))); + $relation->expects($this->once()) + ->method('getAttribute') + ->with($this->equalTo('docker_compose_viz.type')) + ->will($this->returnValue('unknown')); + + $function($graph, $source, $target); + } + + /** + * @test + * @dataProvider addEdgeIfNotExistsProvider + */ + public function addEdgeIfNotExistInGraph(callable $function): void + { + $source = 'source-service'; + $target = 'target-service'; + $graph = $this->createMock(Graph::class); + $sourceVertex = $this->createMock(Vertex::class); + $targetVertex = $this->createMock(Vertex::class); + $graph->expects($this->exactly(2)) + ->method('getVertex') + ->withConsecutive( + [$this->equalTo(getServiceVertexId($source))], + [$this->equalTo(getServiceVertexId($target))], + ) + ->will( + $this->onConsecutiveCalls( + $this->returnValue($sourceVertex), + $this->returnValue($targetVertex), + ), + ); + $sourceVertex->expects($this->once()) + ->method('hasEdgeTo') + ->with($this->identicalTo($targetVertex)) + ->will($this->returnValue(false)); + $sourceVertex->expects(($this->once())) + ->method('createEdgeTo') + ->with($this->identicalTo($targetVertex)) + ->will($this->returnValue($this->createMock(Directed::class))); + + $function($graph, $source, $target); + } + + private function addEdgeIfNotExistsProvider(): array + { + return [ + 'extends' => ['PMSIpilot\DockerComposeViz\addExtendsRelation'], + 'link' => ['PMSIpilot\DockerComposeViz\addLinkRelation'], + 'external_link' => ['PMSIpilot\DockerComposeViz\addExternalLinkRelation'], + 'depends' => ['PMSIpilot\DockerComposeViz\addDependsRelation'], + 'volumes_from' => ['PMSIpilot\DockerComposeViz\addVolumesFromRelation'], + ]; + } + + /** + * @test + * @dataProvider setEdgeAttributesWhenAddingProvider + */ + public function setEdgeAttributesWhenAdding(callable $function, array ...$setAttributesCalls): void + { + $source = 'source-service'; + $target = 'target-service'; + $graph = $this->createMock(Graph::class); + $sourceVertex = $this->createMock(Vertex::class); + $targetVertex = $this->createMock(Vertex::class); + $relation = $this->createMock(Directed::class); + $graph->expects($this->exactly(2)) + ->method('getVertex') + ->withConsecutive( + [$this->equalTo(getServiceVertexId($source))], + [$this->equalTo(getServiceVertexId($target))], + ) + ->will( + $this->onConsecutiveCalls( + $this->returnValue($sourceVertex), + $this->returnValue($targetVertex), + ), + ); + $sourceVertex->expects($this->once()) + ->method('hasEdgeTo') + ->with($this->identicalTo($targetVertex)) + ->will($this->returnValue(false)); + $sourceVertex->expects(($this->once())) + ->method('createEdgeTo') + ->with($this->identicalTo($targetVertex)) + ->will($this->returnValue($relation)); + $relation->expects($this->exactly(count($setAttributesCalls))) + ->method('setAttribute') + ->withConsecutive(...$setAttributesCalls); + + $function($graph, $source, $target); + } + + private function setEdgeAttributesWhenAddingProvider(): array + { + return [ + 'extends' => [ + 'PMSIpilot\DockerComposeViz\addExtendsRelation', + [$this->equalTo('docker_compose_viz.type'), $this->equalTo('extends')], + [$this->equalTo('graphviz.dir'), $this->equalTo('both')], + [$this->equalTo('graphviz.arrowhead'), $this->equalTo('inv')], + [$this->equalTo('graphviz.arrowtail'), $this->equalTo('dot')], + ], + 'link' => [ + 'PMSIpilot\DockerComposeViz\addLinkRelation', + [$this->equalTo('docker_compose_viz.type'), $this->equalTo('link')], + [$this->equalTo('graphviz.style'), $this->equalTo('solid')], + ], + 'external_link' => [ + 'PMSIpilot\DockerComposeViz\addExternalLinkRelation', + [$this->equalTo('docker_compose_viz.type'), $this->equalTo('external_link')], + [$this->equalTo('graphviz.style'), $this->equalTo('solid')], + [$this->equalTo('graphviz.color'), $this->equalTo('gray')], + ], + 'depends' => [ + 'PMSIpilot\DockerComposeViz\addDependsRelation', + [$this->equalTo('docker_compose_viz.type'), $this->equalTo('depends')], + [$this->equalTo('graphviz.style'), $this->equalTo('dotted')], + ], + 'volumes_from' => [ + 'PMSIpilot\DockerComposeViz\addVolumesFromRelation', + [$this->equalTo('docker_compose_viz.type'), $this->equalTo('volumes_from')], + [$this->equalTo('graphviz.style'), $this->equalTo('dashed')], + ], + ]; + } +} diff --git a/tests/VolumeTest.php b/tests/VolumeTest.php new file mode 100644 index 0000000..7b5f108 --- /dev/null +++ b/tests/VolumeTest.php @@ -0,0 +1,347 @@ +assertEquals([], fetchVolumes([])); + } + + /** + * @test + */ + public function returnVolumesFromDockerComposeConfiguration(): void + { + $configuration = [ + 'volumes' => [ + 'test-volume' => ['external' => true] + ] + ]; + + $this->assertEquals($configuration['volumes'], fetchVolumes($configuration)); + } + + /** + * @test + */ + public function generateVertexId(): void + { + $volume = 'test-volume'; + + $this->assertEquals('volume:'.$volume, getVolumeVertexId($volume)); + } + + /** + * @test + */ + public function checkIfVertexAlreadyExistsInGraph(): void + { + $volume = 'test-volume'; + $graph = $this->createMock(Graph::class); + $graph->expects($this->once()) + ->method('hasVertex') + ->with(getVolumeVertexId($volume)) + ->will($this->returnValue(true)); + $graph->expects($this->once()) + ->method('getVertex') + ->with(getVolumeVertexId($volume)) + ->will($this->returnValue(new Vertex($graph, getVolumeVertexId($volume)))); + + addVolume($graph, $volume); + } + + /** + * @test + */ + public function addVertexIfNotExistInGraph(): void + { + $volume = 'test-volume'; + $graph = $this->createMock(Graph::class); + $graph->expects($this->once()) + ->method('hasVertex') + ->with(getVolumeVertexId($volume)) + ->will($this->returnValue(false)); + $graph->expects($this->once()) + ->method('createVertex') + ->with(getVolumeVertexId($volume)) + ->will($this->returnValue(new Vertex($graph, getVolumeVertexId($volume)))); + + addVolume($graph, $volume); + } + + /** + * @test + */ + public function setVertexAttributesWhenAdding(): void + { + $volume = 'test-volume'; + $graph = $this->createMock(Graph::class); + $vertex = $this->createMock(Vertex::class); + $vertex->expects($this->exactly(3)) + ->method('setAttribute') + ->withConsecutive( + [$this->equalTo('docker_compose_viz.type'), $this->equalTo('volume')], + [$this->equalTo('graphviz.label'), $this->equalTo($volume)], + [$this->equalTo('graphviz.shape'), $this->equalTo('pentagon')], + ); + $graph->expects($this->once()) + ->method('hasVertex') + ->with(getVolumeVertexId($volume)) + ->will($this->returnValue(false)); + $graph->expects($this->once()) + ->method('createVertex') + ->with(getVolumeVertexId($volume)) + ->will($this->returnValue($vertex)); + + addVolume($graph, $volume); + } + + /** + * @test + */ + public function addRelationWithUnknownService(): void + { + $this->expectException(OutOfBoundsException::class); + + addVolumeRelation(new Graph(), 'unknown', ['source' => 'test-volume']); + } + + /** + * @test + */ + public function addRelationWithUnknownVolume(): void + { + $this->expectException(OutOfBoundsException::class); + + $service = 'test-service'; + $graph = new Graph(); + $graph->createVertex($service); + + addVolumeRelation(new Graph(), $service, ['source' => 'test-volume']); + } + + /** + * @test + */ + public function checkIfEdgeOfExpectedTypeAlreadyExistsInGraph(): void + { + $volume = 'test-volume'; + $service = 'test-service'; + $graph = $this->createMock(Graph::class); + $volumeVertex = $this->createMock(Vertex::class); + $serviceVertex = $this->createMock(Vertex::class); + $relation = $this->createMock(Directed::class); + $graph->expects($this->exactly(2)) + ->method('getVertex') + ->withConsecutive( + [$this->equalTo(getServiceVertexId($service))], + [$this->equalTo(getVolumeVertexId($volume))], + ) + ->will( + $this->onConsecutiveCalls( + $this->returnValue($serviceVertex), + $this->returnValue($volumeVertex), + ), + ); + $serviceVertex->expects($this->once()) + ->method('hasEdgeTo') + ->with($this->identicalTo($volumeVertex)) + ->will($this->returnValue(true)); + $serviceVertex->expects($this->once()) + ->method('getEdgesTo') + ->with($this->equalTo($volumeVertex)) + ->will($this->returnValue([$relation])); + $serviceVertex->expects(($this->never())) + ->method('createEdgeTo') + ->with($this->identicalTo($volumeVertex)) + ->will($this->returnValue($this->createMock(Directed::class))); + $relation->expects($this->once()) + ->method('getAttribute') + ->with($this->equalTo('docker_compose_viz.type')) + ->will($this->returnValue('volume')); + + addVolumeRelation($graph, $service, ['source' => 'test-volume']); + } + + /** + * @test + */ + public function addEdgeIfNotExistWithExpectedTypeInGraph(): void + { + $volume = 'test-volume'; + $service = 'test-service'; + $graph = $this->createMock(Graph::class); + $volumeVertex = $this->createMock(Vertex::class); + $serviceVertex = $this->createMock(Vertex::class); + $relation = $this->createMock(Directed::class); + $graph->expects($this->exactly(2)) + ->method('getVertex') + ->withConsecutive( + [$this->equalTo(getServiceVertexId($service))], + [$this->equalTo(getVolumeVertexId($volume))], + ) + ->will( + $this->onConsecutiveCalls( + $this->returnValue($serviceVertex), + $this->returnValue($volumeVertex), + ), + ); + $serviceVertex->expects($this->once()) + ->method('hasEdgeTo') + ->with($this->identicalTo($volumeVertex)) + ->will($this->returnValue(true)); + $serviceVertex->expects($this->once()) + ->method('getEdgesTo') + ->with($this->equalTo($volumeVertex)) + ->will($this->returnValue([$relation])); + $serviceVertex->expects(($this->once())) + ->method('createEdgeTo') + ->with($this->identicalTo($volumeVertex)) + ->will($this->returnValue($this->createMock(Directed::class))); + $relation->expects($this->once()) + ->method('getAttribute') + ->with($this->equalTo('docker_compose_viz.type')) + ->will($this->returnValue('unknown')); + + addVolumeRelation($graph, $service, ['source' => 'test-volume']); + } + + /** + * @test + */ + public function addEdgeIfNotExistInGraph(): void + { + $volume = 'test-volume'; + $service = 'test-service'; + $graph = $this->createMock(Graph::class); + $volumeVertex = $this->createMock(Vertex::class); + $serviceVertex = $this->createMock(Vertex::class); + $graph->expects($this->exactly(2)) + ->method('getVertex') + ->withConsecutive( + [$this->equalTo(getServiceVertexId($service))], + [$this->equalTo(getVolumeVertexId($volume))], + ) + ->will( + $this->onConsecutiveCalls( + $this->returnValue($serviceVertex), + $this->returnValue($volumeVertex), + ), + ); + $serviceVertex->expects($this->once()) + ->method('hasEdgeTo') + ->with($this->identicalTo($volumeVertex)) + ->will($this->returnValue(false)); + $serviceVertex->expects(($this->once())) + ->method('createEdgeTo') + ->with($this->identicalTo($volumeVertex)) + ->will($this->returnValue($this->createMock(Directed::class))); + + addVolumeRelation($graph, $service, ['source' => 'test-volume']); + } + + /** + * @test + */ + public function setEdgeAttributesWhenAdding(): void + { + $volume = 'test-volume'; + $service = 'test-service'; + $graph = $this->createMock(Graph::class); + $volumeVertex = $this->createMock(Vertex::class); + $serviceVertex = $this->createMock(Vertex::class); + $relation = $this->createMock(Directed::class); + $graph->expects($this->exactly(2)) + ->method('getVertex') + ->withConsecutive( + [$this->equalTo(getServiceVertexId($service))], + [$this->equalTo(getVolumeVertexId($volume))] + ) + ->will( + $this->onConsecutiveCalls( + $this->returnValue($serviceVertex), + $this->returnValue($volumeVertex), + ) + ); + $serviceVertex->expects($this->once()) + ->method('hasEdgeTo') + ->with($this->identicalTo($volumeVertex)) + ->will($this->returnValue(false)); + $serviceVertex->expects(($this->once())) + ->method('createEdgeTo') + ->with($this->identicalTo($volumeVertex)) + ->will($this->returnValue($relation)); + $relation->expects($this->exactly(3)) + ->method('setAttribute') + ->withConsecutive( + [$this->equalTo('docker_compose_viz.type'), $this->equalTo('volume')], + [$this->equalTo('graphviz.style'), $this->equalTo('dashed')], + [$this->equalTo('graphviz.dir'), $this->equalTo('both')], + ); + + addVolumeRelation($graph, $service, ['source' => 'test-volume']); + } + + /** + * @test + */ + public function setEdgeAttributesWhenAddingWithDefinition(): void + { + $volume = 'test-volume'; + $service = 'test-service'; + $definition = ['source' => $volume, 'target' => '/target']; + $graph = $this->createMock(Graph::class); + $volumeVertex = $this->createMock(Vertex::class); + $serviceVertex = $this->createMock(Vertex::class); + $relation = $this->createMock(Directed::class); + $graph->expects($this->exactly(2)) + ->method('getVertex') + ->withConsecutive( + [$this->equalTo(getServiceVertexId($service))], + [$this->equalTo(getVolumeVertexId($volume))], + ) + ->will( + $this->onConsecutiveCalls( + $this->returnValue($serviceVertex), + $this->returnValue($volumeVertex), + ), + ); + $serviceVertex->expects($this->once()) + ->method('hasEdgeTo') + ->with($this->identicalTo($volumeVertex)) + ->will($this->returnValue(false)); + $serviceVertex->expects(($this->once())) + ->method('createEdgeTo') + ->with($this->identicalTo($volumeVertex)) + ->will($this->returnValue($relation)); + $relation->expects($this->exactly(4)) + ->method('setAttribute') + ->withConsecutive( + [$this->equalTo('docker_compose_viz.type'), $this->equalTo('volume')], + [$this->equalTo('graphviz.style'), 'dashed'], + [$this->equalTo('graphviz.dir'), $this->equalTo('both')], + [$this->equalTo('graphviz.label'), $definition['target']], + ); + + addVolumeRelation($graph, $service, $definition); + } +}