diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..22159d7 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,66 @@ +name: CI + +on: + push: + # FIXME: Change the branch name to your main branch + branches: [main] + pull_request: ~ + +jobs: + cs: + name: PHP-CS-Fixer + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: PHP-CS-Fixer + uses: docker://oskarstark/php-cs-fixer-ga + with: + args: --diff --dry-run + + phpstan: + name: PHPStan + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Composer dependencies + run: composer update --prefer-dist --no-interaction + + - name: PHPStan + env: + CHECK_PLATFORM_REQUIREMENTS: false + REQUIRE_DEV: true + uses: docker://oskarstark/phpstan-ga + + phpunit: + name: PHPUnit on PHP ${{ matrix.php-version }} ${{ matrix.name }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php-version: ["8.2", "8.3"] + composer-flags: [""] + name: [""] + include: + - php-version: 8.1 + composer-flags: "--prefer-lowest" + name: "(prefer lowest dependencies)" + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + extensions: mbstring, xml + ini-values: phar.readonly="Off" + + - name: Install Composer dependencies + run: composer update --prefer-dist --no-interaction ${{ matrix.composer-flags }} + + - name: Run Tests + run: vendor/bin/simple-phpunit diff --git a/.gitignore b/.gitignore index c8153b5..50b0964 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /composer.lock /vendor/ +/.phpunit.cache/ diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php new file mode 100644 index 0000000..e7dbb96 --- /dev/null +++ b/.php-cs-fixer.php @@ -0,0 +1,45 @@ +ignoreVCSIgnored(true) + ->in(__DIR__) + ->append([ + __FILE__, + ]) +; + +return (new PhpCsFixer\Config()) + ->setRiskyAllowed(true) + ->setRules([ + '@PHP81Migration' => true, + '@PhpCsFixer' => true, + '@Symfony' => true, + '@Symfony:risky' => true, + 'heredoc_indentation' => false, + 'header_comment' => ['header' => $fileHeaderComment], + 'single_line_empty_body' => false, + 'ordered_types' => false, // From @PhpCsFixer but we don't want it + 'php_unit_internal_class' => false, // From @PhpCsFixer but we don't want it + 'php_unit_test_class_requires_covers' => false, // From @PhpCsFixer but we don't want it + 'phpdoc_add_missing_param_annotation' => false, // From @PhpCsFixer but we don't want it + 'concat_space' => ['spacing' => 'one'], + 'ordered_class_elements' => true, // Symfony(PSR12) override the default value, but we don't want + 'blank_line_before_statement' => true, // Symfony(PSR12) override the default value, but we don't want + 'method_chaining_indentation' => false, // Do not work well with Tree builder + ]) + ->setFinder($finder) +; diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 1f33380..0000000 --- a/.travis.yml +++ /dev/null @@ -1,13 +0,0 @@ -language: php - -sudo: false - -php: - - 5.6 - - 7.0 - - 7.1 - - 7.2 - -before_script: - - if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then echo "extension = redis.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi - - composer install --dev diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..dc71083 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,43 @@ +# CHANGELOG + +## v3.0.0 (unreleased) + +### Breaking changes + +* Drop support for PHP < 8.1 +* Drop support for Symfony < 5.4 +* Drop support for zabbix collector +* Drop support for librato collector +* Rename InfluxDB collector to InfluxDbV1 +* Change inner dependency of InfluxDbV1 +* Change inner dependency of Prometheus +* Remove the TaggableCollectorInterface. Tags can be injected in the constructor + instead + +### New features + +* collector: + * Ensure all collectors cannot raise error or exception +* bundle: + * All collectors has alias for autowiring. Use + `#[Target('name-of-the-collector')]` to inject a collector + * All collectors are tagged with `kernel.reset` to reset their state + * All collectors are tagged with + `Beberlei\Metrics\Collector\CollectorInterface` +* add a symfony application in the `examples` folder will all collectors enabled + and visualisation with Grafana + +### Minor changes + +* collector: + * Fix doctrine dbal deprecations +* chore: + * modernise PHP code, use PHP 8.1 features + * add license file, and link it in each PHP files +* ci: + * use symfony/phpunit-bridge instead of phpunit + * add php-cs-fixer + * add phpstan + * replace Travis by GitHub Actions +* composer: + * move tests to it's own folder, and it's own autoloader diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..a96b463 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,19 @@ +Copyright (c) 2004-preset Benjamin Eberlei + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md index abbee4a..cffc412 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # Metrics -[![Build Status](https://travis-ci.org/beberlei/metrics.svg?branch=master)](https://travis-ci.org/beberlei/metrics) - Simple library that abstracts different metrics collectors. I find this necessary to have a consistent and simple metrics API that doesn't cause vendor lock-in. @@ -11,22 +9,20 @@ It also ships with a Symfony Bundle. **This is not a library for displaying metr Currently supported backends: * Doctrine DBAL +* DogStatsD * Graphite -* InfluxDB -* Telegraf -* Librato +* InfluxDb (version 1) * Logger (Psr\Log\LoggerInterface) * Null (Dummy that does nothing) * Prometheus * StatsD -* Zabbix -* DogStatsD +* Telegraf ## Installation Using Composer: -```bash +``` composer require beberlei/metrics ``` @@ -35,16 +31,12 @@ composer require beberlei/metrics You can instantiate clients: ```php -increment('foo.bar'); $collector->decrement('foo.bar'); @@ -56,117 +48,111 @@ $value = 1234; $collector->measure('foo.bar', $value); ``` -Some backends defer sending and aggregate all information, make sure to call +All backends defer sending and aggregate all information, make sure to call flush: ```php -flush(); ``` ## Configuration ```php - 'foo.beberlei.de', - 'server' => 'localhost', - 'port' => 10051, -)); - -$zabbixConfig = \Beberlei\Metrics\Factory::create('zabbix_file', array( - 'hostname' => 'foo.beberlei.de', - 'file' => '/etc/zabbix/zabbix_agentd.conf' -)); - -$librato = \Beberlei\Metrics\Factory::create('librato', array( - 'hostname' => 'foo.beberlei.de', - 'username' => 'foo', - 'password' => 'bar', -)); $null = \Beberlei\Metrics\Factory::create('null'); ``` ## Symfony Bundle Integration -Register Bundle into Kernel: +Register Bundle in bundles.php ```php - ['all' => true], +]; -class AppKernel extends Kernel -{ - public function registerBundles() - { - //.. - $bundles[] = new \Beberlei\Bundle\MetricsBundle\BeberleiMetricsBundle(); - //.. - } -} ``` -Do Configuration: +Do some configuration: ```yaml # app/config/config.yml beberlei_metrics: - default: foo + default: statsd collectors: - foo: - type: statsd - bar: - type: zabbix - prefix: foo.beberlei.de - host: localhost - port: 10051 - baz: - type: zabbix_file - prefix: foo.beberlei.de - file: /etc/zabbix/zabbix_agentd.conf - librato: - type: librato - username: foo - password: bar - source: hermes10 - dbal: - type: doctrine_dbal - connection: metrics # using the connection named "metrics" - monolog: - type: monolog influxdb: type: influxdb - influxdb_client: influxdb_client_service # using the InfluxDB client service named "influxdb_client_service" - tags: + database: metrics + # host: localhost # option + # username: username # optional + # password: password # optional + # port: 8086 # optional + # If you want to use a custom database service + # It must be an instance of "InfluxDB\Database" + # In this case, you can omit de "database" option + # service: my.service.id + tags: # optional dc: "west" node_instance: "hermes10" prometheus: type: prometheus - prometheus_collector_registry: prometheus_collector_registry_service # using the Prometheus collector registry service named "prometheus_collector_registry_service" + # If you want to use a custom registry service + # It must be an instance of "Prometheus\CollectorRegistry" + # By default it uses an "Prometheus\Storage\InMemory" adapter + # service: my.service.id namespace: app_name # optional - tags: + tags: # optional dc: "west" node_instance: "hermes10" + statsd: + type: statsd + # host: localhost # default + # port: 8125 # default + # prefix: '' # default + dogstatsd: + type: dogstatsd + # host: localhost # default + # port: 8125 # default + # prefix: '' # default + dbal: + type: doctrine_dbal + # Use another connection, by default it uses the default connection + # connection: metrics + monolog: + type: monolog ``` -This adds collectors to the Metrics registry. The functions are automatically -included in the Bundle class so that in your code you can just start using the -convenient functions. Metrics are also added as services: +Then, you can inject the `Beberlei\Metrics\Collector\CollectorInterface` and +start using it: ```php -get('beberlei_metrics.collector.foo'); + public function doSomething(): void + { + $this->collector->increment('foo.bar'); + } +} ``` -and the default collector can be fetched: +The `Beberlei\Metrics\Collector\CollectorInterface` is automatically aliased to +the default collector. +If you want to inject a specific collector, you must use the `#[Target]` attribute: ```php -get('beberlei_metrics.collector'); +public function __construct( + #[Target('name_of_the_collector')] + CollectorInterface $memoryCollector, +) { ``` - diff --git a/UPGRADE-2.0.md b/UPGRADE-2.0.md deleted file mode 100644 index b1def08..0000000 --- a/UPGRADE-2.0.md +++ /dev/null @@ -1,17 +0,0 @@ -Upgrade from 1.x to 2.0 -======================= - -* [BC BREAK] Collector `Beberlei\Metrics\Collector\Monolog` is renamed to `Beberlei\Metrics\Collector\Logger`. - -* [BC BREAK] Collector `Beberlei\Metrics\Collector\Monolog` takes a `Psr\Log\LoggerInterface`. - -* [BC BREAK] The bundle does not rely on `Beberlei\Metrics\Factory` and `Beberlei\Metrics\Registry` anymore. - -* [BC BREAK] The bundle configuration has a new "standard" key mapping. The -`hostname` key for `Librato` is now `source`. The `hostname` key for `Zabbix` is -now `prefix`. `hostname`, and `servername` are now `host`. `serverport` is now -`port`. - -* [BC BREAK] The `Registry` is removed. - -* [BC BREAK] The `functions.php` is removed. diff --git a/composer.json b/composer.json index 7679a40..7ff4fbe 100644 --- a/composer.json +++ b/composer.json @@ -2,6 +2,7 @@ "name": "beberlei/metrics", "description": "Simple library to talk to metrics collector services.", "keywords": ["metrics", "logging"], + "license": "MIT", "authors": [ { "name": "Benjamin Eberlei", @@ -14,35 +15,41 @@ "role": "Lead Developer" } ], - "license": "MIT", - "suggest": { - "corley/influxdb-sdk": "For InfluxDB integration", - "okitsu/zabbix-sender": "For zabbix integration", - "kriswallsmith/buzz": "For Librato integration", - "jimdo/prometheus_client_php": "For Prometheus integration" - }, "require": { "psr/log": "^1.0 || ^2.0 || ^3.0" }, "require-dev": { - "corley/influxdb-sdk": "^0.5.1", - "doctrine/dbal": "~2.0", - "jimdo/prometheus_client_php": "^0.5", - "kriswallsmith/buzz": "*", - "okitsu/zabbix-sender": "*@dev", - "symfony/config": "~3.3 || ~4.0", - "symfony/dependency-injection": "~3.3 || ~4.0", - "symfony/http-kernel": "~3.3 || ~4.0" + "php": ">=8.1", + "doctrine/dbal": "^2.0", + "influxdb/influxdb-php": "^1.15", + "promphp/prometheus_client_php": "^2", + "symfony/framework-bundle": "^5.4 || ^6.4 || ^7.0", + "symfony/phpunit-bridge": "^6.4.4 || ^7.0" + }, + "conflict": { + "doctrine/dbal": "<2", + "influxdb/influxdb-php": "<1.15", + "promphp/prometheus_client_php": "<2", + "symfony/framework-bundle": "<5.4" }, "autoload": { "psr-4": { - "Beberlei\\Metrics\\": "src/Beberlei/Metrics", - "Beberlei\\Bundle\\MetricsBundle\\": "src/Beberlei/Bundle/MetricsBundle" + "Beberlei\\Metrics\\": "src/Metrics", + "Beberlei\\Bundle\\MetricsBundle\\": "src/MetricsBundle" } }, - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" + "autoload-dev": { + "psr-4": { + "Beberlei\\Bundle\\MetricsBundle\\": "src/MetricsBundle" } + }, + "suggest": { + "doctrine/dbal": "For Doctrine DBAL integration", + "influxdb/influxdb-php": "For InfluxDB integration", + "symfony/framework-bundle": "For Symfony integration", + "promphp/prometheus_client_php": "For Prometheus integration" + }, + "config": { + "sort-packages": true } } diff --git a/example/statsd.php b/example/statsd.php deleted file mode 100644 index 2fb3b68..0000000 --- a/example/statsd.php +++ /dev/null @@ -1,13 +0,0 @@ -increment('foo.bar'); - $metrics->decrement('foo.baz'); - $metrics->measure('foo', rand(1, 10)); - $metrics->flush(); - usleep(500); -} diff --git a/examples/.castor/docker.php b/examples/.castor/docker.php new file mode 100644 index 0000000..99938d1 --- /dev/null +++ b/examples/.castor/docker.php @@ -0,0 +1,472 @@ +title('About this project'); + + io()->comment('Run castor to display all available commands.'); + io()->comment('Run castor about to display this project help.'); + io()->comment('Run castor help [command] to display Castor help.'); + + io()->section('Available URLs for this project:'); + $urls = [variable('root_domain'), ...variable('extra_domains')]; + + $payload = @file_get_contents(sprintf('http://%s:8080/api/http/routers', variable('root_domain'))); + if ($payload) { + $routers = json_decode($payload, true); + $projectName = variable('project_name'); + foreach ($routers as $router) { + if (!preg_match("{^{$projectName}-(.*)@docker$}", $router['name'])) { + continue; + } + if ("frontend-{$projectName}" === $router['service']) { + continue; + } + if (!preg_match('{^Host\\(`(?P.*)`\\)$}', $router['rule'], $matches)) { + continue; + } + $hosts = explode('`) || Host(`', $matches['hosts']); + $urls = [...$urls, ...$hosts]; + } + } + io()->listing(array_map(fn ($url) => "https://{$url}", $urls)); +} + +#[AsTask(description: 'Opens the project in your browser', namespace: '')] +function open(): void +{ + run(['open', 'https://' . variable('root_domain')], quiet: true); +} + +#[AsTask(description: 'Builds the infrastructure', aliases: ['build'])] +function build(): void +{ + io()->title('Building infrastructure'); + + $userId = variable('user_id'); + $phpVersion = variable('php_version'); + + $command = [ + 'build', + '--build-arg', "USER_ID={$userId}", + '--build-arg', "PHP_VERSION={$phpVersion}", + ]; + + docker_compose($command, withBuilder: true); +} + +#[AsTask(description: 'Builds and starts the infrastructure', aliases: ['up'])] +function up(): void +{ + io()->title('Starting infrastructure'); + + try { + docker_compose(['up', '--detach', '--no-build']); + } catch (ExceptionInterface $e) { + io()->error('An error occured while starting the infrastructure.'); + io()->note('Did you forget to run "castor docker:build"?'); + io()->note('Or you forget to login to the registry?'); + + throw $e; + } +} + +#[AsTask(description: 'Stops the infrastructure', aliases: ['stop'])] +function stop(): void +{ + io()->title('Stopping infrastructure'); + + docker_compose(['stop']); +} + +#[AsTask(description: 'Opens a shell (bash) into a builder container', aliases: ['builder'])] +function builder(): void +{ + $c = context() + ->withTimeout(null) + ->withTty() + ->withEnvironment($_ENV + $_SERVER) + ->withAllowFailure() + ; + docker_compose_run('bash', c: $c); +} + +#[AsTask(description: 'Displays infrastructure logs', aliases: ['logs'])] +function logs(): void +{ + docker_compose(['logs', '-f', '--tail', '150'], c: context()->withTty()); +} + +#[AsTask(description: 'Lists containers status', aliases: ['ps'])] +function ps(): void +{ + docker_compose(['ps'], withBuilder: false); +} + +#[AsTask(description: 'Cleans the infrastructure (remove container, volume, networks)', aliases: ['destroy'])] +function destroy( + #[AsOption(description: 'Force the destruction without confirmation', shortcut: 'f')] + bool $force = false, +): void { + io()->title('Destroying infrastructure'); + + if (!$force) { + io()->warning('This will permanently remove all containers, volumes, networks... created for this project.'); + io()->note('You can use the --force option to avoid this confirmation.'); + if (!io()->confirm('Are you sure?', false)) { + io()->comment('Aborted.'); + + return; + } + } + + docker_compose(['down', '--remove-orphans', '--volumes', '--rmi=local'], withBuilder: true); + $files = finder() + ->in(variable('root_dir') . '/infrastructure/docker/services/router/certs/') + ->name('*.pem') + ->files() + ; + fs()->remove($files); +} + +#[AsTask(description: 'Generates SSL certificates (with mkcert if available or self-signed if not)', namespace: '')] +function generate_certificates( + #[AsOption(description: 'Force the certificates re-generation without confirmation', shortcut: 'f')] + bool $force = false, +): void { + $sslDir = variable('root_dir') . '/infrastructure/docker/services/router/certs'; + + if (file_exists("{$sslDir}/cert.pem") && !$force) { + io()->comment('SSL certificates already exists.'); + io()->note('Run "castor docker:generate-certificates --force" to generate new certificates.'); + + return; + } + + io()->title('Generating SSL certificates'); + + if ($force) { + if (file_exists($f = "{$sslDir}/cert.pem")) { + io()->comment('Removing existing certificates in infrastructure/docker/services/router/certs/*.pem.'); + unlink($f); + } + + if (file_exists($f = "{$sslDir}/key.pem")) { + unlink($f); + } + } + + $finder = new ExecutableFinder(); + $mkcert = $finder->find('mkcert'); + + if ($mkcert) { + $pathCaRoot = capture(['mkcert', '-CAROOT']); + + if (!is_dir($pathCaRoot)) { + io()->warning('You must have mkcert CA Root installed on your host with "mkcert -install" command.'); + + return; + } + + $rootDomain = variable('root_domain'); + + run([ + 'mkcert', + '-cert-file', "{$sslDir}/cert.pem", + '-key-file', "{$sslDir}/key.pem", + $rootDomain, + "*.{$rootDomain}", + ...variable('extra_domains'), + ]); + + io()->success('Successfully generated SSL certificates with mkcert.'); + + if ($force) { + io()->note('Please restart the infrastructure to use the new certificates with "castor up" or "castor start".'); + } + + return; + } + + run(['infrastructure/docker/services/router/generate-ssl.sh'], quiet: true); + + io()->success('Successfully generated self-signed SSL certificates in infrastructure/docker/services/router/certs/*.pem.'); + io()->comment('Consider installing mkcert to generate locally trusted SSL certificates and run "castor docker:generate-certificates --force".'); + + if ($force) { + io()->note('Please restart the infrastructure to use the new certificates with "castor up" or "castor start".'); + } +} + +#[AsTask(description: 'Starts the workers', namespace: 'docker:worker', name: 'start', aliases: ['start-workers'])] +function workers_start(): void +{ + io()->title('Starting workers'); + + $workers = get_workers(); + + if (!$workers) { + return; + } + + run([ + 'docker', + 'update', + '--restart=unless-stopped', + ...$workers, + ], quiet: true); + + run([ + 'docker', + 'start', + ...$workers, + ], quiet: true); +} + +#[AsTask(description: 'Stops the workers', namespace: 'docker:worker', name: 'stop', aliases: ['stop-workers'])] +function workers_stop(): void +{ + io()->title('Stopping workers'); + + $workers = get_workers(); + + if (!$workers) { + return; + } + + run([ + 'docker', + 'update', + '--restart=no', + ...$workers, + ]); + + run([ + 'docker', + 'stop', + ...$workers, + ]); +} + +#[AsContext(default: true)] +function create_default_context(): Context +{ + $data = create_default_variables() + [ + 'project_name' => 'app', + 'root_domain' => 'app.test', + 'extra_domains' => [], + 'php_version' => '8.3', + 'docker_compose_files' => [ + 'docker-compose.yml', + 'docker-compose.worker.yml', + ], + 'macos' => false, + 'power_shell' => false, + 'user_id' => posix_geteuid(), + 'root_dir' => \dirname(__DIR__), + 'env' => $_SERVER['CI'] ?? false ? 'ci' : 'dev', + ]; + + if (file_exists($data['root_dir'] . '/infrastructure/docker/docker-compose.override.yml')) { + $data['docker_compose_files'][] = 'docker-compose.override.yml'; + } + + // We need an empty context to run command, since the default context has + // not been set in castor, since we ARE creating it right now + $emptyContext = new Context(); + + $data['composer_cache_dir'] = cache('composer_cache_dir', function () use ($emptyContext): string { + $composerCacheDir = capture(['composer', 'global', 'config', 'cache-dir', '-q'], onFailure: '', context: $emptyContext); + // If PHP is broken, the output will not be a valid path but an error message + if (!is_dir($composerCacheDir)) { + $composerCacheDir = sys_get_temp_dir() . '/castor/composer'; + // If the directory does not exist, we create it. Otherwise, docker + // will do, as root, and the user will not be able to write in it. + if (!is_dir($composerCacheDir)) { + mkdir($composerCacheDir, 0o777, true); + } + } + + return $composerCacheDir; + }); + + $platform = strtolower(php_uname('s')); + if (str_contains($platform, 'darwin')) { + $data['macos'] = true; + $data['docker_compose_files'][] = 'docker-compose.docker-for-x.yml'; + } elseif (\in_array($platform, ['win32', 'win64'])) { + $data['docker_compose_files'][] = 'docker-compose.docker-for-x.yml'; + $data['power_shell'] = true; + } + + if ($data['user_id'] > 256000) { + $data['user_id'] = 1000; + } + + if (0 === $data['user_id']) { + log('Running as root? Fallback to fake user id.', 'warning'); + $data['user_id'] = 1000; + } + + return new Context($data, pty: 'dev' === $data['env']); +} + +/** + * @param array $subCommand + */ +function docker_compose(array $subCommand, ?Context $c = null, bool $withBuilder = false): Process +{ + $c ??= context(); + + $domains = [variable('root_domain'), ...variable('extra_domains')]; + $domains = '`' . implode('`) || Host(`', $domains) . '`'; + + $c = $c + ->withTimeout(null) + ->withEnvironment([ + 'PROJECT_NAME' => variable('project_name'), + 'PROJECT_ROOT_DOMAIN' => variable('root_domain'), + 'PROJECT_DOMAINS' => $domains, + 'USER_ID' => variable('user_id'), + 'COMPOSER_CACHE_DIR' => variable('composer_cache_dir'), + 'PHP_VERSION' => variable('php_version'), + 'BUILDKIT_PROGRESS' => 'plain', + ]) + ; + + $command = [ + 'docker', + 'compose', + '-p', variable('project_name'), + ]; + + foreach (variable('docker_compose_files') as $file) { + $command[] = '-f'; + $command[] = variable('root_dir') . '/infrastructure/docker/' . $file; + } + + if ($withBuilder) { + $command[] = '-f'; + $command[] = variable('root_dir') . '/infrastructure/docker/docker-compose.builder.yml'; + } + + $command = array_merge($command, $subCommand); + + return run($command, context: $c); +} + +function docker_compose_run( + string $runCommand, + ?Context $c = null, + string $service = 'builder', + bool $noDeps = true, + ?string $workDir = null, + bool $portMapping = false, + bool $withBuilder = true, +): Process { + $command = [ + 'run', + '--rm', + ]; + + if ($noDeps) { + $command[] = '--no-deps'; + } + + if ($portMapping) { + $command[] = '--service-ports'; + } + + if (null !== $workDir) { + $command[] = '-w'; + $command[] = $workDir; + } + + $command[] = $service; + $command[] = '/bin/sh'; + $command[] = '-c'; + $command[] = "exec {$runCommand}"; + + return docker_compose($command, c: $c, withBuilder: $withBuilder); +} + +function docker_exit_code( + string $runCommand, + ?Context $c = null, + string $service = 'builder', + bool $noDeps = true, + ?string $workDir = null, + bool $withBuilder = true, +): int { + $c = ($c ?? context())->withAllowFailure(); + + $process = docker_compose_run( + runCommand: $runCommand, + c: $c, + service: $service, + noDeps: $noDeps, + workDir: $workDir, + withBuilder: $withBuilder, + ); + + return $process->getExitCode() ?? 0; +} + +// Mac users have a lot of problems running Yarn / Webpack on the Docker stack +// so this func allow them to run these tools on their host +function run_in_docker_or_locally_for_mac(string $command, ?Context $c = null): void +{ + $c ??= context(); + + if (variable('macos')) { + run($command, context: $c->withPath(variable('root_dir'))); + } else { + docker_compose_run($command, c: $c); + } +} + +/** + * Find worker containers for the current project. + * + * @return array + */ +function get_workers(): array +{ + $command = [ + 'docker', + 'ps', + '-a', + '--filter', 'label=docker-starter.worker.' . variable('project_name'), + '--quiet', + ]; + $out = capture($command); + + if (!$out) { + return []; + } + + $workers = explode("\n", $out); + + return array_map('trim', $workers); +} diff --git a/examples/.castor/qa.php b/examples/.castor/qa.php new file mode 100644 index 0000000..b068c4d --- /dev/null +++ b/examples/.castor/qa.php @@ -0,0 +1,51 @@ +title('Installing QA tooling'); + + docker_compose_run('composer install -o', workDir: '/var/www/tools/php-cs-fixer'); + docker_compose_run('composer install -o', workDir: '/var/www/tools/phpstan'); +} + +// #[AsTask(description: 'Runs PHPUnit', aliases: ['phpunit'])] +// function phpunit(): int +// { +// return docker_exit_code('phpunit'); +// } + +#[AsTask(description: 'Runs PHPStan', aliases: ['phpstan'])] +function phpstan(): int +{ + return docker_exit_code('phpstan', workDir: '/var/www'); +} + +#[AsTask(description: 'Fixes Coding Style', aliases: ['cs'])] +function cs(bool $dryRun = false): int +{ + if ($dryRun) { + return docker_exit_code('php-cs-fixer fix --dry-run --diff', workDir: '/var/www'); + } + + return docker_exit_code('php-cs-fixer fix', workDir: '/var/www'); +} diff --git a/examples/.env b/examples/.env new file mode 100644 index 0000000..a4f9456 --- /dev/null +++ b/examples/.env @@ -0,0 +1,30 @@ +# In all environments, the following files are loaded if they exist, +# the latter taking precedence over the former: +# +# * .env contains default values for the environment variables needed by the app +# * .env.local uncommitted file with local overrides +# * .env.$APP_ENV committed environment-specific defaults +# * .env.$APP_ENV.local uncommitted environment-specific overrides +# +# Real environment variables win over .env files. +# +# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES. +# https://symfony.com/doc/current/configuration/secrets.html +# +# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2). +# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration + +###> symfony/framework-bundle ### +APP_ENV=dev +APP_SECRET=acbff843d79416cbb1432679dc4f3ead +###< symfony/framework-bundle ### + +###> doctrine/doctrine-bundle ### +# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url +# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml +# +# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db" +# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8.0.32&charset=utf8mb4" +# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4" +DATABASE_URL="postgresql://app:app@postgres:5432/app?serverVersion=16&charset=utf8" +###< doctrine/doctrine-bundle ### diff --git a/examples/.env.test b/examples/.env.test new file mode 100644 index 0000000..9e7162f --- /dev/null +++ b/examples/.env.test @@ -0,0 +1,6 @@ +# define your env variables for the test env here +KERNEL_CLASS='App\Kernel' +APP_SECRET='$ecretf0rt3st' +SYMFONY_DEPRECATIONS_HELPER=999999 +PANTHER_APP_ENV=panther +PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots diff --git a/examples/.gitattributes b/examples/.gitattributes new file mode 100644 index 0000000..1823cf6 --- /dev/null +++ b/examples/.gitattributes @@ -0,0 +1,2 @@ +# Force LF line ending (mandatory for Windows) +* text=auto eol=lf diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 0000000..1a2a239 --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1,25 @@ +###> symfony/framework-bundle ### +/.env.local +/.env.local.php +/.env.*.local +/config/secrets/prod/prod.decrypt.private.php +/public/bundles/ +/var/ +/vendor/ +###< symfony/framework-bundle ### + +# Infra & tooling +/.castor.stub.php +/infrastructure/docker/docker-compose.override.yml +/infrastructure/docker/services/router/certs/*.pem +.php-cs-fixer.cache + +###> phpunit/phpunit ### +/phpunit.xml +.phpunit.result.cache +###< phpunit/phpunit ### + +###> symfony/phpunit-bridge ### +.phpunit.result.cache +/phpunit.xml +###< symfony/phpunit-bridge ### diff --git a/examples/.php-cs-fixer.php b/examples/.php-cs-fixer.php new file mode 100644 index 0000000..29a627d --- /dev/null +++ b/examples/.php-cs-fixer.php @@ -0,0 +1,27 @@ +ignoreVCSIgnored(true) + ->ignoreDotFiles(false) + ->in(__DIR__) + ->append([ + __FILE__, + ]) +; + +return (new PhpCsFixer\Config()) + ->setRiskyAllowed(true) + ->setRules([ + '@PHP81Migration' => true, + '@PhpCsFixer' => true, + '@Symfony' => true, + '@Symfony:risky' => true, + 'php_unit_internal_class' => false, // From @PhpCsFixer but we don't want it + 'php_unit_test_class_requires_covers' => false, // From @PhpCsFixer but we don't want it + 'phpdoc_add_missing_param_annotation' => false, // From @PhpCsFixer but we don't want it + 'concat_space' => ['spacing' => 'one'], + 'ordered_class_elements' => true, // Symfony(PSR12) override the default value, but we don't want + 'blank_line_before_statement' => true, // Symfony(PSR12) override the default value, but we don't want + ]) + ->setFinder($finder) +; diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..5749442 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,104 @@ +# Symfony demo application + +This application use +[jolicode/docker-starter](https://github.com/jolicode/docker-starter) to provide +a local development environment. + +## Running the application locally + +### Requirements + +A Docker environment is provided and requires you to have these tools available: + + * Docker + * [Castor](https://github.com/jolicode/castor#installation) + +#### Castor + +Once castor is installed, in order to improve your usage of castor scripts, you +can install console autocompletion script. + +If you are using bash: + +```bash +castor completion | sudo tee /etc/bash_completion.d/castor +``` + +If you are using something else, please refer to your shell documentation. You +may need to use `castor completion > /to/somewhere`. + +Castor supports completion for `bash`, `zsh` & `fish` shells. + +### Docker environment + +The Docker infrastructure provides a web stack with: + - NGINX + - PostgreSQL + - PHP + - Traefik + - A container with some tooling: + - Composer + - Node + - Yarn / NPM + +### Domain configuration (first time only) + +Before running the application for the first time, ensure your domain names +point the IP of your Docker daemon by editing your `/etc/hosts` file. + +This IP is probably `127.0.0.1` unless you run Docker in a special VM (like docker-machine for example). + +> [!NOTE] +> The router binds port 80 and 443, that's why it will work with `127.0.0.1` + +``` +echo '127.0.0.1 symfony-metrics.test grafana.symfony-metrics.test' | sudo tee -a /etc/hosts +``` + +### Starting the stack + +Launch the stack by running this command: + +```bash +castor start +``` + +> [!NOTE] +> the first start of the stack should take a few minutes. + +The site is now accessible at the hostnames your have configured over HTTPS +(you may need to accept self-signed SSL certificate if you do not have mkcert +installed on your computer - see below). + +### SSL certificates + +HTTPS is supported out of the box. SSL certificates are not versioned and will +be generated the first time you start the infrastructure (`castor start`) or if +you run `castor infra:generate-certificates`. + +If you have `mkcert` installed on your computer, it will be used to generate +locally trusted certificates. See [`mkcert` documentation](https://github.com/FiloSottile/mkcert#installation) +to understand how to install it. Do not forget to install CA root from mkcert +by running `mkcert -install`. + +If you don't have `mkcert`, then self-signed certificates will instead be +generated with openssl. You can configure [infrastructure/docker/services/router/openssl.cnf](infrastructure/docker/services/router/openssl.cnf) +to tweak certificates. + +You can run `castor infra:generate-certificates --force` to recreate new certificates +if some were already generated. Remember to restart the infrastructure to make +use of the new certificates with `castor up` or `castor start`. + +### Builder + +Having some composer, yarn or other modifications to make on the project? +Start the builder which will give you access to a container with all these +tools available: + +```bash +castor builder +``` + +### Other tasks + +Checkout `castor` to have the list of available tasks. diff --git a/examples/bin/console b/examples/bin/console new file mode 100755 index 0000000..c933dc5 --- /dev/null +++ b/examples/bin/console @@ -0,0 +1,17 @@ +#!/usr/bin/env php += 80000) { + require dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit'; + } else { + define('PHPUNIT_COMPOSER_INSTALL', dirname(__DIR__).'/vendor/autoload.php'); + require PHPUNIT_COMPOSER_INSTALL; + PHPUnit\TextUI\Command::main(); + } +} else { + if (!is_file(dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php')) { + echo "Unable to find the `simple-phpunit.php` script in `vendor/symfony/phpunit-bridge/bin/`.\n"; + exit(1); + } + + require dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php'; +} diff --git a/examples/castor.php b/examples/castor.php new file mode 100644 index 0000000..6ac71e5 --- /dev/null +++ b/examples/castor.php @@ -0,0 +1,82 @@ + + */ +function create_default_variables(): array +{ + return [ + 'project_name' => 'symfony-metrics', + 'root_domain' => 'symfony-metrics.test', + ]; +} + +#[AsTask(description: 'Builds and starts the infrastructure, then install the application (composer, ...)')] +function start(): void +{ + io()->title('Starting the stack'); + + workers_stop(); + generate_certificates(force: false); + build(); + up(); + cache_clear(); + install(); + migrate(); + workers_start(); + + notify('The stack is now up and running.'); + io()->success('The stack is now up and running.'); + + about(); +} + +#[AsTask(description: 'Installs the application (composer, ...)', namespace: 'app', aliases: ['install'])] +function install(): void +{ + io()->title('Installing the application'); + + io()->section('Installing PHP dependencies'); + docker_compose_run('composer install -n --prefer-dist --optimize-autoloader'); + + qa\install(); +} + +#[AsTask(description: 'Clear the application cache', namespace: 'app', aliases: ['cache-clear'])] +function cache_clear(): void +{ + io()->title('Clearing the application cache'); + + docker_compose_run('rm -rf var/cache/ && bin/console cache:warmup'); +} + +#[AsTask(description: 'Migrates database schema', namespace: 'app:db', aliases: ['migrate'])] +function migrate(): void +{ + io()->title('Migrating the database schema'); + + docker_compose_run('bin/console doctrine:database:create --if-not-exists'); + docker_compose_run('bin/console doctrine:migration:migrate -n --allow-no-migration'); +} diff --git a/examples/composer.json b/examples/composer.json new file mode 100644 index 0000000..9ccc0a6 --- /dev/null +++ b/examples/composer.json @@ -0,0 +1,91 @@ +{ + "type": "project", + "license": "proprietary", + "minimum-stability": "stable", + "prefer-stable": true, + "repositories": [ + { + "type": "path", + "url": "/metrics" + } + ], + "require": { + "php": ">=8.3", + "ext-ctype": "*", + "ext-iconv": "*", + "beberlei/metrics": "*@dev", + "doctrine/dbal": "^3", + "doctrine/doctrine-bundle": "^2.11", + "doctrine/doctrine-migrations-bundle": "^3.3", + "doctrine/orm": "^3.0", + "influxdb/influxdb-php": "^1.15", + "phpstan/phpdoc-parser": "^1.26", + "promphp/prometheus_client_php": "^2.10", + "symfony/console": "7.0.*", + "symfony/dotenv": "7.0.*", + "symfony/flex": "^2", + "symfony/framework-bundle": "7.0.*", + "symfony/monolog-bundle": "^3.0", + "symfony/runtime": "7.0.*", + "symfony/twig-bundle": "7.0.*", + "symfony/uid": "7.0.*", + "symfony/yaml": "7.0.*" + }, + "config": { + "allow-plugins": { + "php-http/discovery": true, + "symfony/flex": true, + "symfony/runtime": true + }, + "sort-packages": true + }, + "autoload": { + "psr-4": { + "App\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "App\\Tests\\": "tests/" + } + }, + "replace": { + "symfony/polyfill-ctype": "*", + "symfony/polyfill-iconv": "*", + "symfony/polyfill-php72": "*", + "symfony/polyfill-php73": "*", + "symfony/polyfill-php74": "*", + "symfony/polyfill-php80": "*", + "symfony/polyfill-php81": "*", + "symfony/polyfill-php82": "*" + }, + "scripts": { + "auto-scripts": { + "cache:clear": "symfony-cmd", + "assets:install %PUBLIC_DIR%": "symfony-cmd" + }, + "post-install-cmd": [ + "@auto-scripts" + ], + "post-update-cmd": [ + "@auto-scripts" + ] + }, + "conflict": { + "symfony/symfony": "*" + }, + "extra": { + "symfony": { + "allow-contrib": false, + "require": "7.0.*" + } + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "symfony/debug-bundle": "7.0.*", + "symfony/maker-bundle": "^1.0", + "symfony/phpunit-bridge": "^7.0", + "symfony/stopwatch": "7.0.*", + "symfony/web-profiler-bundle": "7.0.*" + } +} diff --git a/examples/composer.lock b/examples/composer.lock new file mode 100644 index 0000000..91d0b4d --- /dev/null +++ b/examples/composer.lock @@ -0,0 +1,7390 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "13387979a3a0d1d914b168e3a8ae7c53", + "packages": [ + { + "name": "beberlei/metrics", + "version": "dev-massive-code-grooming", + "dist": { + "type": "path", + "url": "/metrics", + "reference": "e533b9807f8b9a32ca8ed051deaca9716e01f8aa" + }, + "require": { + "psr/log": "^1.0 || ^2.0 || ^3.0" + }, + "conflict": { + "doctrine/dbal": "<2", + "influxdb/influxdb-php": "<1.15", + "promphp/prometheus_client_php": "<2", + "symfony/framework-bundle": "<5.4" + }, + "require-dev": { + "doctrine/dbal": "^2.0", + "influxdb/influxdb-php": "^1.15", + "php": ">=8.1", + "promphp/prometheus_client_php": "^2", + "symfony/framework-bundle": "^5.4 || ^6.4 || ^7.0", + "symfony/phpunit-bridge": "^6.4.4 || ^7.0" + }, + "suggest": { + "doctrine/dbal": "For Doctrine DBAL integration", + "influxdb/influxdb-php": "For InfluxDB integration", + "promphp/prometheus_client_php": "For Prometheus integration", + "symfony/framework-bundle": "For Symfony integration" + }, + "type": "library", + "autoload": { + "psr-4": { + "Beberlei\\Metrics\\": "src/Metrics", + "Beberlei\\Bundle\\MetricsBundle\\": "src/MetricsBundle" + } + }, + "autoload-dev": { + "psr-4": { + "Beberlei\\Bundle\\MetricsBundle\\": "src/MetricsBundle" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de", + "role": "Project Founder" + }, + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info", + "role": "Lead Developer" + } + ], + "description": "Simple library to talk to metrics collector services.", + "keywords": [ + "logging", + "metrics" + ], + "transport-options": { + "relative": false + } + }, + { + "name": "doctrine/cache", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/cache.git", + "reference": "1ca8f21980e770095a31456042471a57bc4c68fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/cache/zipball/1ca8f21980e770095a31456042471a57bc4c68fb", + "reference": "1ca8f21980e770095a31456042471a57bc4c68fb", + "shasum": "" + }, + "require": { + "php": "~7.1 || ^8.0" + }, + "conflict": { + "doctrine/common": ">2.2,<2.4" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/coding-standard": "^9", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "symfony/cache": "^4.4 || ^5.4 || ^6", + "symfony/var-exporter": "^4.4 || ^5.4 || ^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" + } + }, + "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": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", + "homepage": "https://www.doctrine-project.org/projects/cache.html", + "keywords": [ + "abstraction", + "apcu", + "cache", + "caching", + "couchdb", + "memcached", + "php", + "redis", + "xcache" + ], + "support": { + "issues": "https://github.com/doctrine/cache/issues", + "source": "https://github.com/doctrine/cache/tree/2.2.0" + }, + "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%2Fcache", + "type": "tidelift" + } + ], + "time": "2022-05-20T20:07:39+00:00" + }, + { + "name": "doctrine/collections", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/collections.git", + "reference": "07e16cd7b80a2cffed99e36b541876af172f0257" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/collections/zipball/07e16cd7b80a2cffed99e36b541876af172f0257", + "reference": "07e16cd7b80a2cffed99e36b541876af172f0257", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1", + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "ext-json": "*", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^10.5", + "vimeo/psalm": "^5.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Collections\\": "src" + } + }, + "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": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.", + "homepage": "https://www.doctrine-project.org/projects/collections.html", + "keywords": [ + "array", + "collections", + "iterators", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/collections/issues", + "source": "https://github.com/doctrine/collections/tree/2.2.0" + }, + "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%2Fcollections", + "type": "tidelift" + } + ], + "time": "2024-02-25T22:55:36+00:00" + }, + { + "name": "doctrine/dbal", + "version": "3.8.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/dbal.git", + "reference": "a19a1d05ca211f41089dffcc387733a6875196cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/a19a1d05ca211f41089dffcc387733a6875196cb", + "reference": "a19a1d05ca211f41089dffcc387733a6875196cb", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2", + "doctrine/cache": "^1.11|^2.0", + "doctrine/deprecations": "^0.5.3|^1", + "doctrine/event-manager": "^1|^2", + "php": "^7.4 || ^8.0", + "psr/cache": "^1|^2|^3", + "psr/log": "^1|^2|^3" + }, + "require-dev": { + "doctrine/coding-standard": "12.0.0", + "fig/log-test": "^1", + "jetbrains/phpstorm-stubs": "2023.1", + "phpstan/phpstan": "1.10.57", + "phpstan/phpstan-strict-rules": "^1.5", + "phpunit/phpunit": "9.6.16", + "psalm/plugin-phpunit": "0.18.4", + "slevomat/coding-standard": "8.13.1", + "squizlabs/php_codesniffer": "3.8.1", + "symfony/cache": "^5.4|^6.0|^7.0", + "symfony/console": "^4.4|^5.4|^6.0|^7.0", + "vimeo/psalm": "4.30.0" + }, + "suggest": { + "symfony/console": "For helpful console commands such as SQL execution and import of files." + }, + "bin": [ + "bin/doctrine-dbal" + ], + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\DBAL\\": "src" + } + }, + "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" + } + ], + "description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.", + "homepage": "https://www.doctrine-project.org/projects/dbal.html", + "keywords": [ + "abstraction", + "database", + "db2", + "dbal", + "mariadb", + "mssql", + "mysql", + "oci8", + "oracle", + "pdo", + "pgsql", + "postgresql", + "queryobject", + "sasql", + "sql", + "sqlite", + "sqlserver", + "sqlsrv" + ], + "support": { + "issues": "https://github.com/doctrine/dbal/issues", + "source": "https://github.com/doctrine/dbal/tree/3.8.2" + }, + "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%2Fdbal", + "type": "tidelift" + } + ], + "time": "2024-02-12T18:36:36+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", + "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "phpstan/phpstan": "1.4.10 || 1.10.15", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "psalm/plugin-phpunit": "0.18.4", + "psr/log": "^1 || ^2 || ^3", + "vimeo/psalm": "4.30.0 || 5.12.0" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.3" + }, + "time": "2024-01-30T19:34:25+00:00" + }, + { + "name": "doctrine/doctrine-bundle", + "version": "2.11.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/DoctrineBundle.git", + "reference": "492725310ae9a1b5b20d6ae09fb5ae6404616e68" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/492725310ae9a1b5b20d6ae09fb5ae6404616e68", + "reference": "492725310ae9a1b5b20d6ae09fb5ae6404616e68", + "shasum": "" + }, + "require": { + "doctrine/cache": "^1.11 || ^2.0", + "doctrine/dbal": "^3.7.0 || ^4.0", + "doctrine/persistence": "^2.2 || ^3", + "doctrine/sql-formatter": "^1.0.1", + "php": "^7.4 || ^8.0", + "symfony/cache": "^5.4 || ^6.0 || ^7.0", + "symfony/config": "^5.4 || ^6.0 || ^7.0", + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", + "symfony/deprecation-contracts": "^2.1 || ^3", + "symfony/doctrine-bridge": "^5.4.19 || ^6.0.7 || ^7.0", + "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0", + "symfony/polyfill-php80": "^1.15", + "symfony/service-contracts": "^1.1.1 || ^2.0 || ^3" + }, + "conflict": { + "doctrine/annotations": ">=3.0", + "doctrine/orm": "<2.17 || >=4.0", + "twig/twig": "<1.34 || >=2.0 <2.4" + }, + "require-dev": { + "doctrine/annotations": "^1 || ^2", + "doctrine/coding-standard": "^12", + "doctrine/deprecations": "^1.0", + "doctrine/orm": "^2.17 || ^3.0", + "friendsofphp/proxy-manager-lts": "^1.0", + "phpunit/phpunit": "^9.5.26", + "psalm/plugin-phpunit": "^0.18.4", + "psalm/plugin-symfony": "^5", + "psr/log": "^1.1.4 || ^2.0 || ^3.0", + "symfony/phpunit-bridge": "^6.1 || ^7.0", + "symfony/property-info": "^5.4 || ^6.0 || ^7.0", + "symfony/proxy-manager-bridge": "^5.4 || ^6.0 || ^7.0", + "symfony/security-bundle": "^5.4 || ^6.0 || ^7.0", + "symfony/string": "^5.4 || ^6.0 || ^7.0", + "symfony/twig-bridge": "^5.4 || ^6.0 || ^7.0", + "symfony/validator": "^5.4 || ^6.0 || ^7.0", + "symfony/var-exporter": "^5.4 || ^6.2 || ^7.0", + "symfony/web-profiler-bundle": "^5.4 || ^6.0 || ^7.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0", + "twig/twig": "^1.34 || ^2.12 || ^3.0", + "vimeo/psalm": "^5.15" + }, + "suggest": { + "doctrine/orm": "The Doctrine ORM integration is optional in the bundle.", + "ext-pdo": "*", + "symfony/web-profiler-bundle": "To use the data collector." + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Doctrine\\Bundle\\DoctrineBundle\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + }, + { + "name": "Doctrine Project", + "homepage": "https://www.doctrine-project.org/" + } + ], + "description": "Symfony DoctrineBundle", + "homepage": "https://www.doctrine-project.org", + "keywords": [ + "database", + "dbal", + "orm", + "persistence" + ], + "support": { + "issues": "https://github.com/doctrine/DoctrineBundle/issues", + "source": "https://github.com/doctrine/DoctrineBundle/tree/2.11.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%2Fdoctrine-bundle", + "type": "tidelift" + } + ], + "time": "2024-02-10T20:56:20+00:00" + }, + { + "name": "doctrine/doctrine-migrations-bundle", + "version": "3.3.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/DoctrineMigrationsBundle.git", + "reference": "1dd42906a5fb9c5960723e2ebb45c68006493835" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/1dd42906a5fb9c5960723e2ebb45c68006493835", + "reference": "1dd42906a5fb9c5960723e2ebb45c68006493835", + "shasum": "" + }, + "require": { + "doctrine/doctrine-bundle": "^2.4", + "doctrine/migrations": "^3.2", + "php": "^7.2|^8.0", + "symfony/deprecation-contracts": "^2.1 || ^3", + "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "doctrine/orm": "^2.6 || ^3", + "doctrine/persistence": "^2.0 || ^3 ", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-deprecation-rules": "^1", + "phpstan/phpstan-phpunit": "^1", + "phpstan/phpstan-strict-rules": "^1.1", + "phpstan/phpstan-symfony": "^1.3", + "phpunit/phpunit": "^8.5|^9.5", + "psalm/plugin-phpunit": "^0.18.4", + "psalm/plugin-symfony": "^3 || ^5", + "symfony/phpunit-bridge": "^6.3 || ^7", + "symfony/var-exporter": "^5.4 || ^6 || ^7", + "vimeo/psalm": "^4.30 || ^5.15" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Doctrine\\Bundle\\MigrationsBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Doctrine Project", + "homepage": "https://www.doctrine-project.org" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony DoctrineMigrationsBundle", + "homepage": "https://www.doctrine-project.org", + "keywords": [ + "dbal", + "migrations", + "schema" + ], + "support": { + "issues": "https://github.com/doctrine/DoctrineMigrationsBundle/issues", + "source": "https://github.com/doctrine/DoctrineMigrationsBundle/tree/3.3.0" + }, + "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%2Fdoctrine-migrations-bundle", + "type": "tidelift" + } + ], + "time": "2023-11-13T19:44:41+00:00" + }, + { + "name": "doctrine/event-manager", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/event-manager.git", + "reference": "750671534e0241a7c50ea5b43f67e23eb5c96f32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/750671534e0241a7c50ea5b43f67e23eb5c96f32", + "reference": "750671534e0241a7c50ea5b43f67e23eb5c96f32", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "conflict": { + "doctrine/common": "<2.9" + }, + "require-dev": { + "doctrine/coding-standard": "^10", + "phpstan/phpstan": "^1.8.8", + "phpunit/phpunit": "^9.5", + "vimeo/psalm": "^4.28" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "src" + } + }, + "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" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/event-manager.html", + "keywords": [ + "event", + "event dispatcher", + "event manager", + "event system", + "events" + ], + "support": { + "issues": "https://github.com/doctrine/event-manager/issues", + "source": "https://github.com/doctrine/event-manager/tree/2.0.0" + }, + "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%2Fevent-manager", + "type": "tidelift" + } + ], + "time": "2022-10-12T20:59:15+00:00" + }, + { + "name": "doctrine/inflector", + "version": "2.0.10", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/5817d0659c5b50c9b950feb9af7b9668e2c436bc", + "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^11.0", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.3", + "phpunit/phpunit": "^8.5 || ^9.5", + "vimeo/psalm": "^4.25 || ^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" + } + }, + "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": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "keywords": [ + "inflection", + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" + ], + "support": { + "issues": "https://github.com/doctrine/inflector/issues", + "source": "https://github.com/doctrine/inflector/tree/2.0.10" + }, + "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%2Finflector", + "type": "tidelift" + } + ], + "time": "2024-02-18T20:23:39+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^11", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^5.4" + }, + "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/2.0.0" + }, + "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-12-30T00:23:10+00:00" + }, + { + "name": "doctrine/lexer", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5", + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^5.21" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "src" + } + }, + "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/3.0.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%2Flexer", + "type": "tidelift" + } + ], + "time": "2024-02-05T11:56:58+00:00" + }, + { + "name": "doctrine/migrations", + "version": "3.7.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/migrations.git", + "reference": "47af29eef49f29ebee545947e8b2a4b3be318c8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/migrations/zipball/47af29eef49f29ebee545947e8b2a4b3be318c8a", + "reference": "47af29eef49f29ebee545947e8b2a4b3be318c8a", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2", + "doctrine/dbal": "^3.5.1 || ^4", + "doctrine/deprecations": "^0.5.3 || ^1", + "doctrine/event-manager": "^1.2 || ^2.0", + "php": "^8.1", + "psr/log": "^1.1.3 || ^2 || ^3", + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0", + "symfony/var-exporter": "^6.2 || ^7.0" + }, + "conflict": { + "doctrine/orm": "<2.12 || >=4" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "doctrine/orm": "^2.13 || ^3", + "doctrine/persistence": "^2 || ^3", + "doctrine/sql-formatter": "^1.0", + "ext-pdo_sqlite": "*", + "phpstan/phpstan": "^1.10", + "phpstan/phpstan-deprecation-rules": "^1.1", + "phpstan/phpstan-phpunit": "^1.3", + "phpstan/phpstan-strict-rules": "^1.4", + "phpstan/phpstan-symfony": "^1.3", + "phpunit/phpunit": "^10.3", + "symfony/cache": "^5.4 || ^6.0 || ^7.0", + "symfony/process": "^5.4 || ^6.0 || ^7.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0" + }, + "suggest": { + "doctrine/sql-formatter": "Allows to generate formatted SQL with the diff command.", + "symfony/yaml": "Allows the use of yaml for migration configuration files." + }, + "bin": [ + "bin/doctrine-migrations" + ], + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Migrations\\": "lib/Doctrine/Migrations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Michael Simonson", + "email": "contact@mikesimonson.com" + } + ], + "description": "PHP Doctrine Migrations project offer additional functionality on top of the database abstraction layer (DBAL) for versioning your database schema and easily deploying changes to it. It is a very easy to use and a powerful tool.", + "homepage": "https://www.doctrine-project.org/projects/migrations.html", + "keywords": [ + "database", + "dbal", + "migrations" + ], + "support": { + "issues": "https://github.com/doctrine/migrations/issues", + "source": "https://github.com/doctrine/migrations/tree/3.7.2" + }, + "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%2Fmigrations", + "type": "tidelift" + } + ], + "time": "2023-12-05T11:35:05+00:00" + }, + { + "name": "doctrine/orm", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/orm.git", + "reference": "2a250b5814de192a23438c0a43e15da7e77890a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/orm/zipball/2a250b5814de192a23438c0a43e15da7e77890a7", + "reference": "2a250b5814de192a23438c0a43e15da7e77890a7", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2", + "doctrine/collections": "^2.1", + "doctrine/dbal": "^3.8.2 || ^4", + "doctrine/deprecations": "^0.5.3 || ^1", + "doctrine/event-manager": "^1.2 || ^2", + "doctrine/inflector": "^1.4 || ^2.0", + "doctrine/instantiator": "^1.3 || ^2", + "doctrine/lexer": "^3", + "doctrine/persistence": "^3.1.1", + "ext-ctype": "*", + "php": "^8.1", + "psr/cache": "^1 || ^2 || ^3", + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/var-exporter": "~6.2.13 || ^6.3.2 || ^7.0" + }, + "require-dev": { + "doctrine/coding-standard": "^12.0", + "phpbench/phpbench": "^1.0", + "phpstan/phpstan": "1.10.35", + "phpunit/phpunit": "^10.4.0", + "psr/log": "^1 || ^2 || ^3", + "squizlabs/php_codesniffer": "3.7.2", + "symfony/cache": "^5.4 || ^6.2 || ^7.0", + "vimeo/psalm": "5.16.0" + }, + "suggest": { + "ext-dom": "Provides support for XSD validation for XML mapping files", + "symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\ORM\\": "src" + } + }, + "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": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "Object-Relational-Mapper for PHP", + "homepage": "https://www.doctrine-project.org/projects/orm.html", + "keywords": [ + "database", + "orm" + ], + "support": { + "issues": "https://github.com/doctrine/orm/issues", + "source": "https://github.com/doctrine/orm/tree/3.0.1" + }, + "time": "2024-02-22T12:23:53+00:00" + }, + { + "name": "doctrine/persistence", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/persistence.git", + "reference": "63fee8c33bef740db6730eb2a750cd3da6495603" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/persistence/zipball/63fee8c33bef740db6730eb2a750cd3da6495603", + "reference": "63fee8c33bef740db6730eb2a750cd3da6495603", + "shasum": "" + }, + "require": { + "doctrine/event-manager": "^1 || ^2", + "php": "^7.2 || ^8.0", + "psr/cache": "^1.0 || ^2.0 || ^3.0" + }, + "conflict": { + "doctrine/common": "<2.10" + }, + "require-dev": { + "composer/package-versions-deprecated": "^1.11", + "doctrine/coding-standard": "^11", + "doctrine/common": "^3.0", + "phpstan/phpstan": "1.9.4", + "phpstan/phpstan-phpunit": "^1", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.5", + "symfony/cache": "^4.4 || ^5.4 || ^6.0", + "vimeo/psalm": "4.30.0 || 5.3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Persistence\\": "src/Persistence" + } + }, + "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" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "The Doctrine Persistence project is a set of shared interfaces and functionality that the different Doctrine object mappers share.", + "homepage": "https://www.doctrine-project.org/projects/persistence.html", + "keywords": [ + "mapper", + "object", + "odm", + "orm", + "persistence" + ], + "support": { + "issues": "https://github.com/doctrine/persistence/issues", + "source": "https://github.com/doctrine/persistence/tree/3.2.0" + }, + "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%2Fpersistence", + "type": "tidelift" + } + ], + "time": "2023-05-17T18:32:04+00:00" + }, + { + "name": "doctrine/sql-formatter", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/sql-formatter.git", + "reference": "a321d114e0a18e6497f8a2cd6f890e000cc17ecc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/sql-formatter/zipball/a321d114e0a18e6497f8a2cd6f890e000cc17ecc", + "reference": "a321d114e0a18e6497f8a2cd6f890e000cc17ecc", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4" + }, + "bin": [ + "bin/sql-formatter" + ], + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\SqlFormatter\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jeremy Dorn", + "email": "jeremy@jeremydorn.com", + "homepage": "https://jeremydorn.com/" + } + ], + "description": "a PHP SQL highlighting library", + "homepage": "https://github.com/doctrine/sql-formatter/", + "keywords": [ + "highlight", + "sql" + ], + "support": { + "issues": "https://github.com/doctrine/sql-formatter/issues", + "source": "https://github.com/doctrine/sql-formatter/tree/1.2.0" + }, + "time": "2023-08-16T21:49:04+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.8.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "41042bc7ab002487b876a0683fc8dce04ddce104" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104", + "reference": "41042bc7ab002487b876a0683fc8dce04ddce104", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.5.3 || ^2.0.1", + "guzzlehttp/psr7": "^1.9.1 || ^2.5.1", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-curl": "*", + "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.36 || ^9.6.15", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.8.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2023-12-03T20:35:24+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/bbff78d96034045e58e13dedd6ad91b5d1253223", + "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.36 || ^9.6.15" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2023-12-03T20:19:20+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.6.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/45b30f99ac27b5ca93cb4831afe16285f57b8221", + "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.36 || ^9.6.15" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.6.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2023-12-03T20:05:35+00:00" + }, + { + "name": "influxdb/influxdb-php", + "version": "1.15.2", + "source": { + "type": "git", + "url": "https://github.com/influxdata/influxdb-php.git", + "reference": "d6e59f4f04ab9107574fda69c2cbe36671253d03" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/influxdata/influxdb-php/zipball/d6e59f4f04ab9107574fda69c2cbe36671253d03", + "reference": "d6e59f4f04ab9107574fda69c2cbe36671253d03", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^6.0|^7.0", + "php": "^5.5 || ^7.0 || ^8.0" + }, + "require-dev": { + "dms/phpunit-arraysubset-asserts": "^0.2.1", + "phpunit/phpunit": "^9.5" + }, + "suggest": { + "ext-curl": "Curl extension, needed for Curl driver", + "stefanotorresi/influxdb-php-async": "An asyncronous client for InfluxDB, implemented via ReactPHP." + }, + "type": "library", + "autoload": { + "psr-4": { + "InfluxDB\\": "src/InfluxDB" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Stephen Hoogendijk", + "email": "stephen@tca0.nl" + }, + { + "name": "Daniel Martinez", + "email": "danimartcas@hotmail.com" + }, + { + "name": "Gianluca Arbezzano", + "email": "gianarb92@gmail.com" + } + ], + "description": "InfluxDB client library for PHP", + "keywords": [ + "client", + "influxdata", + "influxdb", + "influxdb class", + "influxdb client", + "influxdb library", + "time series" + ], + "support": { + "issues": "https://github.com/influxdata/influxdb-php/issues", + "source": "https://github.com/influxdata/influxdb-php/tree/1.15.2" + }, + "abandoned": true, + "time": "2020-12-26T17:45:17+00:00" + }, + { + "name": "monolog/monolog", + "version": "3.5.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "c915e2634718dbc8a4a15c61b0e62e7a44e14448" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/c915e2634718dbc8a4a15c61b0e62e7a44e14448", + "reference": "c915e2634718dbc8a4a15c61b0e62e7a44e14448", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2.0", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "phpstan/phpstan": "^1.9", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-strict-rules": "^1.4", + "phpunit/phpunit": "^10.1", + "predis/predis": "^1.1 || ^2", + "ruflin/elastica": "^7", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/3.5.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2023-10-27T15:32:31+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.26.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "231e3186624c03d7e7c890ec662b81e6b0405227" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/231e3186624c03d7e7c890ec662b81e6b0405227", + "reference": "231e3186624c03d7e7c890ec662b81e6b0405227", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^4.15", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.26.0" + }, + "time": "2024-02-23T16:05:55+00:00" + }, + { + "name": "promphp/prometheus_client_php", + "version": "v2.10.0", + "source": { + "type": "git", + "url": "https://github.com/PromPHP/prometheus_client_php.git", + "reference": "a09ea80ec1ec26dd1d4853e9af2a811e898dbfeb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PromPHP/prometheus_client_php/zipball/a09ea80ec1ec26dd1d4853e9af2a811e898dbfeb", + "reference": "a09ea80ec1ec26dd1d4853e9af2a811e898dbfeb", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.2|^8.0" + }, + "replace": { + "endclothing/prometheus_client_php": "*", + "jimdo/prometheus_client_php": "*", + "lkaemmerling/prometheus_client_php": "*" + }, + "require-dev": { + "guzzlehttp/guzzle": "^6.3|^7.0", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5.4", + "phpstan/phpstan-phpunit": "^1.1.0", + "phpstan/phpstan-strict-rules": "^1.1.0", + "phpunit/phpunit": "^9.4", + "squizlabs/php_codesniffer": "^3.6", + "symfony/polyfill-apcu": "^1.6" + }, + "suggest": { + "ext-apc": "Required if using APCu.", + "ext-redis": "Required if using Redis.", + "promphp/prometheus_push_gateway_php": "An easy client for using Prometheus PushGateway.", + "symfony/polyfill-apcu": "Required if you use APCu on PHP8.0+" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Prometheus\\": "src/Prometheus/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Lukas Kämmerling", + "email": "kontakt@lukas-kaemmerling.de" + } + ], + "description": "Prometheus instrumentation library for PHP applications.", + "support": { + "issues": "https://github.com/PromPHP/prometheus_client_php/issues", + "source": "https://github.com/PromPHP/prometheus_client_php/tree/v2.10.0" + }, + "time": "2024-02-01T13:28:34+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/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "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": "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/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "e616d01114759c4c489f93b099585439f795fe35" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", + "reference": "e616d01114759c4c489f93b099585439f795fe35", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/1.0.2" + }, + "time": "2023-04-10T20:10:41+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.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/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "symfony/cache", + "version": "v7.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "fc822951dd360a593224bb2cef90a087d0dff60f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/fc822951dd360a593224bb2cef90a087d0dff60f", + "reference": "fc822951dd360a593224bb2cef90a087d0dff60f", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/cache": "^2.0|^3.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^2.5|^3", + "symfony/service-contracts": "^2.5|^3", + "symfony/var-exporter": "^6.4|^7.0" + }, + "conflict": { + "doctrine/dbal": "<3.6", + "symfony/dependency-injection": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/var-dumper": "<6.4" + }, + "provide": { + "psr/cache-implementation": "2.0|3.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0", + "symfony/cache-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/dbal": "^3.6|^4", + "predis/predis": "^1.1|^2.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/filesystem": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "classmap": [ + "Traits/ValueWrapper.php" + ], + "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 extended PSR-6, PSR-16 (and tags) implementations", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "support": { + "source": "https://github.com/symfony/cache/tree/v7.0.4" + }, + "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": "2024-02-22T20:27:20+00:00" + }, + { + "name": "symfony/cache-contracts", + "version": "v3.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "1d74b127da04ffa87aa940abe15446fa89653778" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/1d74b127da04ffa87aa940abe15446fa89653778", + "reference": "1d74b127da04ffa87aa940abe15446fa89653778", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/cache": "^3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "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 caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/cache-contracts/tree/v3.4.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": "2023-09-25T12:52:38+00:00" + }, + { + "name": "symfony/config", + "version": "v7.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "44deeba7233f08f383185ffa37dace3b3bc87364" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/44deeba7233f08f383185ffa37dace3b3bc87364", + "reference": "44deeba7233f08f383185ffa37dace3b3bc87364", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/filesystem": "^6.4|^7.0", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/finder": "<6.4", + "symfony/service-contracts": "<2.5" + }, + "require-dev": { + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "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": "Helps you find, load, combine, autofill and validate configuration values of any kind", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/config/tree/v7.0.4" + }, + "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": "2024-02-26T07:52:39+00:00" + }, + { + "name": "symfony/console", + "version": "v7.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "6b099f3306f7c9c2d2786ed736d0026b2903205f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/6b099f3306f7c9c2d2786ed736d0026b2903205f", + "reference": "6b099f3306f7c9c2d2786ed736d0026b2903205f", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^6.4|^7.0" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "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": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v7.0.4" + }, + "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": "2024-02-22T20:27:20+00:00" + }, + { + "name": "symfony/dependency-injection", + "version": "v7.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "47f37af245df8457ea63409fc242b3cc825ce5eb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/47f37af245df8457ea63409fc242b3cc825ce5eb", + "reference": "47f37af245df8457ea63409fc242b3cc825ce5eb", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/service-contracts": "^3.3", + "symfony/var-exporter": "^6.4|^7.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2", + "symfony/config": "<6.4", + "symfony/finder": "<6.4", + "symfony/yaml": "<6.4" + }, + "provide": { + "psr/container-implementation": "1.1|2.0", + "symfony/service-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "symfony/config": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "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": "Allows you to standardize and centralize the way objects are constructed in your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dependency-injection/tree/v7.0.4" + }, + "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": "2024-02-22T20:27:20+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "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": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.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": "2023-05-23T14:45:45+00:00" + }, + { + "name": "symfony/doctrine-bridge", + "version": "v7.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/doctrine-bridge.git", + "reference": "aded7ef586f9c75a04627326a5748f29ceba3506" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/aded7ef586f9c75a04627326a5748f29ceba3506", + "reference": "aded7ef586f9c75a04627326a5748f29ceba3506", + "shasum": "" + }, + "require": { + "doctrine/event-manager": "^2", + "doctrine/persistence": "^3.1", + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "doctrine/dbal": "<3.6", + "doctrine/lexer": "<1.1", + "doctrine/orm": "<2.15", + "symfony/cache": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/form": "<6.4", + "symfony/http-foundation": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/lock": "<6.4", + "symfony/messenger": "<6.4", + "symfony/property-info": "<6.4", + "symfony/security-bundle": "<6.4", + "symfony/security-core": "<6.4", + "symfony/validator": "<6.4" + }, + "require-dev": { + "doctrine/collections": "^1.0|^2.0", + "doctrine/data-fixtures": "^1.1", + "doctrine/dbal": "^3.6|^4", + "doctrine/orm": "^2.15|^3", + "psr/log": "^1|^2|^3", + "symfony/cache": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/doctrine-messenger": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/uid": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "symfony-bridge", + "autoload": { + "psr-4": { + "Symfony\\Bridge\\Doctrine\\": "" + }, + "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": "Provides integration for Doctrine with various Symfony components", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/doctrine-bridge/tree/v7.0.4" + }, + "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": "2024-02-04T16:21:40+00:00" + }, + { + "name": "symfony/dotenv", + "version": "v7.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/dotenv.git", + "reference": "8017ea2f0ff4fbda6ae1bf3f5409d5ecff982067" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/8017ea2f0ff4fbda6ae1bf3f5409d5ecff982067", + "reference": "8017ea2f0ff4fbda6ae1bf3f5409d5ecff982067", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "conflict": { + "symfony/console": "<6.4", + "symfony/process": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Dotenv\\": "" + }, + "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": "Registers environment variables from a .env file", + "homepage": "https://symfony.com", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "source": "https://github.com/symfony/dotenv/tree/v7.0.4" + }, + "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": "2024-02-09T10:53:15+00:00" + }, + { + "name": "symfony/error-handler", + "version": "v7.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/error-handler.git", + "reference": "677b24759decff69e65b1e9d1471d90f95ced880" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/677b24759decff69e65b1e9d1471d90f95ced880", + "reference": "677b24759decff69e65b1e9d1471d90f95ced880", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/var-dumper": "^6.4|^7.0" + }, + "conflict": { + "symfony/deprecation-contracts": "<2.5", + "symfony/http-kernel": "<6.4" + }, + "require-dev": { + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0" + }, + "bin": [ + "Resources/bin/patch-type-declarations" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\ErrorHandler\\": "" + }, + "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": "Provides tools to manage errors and ease debugging PHP code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/error-handler/tree/v7.0.4" + }, + "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": "2024-02-22T20:27:20+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v7.0.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "834c28d533dd0636f910909d01b9ff45cc094b5e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/834c28d533dd0636f910909d01b9ff45cc094b5e", + "reference": "834c28d533dd0636f910909d01b9ff45cc094b5e", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/error-handler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "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": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v7.0.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": "2024-01-23T15:02:46+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "a76aed96a42d2b521153fb382d418e30d18b59df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/a76aed96a42d2b521153fb382d418e30d18b59df", + "reference": "a76aed96a42d2b521153fb382d418e30d18b59df", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "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 dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.4.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": "2023-05-23T14:45:45+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v7.0.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "2890e3a825bc0c0558526c04499c13f83e1b6b12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/2890e3a825bc0c0558526c04499c13f83e1b6b12", + "reference": "2890e3a825bc0c0558526c04499c13f83e1b6b12", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "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": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v7.0.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": "2024-01-23T15:02:46+00:00" + }, + { + "name": "symfony/finder", + "version": "v7.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "6e5688d69f7cfc4ed4a511e96007e06c2d34ce56" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/6e5688d69f7cfc4ed4a511e96007e06c2d34ce56", + "reference": "6e5688d69f7cfc4ed4a511e96007e06c2d34ce56", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/filesystem": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "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": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v7.0.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": "2023-10-31T17:59:56+00:00" + }, + { + "name": "symfony/flex", + "version": "v2.4.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/flex.git", + "reference": "bec213c39511eda66663baa2ee7440c65f89c695" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/flex/zipball/bec213c39511eda66663baa2ee7440c65f89c695", + "reference": "bec213c39511eda66663baa2ee7440c65f89c695", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.1", + "php": ">=8.0" + }, + "require-dev": { + "composer/composer": "^2.1", + "symfony/dotenv": "^5.4|^6.0", + "symfony/filesystem": "^5.4|^6.0", + "symfony/phpunit-bridge": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Symfony\\Flex\\Flex" + }, + "autoload": { + "psr-4": { + "Symfony\\Flex\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien.potencier@gmail.com" + } + ], + "description": "Composer plugin for Symfony", + "support": { + "issues": "https://github.com/symfony/flex/issues", + "source": "https://github.com/symfony/flex/tree/v2.4.4" + }, + "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": "2024-02-05T18:04:53+00:00" + }, + { + "name": "symfony/framework-bundle", + "version": "v7.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/framework-bundle.git", + "reference": "b58bcb2f9c32405b8fbaa24a1e38c8a10bad7b21" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/b58bcb2f9c32405b8fbaa24a1e38c8a10bad7b21", + "reference": "b58bcb2f9c32405b8fbaa24a1e38c8a10bad7b21", + "shasum": "" + }, + "require": { + "composer-runtime-api": ">=2.1", + "ext-xml": "*", + "php": ">=8.2", + "symfony/cache": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/filesystem": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/routing": "^6.4|^7.0" + }, + "conflict": { + "doctrine/persistence": "<1.3", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/asset": "<6.4", + "symfony/asset-mapper": "<6.4", + "symfony/clock": "<6.4", + "symfony/console": "<6.4", + "symfony/dom-crawler": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/form": "<6.4", + "symfony/http-client": "<6.4", + "symfony/lock": "<6.4", + "symfony/mailer": "<6.4", + "symfony/messenger": "<6.4", + "symfony/mime": "<6.4", + "symfony/property-access": "<6.4", + "symfony/property-info": "<6.4", + "symfony/scheduler": "<6.4.4|>=7.0.0,<7.0.4", + "symfony/security-core": "<6.4", + "symfony/security-csrf": "<6.4", + "symfony/serializer": "<6.4", + "symfony/stopwatch": "<6.4", + "symfony/translation": "<6.4", + "symfony/twig-bridge": "<6.4", + "symfony/twig-bundle": "<6.4", + "symfony/validator": "<6.4", + "symfony/web-profiler-bundle": "<6.4", + "symfony/workflow": "<6.4" + }, + "require-dev": { + "doctrine/persistence": "^1.3|^2|^3", + "dragonmantank/cron-expression": "^3.1", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "seld/jsonlint": "^1.10", + "symfony/asset": "^6.4|^7.0", + "symfony/asset-mapper": "^6.4|^7.0", + "symfony/browser-kit": "^6.4|^7.0", + "symfony/clock": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/css-selector": "^6.4|^7.0", + "symfony/dom-crawler": "^6.4|^7.0", + "symfony/dotenv": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/html-sanitizer": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/mailer": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/notifier": "^6.4|^7.0", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/process": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0", + "symfony/scheduler": "^6.4.4|^7.0.4", + "symfony/security-bundle": "^6.4|^7.0", + "symfony/semaphore": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/string": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/twig-bundle": "^6.4|^7.0", + "symfony/uid": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/web-link": "^6.4|^7.0", + "symfony/workflow": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0", + "twig/twig": "^3.0.4" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Symfony\\Bundle\\FrameworkBundle\\": "" + }, + "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": "Provides a tight integration between Symfony components and the Symfony full-stack framework", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/framework-bundle/tree/v7.0.4" + }, + "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": "2024-02-26T07:52:39+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v7.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "439fdfdd344943254b1ef6278613e79040548045" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/439fdfdd344943254b1ef6278613e79040548045", + "reference": "439fdfdd344943254b1ef6278613e79040548045", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php83": "^1.27" + }, + "conflict": { + "doctrine/dbal": "<3.6", + "symfony/cache": "<6.4" + }, + "require-dev": { + "doctrine/dbal": "^3.6|^4", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "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": "Defines an object-oriented layer for the HTTP specification", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-foundation/tree/v7.0.4" + }, + "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": "2024-02-08T19:22:56+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v7.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "065e2234d907c0fc4fc942bf223f7cfc016d0ac7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/065e2234d907c0fc4fc942bf223f7cfc016d0ac7", + "reference": "065e2234d907c0fc4fc942bf223f7cfc016d0ac7", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/error-handler": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/browser-kit": "<6.4", + "symfony/cache": "<6.4", + "symfony/config": "<6.4", + "symfony/console": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/doctrine-bridge": "<6.4", + "symfony/form": "<6.4", + "symfony/http-client": "<6.4", + "symfony/http-client-contracts": "<2.5", + "symfony/mailer": "<6.4", + "symfony/messenger": "<6.4", + "symfony/translation": "<6.4", + "symfony/translation-contracts": "<2.5", + "symfony/twig-bridge": "<6.4", + "symfony/validator": "<6.4", + "symfony/var-dumper": "<6.4", + "twig/twig": "<3.0.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/browser-kit": "^6.4|^7.0", + "symfony/clock": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/css-selector": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/dom-crawler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/http-client-contracts": "^2.5|^3", + "symfony/process": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/routing": "^6.4|^7.0", + "symfony/serializer": "^6.4.4|^7.0.4", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/uid": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/var-exporter": "^6.4|^7.0", + "twig/twig": "^3.0.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "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": "Provides a structured process for converting a Request into a Response", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-kernel/tree/v7.0.4" + }, + "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": "2024-02-27T06:35:35+00:00" + }, + { + "name": "symfony/monolog-bridge", + "version": "v7.0.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/monolog-bridge.git", + "reference": "5d4f188e60d1e38a1d9d4bb6fbbbc13111dff2b1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/5d4f188e60d1e38a1d9d4bb6fbbbc13111dff2b1", + "reference": "5d4f188e60d1e38a1d9d4bb6fbbbc13111dff2b1", + "shasum": "" + }, + "require": { + "monolog/monolog": "^3", + "php": ">=8.2", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/console": "<6.4", + "symfony/http-foundation": "<6.4", + "symfony/security-core": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/mailer": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "symfony-bridge", + "autoload": { + "psr-4": { + "Symfony\\Bridge\\Monolog\\": "" + }, + "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": "Provides integration for Monolog with various Symfony components", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/monolog-bridge/tree/v7.0.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": "2024-01-23T15:02:46+00:00" + }, + { + "name": "symfony/monolog-bundle", + "version": "v3.10.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/monolog-bundle.git", + "reference": "414f951743f4aa1fd0f5bf6a0e9c16af3fe7f181" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/monolog-bundle/zipball/414f951743f4aa1fd0f5bf6a0e9c16af3fe7f181", + "reference": "414f951743f4aa1fd0f5bf6a0e9c16af3fe7f181", + "shasum": "" + }, + "require": { + "monolog/monolog": "^1.25.1 || ^2.0 || ^3.0", + "php": ">=7.2.5", + "symfony/config": "^5.4 || ^6.0 || ^7.0", + "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", + "symfony/http-kernel": "^5.4 || ^6.0 || ^7.0", + "symfony/monolog-bridge": "^5.4 || ^6.0 || ^7.0" + }, + "require-dev": { + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/phpunit-bridge": "^6.3 || ^7.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Bundle\\MonologBundle\\": "" + }, + "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": "Symfony MonologBundle", + "homepage": "https://symfony.com", + "keywords": [ + "log", + "logging" + ], + "support": { + "issues": "https://github.com/symfony/monolog-bundle/issues", + "source": "https://github.com/symfony/monolog-bundle/tree/v3.10.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": "2023-11-06T17:08:13+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/32a9da87d7b3245e09ac426c83d334ae9f06f80f", + "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "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": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.29.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": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "bc45c394692b948b4d383a08d7753968bed9a83d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/bc45c394692b948b4d383a08d7753968bed9a83d", + "reference": "bc45c394692b948b4d383a08d7753968bed9a83d", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "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": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.29.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": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "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": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.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": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-php83", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "86fcae159633351e5fd145d1c47de6c528f8caff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/86fcae159633351e5fd145d1c47de6c528f8caff", + "reference": "86fcae159633351e5fd145d1c47de6c528f8caff", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "symfony/polyfill-php80": "^1.14" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "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": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.29.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": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-uuid", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-uuid.git", + "reference": "3abdd21b0ceaa3000ee950097bc3cf9efc137853" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/3abdd21b0ceaa3000ee950097bc3cf9efc137853", + "reference": "3abdd21b0ceaa3000ee950097bc3cf9efc137853", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-uuid": "*" + }, + "suggest": { + "ext-uuid": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Uuid\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for uuid functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.29.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": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/routing", + "version": "v7.0.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "858b26756ffc35a11238b269b484ee3a393a74d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/858b26756ffc35a11238b269b484ee3a393a74d3", + "reference": "858b26756ffc35a11238b269b484ee3a393a74d3", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/config": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/yaml": "<6.4" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "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": "Maps an HTTP request to a set of configuration variables", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "support": { + "source": "https://github.com/symfony/routing/tree/v7.0.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": "2024-01-30T13:55:15+00:00" + }, + { + "name": "symfony/runtime", + "version": "v7.0.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/runtime.git", + "reference": "ef2c2fd4b40fb8cd22221154399ad8888e81cdb5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/runtime/zipball/ef2c2fd4b40fb8cd22221154399ad8888e81cdb5", + "reference": "ef2c2fd4b40fb8cd22221154399ad8888e81cdb5", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0|^2.0", + "php": ">=8.2" + }, + "conflict": { + "symfony/dotenv": "<6.4" + }, + "require-dev": { + "composer/composer": "^2.6", + "symfony/console": "^6.4|^7.0", + "symfony/dotenv": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Symfony\\Component\\Runtime\\Internal\\ComposerPlugin" + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Runtime\\": "", + "Symfony\\Runtime\\Symfony\\Component\\": "Internal/" + }, + "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": "Enables decoupling PHP applications from global state", + "homepage": "https://symfony.com", + "keywords": [ + "runtime" + ], + "support": { + "source": "https://github.com/symfony/runtime/tree/v7.0.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": "2024-01-23T15:02:46+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.4.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "fe07cbc8d837f60caf7018068e350cc5163681a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/fe07cbc8d837f60caf7018068e350cc5163681a0", + "reference": "fe07cbc8d837f60caf7018068e350cc5163681a0", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-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.4.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": "2023-12-26T14:02:43+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v7.0.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "983900d6fddf2b0cbaacacbbad07610854bd8112" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/983900d6fddf2b0cbaacacbbad07610854bd8112", + "reference": "983900d6fddf2b0cbaacacbbad07610854bd8112", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/service-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "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": "Provides a way to profile code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/stopwatch/tree/v7.0.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": "2024-01-23T15:02:46+00:00" + }, + { + "name": "symfony/string", + "version": "v7.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "f5832521b998b0bec40bee688ad5de98d4cf111b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/f5832521b998b0bec40bee688ad5de98d4cf111b", + "reference": "f5832521b998b0bec40bee688ad5de98d4cf111b", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "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.5" + }, + "require-dev": { + "symfony/error-handler": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.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/v7.0.4" + }, + "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": "2024-02-01T13:17:36+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.4.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "06450585bf65e978026bda220cdebca3f867fde7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/06450585bf65e978026bda220cdebca3f867fde7", + "reference": "06450585bf65e978026bda220cdebca3f867fde7", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + }, + "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 translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v3.4.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": "2023-12-26T14:02:43+00:00" + }, + { + "name": "symfony/twig-bridge", + "version": "v7.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/twig-bridge.git", + "reference": "d16aa4eb5bdaeb6e7407782431dc70530f3b1df5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/d16aa4eb5bdaeb6e7407782431dc70530f3b1df5", + "reference": "d16aa4eb5bdaeb6e7407782431dc70530f3b1df5", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/translation-contracts": "^2.5|^3", + "twig/twig": "^3.0.4" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/console": "<6.4", + "symfony/form": "<6.4", + "symfony/http-foundation": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/mime": "<6.4", + "symfony/serializer": "<6.4", + "symfony/translation": "<6.4", + "symfony/workflow": "<6.4" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/asset": "^6.4|^7.0", + "symfony/asset-mapper": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/html-sanitizer": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/routing": "^6.4|^7.0", + "symfony/security-acl": "^2.8|^3.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/security-csrf": "^6.4|^7.0", + "symfony/security-http": "^6.4|^7.0", + "symfony/serializer": "^6.4.3|^7.0.3", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/web-link": "^6.4|^7.0", + "symfony/workflow": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0", + "twig/cssinliner-extra": "^2.12|^3", + "twig/inky-extra": "^2.12|^3", + "twig/markdown-extra": "^2.12|^3" + }, + "type": "symfony-bridge", + "autoload": { + "psr-4": { + "Symfony\\Bridge\\Twig\\": "" + }, + "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": "Provides integration for Twig with various Symfony components", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/twig-bridge/tree/v7.0.4" + }, + "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": "2024-02-15T11:33:06+00:00" + }, + { + "name": "symfony/twig-bundle", + "version": "v7.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/twig-bundle.git", + "reference": "acab2368f53491e018bf31ef48b39df55a6812ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/acab2368f53491e018bf31ef48b39df55a6812ef", + "reference": "acab2368f53491e018bf31ef48b39df55a6812ef", + "shasum": "" + }, + "require": { + "composer-runtime-api": ">=2.1", + "php": ">=8.2", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/twig-bridge": "^6.4|^7.0", + "twig/twig": "^3.0.4" + }, + "conflict": { + "symfony/framework-bundle": "<6.4", + "symfony/translation": "<6.4" + }, + "require-dev": { + "symfony/asset": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/routing": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/web-link": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Symfony\\Bundle\\TwigBundle\\": "" + }, + "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": "Provides a tight integration of Twig into the Symfony full-stack framework", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/twig-bundle/tree/v7.0.4" + }, + "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": "2024-02-15T11:33:06+00:00" + }, + { + "name": "symfony/uid", + "version": "v7.0.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/uid.git", + "reference": "87cedaf3fabd7b733859d4d77aa4ca598259054b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/uid/zipball/87cedaf3fabd7b733859d4d77aa4ca598259054b", + "reference": "87cedaf3fabd7b733859d4d77aa4ca598259054b", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-uuid": "^1.15" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Uid\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to generate and represent UIDs", + "homepage": "https://symfony.com", + "keywords": [ + "UID", + "ulid", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/uid/tree/v7.0.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": "2024-01-23T15:02:46+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v7.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "e03ad7c1535e623edbb94c22cc42353e488c6670" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/e03ad7c1535e623edbb94c22cc42353e488c6670", + "reference": "e03ad7c1535e623edbb94c22cc42353e488c6670", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/console": "<6.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/uid": "^6.4|^7.0", + "twig/twig": "^3.0.4" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "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 mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v7.0.4" + }, + "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": "2024-02-15T11:33:06+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v7.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "dfb0acb6803eb714f05d97dd4c5abe6d5fa9fe41" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/dfb0acb6803eb714f05d97dd4c5abe6d5fa9fe41", + "reference": "dfb0acb6803eb714f05d97dd4c5abe6d5fa9fe41", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "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": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "lazy-loading", + "proxy", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v7.0.4" + }, + "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": "2024-02-26T10:35:24+00:00" + }, + { + "name": "symfony/yaml", + "version": "v7.0.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "2d4fca631c00700597e9442a0b2451ce234513d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/2d4fca631c00700597e9442a0b2451ce234513d3", + "reference": "2d4fca631c00700597e9442a0b2451ce234513d3", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0" + }, + "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/v7.0.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": "2024-01-23T15:02:46+00:00" + }, + { + "name": "twig/twig", + "version": "v3.8.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/9d15f0ac07f44dc4217883ec6ae02fd555c6f71d", + "reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php80": "^1.22" + }, + "require-dev": { + "psr/container": "^1.0|^2.0", + "symfony/phpunit-bridge": "^5.4.9|^6.3|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Twig\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Twig Team", + "role": "Contributors" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", + "keywords": [ + "templating" + ], + "support": { + "issues": "https://github.com/twigphp/Twig/issues", + "source": "https://github.com/twigphp/Twig/tree/v3.8.0" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2023-11-21T18:54:41+00:00" + } + ], + "packages-dev": [ + { + "name": "myclabs/deep-copy", + "version": "1.11.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "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.1" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2023-03-08T13:26:56+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.0.1", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "2218c2252c874a4624ab2f613d86ac32d227bc69" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/2218c2252c874a4624ab2f613d86ac32d227bc69", + "reference": "2218c2252c874a4624ab2f613d86ac32d227bc69", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-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/v5.0.1" + }, + "time": "2024-02-21T19:24:10+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.30", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca2bd87d2f9215904682a9cb9bb37dda98e76089", + "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.18 || ^5.0", + "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": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "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", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.30" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-22T06:47:57+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.6.17", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "1a156980d78a6666721b7e8e8502fe210b587fcd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1a156980d78a6666721b7e8e8502fe210b587fcd", + "reference": "1a156980d78a6666721b7e8e8502fe210b587fcd", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.3.1 || ^2", + "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.28", + "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": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.6-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", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.17" + }, + "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": "2024-02-23T13:14:51+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.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "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.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-22T06:19:30+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "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": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-05-07T05:35:17+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "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.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:03:51+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": [ + "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": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "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": "sebastian/global-state", + "version": "5.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bde739e7565280bda77be70044ac1047bc007e34" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34", + "reference": "bde739e7565280bda77be70044ac1047bc007e34", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "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" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-08-02T09:26:13+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "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 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.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-22T06:20:34+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^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" + } + ], + "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-10-26T13:12:34+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "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": "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": "2020-10-26T13:14:26+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "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": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:07:39+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "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" + } + ], + "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": "sebastian/type", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.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": "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.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:13:03+00:00" + }, + { + "name": "sebastian/version", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", + "shasum": "" + }, + "require": { + "php": ">=7.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": "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": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" + }, + { + "name": "symfony/debug-bundle", + "version": "v7.0.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug-bundle.git", + "reference": "b0db5c443883ce5c10c2265c77feb9833c3d9d6d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug-bundle/zipball/b0db5c443883ce5c10c2265c77feb9833c3d9d6d", + "reference": "b0db5c443883ce5c10c2265c77feb9833c3d9d6d", + "shasum": "" + }, + "require": { + "ext-xml": "*", + "php": ">=8.2", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/twig-bridge": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "conflict": { + "symfony/config": "<6.4", + "symfony/dependency-injection": "<6.4" + }, + "require-dev": { + "symfony/config": "^6.4|^7.0", + "symfony/web-profiler-bundle": "^6.4|^7.0" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Symfony\\Bundle\\DebugBundle\\": "" + }, + "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": "Provides a tight integration of the Symfony VarDumper component and the ServerLogCommand from MonologBridge into the Symfony full-stack framework", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/debug-bundle/tree/v7.0.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": "2024-01-23T15:02:46+00:00" + }, + { + "name": "symfony/maker-bundle", + "version": "v1.55.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/maker-bundle.git", + "reference": "11a9d3125c5b93ab4043f0f2e9927fdc55881c17" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/11a9d3125c5b93ab4043f0f2e9927fdc55881c17", + "reference": "11a9d3125c5b93ab4043f0f2e9927fdc55881c17", + "shasum": "" + }, + "require": { + "doctrine/inflector": "^2.0", + "nikic/php-parser": "^4.18|^5.0", + "php": ">=8.1", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/deprecation-contracts": "^2.2|^3", + "symfony/filesystem": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0" + }, + "conflict": { + "doctrine/doctrine-bundle": "<2.10", + "doctrine/orm": "<2.15" + }, + "require-dev": { + "composer/semver": "^3.0", + "doctrine/doctrine-bundle": "^2.5.0", + "doctrine/orm": "^2.15|^3", + "symfony/http-client": "^6.4|^7.0", + "symfony/phpunit-bridge": "^6.4.1|^7.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0", + "twig/twig": "^3.0|^4.x-dev" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Bundle\\MakerBundle\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Maker helps you create empty commands, controllers, form classes, tests and more so you can forget about writing boilerplate code.", + "homepage": "https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html", + "keywords": [ + "code generator", + "dev", + "generator", + "scaffold", + "scaffolding" + ], + "support": { + "issues": "https://github.com/symfony/maker-bundle/issues", + "source": "https://github.com/symfony/maker-bundle/tree/v1.55.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": "2024-02-21T13:41:51+00:00" + }, + { + "name": "symfony/phpunit-bridge", + "version": "v7.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/phpunit-bridge.git", + "reference": "54ca13ec990a40411ad978e08d994fca6cdd865f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/54ca13ec990a40411ad978e08d994fca6cdd865f", + "reference": "54ca13ec990a40411ad978e08d994fca6cdd865f", + "shasum": "" + }, + "require": { + "php": ">=7.2.5" + }, + "conflict": { + "phpunit/phpunit": "<7.5|9.1.2" + }, + "require-dev": { + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/error-handler": "^5.4|^6.4|^7.0", + "symfony/polyfill-php81": "^1.27" + }, + "bin": [ + "bin/simple-phpunit" + ], + "type": "symfony-bridge", + "extra": { + "thanks": { + "name": "phpunit/phpunit", + "url": "https://github.com/sebastianbergmann/phpunit" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Bridge\\PhpUnit\\": "" + }, + "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 utilities for PHPUnit, especially user deprecation notices management", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/phpunit-bridge/tree/v7.0.4" + }, + "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": "2024-02-08T19:22:56+00:00" + }, + { + "name": "symfony/process", + "version": "v7.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "0e7727191c3b71ebec6d529fa0e50a01ca5679e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/0e7727191c3b71ebec6d529fa0e50a01ca5679e9", + "reference": "0e7727191c3b71ebec6d529fa0e50a01ca5679e9", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "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": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v7.0.4" + }, + "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": "2024-02-22T20:27:20+00:00" + }, + { + "name": "symfony/web-profiler-bundle", + "version": "v7.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/web-profiler-bundle.git", + "reference": "542daea1345fe181cbfd52db00717174a838ea0a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/542daea1345fe181cbfd52db00717174a838ea0a", + "reference": "542daea1345fe181cbfd52db00717174a838ea0a", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/config": "^6.4|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/routing": "^6.4|^7.0", + "symfony/twig-bundle": "^6.4|^7.0", + "twig/twig": "^3.0.4" + }, + "conflict": { + "symfony/form": "<6.4", + "symfony/mailer": "<6.4", + "symfony/messenger": "<6.4" + }, + "require-dev": { + "symfony/browser-kit": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/css-selector": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Symfony\\Bundle\\WebProfilerBundle\\": "" + }, + "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": "Provides a development tool that gives detailed information about the execution of any request", + "homepage": "https://symfony.com", + "keywords": [ + "dev" + ], + "support": { + "source": "https://github.com/symfony/web-profiler-bundle/tree/v7.0.4" + }, + "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": "2024-02-22T20:27:20+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "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" + } + ], + "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.2" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2023-11-20T00:12:19+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": { + "beberlei/metrics": 20 + }, + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": ">=8.3", + "ext-ctype": "*", + "ext-iconv": "*" + }, + "platform-dev": [], + "plugin-api-version": "2.6.0" +} diff --git a/examples/config/bundles.php b/examples/config/bundles.php new file mode 100644 index 0000000..53ad6f5 --- /dev/null +++ b/examples/config/bundles.php @@ -0,0 +1,20 @@ + ['all' => true], + Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], + Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], + Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true], + Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], + Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], + Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true], + Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], + Beberlei\Bundle\MetricsBundle\BeberleiMetricsBundle::class => ['all' => true], +]; diff --git a/examples/config/packages/cache.yaml b/examples/config/packages/cache.yaml new file mode 100644 index 0000000..6899b72 --- /dev/null +++ b/examples/config/packages/cache.yaml @@ -0,0 +1,19 @@ +framework: + cache: + # Unique name of your app: used to compute stable namespaces for cache keys. + #prefix_seed: your_vendor_name/app_name + + # The "app" cache stores to the filesystem by default. + # The data in this cache should persist between deploys. + # Other options include: + + # Redis + #app: cache.adapter.redis + #default_redis_provider: redis://localhost + + # APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues) + #app: cache.adapter.apcu + + # Namespaced pools use the above "app" backend by default + #pools: + #my.dedicated.cache: null diff --git a/examples/config/packages/debug.yaml b/examples/config/packages/debug.yaml new file mode 100644 index 0000000..ad874af --- /dev/null +++ b/examples/config/packages/debug.yaml @@ -0,0 +1,5 @@ +when@dev: + debug: + # Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser. + # See the "server:dump" command to start a new server. + dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%" diff --git a/examples/config/packages/doctrine.yaml b/examples/config/packages/doctrine.yaml new file mode 100644 index 0000000..d42c52d --- /dev/null +++ b/examples/config/packages/doctrine.yaml @@ -0,0 +1,50 @@ +doctrine: + dbal: + url: '%env(resolve:DATABASE_URL)%' + + # IMPORTANT: You MUST configure your server version, + # either here or in the DATABASE_URL env var (see .env file) + #server_version: '16' + + profiling_collect_backtrace: '%kernel.debug%' + use_savepoints: true + orm: + auto_generate_proxy_classes: true + enable_lazy_ghost_objects: true + report_fields_where_declared: true + validate_xml_mapping: true + naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware + auto_mapping: true + mappings: + App: + type: attribute + is_bundle: false + dir: '%kernel.project_dir%/src/Entity' + prefix: 'App\Entity' + alias: App + +when@test: + doctrine: + dbal: + # "TEST_TOKEN" is typically set by ParaTest + dbname_suffix: '_test%env(default::TEST_TOKEN)%' + +when@prod: + doctrine: + orm: + auto_generate_proxy_classes: false + proxy_dir: '%kernel.build_dir%/doctrine/orm/Proxies' + query_cache_driver: + type: pool + pool: doctrine.system_cache_pool + result_cache_driver: + type: pool + pool: doctrine.result_cache_pool + + framework: + cache: + pools: + doctrine.result_cache_pool: + adapter: cache.app + doctrine.system_cache_pool: + adapter: cache.system diff --git a/examples/config/packages/doctrine_migrations.yaml b/examples/config/packages/doctrine_migrations.yaml new file mode 100644 index 0000000..29231d9 --- /dev/null +++ b/examples/config/packages/doctrine_migrations.yaml @@ -0,0 +1,6 @@ +doctrine_migrations: + migrations_paths: + # namespace is arbitrary but should be different from App\Migrations + # as migrations classes should NOT be autoloaded + 'DoctrineMigrations': '%kernel.project_dir%/migrations' + enable_profiler: false diff --git a/examples/config/packages/framework.yaml b/examples/config/packages/framework.yaml new file mode 100644 index 0000000..877eb25 --- /dev/null +++ b/examples/config/packages/framework.yaml @@ -0,0 +1,16 @@ +# see https://symfony.com/doc/current/reference/configuration/framework.html +framework: + secret: '%env(APP_SECRET)%' + #csrf_protection: true + + # Note that the session will be started ONLY if you read or write from it. + session: true + + #esi: true + #fragments: true + +when@test: + framework: + test: true + session: + storage_factory_id: session.storage.factory.mock_file diff --git a/examples/config/packages/metrics.yaml b/examples/config/packages/metrics.yaml new file mode 100644 index 0000000..6b5369a --- /dev/null +++ b/examples/config/packages/metrics.yaml @@ -0,0 +1,31 @@ +beberlei_metrics: + default: logger + collectors: + influxdb_v1: + type: influxdb_v1 + host: influxdb1 + database: app + prometheus: + type: prometheus + host: prometheus + service: Prometheus\CollectorRegistry + namespace: app + graphite: + type: graphite + host: graphite + statsd: + type: statsd + host: graphite + prefix: 'app.statsd.' + dogstatsd: + type: dogstatsd + host: graphite + prefix: 'app.dogstatsd.' + dbal: + type: doctrine_dbal + logger: + type: logger + memory: + type: memory + 'null': + type: 'null' diff --git a/examples/config/packages/monolog.yaml b/examples/config/packages/monolog.yaml new file mode 100644 index 0000000..9db7d8a --- /dev/null +++ b/examples/config/packages/monolog.yaml @@ -0,0 +1,62 @@ +monolog: + channels: + - deprecation # Deprecations are logged in the dedicated "deprecation" channel when it exists + +when@dev: + monolog: + handlers: + main: + type: stream + path: "%kernel.logs_dir%/%kernel.environment%.log" + level: debug + channels: ["!event"] + # uncomment to get logging in your browser + # you may have to allow bigger header sizes in your Web server configuration + #firephp: + # type: firephp + # level: info + #chromephp: + # type: chromephp + # level: info + console: + type: console + process_psr_3_messages: false + channels: ["!event", "!doctrine", "!console"] + +when@test: + monolog: + handlers: + main: + type: fingers_crossed + action_level: error + handler: nested + excluded_http_codes: [404, 405] + channels: ["!event"] + nested: + type: stream + path: "%kernel.logs_dir%/%kernel.environment%.log" + level: debug + +when@prod: + monolog: + handlers: + main: + type: fingers_crossed + action_level: error + handler: nested + excluded_http_codes: [404, 405] + buffer_size: 50 # How many messages should be saved? Prevent memory leaks + nested: + type: stream + path: php://stderr + level: debug + formatter: monolog.formatter.json + console: + type: console + process_psr_3_messages: false + channels: ["!event", "!doctrine"] + deprecation: + type: stream + channels: [deprecation] + path: php://stderr + formatter: monolog.formatter.json diff --git a/examples/config/packages/routing.yaml b/examples/config/packages/routing.yaml new file mode 100644 index 0000000..8166181 --- /dev/null +++ b/examples/config/packages/routing.yaml @@ -0,0 +1,10 @@ +framework: + router: + # Configure how to generate URLs in non-HTTP contexts, such as CLI commands. + # See https://symfony.com/doc/current/routing.html#generating-urls-in-commands + #default_uri: http://localhost + +when@prod: + framework: + router: + strict_requirements: null diff --git a/examples/config/packages/twig.yaml b/examples/config/packages/twig.yaml new file mode 100644 index 0000000..5d1e199 --- /dev/null +++ b/examples/config/packages/twig.yaml @@ -0,0 +1,8 @@ +twig: + file_name_pattern: '*.twig' + globals: + project_dir: '%kernel.project_dir%' + +when@test: + twig: + strict_variables: true diff --git a/examples/config/packages/web_profiler.yaml b/examples/config/packages/web_profiler.yaml new file mode 100644 index 0000000..b946111 --- /dev/null +++ b/examples/config/packages/web_profiler.yaml @@ -0,0 +1,17 @@ +when@dev: + web_profiler: + toolbar: true + intercept_redirects: false + + framework: + profiler: + only_exceptions: false + collect_serializer_data: true + +when@test: + web_profiler: + toolbar: false + intercept_redirects: false + + framework: + profiler: { collect: false } diff --git a/examples/config/preload.php b/examples/config/preload.php new file mode 100644 index 0000000..f12aeeb --- /dev/null +++ b/examples/config/preload.php @@ -0,0 +1,12 @@ + /etc/grafana/provisioning/datasources/loki.yaml + apiVersion: 1 + datasources: + - name: Loki + type: loki + access: proxy + orgId: 1 + url: http://loki:3100 + basicAuth: false + isDefault: true + version: 1 + editable: false + - name: ClickHouse + type: grafana-clickhouse-datasource + access: proxy + orgId: 1 + isDefault: false + jsonData: + host: clickhouse + port: 9000 + defaultDatabase: observability + username: observability + defaultDatasource: true + secureJsonData: + password: observability + version: 1 + editable: false + EOF + /run.sh + labels: + - "traefik.enable=true" + - "traefik.http.routers.${PROJECT_NAME}-grafana.rule=Host(`grafana.${PROJECT_ROOT_DOMAIN}`)" + - "traefik.http.routers.${PROJECT_NAME}-grafana.tls=true" + + graphite: + image: graphiteapp/graphite-statsd:1.1.10-5 + volumes: + - graphite-data:/opt/graphite/storage + expose: + - 80 + labels: + - "traefik.enable=true" + - "traefik.http.routers.${PROJECT_NAME}-graphite.rule=Host(`graphite.${PROJECT_ROOT_DOMAIN}`)" + - "traefik.http.routers.${PROJECT_NAME}-graphite.tls=true" + + influxdb1: + image: influxdb:1.8 + environment: + - INFLUXDB_DB=app + volumes: + - influxdb-data:/var/lib/influxdb + + prometheus: + image: prom/prometheus:v2.50.1 + volumes: + - ./services/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:cached + - prometheus-data:/var/lib/prometheus + + # redis: + # image: redislabs/redistimeseries:1.10.11 + # volumes: + # - "redis-data:/data" + + # redis-insight: + # image: redislabs/redisinsight:1.14.0 + # environment: + # RITRUSTEDORIGINS: https://redis.symfony-metrics.test + # labels: + # - "traefik.enable=true" + # - "traefik.http.routers.${PROJECT_NAME}-redis.rule=Host(`redis.${PROJECT_ROOT_DOMAIN}`)" + # - "traefik.http.routers.${PROJECT_NAME}-redis.tls=true" + + # vector: + # image: timberio/vector:0.36.0-debian + # volumes: + # - ./services/vector/vector.yaml:/etc/vector/vector.yaml:cached + # - vector-data:/var/lib/vector + # expose: + # - 8686 + # labels: + # - "traefik.enable=true" + # - "traefik.http.routers.${PROJECT_NAME}-vector.rule=Host(`vector.${PROJECT_ROOT_DOMAIN}`)" + # - "traefik.http.routers.${PROJECT_NAME}-vector.tls=true" diff --git a/examples/infrastructure/docker/services/php/Dockerfile b/examples/infrastructure/docker/services/php/Dockerfile new file mode 100644 index 0000000..c4ef231 --- /dev/null +++ b/examples/infrastructure/docker/services/php/Dockerfile @@ -0,0 +1,121 @@ +# hadolint global ignore=DL3008 + +FROM debian:12.5-slim as php-base + +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + curl \ + ca-certificates \ + gnupg \ + && curl -sSLo /tmp/debsuryorg-archive-keyring.deb https://packages.sury.org/debsuryorg-archive-keyring.deb \ + && dpkg -i /tmp/debsuryorg-archive-keyring.deb \ + && echo "deb [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://packages.sury.org/php bookworm main" > /etc/apt/sources.list.d/sury.list \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/* + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + procps \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/* + +ARG PHP_VERSION + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + "php${PHP_VERSION}-apcu" \ + "php${PHP_VERSION}-bcmath" \ + "php${PHP_VERSION}-cli" \ + "php${PHP_VERSION}-common" \ + "php${PHP_VERSION}-curl" \ + "php${PHP_VERSION}-iconv" \ + "php${PHP_VERSION}-intl" \ + "php${PHP_VERSION}-mbstring" \ + "php${PHP_VERSION}-pgsql" \ + "php${PHP_VERSION}-uuid" \ + "php${PHP_VERSION}-xml" \ + "php${PHP_VERSION}-zip" \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/* + +# Fake user to maps with the one on the host +ARG USER_ID +COPY entrypoint / +RUN addgroup --gid $USER_ID app && \ + adduser --system --uid $USER_ID --home /home/app --shell /bin/bash app && \ + curl -Ls https://github.com/tianon/gosu/releases/download/1.17/gosu-amd64 | \ + install /dev/stdin /usr/local/bin/gosu && \ + sed "s/{{ application_user }}/app/g" -i /entrypoint + +# Configuration +COPY base/php-configuration /etc/php/${PHP_VERSION} + +WORKDIR /var/www +ENTRYPOINT [ "/entrypoint" ] + +FROM php-base as frontend + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + nginx \ + "php${PHP_VERSION}-fpm" \ + runit \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/* \ + && rm -r "/etc/php/${PHP_VERSION}/fpm/pool.d/" + +RUN useradd -s /bin/false nginx + +COPY frontend/php-configuration /etc/php/${PHP_VERSION} +COPY frontend/etc/nginx/. /etc/nginx/ +COPY frontend/etc/service/. /etc/service/ + +RUN phpenmod app-default \ + && phpenmod app-fpm + +EXPOSE 80 + +CMD ["runsvdir", "-P", "/etc/service"] + +FROM php-base as worker + +FROM php-base as builder + +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + +ARG NODEJS_VERSION=20.x +RUN curl -s https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /usr/share/keyrings/nodesource.gpg \ + && echo "deb [signed-by=/usr/share/keyrings/nodesource.gpg] https://deb.nodesource.com/node_${NODEJS_VERSION} nodistro main" > /etc/apt/sources.list.d/nodesource.list + +# Default toys +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + git \ + make \ + nodejs \ + sudo \ + unzip \ + && apt-get clean \ + && npm install -g yarn@1.22 \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/* + +# Config +COPY builder/etc/. /etc/ +COPY builder/php-configuration /etc/php/${PHP_VERSION} +RUN adduser app sudo \ + && mkdir /var/log/php \ + && chmod 777 /var/log/php \ + && phpenmod app-default \ + && phpenmod app-builder + +# Composer +COPY --from=composer/composer:2.7.1 /usr/bin/composer /usr/bin/composer +RUN mkdir -p "/home/app/.composer/cache" \ + && chown app: /home/app/.composer -R + +# Third party tools +ENV PATH="$PATH:/var/www/tools/bin" + +WORKDIR /var/www diff --git a/examples/infrastructure/docker/services/php/base/php-configuration/mods-available/app-default.ini b/examples/infrastructure/docker/services/php/base/php-configuration/mods-available/app-default.ini new file mode 100644 index 0000000..17d01e4 --- /dev/null +++ b/examples/infrastructure/docker/services/php/base/php-configuration/mods-available/app-default.ini @@ -0,0 +1,27 @@ +; priority=30 +[PHP] +short_open_tag = Off +memory_limit = 512M +error_reporting = E_ALL +display_errors = On +display_startup_errors = On +error_log = /proc/self/fd/2 +log_errors = On +log_errors_max_len = 0 +max_execution_time = 0 +always_populate_raw_post_data = -1 +upload_max_filesize = 20M +post_max_size = 20M +[Date] +date.timezone = UTC +[Phar] +phar.readonly = Off +[opcache] +opcache.memory_consumption=256 +opcache.max_accelerated_files=20000 +realpath_cache_size=4096K +realpath_cache_ttl=600 +opcache.error_log = /proc/self/fd/2 +[apc] +apc.enabled=1 +apc.enable_cli=1 diff --git a/examples/infrastructure/docker/services/php/builder/etc/sudoers.d/sudo b/examples/infrastructure/docker/services/php/builder/etc/sudoers.d/sudo new file mode 100644 index 0000000..b835421 --- /dev/null +++ b/examples/infrastructure/docker/services/php/builder/etc/sudoers.d/sudo @@ -0,0 +1 @@ +%sudo ALL=(ALL) NOPASSWD: ALL diff --git a/examples/infrastructure/docker/services/php/builder/php-configuration/mods-available/app-builder.ini b/examples/infrastructure/docker/services/php/builder/php-configuration/mods-available/app-builder.ini new file mode 100644 index 0000000..3016083 --- /dev/null +++ b/examples/infrastructure/docker/services/php/builder/php-configuration/mods-available/app-builder.ini @@ -0,0 +1,5 @@ +; priority=40 +[PHP] +error_log = /var/log/php/error.log +[opcache] +opcache.error_log = /var/log/php/opcache.log diff --git a/examples/infrastructure/docker/services/php/entrypoint b/examples/infrastructure/docker/services/php/entrypoint new file mode 100755 index 0000000..1f15e84 --- /dev/null +++ b/examples/infrastructure/docker/services/php/entrypoint @@ -0,0 +1,26 @@ +#!/bin/sh + +set -e +set -u + +if [ $(id -u) != 0 ]; then + echo "Running this image as non root is not allowed" + exit 1 +fi + +: "${UID:=0}" +: "${GID:=${UID}}" + +if [ "$#" = 0 ]; then + set -- "$(command -v bash 2>/dev/null || command -v sh)" -l +fi + +if [ "$UID" != 0 ]; then + usermod -u "$UID" "{{ application_user }}" >/dev/null 2>/dev/null && { + groupmod -g "$GID" "{{ application_user }}" >/dev/null 2>/dev/null || + usermod -a -G "$GID" "{{ application_user }}" >/dev/null 2>/dev/null + } + set -- gosu "${UID}:${GID}" "${@}" +fi + +exec "$@" diff --git a/examples/infrastructure/docker/services/php/frontend/etc/nginx/environments b/examples/infrastructure/docker/services/php/frontend/etc/nginx/environments new file mode 100644 index 0000000..e69de29 diff --git a/examples/infrastructure/docker/services/php/frontend/etc/nginx/nginx.conf b/examples/infrastructure/docker/services/php/frontend/etc/nginx/nginx.conf new file mode 100644 index 0000000..64f8c39 --- /dev/null +++ b/examples/infrastructure/docker/services/php/frontend/etc/nginx/nginx.conf @@ -0,0 +1,69 @@ +user nginx; +pid /var/run/nginx.pid; +daemon off; +error_log /proc/self/fd/2; +include /etc/nginx/modules-enabled/*.conf; + +http { + access_log /proc/self/fd/1; + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + include /etc/nginx/mime.types; + default_type application/octet-stream; + client_max_body_size 20m; + server_tokens off; + + gzip on; + gzip_disable "msie6"; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_buffers 16 8k; + gzip_http_version 1.1; + gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml; + + server { + listen 0.0.0.0:80; + root /var/www/public; + + # Remove this block if you want to access to PHP FPM monitoring + # dashboard (on URL: /php-fpm-status). WARNING: on production, you must + # secure this page (by user IP address, with a password, for example) + location ~ ^/php-fpm-status$ { + deny all; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_index index.php; + include fastcgi_params; + fastcgi_pass 127.0.0.1:9000; + } + + location / { + # try to serve file directly, fallback to index.php + try_files $uri /index.php$is_args$args; + } + + location ~ ^/index\.php(/|$) { + fastcgi_pass 127.0.0.1:9000; + fastcgi_split_path_info ^(.+\.php)(/.*)$; + + include fastcgi_params; + include environments; + + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param HTTPS on; + fastcgi_param SERVER_NAME $http_host; + # # Uncomment if you want to use /php-fpm-status endpoint **with** + # # real request URI. It may have some side effects, that's why it's + # # commented by default + # fastcgi_param SCRIPT_NAME $request_uri; + } + + error_log /proc/self/fd/2; + access_log /proc/self/fd/1; + } +} + +events {} diff --git a/examples/infrastructure/docker/services/php/frontend/etc/service/nginx/run b/examples/infrastructure/docker/services/php/frontend/etc/service/nginx/run new file mode 100755 index 0000000..14cd019 --- /dev/null +++ b/examples/infrastructure/docker/services/php/frontend/etc/service/nginx/run @@ -0,0 +1,3 @@ +#!/bin/sh + +exec /usr/sbin/nginx diff --git a/examples/infrastructure/docker/services/php/frontend/etc/service/php-fpm/run b/examples/infrastructure/docker/services/php/frontend/etc/service/php-fpm/run new file mode 100755 index 0000000..1bcdb2b --- /dev/null +++ b/examples/infrastructure/docker/services/php/frontend/etc/service/php-fpm/run @@ -0,0 +1,3 @@ +#!/bin/sh + +exec /usr/sbin/php-fpm${PHP_VERSION} -y /etc/php/${PHP_VERSION}/fpm/php-fpm.conf -O diff --git a/examples/infrastructure/docker/services/php/frontend/php-configuration/fpm/php-fpm.conf b/examples/infrastructure/docker/services/php/frontend/php-configuration/fpm/php-fpm.conf new file mode 100644 index 0000000..38b5901 --- /dev/null +++ b/examples/infrastructure/docker/services/php/frontend/php-configuration/fpm/php-fpm.conf @@ -0,0 +1,19 @@ +[global] +pid = /var/run/php-fpm.pid +error_log = /proc/self/fd/2 +daemonize = no + +[www] +user = app +group = app +listen = 127.0.0.1:9000 +pm = dynamic +pm.max_children = 25 +pm.start_servers = 2 +pm.min_spare_servers = 2 +pm.max_spare_servers = 3 +pm.max_requests = 500 +pm.status_path = /php-fpm-status +clear_env = no +request_terminate_timeout = 120s +catch_workers_output = yes diff --git a/examples/infrastructure/docker/services/php/frontend/php-configuration/mods-available/app-fpm.ini b/examples/infrastructure/docker/services/php/frontend/php-configuration/mods-available/app-fpm.ini new file mode 100644 index 0000000..f3f541d --- /dev/null +++ b/examples/infrastructure/docker/services/php/frontend/php-configuration/mods-available/app-fpm.ini @@ -0,0 +1,5 @@ +; priority=40 +[PHP] +expose_php = off +memory_limit = 128M +max_execution_time = 30 diff --git a/examples/infrastructure/docker/services/prometheus/prometheus.yml b/examples/infrastructure/docker/services/prometheus/prometheus.yml new file mode 100644 index 0000000..6936b83 --- /dev/null +++ b/examples/infrastructure/docker/services/prometheus/prometheus.yml @@ -0,0 +1,14 @@ +global: + scrape_interval: 15s + evaluation_interval: 15s + +rule_files: + +scrape_configs: + - job_name: "prometheus" + static_configs: + - targets: ["localhost:9090"] + - job_name: "app" + metrics_path: /prometheus + static_configs: + - targets: ["frontend"] diff --git a/examples/infrastructure/docker/services/router/Dockerfile b/examples/infrastructure/docker/services/router/Dockerfile new file mode 100644 index 0000000..cbd3f79 --- /dev/null +++ b/examples/infrastructure/docker/services/router/Dockerfile @@ -0,0 +1,5 @@ +FROM traefik:v3.0 + +COPY traefik /etc/traefik + +VOLUME [ "/etc/ssl/certs" ] diff --git a/examples/infrastructure/docker/services/router/certs/.gitkeep b/examples/infrastructure/docker/services/router/certs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/examples/infrastructure/docker/services/router/generate-ssl.sh b/examples/infrastructure/docker/services/router/generate-ssl.sh new file mode 100755 index 0000000..f9d49fb --- /dev/null +++ b/examples/infrastructure/docker/services/router/generate-ssl.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +# Script used in dev to generate a basic SSL cert + +BASE=$(dirname $0) + +CERTS_DIR=$BASE/certs + +rm -rf $CERTS_DIR +mkdir -p $CERTS_DIR + +openssl req -x509 -sha256 -newkey rsa:4096 \ + -keyout $CERTS_DIR/key.pem \ + -out $CERTS_DIR/cert.pem \ + -days 3650 -nodes -config \ + $BASE/openssl.cnf diff --git a/examples/infrastructure/docker/services/router/openssl.cnf b/examples/infrastructure/docker/services/router/openssl.cnf new file mode 100644 index 0000000..ff68b84 --- /dev/null +++ b/examples/infrastructure/docker/services/router/openssl.cnf @@ -0,0 +1,19 @@ +# Configuration used in dev to generate a basic SSL cert +[req] +default_bits = 2048 +prompt = no +default_md = sha256 +distinguished_name = dn +x509_extensions = v3_req + +[v3_req] +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid:always,issuer:always +basicConstraints=CA:true +subjectAltName = @alt_names + +[dn] +CN=app.test + +[alt_names] +DNS.1 = app.test diff --git a/examples/infrastructure/docker/services/router/traefik/dynamic_conf.yaml b/examples/infrastructure/docker/services/router/traefik/dynamic_conf.yaml new file mode 100644 index 0000000..5680885 --- /dev/null +++ b/examples/infrastructure/docker/services/router/traefik/dynamic_conf.yaml @@ -0,0 +1,12 @@ +tls: + stores: + default: + defaultCertificate: + certFile: /etc/ssl/certs/cert.pem + keyFile: /etc/ssl/certs/key.pem + +http: + middlewares: + redirect-to-https: + redirectScheme: + scheme: https diff --git a/examples/infrastructure/docker/services/router/traefik/traefik.yaml b/examples/infrastructure/docker/services/router/traefik/traefik.yaml new file mode 100644 index 0000000..a4c101a --- /dev/null +++ b/examples/infrastructure/docker/services/router/traefik/traefik.yaml @@ -0,0 +1,28 @@ +global: + checkNewVersion: false + sendAnonymousUsage: false + +providers: + docker: + exposedByDefault: false + file: + filename: /etc/traefik/dynamic_conf.yaml + +# # Uncomment get all DEBUG logs +#log: +# level: "DEBUG" + +# # Uncomment to view all access logs +#accessLog: {} + +api: + dashboard: true + insecure: true # No authentication are required + +entryPoints: + http: + address: ":80" + https: + address: ":443" + traefik: # this one exists by default + address: ":8080" diff --git a/examples/infrastructure/docker/services/vector/vector.yaml b/examples/infrastructure/docker/services/vector/vector.yaml new file mode 100644 index 0000000..13e9771 --- /dev/null +++ b/examples/infrastructure/docker/services/vector/vector.yaml @@ -0,0 +1,62 @@ +data_dir: /var/lib/vector + +api: + enabled: true + address: '0.0.0.0:8686' + +sources: + syslog: + type: syslog + mode: udp + address: '0.0.0.0:9000' + +transforms: + syslog_json: + inputs: + - syslog + type: remap + source: | + . = parse_json!(.message) + .host = get_hostname() ?? "n/a" + .environment = get_env_var("VECTOR_ENVIRONMENT") ?? "production" + .application = get_env_var("VECTOR_APPLICATION") ?? "symfony" + +sinks: + elasticsearch: + inputs: + - syslog_json + type: elasticsearch + mode: bulk + endpoints: + - 'http://elasticsearch:9200' + bulk: + index: vector-%Y.%m.%d + action: create + loki: + inputs: + - syslog_json + type: loki + encoding: + codec: json + endpoint: 'http://loki:3100' + healthcheck: false + labels: + host: '{{ host }}' + environment: '{{ environment }}' + application: '{{ application }}' + channel: '{{ channel }}' + level: '{{ level_name }}' + clickhouse: + inputs: + - syslog_json + type: clickhouse + # format: json_as_string + endpoint: 'http://clickhouse:8123' + database: observability + table: logs + healthcheck: false + auth: + strategy: basic + password: observability + user: observability + skip_unknown_fields: true diff --git a/examples/migrations/Version20240302012809.php b/examples/migrations/Version20240302012809.php new file mode 100644 index 0000000..28a9ea0 --- /dev/null +++ b/examples/migrations/Version20240302012809.php @@ -0,0 +1,32 @@ +addSql('CREATE TABLE metrics (id UUID DEFAULT gen_random_uuid() NOT NULL, metric VARCHAR(255) NOT NULL, measurement INT NOT NULL, created TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id))'); + $this->addSql('COMMENT ON COLUMN metrics.created IS \'(DC2Type:datetime_immutable)\''); + } + + public function down(Schema $schema): void + { + $this->addSql('DROP TABLE metrics'); + } +} diff --git a/examples/phpstan.neon b/examples/phpstan.neon new file mode 100644 index 0000000..ff5ad2d --- /dev/null +++ b/examples/phpstan.neon @@ -0,0 +1,31 @@ +parameters: + level: 6 + paths: + - src + - public + - castor.php + - .castor/ + scanFiles: + - .castor.stub.php + tmpDir: tools/phpstan/var + inferPrivatePropertyTypeFromConstructor: true + + symfony: + container_xml_path: var/cache/dev/App_KernelDevDebugContainer.xml + + typeAliases: + ContextData: ''' + array{ + project_name: string, + root_domain: string, + extra_domains: string[], + php_version: string, + docker_compose_files: string[], + macos: bool, + power_shell: bool, + user_id: int, + root_dir: string, + env: string, + composer_cache_dir: string, + } + ''' diff --git a/examples/phpunit.xml.dist b/examples/phpunit.xml.dist new file mode 100644 index 0000000..6c4bfed --- /dev/null +++ b/examples/phpunit.xml.dist @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + tests + + + + + + src + + + + + + + + + + diff --git a/examples/public/index.php b/examples/public/index.php new file mode 100644 index 0000000..52e8536 --- /dev/null +++ b/examples/public/index.php @@ -0,0 +1,16 @@ + $collectors + */ + public function __construct( + #[TaggedIterator(CollectorInterface::class)] + private readonly iterable $collectors, + #[Target('memory')] + CollectorInterface $memoryCollector, + ) { + if (!$memoryCollector instanceof InMemory) { + throw new \InvalidArgumentException('The memory collector must be an instance of InMemory.'); + } + $this->memoryCollector = $memoryCollector; + } + + #[Route('/')] + public function index(): Response + { + $random = date('s'); + $timing = (int) ((microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']) * 1000); + $gauges = random_int(0, 100); + + foreach ($this->collectors as $collector) { + $collector->measure('homepage.random', $random); + $collector->increment('homepage.visits'); + $collector->timing('homepage.duration', $timing); + if ($collector instanceof GaugeableCollectorInterface) { + $collector->gauge('homepage.gauge', $gauges); + } + } + + return $this->render('homepage/index.html.twig', [ + 'collectors' => $this->collectors, + 'random' => $this->memoryCollector->getMeasure('homepage.random'), + 'visits' => $this->memoryCollector->getMeasure('homepage.visits'), + 'timing' => $this->memoryCollector->getTiming('homepage.duration'), + 'gauge' => $this->memoryCollector->getGauge('homepage.gauge'), + ]); + } +} diff --git a/examples/src/Controller/PrometheusController.php b/examples/src/Controller/PrometheusController.php new file mode 100644 index 0000000..586b8ff --- /dev/null +++ b/examples/src/Controller/PrometheusController.php @@ -0,0 +1,34 @@ +renderer->render($this->registry->getMetricFamilySamples()), + headers: ['Content-Type' => RenderTextFormat::MIME_TYPE], + ); + } +} diff --git a/examples/src/Entity/Metrics.php b/examples/src/Entity/Metrics.php new file mode 100644 index 0000000..28fdad8 --- /dev/null +++ b/examples/src/Entity/Metrics.php @@ -0,0 +1,36 @@ + 'gen_random_uuid()'])] + public string $id; + + public function __construct( + #[ORM\Column(length: 255)] + public string $metric, + + #[ORM\Column] + public int $measurement, + + #[ORM\Column(type: Types::DATETIME_IMMUTABLE)] + public \DateTimeInterface $created, + ) { + $this->id = Uuid::v6()->toRfc4122(); + } +} diff --git a/examples/src/Kernel.php b/examples/src/Kernel.php new file mode 100644 index 0000000..19556ba --- /dev/null +++ b/examples/src/Kernel.php @@ -0,0 +1,18 @@ + + * + * @method Metrics|null find($id, $lockMode = null, $lockVersion = null) + * @method Metrics|null findOneBy(array $criteria, array $orderBy = null) + * @method Metrics[] findAll() + * @method Metrics[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class MetricsRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Metrics::class); + } +} diff --git a/examples/symfony.lock b/examples/symfony.lock new file mode 100644 index 0000000..feb3c7a --- /dev/null +++ b/examples/symfony.lock @@ -0,0 +1,185 @@ +{ + "beberlei/metrics": { + "version": "dev-massive-code-grooming" + }, + "doctrine/doctrine-bundle": { + "version": "2.11", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "2.10", + "ref": "c170ded8fc587d6bd670550c43dafcf093762245" + }, + "files": [ + "config/packages/doctrine.yaml", + "src/Entity/.gitignore", + "src/Repository/.gitignore" + ] + }, + "doctrine/doctrine-migrations-bundle": { + "version": "3.3", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "3.1", + "ref": "1d01ec03c6ecbd67c3375c5478c9a423ae5d6a33" + }, + "files": [ + "config/packages/doctrine_migrations.yaml", + "migrations/.gitignore" + ] + }, + "phpunit/phpunit": { + "version": "9.6", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "9.6", + "ref": "7364a21d87e658eb363c5020c072ecfdc12e2326" + }, + "files": [ + ".env.test", + "phpunit.xml.dist", + "tests/bootstrap.php" + ] + }, + "symfony/console": { + "version": "7.0", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "5.3", + "ref": "da0c8be8157600ad34f10ff0c9cc91232522e047" + }, + "files": [ + "bin/console" + ] + }, + "symfony/debug-bundle": { + "version": "7.0", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "5.3", + "ref": "5aa8aa48234c8eb6dbdd7b3cd5d791485d2cec4b" + }, + "files": [ + "config/packages/debug.yaml" + ] + }, + "symfony/flex": { + "version": "2.4", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "1.0", + "ref": "146251ae39e06a95be0fe3d13c807bcf3938b172" + }, + "files": [ + ".env" + ] + }, + "symfony/framework-bundle": { + "version": "7.0", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "7.0", + "ref": "6356c19b9ae08e7763e4ba2d9ae63043efc75db5" + }, + "files": [ + "config/packages/cache.yaml", + "config/packages/framework.yaml", + "config/preload.php", + "config/routes/framework.yaml", + "config/services.yaml", + "public/index.php", + "src/Controller/.gitignore", + "src/Kernel.php" + ] + }, + "symfony/maker-bundle": { + "version": "1.55", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "1.0", + "ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f" + } + }, + "symfony/monolog-bundle": { + "version": "3.10", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "3.7", + "ref": "aff23899c4440dd995907613c1dd709b6f59503f" + }, + "files": [ + "config/packages/monolog.yaml" + ] + }, + "symfony/phpunit-bridge": { + "version": "7.0", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "6.3", + "ref": "a411a0480041243d97382cac7984f7dce7813c08" + }, + "files": [ + ".env.test", + "bin/phpunit", + "phpunit.xml.dist", + "tests/bootstrap.php" + ] + }, + "symfony/routing": { + "version": "7.0", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "7.0", + "ref": "21b72649d5622d8f7da329ffb5afb232a023619d" + }, + "files": [ + "config/packages/routing.yaml", + "config/routes.yaml" + ] + }, + "symfony/twig-bundle": { + "version": "7.0", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "6.4", + "ref": "cab5fd2a13a45c266d45a7d9337e28dee6272877" + }, + "files": [ + "config/packages/twig.yaml", + "templates/base.html.twig" + ] + }, + "symfony/uid": { + "version": "7.0", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "7.0", + "ref": "0df5844274d871b37fc3816c57a768ffc60a43a5" + } + }, + "symfony/web-profiler-bundle": { + "version": "7.0", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "6.1", + "ref": "e42b3f0177df239add25373083a564e5ead4e13a" + }, + "files": [ + "config/packages/web_profiler.yaml", + "config/routes/web_profiler.yaml" + ] + } +} diff --git a/examples/templates/base.html.twig b/examples/templates/base.html.twig new file mode 100644 index 0000000..1069c14 --- /dev/null +++ b/examples/templates/base.html.twig @@ -0,0 +1,16 @@ + + + + + {% block title %}Welcome!{% endblock %} + + {% block stylesheets %} + {% endblock %} + + {% block javascripts %} + {% endblock %} + + + {% block body %}{% endblock %} + + diff --git a/examples/templates/homepage/index.html.twig b/examples/templates/homepage/index.html.twig new file mode 100644 index 0000000..b55483b --- /dev/null +++ b/examples/templates/homepage/index.html.twig @@ -0,0 +1,56 @@ +{% extends 'base.html.twig' %} + +{% block title %}Hello{% endblock %} + +{% block body %} +

Metrics demo application

+ +

+ This application demonstrates how to use the + beberlei/metrics library. +
+ This controller uses all collectors defined in the + config/packages/metrics.yaml: +

    + {% for collector in collectors %} +
  • + {{ constant('class', collector) }}
  • + {% endfor %} +
+

+ +

+ The following applications let you explore the collected metrics: +

    +
  • Grafana: application with all database configureds
  • +
  • Graphite: application with graphite, statsd, dogstatsd
  • +
+

+ +

Current collectors data

+ +

Current collectors collected the following metrics:

+ + + + + + + + + + + + + + + + + + + + + + +
MesaureValue
random{{ random }}
visits{{ visits }}
timing{{ timing }}
gauge{{ gauge }}
+{% endblock %} diff --git a/examples/tests/bootstrap.php b/examples/tests/bootstrap.php new file mode 100644 index 0000000..77f8bcf --- /dev/null +++ b/examples/tests/bootstrap.php @@ -0,0 +1,18 @@ +bootEnv(dirname(__DIR__) . '/.env'); +} diff --git a/examples/tools/bin/php-cs-fixer b/examples/tools/bin/php-cs-fixer new file mode 120000 index 0000000..8b6029f --- /dev/null +++ b/examples/tools/bin/php-cs-fixer @@ -0,0 +1 @@ +../php-cs-fixer/vendor/bin/php-cs-fixer \ No newline at end of file diff --git a/examples/tools/bin/phpstan b/examples/tools/bin/phpstan new file mode 120000 index 0000000..c10edfd --- /dev/null +++ b/examples/tools/bin/phpstan @@ -0,0 +1 @@ +../phpstan/vendor/bin/phpstan \ No newline at end of file diff --git a/examples/tools/php-cs-fixer/.gitignore b/examples/tools/php-cs-fixer/.gitignore new file mode 100644 index 0000000..57872d0 --- /dev/null +++ b/examples/tools/php-cs-fixer/.gitignore @@ -0,0 +1 @@ +/vendor/ diff --git a/examples/tools/php-cs-fixer/composer.json b/examples/tools/php-cs-fixer/composer.json new file mode 100644 index 0000000..c216598 --- /dev/null +++ b/examples/tools/php-cs-fixer/composer.json @@ -0,0 +1,12 @@ +{ + "type": "project", + "require": { + "friendsofphp/php-cs-fixer": "^3.47.1" + }, + "config": { + "platform": { + "php": "8.1" + }, + "sort-packages": true + } +} diff --git a/examples/tools/php-cs-fixer/composer.lock b/examples/tools/php-cs-fixer/composer.lock new file mode 100644 index 0000000..6e1c4b1 --- /dev/null +++ b/examples/tools/php-cs-fixer/composer.lock @@ -0,0 +1,1845 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "74ae7a6662eee7cb4b9ed2a50b72ec98", + "packages": [ + { + "name": "composer/pcre", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/00104306927c7a0919b4ced2aaa6782c1e61a3c9", + "reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9", + "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.1.1" + }, + "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": "2023-10-11T07:11:09+00:00" + }, + { + "name": "composer/semver", + "version": "3.4.0", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32", + "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32", + "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": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.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": "2023-08-31T09:50:34+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": "friendsofphp/php-cs-fixer", + "version": "v3.47.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", + "reference": "173c60d1eff911c9c54322704623a45561d3241d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/173c60d1eff911c9c54322704623a45561d3241d", + "reference": "173c60d1eff911c9c54322704623a45561d3241d", + "shasum": "" + }, + "require": { + "composer/semver": "^3.4", + "composer/xdebug-handler": "^3.0.3", + "ext-filter": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0", + "sebastian/diff": "^4.0 || ^5.0", + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/event-dispatcher": "^5.4 || ^6.0 || ^7.0", + "symfony/filesystem": "^5.4 || ^6.0 || ^7.0", + "symfony/finder": "^5.4 || ^6.0 || ^7.0", + "symfony/options-resolver": "^5.4 || ^6.0 || ^7.0", + "symfony/polyfill-mbstring": "^1.28", + "symfony/polyfill-php80": "^1.28", + "symfony/polyfill-php81": "^1.28", + "symfony/process": "^5.4 || ^6.0 || ^7.0", + "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0" + }, + "require-dev": { + "facile-it/paraunit": "^1.3 || ^2.0", + "justinrainbow/json-schema": "^5.2", + "keradus/cli-executor": "^2.1", + "mikey179/vfsstream": "^1.6.11", + "php-coveralls/php-coveralls": "^2.7", + "php-cs-fixer/accessible-object": "^1.1", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.4", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.4", + "phpunit/phpunit": "^9.6 || ^10.5.5", + "symfony/yaml": "^5.4 || ^6.0 || ^7.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", + "keywords": [ + "Static code analysis", + "fixer", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.47.1" + }, + "funding": [ + { + "url": "https://github.com/keradus", + "type": "github" + } + ], + "time": "2024-01-16T18:54:21+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "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": "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": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.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/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + }, + { + "name": "sebastian/diff", + "version": "5.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "fbf413a49e54f6b9b17e12d900ac7f6101591b7f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/fbf413a49e54f6b9b17e12d900ac7f6101591b7f", + "reference": "fbf413a49e54f6b9b17e12d900ac7f6101591b7f", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/5.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-22T10:55:06+00:00" + }, + { + "name": "symfony/console", + "version": "v6.4.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "0254811a143e6bc6c8deea08b589a7e68a37f625" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/0254811a143e6bc6c8deea08b589a7e68a37f625", + "reference": "0254811a143e6bc6c8deea08b589a7e68a37f625", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^5.4|^6.0|^7.0" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/event-dispatcher": "<5.4", + "symfony/lock": "<5.4", + "symfony/process": "<5.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "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": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v6.4.2" + }, + "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": "2023-12-10T16:15:48+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "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": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.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": "2023-05-23T14:45:45+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v6.4.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "e95216850555cd55e71b857eb9d6c2674124603a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/e95216850555cd55e71b857eb9d6c2674124603a", + "reference": "e95216850555cd55e71b857eb9d6c2674124603a", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/error-handler": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "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": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.2" + }, + "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": "2023-12-27T22:16:42+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "a76aed96a42d2b521153fb382d418e30d18b59df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/a76aed96a42d2b521153fb382d418e30d18b59df", + "reference": "a76aed96a42d2b521153fb382d418e30d18b59df", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "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 dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.4.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": "2023-05-23T14:45:45+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v6.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "952a8cb588c3bc6ce76f6023000fb932f16a6e59" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/952a8cb588c3bc6ce76f6023000fb932f16a6e59", + "reference": "952a8cb588c3bc6ce76f6023000fb932f16a6e59", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "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": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v6.4.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": "2023-07-26T17:27:13+00:00" + }, + { + "name": "symfony/finder", + "version": "v6.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "11d736e97f116ac375a81f96e662911a34cd50ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/11d736e97f116ac375a81f96e662911a34cd50ce", + "reference": "11d736e97f116ac375a81f96e662911a34cd50ce", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "symfony/filesystem": "^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "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": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v6.4.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": "2023-10-31T17:30:12+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v6.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "22301f0e7fdeaacc14318928612dee79be99860e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/22301f0e7fdeaacc14318928612dee79be99860e", + "reference": "22301f0e7fdeaacc14318928612dee79be99860e", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "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": "Provides an improved replacement for the array_replace PHP function", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v6.4.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": "2023-08-08T10:16:24+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.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": "2023-01-26T09:26:14+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "875e90aeea2777b6f135677f618529449334a612" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/875e90aeea2777b6f135677f618529449334a612", + "reference": "875e90aeea2777b6f135677f618529449334a612", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "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": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.28.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": "2023-01-26T09:26:14+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", + "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "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": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.28.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": "2023-01-26T09:26:14+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "42292d99c55abe617799667f454222c54c60e229" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", + "reference": "42292d99c55abe617799667f454222c54c60e229", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "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": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.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": "2023-07-28T09:04:16+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-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.28.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": "2023-01-26T09:26:14+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "7581cd600fa9fd681b797d00b02f068e2f13263b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/7581cd600fa9fd681b797d00b02f068e2f13263b", + "reference": "7581cd600fa9fd681b797d00b02f068e2f13263b", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "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": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.28.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": "2023-01-26T09:26:14+00:00" + }, + { + "name": "symfony/process", + "version": "v6.4.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "c4b1ef0bc80533d87a2e969806172f1c2a980241" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/c4b1ef0bc80533d87a2e969806172f1c2a980241", + "reference": "c4b1ef0bc80533d87a2e969806172f1c2a980241", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "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": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v6.4.2" + }, + "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": "2023-12-22T16:42:54+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.4.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "fe07cbc8d837f60caf7018068e350cc5163681a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/fe07cbc8d837f60caf7018068e350cc5163681a0", + "reference": "fe07cbc8d837f60caf7018068e350cc5163681a0", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-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.4.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": "2023-12-26T14:02:43+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v6.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "fc47f1015ec80927ff64ba9094dfe8b9d48fe9f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/fc47f1015ec80927ff64ba9094dfe8b9d48fe9f2", + "reference": "fc47f1015ec80927ff64ba9094dfe8b9d48fe9f2", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/service-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "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": "Provides a way to profile code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/stopwatch/tree/v6.4.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": "2023-02-16T10:14:28+00:00" + }, + { + "name": "symfony/string", + "version": "v6.4.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "7cb80bc10bfcdf6b5492741c0b9357dac66940bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/7cb80bc10bfcdf6b5492741c0b9357dac66940bc", + "reference": "7cb80bc10bfcdf6b5492741c0b9357dac66940bc", + "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.5" + }, + "require-dev": { + "symfony/error-handler": "^5.4|^6.0|^7.0", + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/intl": "^6.2|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^5.4|^6.0|^7.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.4.2" + }, + "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": "2023-12-10T16:15:48+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "platform-overrides": { + "php": "8.1" + }, + "plugin-api-version": "2.6.0" +} diff --git a/examples/tools/phpstan/.gitignore b/examples/tools/phpstan/.gitignore new file mode 100644 index 0000000..31b30cc --- /dev/null +++ b/examples/tools/phpstan/.gitignore @@ -0,0 +1,2 @@ +/var/ +/vendor/ diff --git a/examples/tools/phpstan/composer.json b/examples/tools/phpstan/composer.json new file mode 100644 index 0000000..2c38af5 --- /dev/null +++ b/examples/tools/phpstan/composer.json @@ -0,0 +1,17 @@ +{ + "type": "project", + "require": { + "phpstan/extension-installer": "^1.3.1", + "phpstan/phpstan": "^1.10.56", + "phpstan/phpstan-symfony": "^1.3.7" + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true + }, + "platform": { + "php": "8.1" + }, + "sort-packages": true + } +} diff --git a/examples/tools/phpstan/composer.lock b/examples/tools/phpstan/composer.lock new file mode 100644 index 0000000..e91f1bc --- /dev/null +++ b/examples/tools/phpstan/composer.lock @@ -0,0 +1,200 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "e2bde0ca0bcc114a0ba6528759a1ec8e", + "packages": [ + { + "name": "phpstan/extension-installer", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "f45734bfb9984c6c56c4486b71230355f066a58a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/f45734bfb9984c6c56c4486b71230355f066a58a", + "reference": "f45734bfb9984c6c56c4486b71230355f066a58a", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0", + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.9.0" + }, + "require-dev": { + "composer/composer": "^2.0", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12 || ^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.3.1" + }, + "time": "2023-05-24T08:59:17+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.10.56", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "27816a01aea996191ee14d010f325434c0ee76fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/27816a01aea996191ee14d010f325434c0ee76fa", + "reference": "27816a01aea996191ee14d010f325434c0ee76fa", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2024-01-15T10:43:00+00:00" + }, + { + "name": "phpstan/phpstan-symfony", + "version": "1.3.7", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-symfony.git", + "reference": "ef7db637be9b85fa00278fc3477ac66abe8eb7d1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/ef7db637be9b85fa00278fc3477ac66abe8eb7d1", + "reference": "ef7db637be9b85fa00278fc3477ac66abe8eb7d1", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.10.36" + }, + "conflict": { + "symfony/framework-bundle": "<3.0" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.3.11", + "phpstan/phpstan-strict-rules": "^1.5.1", + "phpunit/phpunit": "^8.5.29 || ^9.5", + "psr/container": "1.0 || 1.1.1", + "symfony/config": "^5.4 || ^6.1", + "symfony/console": "^5.4 || ^6.1", + "symfony/dependency-injection": "^5.4 || ^6.1", + "symfony/form": "^5.4 || ^6.1", + "symfony/framework-bundle": "^5.4 || ^6.1", + "symfony/http-foundation": "^5.4 || ^6.1", + "symfony/messenger": "^5.4", + "symfony/polyfill-php80": "^1.24", + "symfony/serializer": "^5.4", + "symfony/service-contracts": "^2.2.0" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon", + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lukáš Unger", + "email": "looky.msc@gmail.com", + "homepage": "https://lookyman.net" + } + ], + "description": "Symfony Framework extensions and rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-symfony/issues", + "source": "https://github.com/phpstan/phpstan-symfony/tree/1.3.7" + }, + "time": "2024-01-10T21:54:42+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "platform-overrides": { + "php": "8.1" + }, + "plugin-api-version": "2.6.0" +} diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 0000000..842fe65 --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,16 @@ +parameters: + ignoreErrors: + - + message: "#^Parameter \\#1 \\$callback of function set_error_handler expects \\(callable\\(int, string, string, int\\)\\: bool\\)\\|null, Closure\\(\\)\\: null given\\.$#" + count: 1 + path: src/Metrics/Utils/Box.php + + - + message: "#^Cannot access offset 'null' on 0\\|0\\.0\\|''\\|'0'\\|array\\{\\}\\|false\\|null\\.$#" + count: 1 + path: src/MetricsBundle/DependencyInjection/BeberleiMetricsExtension.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeDefinition\\:\\:children\\(\\)\\.$#" + count: 1 + path: src/MetricsBundle/DependencyInjection/Configuration.php diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..cdf3e39 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,14 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: 8 + paths: + - src + inferPrivatePropertyTypeFromConstructor: true + checkGenericClassInNonGenericObjectType: false + ignoreErrors: + # Issue about array shape + - '{Property .* type has no value type specified in iterable type array\.}' + - '{Method .* has parameter \$\w+ with no value type specified in iterable type array\.}' + diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..5d58267 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,27 @@ + + + + + tests + + + + + + src + + + diff --git a/phpunit.xml.dist b/phpunit.xml.dist deleted file mode 100644 index 2023ce8..0000000 --- a/phpunit.xml.dist +++ /dev/null @@ -1,29 +0,0 @@ - - - - - ./src/Beberlei/Bundle/MetricsBundle/Tests - ./src/Beberlei/Metrics/Tests/ - - - - - - ./src - - ./src/Beberlei/Bundle/MetricsBundle/Tests - ./src/Beberlei/Metrics/Tests/ - - - - diff --git a/src/Beberlei/Bundle/MetricsBundle/BeberleiMetricsBundle.php b/src/Beberlei/Bundle/MetricsBundle/BeberleiMetricsBundle.php deleted file mode 100644 index 8eba1a6..0000000 --- a/src/Beberlei/Bundle/MetricsBundle/BeberleiMetricsBundle.php +++ /dev/null @@ -1,20 +0,0 @@ -getConfiguration($configs, $container); - $config = $this->processConfiguration($configuration, $configs); - - $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); - $loader->load('metrics.xml'); - - foreach ($config['collectors'] as $name => $colConfig) { - $definition = $this->createCollector($colConfig['type'], $colConfig); - $container->setDefinition('beberlei_metrics.collector.'.$name, $definition); - } - - if ($config['default'] && $container->hasDefinition('beberlei_metrics.collector.'.$config['default'])) { - $name = $config['default']; - } elseif (1 !== count($config['collectors'])) { - throw new \LogicException('You should select a default collector'); - } - - $container->setAlias('beberlei_metrics.collector', 'beberlei_metrics.collector.'.$name); - $container->setAlias(Collector::class, 'beberlei_metrics.collector'); - } - - private function createCollector($type, $config) - { - $definition = new ChildDefinition('beberlei_metrics.collector_proto.'.$config['type']); - - // Theses listeners should be as late as possible - $definition->addTag('kernel.event_listener', array( - 'method' => 'flush', - 'priority' => -1024, - 'event' => 'kernel.terminate', - )); - $definition->addTag('kernel.event_listener', array( - 'method' => 'flush', - 'priority' => -1024, - 'event' => 'console.terminate', - )); - - if (count($config['tags']) > 0) { - $definition->addMethodCall('setTags', array($config['tags'])); - } - - switch ($type) { - case 'doctrine_dbal': - $ref = $config['connection'] ? sprintf('doctrine.dbal.%s_connection', $config['connection']) : 'database_connection'; - $definition->replaceArgument(0, new Reference($ref)); - - return $definition; - case 'graphite': - $definition->replaceArgument(0, $config['host'] ?: 'localhost'); - $definition->replaceArgument(1, $config['port'] ?: 2003); - $definition->replaceArgument(2, $config['protocol'] ?: 'tcp'); - - return $definition; - case 'influxdb': - $definition->replaceArgument(0, new Reference($config['influxdb_client'])); - - return $definition; - case 'librato': - $definition->replaceArgument(1, $config['source']); - $definition->replaceArgument(2, $config['username']); - $definition->replaceArgument(3, $config['password']); - - return $definition; - case 'logger': - case 'null': - case 'memory': - return $definition; - case 'prometheus': - $definition->replaceArgument(0, new Reference($config['prometheus_collector_registry'])); - $definition->replaceArgument(1, $config['namespace']); - - return $definition; - case 'statsd': - case 'dogstatsd': - $definition->replaceArgument(0, $config['host'] ?: 'localhost'); - $definition->replaceArgument(1, $config['port'] ?: 8125); - $definition->replaceArgument(2, (string) $config['prefix']); - - return $definition; - case 'telegraf': - $definition->replaceArgument(0, $config['host'] ?: 'localhost'); - $definition->replaceArgument(1, $config['port'] ?: 8125); - $definition->replaceArgument(2, (string) $config['prefix']); - - return $definition; - case 'zabbix': - $sender = new Definition('Net\Zabbix\Sender'); - if ($config['file']) { - $senderConfig = new Definition('Net\Zabbix\Agent\Config'); - $senderConfig->addArgument($config['file']); - $sender->addMethodCall('importAgentConfig', array($senderConfig)); - } else { - $sender->addArgument($config['host'] ?: 'localhost'); - $sender->addArgument((int) $config['port'] ?: 10051); - } - - $definition->replaceArgument(0, $sender); - $definition->replaceArgument(1, $config['prefix']); - - return $definition; - default: - throw new \InvalidArgumentException(sprintf('The type "%s" is not supported', $type)); - } - } -} diff --git a/src/Beberlei/Bundle/MetricsBundle/DependencyInjection/Configuration.php b/src/Beberlei/Bundle/MetricsBundle/DependencyInjection/Configuration.php deleted file mode 100644 index 3fee12e..0000000 --- a/src/Beberlei/Bundle/MetricsBundle/DependencyInjection/Configuration.php +++ /dev/null @@ -1,83 +0,0 @@ -getRootNode(); - } else { - $rootNode = $treeBuilder->root('beberlei_metrics'); - } - - $rootNode - ->children() - ->scalarNode('default') - ->defaultNull() - ->end() - ->arrayNode('collectors') - ->isRequired() - ->useAttributeAsKey('name') - ->prototype('array') - ->children() - ->scalarNode('type')->isRequired()->end() - ->scalarNode('connection')->defaultNull()->end() - ->scalarNode('file')->defaultNull()->end() - ->scalarNode('host')->defaultNull()->end() - ->scalarNode('password')->defaultNull()->end() - ->integerNode('port')->defaultNull()->end() - ->scalarNode('prefix')->defaultNull()->end() - ->scalarNode('protocol')->defaultNull()->end() - ->scalarNode('source')->defaultNull()->end() - ->scalarNode('username')->defaultNull()->end() - ->scalarNode('influxdb_client')->defaultNull()->end() - ->scalarNode('prometheus_collector_registry')->defaultNull()->info('It must to contain service id for Prometheus\\CollectorRegistry class instance.')->end() - ->scalarNode('namespace')->defaultValue('')->end() - ->arrayNode('tags') - ->defaultValue(array()) - ->prototype('scalar')->end() - ->end() - ->end() - ->validate() - ->ifTrue(function ($v) { - return 'librato' === $v['type'] && empty($v['source']); - }) - ->thenInvalid('The source has to be specified to use a Librato') - ->end() - ->validate() - ->ifTrue(function ($v) { - return 'librato' === $v['type'] && empty($v['username']); - }) - ->thenInvalid('The username has to be specified to use a Librato') - ->end() - ->validate() - ->ifTrue(function ($v) { - return 'librato' === $v['type'] && empty($v['password']); - }) - ->thenInvalid('The password has to be specified to use a Librato') - ->end() - ->validate() - ->ifTrue(function ($v) { - return 'prometheus' === $v['type'] && empty($v['prometheus_collector_registry']); - }) - ->thenInvalid('The prometheus_collector_registry has to be specified to use a Prometheus') - ->end() - ->end() - ->end() - ->end() - ; - - return $treeBuilder; - } -} diff --git a/src/Beberlei/Bundle/MetricsBundle/Resources/config/metrics.xml b/src/Beberlei/Bundle/MetricsBundle/Resources/config/metrics.xml deleted file mode 100644 index 128fd63..0000000 --- a/src/Beberlei/Bundle/MetricsBundle/Resources/config/metrics.xml +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Beberlei/Bundle/MetricsBundle/Tests/DependencyInjection/BeberleiMetricsExtensionTest.php b/src/Beberlei/Bundle/MetricsBundle/Tests/DependencyInjection/BeberleiMetricsExtensionTest.php deleted file mode 100644 index 5fdee8d..0000000 --- a/src/Beberlei/Bundle/MetricsBundle/Tests/DependencyInjection/BeberleiMetricsExtensionTest.php +++ /dev/null @@ -1,464 +0,0 @@ -createContainer(array( - 'default' => 'simple', - 'collectors' => array( - 'simple' => array( - 'type' => 'graphite', - ), - 'full' => array( - 'type' => 'graphite', - 'host' => 'graphite.localhost', - 'port' => 1234, - 'protocol' => 'udp', - ), - ), - ), array( - 'beberlei_metrics.collector.simple', - 'beberlei_metrics.collector.full' - )); - - $collector = $container->get('beberlei_metrics.collector.simple'); - $this->assertInstanceOf('Beberlei\Metrics\Collector\Graphite', $collector); - $this->assertSame('tcp', $this->getProperty($collector, 'protocol')); - $this->assertSame('localhost', $this->getProperty($collector, 'host')); - $this->assertSame(2003, $this->getProperty($collector, 'port')); - - $collector = $container->get('beberlei_metrics.collector.full'); - $this->assertInstanceOf('Beberlei\Metrics\Collector\Graphite', $collector); - $this->assertSame('udp', $this->getProperty($collector, 'protocol')); - $this->assertSame('graphite.localhost', $this->getProperty($collector, 'host')); - $this->assertSame(1234, $this->getProperty($collector, 'port')); - } - - /** - * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException - * The source has to be specified to use a Librato - */ - public function testWithLibratoAndInvalidConfiguration() - { - $container = $this->createContainer(array( - 'collectors' => array( - 'simple' => array( - 'type' => 'librato', - ), - ), - ), array('beberlei_metrics.collector.librato')); - - $this->assertInstanceOf('Beberlei\Metrics\Collector\Librato', $container->get('beberlei_metrics.collector.librato')); - } - - public function testWithLibrato() - { - $container = $this->createContainer(array( - 'collectors' => array( - 'full' => array( - 'type' => 'librato', - 'source' => 'foo.beberlei.de', - 'username' => 'foo', - 'password' => 'bar', - ), - ), - ), array('beberlei_metrics.collector.full')); - - $collector = $container->get('beberlei_metrics.collector.full'); - $this->assertInstanceOf('Beberlei\Metrics\Collector\Librato', $collector); - $this->assertSame('foo.beberlei.de', $this->getProperty($collector, 'source')); - $this->assertSame('foo', $this->getProperty($collector, 'username')); - $this->assertSame('bar', $this->getProperty($collector, 'password')); - } - - public function testWithLogger() - { - $container = $this->createContainer(array( - 'collectors' => array( - 'logger' => array( - 'type' => 'logger', - ), - ), - ), array('beberlei_metrics.collector.logger')); - - $this->assertInstanceOf('Beberlei\Metrics\Collector\Logger', $container->get('beberlei_metrics.collector.logger')); - } - - public function testWithNullCollector() - { - $container = $this->createContainer(array( - 'collectors' => array( - 'null' => array( - 'type' => 'null', - ), - ), - ), array('beberlei_metrics.collector.null')); - - $this->assertInstanceOf('Beberlei\Metrics\Collector\NullCollector', $container->get('beberlei_metrics.collector.null')); - } - - public function testWithStatsD() - { - $container = $this->createContainer(array( - 'default' => 'simple', - 'collectors' => array( - 'simple' => array( - 'type' => 'statsd', - ), - 'full' => array( - 'type' => 'statsd', - 'host' => 'statsd.localhost', - 'port' => 1234, - 'prefix' => 'application.com.symfony.', - ), - ), - ), array( - 'beberlei_metrics.collector.simple', - 'beberlei_metrics.collector.full' - )); - - $collector = $container->get('beberlei_metrics.collector.simple'); - $this->assertInstanceOf('Beberlei\Metrics\Collector\StatsD', $collector); - $this->assertSame('localhost', $this->getProperty($collector, 'host')); - $this->assertSame(8125, $this->getProperty($collector, 'port')); - $this->assertSame('', $this->getProperty($collector, 'prefix')); - - $collector = $container->get('beberlei_metrics.collector.full'); - $this->assertInstanceOf('Beberlei\Metrics\Collector\StatsD', $collector); - $this->assertSame('statsd.localhost', $this->getProperty($collector, 'host')); - $this->assertSame(1234, $this->getProperty($collector, 'port')); - $this->assertSame('application.com.symfony.', $this->getProperty($collector, 'prefix')); - } - - public function testWithDogStatsD() - { - $container = $this->createContainer(array( - 'default' => 'simple', - 'collectors' => array( - 'simple' => array( - 'type' => 'dogstatsd', - ), - 'full' => array( - 'type' => 'dogstatsd', - 'host' => 'dogstatsd.localhost', - 'port' => 1234, - 'prefix' => 'application.com.symfony.', - ), - ), - ), array( - 'beberlei_metrics.collector.simple', - 'beberlei_metrics.collector.full' - )); - - $collector = $container->get('beberlei_metrics.collector.simple'); - $this->assertInstanceOf('Beberlei\Metrics\Collector\DogStatsD', $collector); - $this->assertSame('localhost', $this->getProperty($collector, 'host')); - $this->assertSame(8125, $this->getProperty($collector, 'port')); - $this->assertSame('', $this->getProperty($collector, 'prefix')); - - $collector = $container->get('beberlei_metrics.collector.full'); - $this->assertInstanceOf('Beberlei\Metrics\Collector\DogStatsD', $collector); - $this->assertSame('dogstatsd.localhost', $this->getProperty($collector, 'host')); - $this->assertSame(1234, $this->getProperty($collector, 'port')); - $this->assertSame('application.com.symfony.', $this->getProperty($collector, 'prefix')); - } - - public function testWithTelegraf() - { - $expectedTags = array( - 'string_tag' => 'first_value', - 'int_tag' => 123, - ); - - $container = $this->createContainer(array( - 'default' => 'simple', - 'collectors' => array( - 'simple' => array( - 'type' => 'telegraf', - ), - 'full' => array( - 'type' => 'telegraf', - 'host' => 'telegraf.localhost', - 'port' => 1234, - 'prefix' => 'application.com.symfony.', - 'tags' => $expectedTags, - ), - ), - ), array( - 'beberlei_metrics.collector.simple', - 'beberlei_metrics.collector.full' - )); - - $collector = $container->get('beberlei_metrics.collector.simple'); - $this->assertInstanceOf('Beberlei\Metrics\Collector\Telegraf', $collector); - $this->assertSame('localhost', $this->getProperty($collector, 'host')); - $this->assertSame(8125, $this->getProperty($collector, 'port')); - $this->assertSame('', $this->getProperty($collector, 'prefix')); - - $collector = $container->get('beberlei_metrics.collector.full'); - $this->assertInstanceOf('Beberlei\Metrics\Collector\Telegraf', $collector); - $this->assertSame('telegraf.localhost', $this->getProperty($collector, 'host')); - $this->assertSame(1234, $this->getProperty($collector, 'port')); - $this->assertSame('application.com.symfony.', $this->getProperty($collector, 'prefix')); - - $this->assertEquals(',string_tag=first_value,int_tag=123', $this->getProperty($collector, 'tags')); - } - - public function testWithZabbix() - { - $container = $this->createContainer(array( - 'default' => 'simple', - 'collectors' => array( - 'simple' => array( - 'type' => 'zabbix', - ), - 'full' => array( - 'type' => 'zabbix', - 'prefix' => 'foo.beberlei.de', - 'host' => 'zabbix.localhost', - 'port' => 1234, - ), - 'file' => array( - 'type' => 'zabbix', - 'prefix' => 'foo.beberlei.de', - 'file' => '/etc/zabbix/zabbix_agentd.conf', - ), - ), - ), array( - 'beberlei_metrics.collector.simple', - 'beberlei_metrics.collector.full', - 'beberlei_metrics.collector.file' - )); - - $collector = $container->get('beberlei_metrics.collector.simple'); - $this->assertInstanceOf('Beberlei\Metrics\Collector\Zabbix', $collector); - $this->assertSame(gethostname(), $this->getProperty($collector, 'prefix')); - $sender = $this->getProperty($collector, 'sender'); - $this->assertInstanceOf('Net\Zabbix\Sender', $sender); - $this->assertSame('localhost', $this->getProperty($sender, '_servername')); - $this->assertSame(10051, $this->getProperty($sender, '_serverport')); - - $collector = $container->get('beberlei_metrics.collector.full'); - $this->assertInstanceOf('Beberlei\Metrics\Collector\Zabbix', $collector); - $this->assertSame('foo.beberlei.de', $this->getProperty($collector, 'prefix')); - $sender = $this->getProperty($collector, 'sender'); - $this->assertInstanceOf('Net\Zabbix\Sender', $sender); - $this->assertSame('zabbix.localhost', $this->getProperty($sender, '_servername')); - $this->assertSame(1234, $this->getProperty($sender, '_serverport')); - - $collector = $container->get('beberlei_metrics.collector.file'); - $this->assertInstanceOf('Beberlei\Metrics\Collector\Zabbix', $collector); - $this->assertSame('foo.beberlei.de', $this->getProperty($collector, 'prefix')); - $sender = $this->getProperty($collector, 'sender'); - $this->assertInstanceOf('Net\Zabbix\Sender', $sender); - } - - public function testWithInfluxDB() - { - $influxDBClientMock = $this->getMockBuilder('InfluxDB\Client') - ->disableOriginalConstructor() - ->getMock() - ; - - $container = $this->createContainer(array( - 'collectors' => array( - 'influxdb' => array( - 'type' => 'influxdb', - 'influxdb_client' => 'influxdb_client_mock', - ), - ), - ), array('beberlei_metrics.collector.influxdb'), array( - 'influxdb_client_mock' => $influxDBClientMock, - )); - - $collector = $container->get('beberlei_metrics.collector.influxdb'); - $this->assertInstanceOf('Beberlei\Metrics\Collector\InfluxDB', $collector); - $this->assertSame($influxDBClientMock, $this->getProperty($collector, 'client')); - } - - public function testWithInfluxDBAndWithTags() - { - $expectedTags = array( - 'string_tag' => 'first_value', - 'int_tag' => 123, - ); - - $influxDBClientMock = $this->getMockBuilder('InfluxDB\Client') - ->disableOriginalConstructor() - ->getMock() - ; - - $container = $this->createContainer(array( - 'collectors' => array( - 'influxdb' => array( - 'type' => 'influxdb', - 'influxdb_client' => 'influxdb_client_mock', - 'tags' => $expectedTags, - ), - ), - ), array('beberlei_metrics.collector.influxdb'), array( - 'influxdb_client_mock' => $influxDBClientMock, - )); - - $collector = $container->get('beberlei_metrics.collector.influxdb'); - $this->assertInstanceOf('Beberlei\Metrics\Collector\InfluxDB', $collector); - $this->assertEquals($expectedTags, $this->getProperty($collector, 'tags')); - } - - public function testWithPrometheus() - { - $prometheusCollectorRegistryMock = $this->getMockBuilder('\\Prometheus\\CollectorRegistry') - ->disableOriginalConstructor() - ->getMock() - ; - - $container = $this->createContainer(array( - 'collectors' => array( - 'prometheus' => array( - 'type' => 'prometheus', - 'prometheus_collector_registry' => 'prometheus_collector_registry_mock', - ), - ), - ), array('beberlei_metrics.collector.prometheus'), array( - 'prometheus_collector_registry_mock' => $prometheusCollectorRegistryMock, - )); - - $collector = $container->get('beberlei_metrics.collector.prometheus'); - $this->assertInstanceOf('Beberlei\Metrics\Collector\Prometheus', $collector); - $this->assertSame($prometheusCollectorRegistryMock, $this->getProperty($collector, 'collectorRegistry')); - $this->assertSame('', $this->getProperty($collector, 'namespace')); - } - - public function testWithInMemory() - { - $container = $this->createContainer(array( - 'collectors' => array( - 'memory' => array( - 'type' => 'memory', - ), - ), - ), array('beberlei_metrics.collector.memory')); - $collector = $container->get('beberlei_metrics.collector.memory'); - $this->assertInstanceOf('Beberlei\Metrics\Collector\InMemory', $collector); - } - - public function testWithPrometheusAndWithNamespace() - { - $expectedNamespace = 'some_namespace'; - - $prometheusCollectorRegistryMock = $this->getMockBuilder('\\Prometheus\\CollectorRegistry') - ->disableOriginalConstructor() - ->getMock() - ; - - $container = $this->createContainer(array( - 'collectors' => array( - 'prometheus' => array( - 'type' => 'prometheus', - 'prometheus_collector_registry' => 'prometheus_collector_registry_mock', - 'namespace' => $expectedNamespace, - ), - ), - ), array('beberlei_metrics.collector.prometheus'), array( - 'prometheus_collector_registry_mock' => $prometheusCollectorRegistryMock, - )); - - $collector = $container->get('beberlei_metrics.collector.prometheus'); - $this->assertInstanceOf('Beberlei\Metrics\Collector\Prometheus', $collector); - $this->assertSame($prometheusCollectorRegistryMock, $this->getProperty($collector, 'collectorRegistry')); - $this->assertSame($expectedNamespace, $this->getProperty($collector, 'namespace')); - } - - public function testWithPrometheusAndWithTags() - { - $expectedTags = array( - 'string_tag' => 'first_value', - 'int_tag' => 123, - ); - - $prometheusCollectorRegistryMock = $this->getMockBuilder('\\Prometheus\\CollectorRegistry') - ->disableOriginalConstructor() - ->getMock() - ; - - $container = $this->createContainer(array( - 'collectors' => array( - 'prometheus' => array( - 'type' => 'prometheus', - 'prometheus_collector_registry' => 'prometheus_collector_registry_mock', - 'tags' => $expectedTags, - ), - ), - ), array('beberlei_metrics.collector.prometheus'), array( - 'prometheus_collector_registry_mock' => $prometheusCollectorRegistryMock, - )); - - $collector = $container->get('beberlei_metrics.collector.prometheus'); - $this->assertInstanceOf('Beberlei\Metrics\Collector\Prometheus', $collector); - $this->assertEquals($expectedTags, $this->getProperty($collector, 'tags')); - } - - /** - * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException - * @expectedExceptionMessage The prometheus_collector_registry has to be specified to use a Prometheus - */ - public function testValidationWhenTypeIsPrometheusAndPrometheusCollectorRegistryIsNotSpecified() - { - $this->createContainer(array( - 'collectors' => array( - 'prometheus' => array( - 'type' => 'prometheus', - ), - ), - )); - } - - private function createContainer($configs, $publicServices = array(), $additionalServices = array()) - { - $container = new ContainerBuilder(); - - $extension = new BeberleiMetricsExtension(); - $extension->load(array($configs), $container); - // Needed for logger collector - $container->setDefinition('logger', new Definition('Psr\Log\NullLogger')); - - foreach ($additionalServices as $serviceId => $additionalService) { - $container->set($serviceId, $additionalService); - } - - foreach($publicServices as $serviceId) { - $container->getDefinition($serviceId)->setPublic(true); - } - - $container->compile(); - - return $container; - } - - private function getProperty($object, $property) - { - $reflectionProperty = new \ReflectionProperty(get_class($object), $property); - $reflectionProperty->setAccessible(true); - - return $reflectionProperty->getValue($object); - } -} diff --git a/src/Beberlei/Metrics/Collector/Collector.php b/src/Beberlei/Metrics/Collector/Collector.php deleted file mode 100644 index 51d44a0..0000000 --- a/src/Beberlei/Metrics/Collector/Collector.php +++ /dev/null @@ -1,55 +0,0 @@ -conn = $conn; - } - - /** - * {@inheritdoc} - */ - public function timing($stat, $time) - { - $this->data[] = array($stat, $time, date('Y-m-d')); - } - - /** - * {@inheritdoc} - */ - public function increment($stats) - { - $this->data[] = array($stats, 1, date('Y-m-d')); - } - - /** - * {@inheritdoc} - */ - public function decrement($stats) - { - $this->data[] = array($stats, -1, date('Y-m-d')); - } - - /** - * {@inheritdoc} - */ - public function measure($variable, $value) - { - $this->data[] = array($variable, $value, date('Y-m-d')); - } - - /** - * {@inheritdoc} - */ - public function flush() - { - if (!$this->data) { - return; - } - - try { - $this->conn->beginTransaction(); - - $stmt = $this->conn->prepare('INSERT INTO metrics (metric, measurement, created) VALUES (?, ?, ?)'); - - foreach ($this->data as $measurement) { - $stmt->bindParam(1, $measurement[0]); - $stmt->bindParam(2, $measurement[1]); - $stmt->bindParam(3, $measurement[2]); - $stmt->execute(); - } - - $this->conn->commit(); - } catch (Exception $e) { - $this->conn->rollback(); - } - - $this->data = array(); - } -} diff --git a/src/Beberlei/Metrics/Collector/DogStatsD.php b/src/Beberlei/Metrics/Collector/DogStatsD.php deleted file mode 100644 index 7f9a82d..0000000 --- a/src/Beberlei/Metrics/Collector/DogStatsD.php +++ /dev/null @@ -1,133 +0,0 @@ -host = $host; - $this->port = $port; - $this->prefix = $prefix; - $this->data = array(); - } - - /** - * {@inheritdoc} - */ - public function measure($variable, $value, $tags = array()) - { - $this->data[] = sprintf('%s:%s|c%s', $variable, $value, $this->buildTagString($tags)); - } - - /** - * {@inheritdoc} - */ - public function increment($variable, $tags = array()) - { - $this->data[] = $variable.':1|c'.$this->buildTagString($tags); - } - - /** - * {@inheritdoc} - */ - public function decrement($variable, $tags = array()) - { - $this->data[] = $variable.':-1|c'.$this->buildTagString($tags); - } - - /** - * {@inheritdoc} - */ - public function timing($variable, $time, $tags = array()) - { - $this->data[] = sprintf('%s:%s|ms%s', $variable, $time, $this->buildTagString($tags)); - } - - /** - * {@inheritdoc} - */ - public function gauge($variable, $value, $tags = array()) - { - $this->data[] = sprintf('%s:%s|g%s', $variable, $value, $this->buildTagString($tags)); - } - - /** - * {@inheritdoc} - */ - public function flush() - { - if (!$this->data) { - return; - } - - $fp = fsockopen('udp://'.$this->host, $this->port, $errno, $errstr, 1.0); - - if (!$fp) { - return; - } - - $level = error_reporting(0); - foreach ($this->data as $line) { - fwrite($fp, $this->prefix.$line); - } - error_reporting($level); - - fclose($fp); - - $this->data = array(); - } - - /** - * Given a key/value map of metric tags, builds them into a - * DogStatsD tag string and returns the string. - * - * @param $tags array - * - * @return string - */ - private function buildTagString($tags) - { - $results = array(); - - foreach ($tags as $key => $value) { - $results[] = sprintf('%s:%s', $key, $value); - } - - $tagString = implode(',', $results); - - if (strlen($tagString)) { - $tagString = sprintf('|#%s', $tagString); - } - - return $tagString; - } -} diff --git a/src/Beberlei/Metrics/Collector/GaugeableCollector.php b/src/Beberlei/Metrics/Collector/GaugeableCollector.php deleted file mode 100644 index ffeb98f..0000000 --- a/src/Beberlei/Metrics/Collector/GaugeableCollector.php +++ /dev/null @@ -1,25 +0,0 @@ -host = $host; - $this->port = $port; - $this->protocol = $protocol; - } - - /** - * {@inheritdoc} - */ - public function timing($variable, $time) - { - $this->push($variable, $time); - } - - /** - * {@inheritdoc} - */ - public function increment($variable) - { - $this->push($variable, 1); - } - - /** - * {@inheritdoc} - */ - public function decrement($variable) - { - $this->push($variable, -1); - } - - /** - * {@inheritdoc} - */ - public function measure($variable, $value) - { - $this->push($variable, $value); - } - - /** - * {@inheritdoc} - */ - public function flush() - { - if (!$this->data) { - return; - } - - try { - $fp = fsockopen($this->protocol.'://'.$this->host, $this->port); - - if (!$fp) { - return; - } - - foreach ($this->data as $line) { - fwrite($fp, $line); - } - - fclose($fp); - } catch (Exception $e) { - } - - $this->data = array(); - } - - public function push($stat, $value, $time = null) - { - $this->data[] = sprintf( - is_float($value) ? "%s %.18f %d\n" : "%s %d %d\n", - $stat, - $value, - $time ?: time() - ); - } -} diff --git a/src/Beberlei/Metrics/Collector/InMemory.php b/src/Beberlei/Metrics/Collector/InMemory.php deleted file mode 100644 index 95df43b..0000000 --- a/src/Beberlei/Metrics/Collector/InMemory.php +++ /dev/null @@ -1,154 +0,0 @@ -incrementData[$variable])) { - $this->incrementData[$variable] = 0; - } - $this->incrementData[$variable] += $value; - } - - /** - * Increments a counter. - * - * @param string $variable - */ - public function increment($variable) - { - $this->measure($variable, 1); - } - - /** - * Decrements a counter. - * - * @param string $variable - */ - public function decrement($variable) - { - $this->measure($variable, -1); - } - - /** - * Records a timing. - * - * @param string $variable - * @param int $time The duration of the timing in milliseconds - */ - public function timing($variable, $time) - { - if (!isset($this->timingData[$variable])) { - $this->timingData[$variable] = 0; - } - $this->timingData[$variable] = $time; - } - - /** - * Sends the metrics to the adapter backend. - */ - public function flush() - { - $this->timingData = []; - $this->gaugeData = []; - $this->incrementData = []; - } - - /** - * Updates a gauge by an arbitrary amount. - * - * @param string $variable - * @param int $value - */ - public function gauge($variable, $value) - { - $sign = substr($value, 0, 1); - - if (in_array($sign, ['-', '+'])) { - $this->gaugeIncrement($variable, (int) $value); - - return; - } - - $this->gaugeData[$variable] = $value; - } - - /** - * Returns current value of incremented/decremented/measured variable. - * - * @param string $variable - * - * @return int - */ - public function getMeasure($variable) - { - return isset($this->incrementData[$variable]) ? $this->incrementData[$variable] : 0; - } - - /** - * Returns current value of gauged variable. - * - * @param string $variable - * - * @return int - */ - public function getGauge($variable) - { - return isset($this->gaugeData[$variable]) ? $this->gaugeData[$variable] : 0; - } - - /** - * Returns current value of timed variable. - * - * @param string $variable - * - * @return int - */ - public function getTiming($variable) - { - return isset($this->timingData[$variable]) ? $this->timingData[$variable] : 0; - } - - /** - * @param string $variable - * @param int $value - */ - private function gaugeIncrement($variable, $value) - { - if (!isset($this->gaugeData[$variable])) { - $this->gaugeData[$variable] = 0; - } - - $this->gaugeData[$variable] += $value; - } -} diff --git a/src/Beberlei/Metrics/Collector/InfluxDB.php b/src/Beberlei/Metrics/Collector/InfluxDB.php deleted file mode 100644 index ae657b8..0000000 --- a/src/Beberlei/Metrics/Collector/InfluxDB.php +++ /dev/null @@ -1,96 +0,0 @@ -client = $client; - } - - /** - * {@inheritdoc} - */ - public function increment($variable) - { - $this->data[] = array($variable, 1); - } - - /** - * {@inheritdoc} - */ - public function decrement($variable) - { - $this->data[] = array($variable, -1); - } - - /** - * {@inheritdoc} - */ - public function timing($variable, $time) - { - $this->data[] = array($variable, $time); - } - - /** - * {@inheritdoc} - */ - public function measure($variable, $value) - { - $this->data[] = array($variable, $value); - } - - /** - * {@inheritdoc} - */ - public function flush() - { - foreach ($this->data as $data) { - $this->client->mark(array( - 'points' => array( - array( - 'measurement' => $data[0], - 'fields' => array('value' => $data[1]), - ), - ), - 'tags' => $this->tags, - )); - } - - $this->data = array(); - } - - /** - * {@inheritdoc} - */ - public function setTags($tags) - { - $this->tags = $tags; - } -} diff --git a/src/Beberlei/Metrics/Collector/InlineTaggableGaugeableCollector.php b/src/Beberlei/Metrics/Collector/InlineTaggableGaugeableCollector.php deleted file mode 100644 index 1fdebb1..0000000 --- a/src/Beberlei/Metrics/Collector/InlineTaggableGaugeableCollector.php +++ /dev/null @@ -1,65 +0,0 @@ - array(), - 'gauges' => array(), - ); - - /** - * @param \Buzz\Browser $browser - * @param string $source - * @param string $username - * @param string $password - */ - public function __construct(Browser $browser, $source, $username, $password) - { - $this->browser = $browser; - $this->source = $source; - $this->username = $username; - $this->password = $password; - } - - /** - * {@inheritdoc} - */ - public function increment($variable) - { - $this->data['counters'][] = array( - 'source' => $this->source, - 'name' => $variable, - 'value' => 1, - ); - } - - /** - * {@inheritdoc} - */ - public function decrement($variable) - { - $this->data['counters'][] = array( - 'source' => $this->source, - 'name' => $variable, - 'value' => -1, - ); - } - - /** - * {@inheritdoc} - */ - public function timing($variable, $time) - { - $this->data['gauges'][] = array( - 'source' => $this->source, - 'name' => $variable, - 'value' => $time, - ); - } - - /** - * {@inheritdoc} - */ - public function measure($variable, $value) - { - $this->data['gauges'][] = array( - 'source' => $this->source, - 'name' => $variable, - 'value' => $value, - ); - } - - /** - * {@inheritdoc} - */ - public function flush() - { - if (!$this->data['gauges'] && !$this->data['counters']) { - return; - } - - try { - $this->browser->post('https://metrics-api.librato.com/v1/metrics', array( - 'Authorization: Basic '.base64_encode($this->username.':'.$this->password), - 'Content-Type: application/json', - ), json_encode($this->data)); - $this->data = array('gauges' => array(), 'counters' => array()); - } catch (\Exception $e) { - } - } -} diff --git a/src/Beberlei/Metrics/Collector/Logger.php b/src/Beberlei/Metrics/Collector/Logger.php deleted file mode 100644 index c501981..0000000 --- a/src/Beberlei/Metrics/Collector/Logger.php +++ /dev/null @@ -1,78 +0,0 @@ -logger = $logger; - } - - /** - * {@inheritdoc} - */ - public function increment($variable) - { - $this->logger->debug('increment:'.$variable); - } - - /** - * {@inheritdoc} - */ - public function decrement($variable) - { - $this->logger->debug('decrement:'.$variable); - } - - /** - * {@inheritdoc} - */ - public function timing($variable, $time) - { - $this->logger->debug(sprintf('timing:%s:%s', $variable, $time)); - } - - /** - * {@inheritdoc} - */ - public function measure($variable, $value) - { - $this->logger->debug(sprintf('measure:%s:%s', $variable, $value)); - } - - /** - * {@inheritdoc} - */ - public function gauge($variable, $value) - { - $this->logger->debug(sprintf('gauge:%s:%s', $variable, $value)); - } - - /** - * {@inheritdoc} - */ - public function flush() - { - $this->logger->debug('flush'); - } -} diff --git a/src/Beberlei/Metrics/Collector/Null.php b/src/Beberlei/Metrics/Collector/Null.php deleted file mode 100644 index f651dc8..0000000 --- a/src/Beberlei/Metrics/Collector/Null.php +++ /dev/null @@ -1,21 +0,0 @@ - array(), - 'gauges' => array(), - ); - - /** - * @var array - */ - private $tags = array(); - - /** - * @param CollectorRegistry $collectorRegistry - * @param string $namespace - */ - public function __construct(CollectorRegistry $collectorRegistry, $namespace = '') - { - $this->collectorRegistry = $collectorRegistry; - $this->namespace = $namespace; - } - - /** - * {@inheritdoc} - */ - public function measure($variable, $value) - { - $this->data['gauges'][] = array( - 'name' => $variable, - 'value' => $value, - ); - } - - /** - * {@inheritdoc} - */ - public function increment($variable) - { - $this->data['counters'][] = array( - 'name' => $variable, - 'value' => 1, - ); - } - - /** - * {@inheritdoc} - */ - public function decrement($variable) - { - $this->data['counters'][] = array( - 'name' => $variable, - 'value' => -1, - ); - } - - /** - * {@inheritdoc} - */ - public function timing($variable, $time) - { - $this->measure($variable, $time); - } - - /** - * {@inheritdoc} - */ - public function flush() - { - if (!$this->data['gauges'] && !$this->data['counters']) { - return; - } - - $tagsValues = array_values($this->tags); - - foreach ($this->data['counters'] as $counterData) { - $gauge = $this->getOrRegisterGaugeForVariable($counterData['name']); - - if ($counterData['value'] > 0) { - $gauge->inc($tagsValues); - } elseif ($counterData['value'] < 0) { - $gauge->dec($tagsValues); - } - } - - foreach ($this->data['gauges'] as $gaugeData) { - $gauge = $this->getOrRegisterGaugeForVariable($gaugeData['name']); - - $gauge->set($gaugeData['value'], $tagsValues); - } - - $this->data = array('counters' => array(), 'gauges' => array()); - } - - /** - * {@inheritdoc} - */ - public function setTags($tags) - { - $this->tags = $tags; - } - - /** - * @param string $variable - * - * @return \Prometheus\Gauge - */ - private function getOrRegisterGaugeForVariable($variable) - { - try { - $gauge = $this->collectorRegistry->getGauge($this->namespace, $variable); - } catch (MetricNotFoundException $e) { - $gauge = $this->collectorRegistry->registerGauge( - $this->namespace, - $variable, - '', - array_keys($this->tags) - ); - } - - return $gauge; - } -} diff --git a/src/Beberlei/Metrics/Collector/StatsD.php b/src/Beberlei/Metrics/Collector/StatsD.php deleted file mode 100644 index e6c92fc..0000000 --- a/src/Beberlei/Metrics/Collector/StatsD.php +++ /dev/null @@ -1,111 +0,0 @@ -host = $host; - $this->port = $port; - $this->prefix = $prefix; - $this->data = array(); - } - - /** - * {@inheritdoc} - */ - public function timing($variable, $time) - { - $this->data[] = sprintf('%s:%s|ms', $variable, $time); - } - - /** - * {@inheritdoc} - */ - public function increment($variable) - { - $this->data[] = $variable.':1|c'; - } - - /** - * {@inheritdoc} - */ - public function decrement($variable) - { - $this->data[] = $variable.':-1|c'; - } - - /** - * {@inheritdoc} - */ - public function measure($variable, $value) - { - $this->data[] = sprintf('%s:%s|c', $variable, $value); - } - - /** - * {@inheritdoc} - */ - public function gauge($variable, $value) - { - $this->data[] = sprintf('%s:%s|g', $variable, $value); - } - - /** - * {@inheritdoc} - */ - public function flush() - { - if (!$this->data) { - return; - } - - $fp = fsockopen('udp://'.$this->host, $this->port, $errno, $errstr, 1.0); - - if (!$fp) { - return; - } - - $level = error_reporting(0); - foreach ($this->data as $line) { - fwrite($fp, $this->prefix.$line); - } - error_reporting($level); - - fclose($fp); - - $this->data = array(); - } -} diff --git a/src/Beberlei/Metrics/Collector/TaggableCollector.php b/src/Beberlei/Metrics/Collector/TaggableCollector.php deleted file mode 100644 index 2e9cda0..0000000 --- a/src/Beberlei/Metrics/Collector/TaggableCollector.php +++ /dev/null @@ -1,27 +0,0 @@ -host = $host; - $this->port = $port; - $this->prefix = $prefix; - $this->data = array(); - } - - /** - * {@inheritdoc} - */ - public function setTags($tags) - { - $this->tags = http_build_query($tags, '', ','); - $this->tags = (strlen($this->tags) > 0 ? ','.$this->tags : $this->tags); - } - - /** - * {@inheritdoc} - */ - public function timing($variable, $time) - { - $this->data[] = sprintf('%s%s:%s|ms', $variable, $this->tags, $time); - } - - /** - * {@inheritdoc} - */ - public function increment($variable) - { - $this->data[] = $variable.$this->tags.':1|c'; - } - - /** - * {@inheritdoc} - */ - public function decrement($variable) - { - $this->data[] = $variable.$this->tags.':-1|c'; - } - - /** - * {@inheritdoc} - */ - public function measure($variable, $value) - { - $this->data[] = sprintf('%s%s:%s|c', $variable, $this->tags, $value); - } - - /** - * {@inheritdoc} - */ - public function gauge($variable, $value) - { - $this->data[] = sprintf('%s%s:%s|g', $variable, $this->tags, $value); - } - - /** - * @param $variable - * @param $value - */ - public function set($variable, $value) - { - $this->data[] = sprintf('%s%s:%s|s', $variable, $this->tags, $value); - } - - /** - * {@inheritdoc} - */ - public function flush() - { - if (!$this->data) { - return; - } - - $fp = fsockopen('udp://'.$this->host, $this->port, $errno, $errstr, 1.0); - - if (!$fp) { - return; - } - - $level = error_reporting(0); - foreach ($this->data as $line) { - fwrite($fp, $this->prefix.$line); - } - error_reporting($level); - - fclose($fp); - - $this->data = array(); - } -} diff --git a/src/Beberlei/Metrics/Collector/Zabbix.php b/src/Beberlei/Metrics/Collector/Zabbix.php deleted file mode 100644 index 214e064..0000000 --- a/src/Beberlei/Metrics/Collector/Zabbix.php +++ /dev/null @@ -1,78 +0,0 @@ -sender = $sender; - $this->prefix = $prefix ?: gethostname(); - } - - /** - * {@inheritdoc} - */ - public function increment($variable) - { - $this->sender->addData($this->prefix, $variable, '1'); - } - - /** - * {@inheritdoc} - */ - public function decrement($variable) - { - $this->sender->addData($this->prefix, $variable, '-1'); - } - - /** - * {@inheritdoc} - */ - public function timing($variable, $time) - { - $this->sender->addData($this->prefix, $variable, $time); - } - - /** - * {@inheritdoc} - */ - public function measure($variable, $value) - { - $this->sender->addData($this->prefix, $variable, $value); - } - - /** - * {@inheritdoc} - */ - public function flush() - { - $this->sender->send(); - } -} diff --git a/src/Beberlei/Metrics/Factory.php b/src/Beberlei/Metrics/Factory.php deleted file mode 100644 index 0e6ba73..0000000 --- a/src/Beberlei/Metrics/Factory.php +++ /dev/null @@ -1,203 +0,0 @@ -importAgentConfig(new Config($file)); - - return new Collector\Zabbix($sender, $options['hostname']); - - case 'librato': - if (!isset($options['hostname'])) { - throw new MetricsException('Hostname is required for librato collector.'); - } - - if (!isset($options['username'])) { - throw new MetricsException('No username given for librato collector.'); - } - - if (!isset($options['password'])) { - throw new MetricsException('No password given for librato collector.'); - } - - return new Collector\Librato(self::getHttpClient(), $options['hostname'], $options['username'], $options['password']); - - case 'doctrine_dbal': - if (!isset($options['connection'])) { - throw new MetricsException('connection is required for Doctrine DBAL collector.'); - } - - return new Collector\DoctrineDBAL($options['connection']); - - case 'logger': - if (!isset($options['logger'])) { - throw new MetricsException("Missing 'logger' key with logger service."); - } - - return new Collector\Logger($options['logger']); - - case 'influxdb': - if (!isset($options['client'])) { - throw new MetricsException('Missing \'client\' key for InfluxDB collector.'); - } - - return new Collector\InfluxDB($options['client']); - - case 'null': - return new Collector\NullCollector(); - - case 'null_inlinetaggable': - return new Collector\InlineTaggableGaugeableNullCollector(); - - case 'prometheus': - if (!isset($options['collector_registry'])) { - throw new MetricsException('Missing \'collector_registry\' key for Prometheus collector.'); - } - - $namespace = isset($options['namespace']) ? $options['namespace'] : ''; - - return new Collector\Prometheus($options['collector_registry'], $namespace); - - default: - throw new MetricsException(sprintf('Unknown metrics collector given (%s).', $type)); - } - } - - private static function getHttpClient() - { - if (self::$httpClient === null) { - self::$httpClient = new Browser(new Curl()); - } - - return self::$httpClient; - } -} diff --git a/src/Beberlei/Metrics/MetricsException.php b/src/Beberlei/Metrics/MetricsException.php deleted file mode 100644 index 2100609..0000000 --- a/src/Beberlei/Metrics/MetricsException.php +++ /dev/null @@ -1,18 +0,0 @@ -client = $this->getMockBuilder('\\InfluxDB\\Client') - ->disableOriginalConstructor() - ->getMock(); - $this->collector = new InfluxDB($this->client); - } - - public function testCollectIncrement() - { - $expectedArgs = array( - 'points' => array( - array( - 'measurement' => 'series-name', - 'fields' => array('value' => 1), - ), - ), - 'tags' => array(), - ); - - $this->client->expects($this->once()) - ->method('mark') - ->with($expectedArgs); - - $this->collector->increment('series-name'); - $this->collector->flush(); - } - - public function testCollectDecrement() - { - $expectedArgs = array( - 'points' => array( - array( - 'measurement' => 'series-name', - 'fields' => array('value' => -1), - ), - ), - 'tags' => array(), - ); - - $this->client->expects($this->once()) - ->method('mark') - ->with($expectedArgs); - - $this->collector->decrement('series-name'); - $this->collector->flush(); - } - - public function testCollectTiming() - { - $expectedArgs = array( - 'points' => array( - array( - 'measurement' => 'series-name', - 'fields' => array('value' => 47.11), - ), - ), - 'tags' => array(), - ); - - $this->client->expects($this->once()) - ->method('mark') - ->with($expectedArgs); - - $this->collector->timing('series-name', 47.11); - $this->collector->flush(); - } - - public function testCollectMeasure() - { - $expectedArgs = array( - 'points' => array( - array( - 'measurement' => 'series-name', - 'fields' => array('value' => 47.11), - ), - ), - 'tags' => array(), - ); - - $this->client->expects($this->once()) - ->method('mark') - ->with($expectedArgs); - - $this->collector->measure('series-name', 47.11); - $this->collector->flush(); - } - - public function testCollectMeasureWithTags() - { - $expectedTags = array( - 'dc' => 'west', - 'node' => 'nemesis101', - ); - - $expectedArgs = array( - 'points' => array( - array( - 'measurement' => 'series-name', - 'fields' => array('value' => 47.11), - ), - ), - 'tags' => $expectedTags, - ); - - $this->client->expects($this->once()) - ->method('mark') - ->with($expectedArgs); - - $this->collector->setTags($expectedTags); - $this->collector->measure('series-name', 47.11); - $this->collector->flush(); - } -} diff --git a/src/Beberlei/Metrics/Tests/Collector/PrometheusTest.php b/src/Beberlei/Metrics/Tests/Collector/PrometheusTest.php deleted file mode 100644 index f42f3ab..0000000 --- a/src/Beberlei/Metrics/Tests/Collector/PrometheusTest.php +++ /dev/null @@ -1,337 +0,0 @@ -collectorRegistryMock = $this->getMockBuilder('\\Prometheus\\CollectorRegistry') - ->disableOriginalConstructor() - ->getMock() - ; - - $this->collector = new Prometheus($this->collectorRegistryMock, self::TEST_NAMESPACE); - } - - public function testMeasure() - { - $expectedVariableValue = 123; - - $gaugeMock = $this->getMockBuilder('\\Prometheus\\Gauge') - ->disableOriginalConstructor() - ->getMock() - ; - $gaugeMock - ->expects($this->once()) - ->method('set') - ->with($expectedVariableValue, array()) - ; - - $this->collectorRegistryMock - ->expects($this->once()) - ->method('getGauge') - ->with(self::TEST_NAMESPACE, self::TEST_VARIABLE_NAME) - ->willReturn($gaugeMock) - ; - - $this->collector->measure(self::TEST_VARIABLE_NAME, $expectedVariableValue); - $this->collector->flush(); - } - - public function testMeasureWithTags() - { - $expectedVariableValue = 123; - $expectedTagsValues = array('value1', 'value2'); - - $gaugeMock = $this->getMockBuilder('\\Prometheus\\Gauge') - ->disableOriginalConstructor() - ->getMock() - ; - $gaugeMock - ->expects($this->once()) - ->method('set') - ->with($expectedVariableValue, $expectedTagsValues) - ; - - $this->collectorRegistryMock - ->expects($this->once()) - ->method('getGauge') - ->with(self::TEST_NAMESPACE, self::TEST_VARIABLE_NAME) - ->willReturn($gaugeMock) - ; - - $this->collector->setTags(array( - 'tag1' => 'value1', - 'tag2' => 'value2', - )); - - $this->collector->measure(self::TEST_VARIABLE_NAME, $expectedVariableValue); - $this->collector->flush(); - } - - public function testIncrement() - { - $gaugeMock = $this->getMockBuilder('\\Prometheus\\Gauge') - ->disableOriginalConstructor() - ->getMock() - ; - $gaugeMock - ->expects($this->once()) - ->method('inc') - ->with(array()) - ; - - $this->collectorRegistryMock - ->expects($this->once()) - ->method('getGauge') - ->with(self::TEST_NAMESPACE, self::TEST_VARIABLE_NAME) - ->willReturn($gaugeMock) - ; - - $this->collector->increment(self::TEST_VARIABLE_NAME); - $this->collector->flush(); - } - - public function testIncrementWithTags() - { - $expectedTagsValues = array('value1', 'value2'); - - $gaugeMock = $this->getMockBuilder('\\Prometheus\\Gauge') - ->disableOriginalConstructor() - ->getMock() - ; - $gaugeMock - ->expects($this->once()) - ->method('inc') - ->with($expectedTagsValues) - ; - - $this->collectorRegistryMock - ->expects($this->once()) - ->method('getGauge') - ->with(self::TEST_NAMESPACE, self::TEST_VARIABLE_NAME) - ->willReturn($gaugeMock) - ; - - $this->collector->setTags(array( - 'tag1' => 'value1', - 'tag2' => 'value2', - )); - - $this->collector->increment(self::TEST_VARIABLE_NAME); - $this->collector->flush(); - } - - public function testDecrement() - { - $gaugeMock = $this->getMockBuilder('\\Prometheus\\Gauge') - ->disableOriginalConstructor() - ->getMock() - ; - $gaugeMock - ->expects($this->once()) - ->method('dec') - ->with(array()) - ; - - $this->collectorRegistryMock - ->expects($this->once()) - ->method('getGauge') - ->with(self::TEST_NAMESPACE, self::TEST_VARIABLE_NAME) - ->willReturn($gaugeMock) - ; - - $this->collector->decrement(self::TEST_VARIABLE_NAME); - $this->collector->flush(); - } - - public function testDecrementWithTags() - { - $expectedTagsValues = array('value1', 'value2'); - - $gaugeMock = $this->getMockBuilder('\\Prometheus\\Gauge') - ->disableOriginalConstructor() - ->getMock() - ; - $gaugeMock - ->expects($this->once()) - ->method('dec') - ->with($expectedTagsValues) - ; - - $this->collectorRegistryMock - ->expects($this->once()) - ->method('getGauge') - ->with(self::TEST_NAMESPACE, self::TEST_VARIABLE_NAME) - ->willReturn($gaugeMock) - ; - - $this->collector->setTags(array( - 'tag1' => 'value1', - 'tag2' => 'value2', - )); - - $this->collector->decrement(self::TEST_VARIABLE_NAME); - $this->collector->flush(); - } - - public function testTiming() - { - $expectedVariableValue = 123; - - $gaugeMock = $this->getMockBuilder('\\Prometheus\\Gauge') - ->disableOriginalConstructor() - ->getMock() - ; - $gaugeMock - ->expects($this->once()) - ->method('set') - ->with($expectedVariableValue, array()) - ; - - $this->collectorRegistryMock - ->expects($this->once()) - ->method('getGauge') - ->with(self::TEST_NAMESPACE, self::TEST_VARIABLE_NAME) - ->willReturn($gaugeMock) - ; - - $this->collector->timing(self::TEST_VARIABLE_NAME, $expectedVariableValue); - $this->collector->flush(); - } - - public function testTimingWithTags() - { - $expectedVariableValue = 123; - $expectedTagsValues = array('value1', 'value2'); - - $gaugeMock = $this->getMockBuilder('\\Prometheus\\Gauge') - ->disableOriginalConstructor() - ->getMock() - ; - $gaugeMock - ->expects($this->once()) - ->method('set') - ->with($expectedVariableValue, $expectedTagsValues) - ; - - $this->collectorRegistryMock - ->expects($this->once()) - ->method('getGauge') - ->with(self::TEST_NAMESPACE, self::TEST_VARIABLE_NAME) - ->willReturn($gaugeMock) - ; - - $this->collector->setTags(array( - 'tag1' => 'value1', - 'tag2' => 'value2', - )); - - $this->collector->timing(self::TEST_VARIABLE_NAME, $expectedVariableValue); - $this->collector->flush(); - } - - public function testMeasureWhenSetNewVariableWithTags() - { - $expectedVariableValue = 123; - $expectedTagsNames = array('tag1', 'tag2'); - $expectedTagsValues = array('value1', 'value2'); - - $gaugeMock = $this->getMockBuilder('\\Prometheus\\Gauge') - ->disableOriginalConstructor() - ->getMock() - ; - $gaugeMock - ->expects($this->once()) - ->method('set') - ->with($expectedVariableValue, $expectedTagsValues) - ; - - $this->collectorRegistryMock - ->expects($this->once()) - ->method('getGauge') - ->with(self::TEST_NAMESPACE, self::TEST_VARIABLE_NAME) - ->willThrowException(new MetricNotFoundException()) - ; - $this->collectorRegistryMock - ->expects($this->once()) - ->method('registerGauge') - ->with(self::TEST_NAMESPACE, self::TEST_VARIABLE_NAME, '', $expectedTagsNames) - ->willReturn($gaugeMock) - ; - - $this->collector->setTags(array( - 'tag1' => 'value1', - 'tag2' => 'value2', - )); - - $this->collector->measure(self::TEST_VARIABLE_NAME, $expectedVariableValue); - $this->collector->flush(); - } - - /** - * Method flush must to reset value of field `data`. - */ - public function testFlushWhenCallsTwiceWithDifferentData() - { - $firstExpectedVariableValue = 123; - $secondExpectedVariableValue = 321; - - $gaugeMock = $this->getMockBuilder('\\Prometheus\\Gauge') - ->disableOriginalConstructor() - ->getMock() - ; - $gaugeMock - ->expects($this->at(0)) - ->method('set') - ->with($firstExpectedVariableValue, array()) - ; - $gaugeMock - ->expects($this->at(1)) - ->method('set') - ->with($secondExpectedVariableValue, array()) - ; - - $this->collectorRegistryMock - ->expects($this->exactly(2)) - ->method('getGauge') - ->with(self::TEST_NAMESPACE, self::TEST_VARIABLE_NAME) - ->willReturn($gaugeMock) - ; - - $this->collector->measure(self::TEST_VARIABLE_NAME, $firstExpectedVariableValue); - $this->collector->flush(); - - $this->collector->measure(self::TEST_VARIABLE_NAME, $secondExpectedVariableValue); - $this->collector->flush(); - } -} diff --git a/src/Beberlei/Metrics/Tests/FactoryTest.php b/src/Beberlei/Metrics/Tests/FactoryTest.php deleted file mode 100644 index fa7cd1a..0000000 --- a/src/Beberlei/Metrics/Tests/FactoryTest.php +++ /dev/null @@ -1,86 +0,0 @@ - 'localhost', 'port' => 1234, 'prefix' => 'prefix')), - array('Beberlei\Metrics\Collector\StatsD', 'statsd', array('host' => 'localhost', 'port' => 1234)), - array('Beberlei\Metrics\Collector\StatsD', 'statsd', array('host' => 'localhost')), - array('Beberlei\Metrics\Collector\DogStatsD', 'dogstatsd'), - array('Beberlei\Metrics\Collector\DogStatsD', 'dogstatsd', array('host' => 'localhost', 'port' => 1234, 'prefix' => 'prefix')), - array('Beberlei\Metrics\Collector\DogStatsD', 'dogstatsd', array('host' => 'localhost', 'port' => 1234)), - array('Beberlei\Metrics\Collector\DogStatsD', 'dogstatsd', array('host' => 'localhost')), - array('Beberlei\Metrics\Collector\Graphite', 'graphite'), - array('Beberlei\Metrics\Collector\Graphite', 'graphite', array('host' => 'localhost', 'port' => 1234)), - array('Beberlei\Metrics\Collector\Zabbix', 'zabbix', array('hostname' => 'foobar.com', 'server' => 'localhost', 'port' => 1234)), - array('Beberlei\Metrics\Collector\Zabbix', 'zabbix_file', array('hostname' => 'foobar.com')), - array('Beberlei\Metrics\Collector\Zabbix', 'zabbix_file', array('hostname' => 'foobar.com', 'file' => '/tmp/foobar')), - array('Beberlei\Metrics\Collector\Librato', 'librato', array('hostname' => 'foobar.com', 'username' => 'username', 'password' => 'password')), - array('Beberlei\Metrics\Collector\DoctrineDBAL', 'doctrine_dbal', array('connection' => $this->getMockBuilder('Doctrine\DBAL\Connection')->disableOriginalConstructor()->getMock())), - array('Beberlei\Metrics\Collector\Logger', 'logger', array('logger' => new NullLogger())), - array('Beberlei\Metrics\Collector\NullCollector', 'null'), - array('Beberlei\Metrics\Collector\InlineTaggableGaugeableNullCollector', 'null_inlinetaggable'), - array('Beberlei\Metrics\Collector\InfluxDB', 'influxdb', array('client' => $this->getMockBuilder('\\InfluxDB\\Client')->disableOriginalConstructor()->getMock())), - array('Beberlei\Metrics\Collector\Prometheus', 'prometheus', array('collector_registry' => $this->getMockBuilder('\\Prometheus\\CollectorRegistry')->disableOriginalConstructor()->getMock())), - array('Beberlei\Metrics\Collector\Prometheus', 'prometheus', array('collector_registry' => $this->getMockBuilder('\\Prometheus\\CollectorRegistry')->disableOriginalConstructor()->getMock(), 'namespace' => 'some_namespace')), - ); - } - - /** - * @dataProvider getCreateValidMetricTests - */ - public function testCreateValidMetric($expectedClass, $type, $options = array()) - { - $this->assertInstanceOf($expectedClass, Factory::create($type, $options)); - } - - public function getCreateThrowExceptionIfOptionsAreInvalidTests() - { - return array( - array('You should specified a host if you specified a port.', 'statsd', array('port' => '1234')), - array('You should specified a host and a port if you specified a prefix.', 'statsd', array('prefix' => 'prefix')), - array('You should specified a host and a port if you specified a prefix.', 'statsd', array('port' => '1234', 'prefix' => 'prefix')), - array('You should specified a host and a port if you specified a prefix.', 'statsd', array('hostname' => 'foobar.com', 'prefix' => 'prefix')), - array('You should specified a host if you specified a port.', 'dogstatsd', array('port' => '1234')), - array('You should specified a host and a port if you specified a prefix.', 'dogstatsd', array('prefix' => 'prefix')), - array('You should specified a host and a port if you specified a prefix.', 'dogstatsd', array('port' => '1234', 'prefix' => 'prefix')), - array('You should specified a host and a port if you specified a prefix.', 'dogstatsd', array('hostname' => 'foobar.com', 'prefix' => 'prefix')), - array('You should specified a host if you specified a port.', 'graphite', array('port' => '1234')), - array('Hostname is required for zabbix collector.', 'zabbix'), - array('Hostname is required for zabbix collector.', 'zabbix', array('hostname', 'foobar.com')), - array('You should specified a server if you specified a port.', 'zabbix', array('hostname' => 'foobar.com', 'port' => '1234')), - array('Hostname is required for zabbix collector.', 'zabbix_file'), - array('Hostname is required for librato collector.', 'librato'), - array('No username given for librato collector.', 'librato', array('hostname' => 'foobar.com')), - array('No password given for librato collector.', 'librato', array('hostname' => 'foobar.com', 'username' => 'username')), - array('connection is required for Doctrine DBAL collector.', 'doctrine_dbal'), - array('Missing \'logger\' key with logger service.', 'logger'), - array('Missing \'client\' key for InfluxDB collector.', 'influxdb'), - array('Missing \'collector_registry\' key for Prometheus collector.', 'prometheus'), - ); - } - - /** - * @dataProvider getCreateThrowExceptionIfOptionsAreInvalidTests - */ - public function testCreateThrowExceptionIfOptionsAreInvalid($expectedMessage, $type, $options = array()) - { - try { - Factory::create($type, $options); - - $this->fail('An expected exception (MetricsException) has not been raised.'); - } catch (\Exception $e) { - $this->assertInstanceOf('Beberlei\Metrics\MetricsException', $e); - $this->assertSame($expectedMessage, $e->getMessage()); - } - } -} diff --git a/src/Metrics/Collector/CollectorInterface.php b/src/Metrics/Collector/CollectorInterface.php new file mode 100644 index 0000000..0e3b1be --- /dev/null +++ b/src/Metrics/Collector/CollectorInterface.php @@ -0,0 +1,40 @@ +data[] = [$variable, $value, date('Y-m-d H:i:s')]; + } + + public function increment(string $variable, array $tags = []): void + { + $this->data[] = [$variable, 1, date('Y-m-d H:i:s')]; + } + + public function decrement(string $variable, array $tags = []): void + { + $this->data[] = [$variable, -1, date('Y-m-d H:i:s')]; + } + + public function timing(string $variable, int $time, array $tags = []): void + { + $this->data[] = [$variable, $time, date('Y-m-d H:i:s')]; + } + + public function flush(): void + { + if (!$this->data) { + return; + } + + try { + $this->conn->beginTransaction(); + + $stmt = $this->conn->prepare('INSERT INTO metrics (metric, measurement, created) VALUES (?, ?, ?)'); + + foreach ($this->data as $measurement) { + $stmt->bindValue(1, $measurement[0]); + $stmt->bindValue(2, $measurement[1], ParameterType::INTEGER); + $stmt->bindValue(3, $measurement[2], ParameterType::STRING); + $stmt->executeStatement(); + } + + $this->conn->commit(); + } catch (\Exception) { + $this->conn->rollback(); + } + + $this->data = []; + } +} diff --git a/src/Metrics/Collector/DogStatsD.php b/src/Metrics/Collector/DogStatsD.php new file mode 100644 index 0000000..72ad0e4 --- /dev/null +++ b/src/Metrics/Collector/DogStatsD.php @@ -0,0 +1,95 @@ +data[] = sprintf('%s:%s|c%s', $variable, $value, $this->buildTagString($tags)); + } + + public function increment(string $variable, array $tags = []): void + { + $this->data[] = $variable . ':1|c' . $this->buildTagString($tags); + } + + public function decrement(string $variable, array $tags = []): void + { + $this->data[] = $variable . ':-1|c' . $this->buildTagString($tags); + } + + public function timing(string $variable, int $time, array $tags = []): void + { + $this->data[] = sprintf('%s:%s|ms%s', $variable, $time, $this->buildTagString($tags)); + } + + public function gauge(string $variable, string|int $value, array $tags = []): void + { + $this->data[] = sprintf('%s:%s|g%s', $variable, $value, $this->buildTagString($tags)); + } + + public function flush(): void + { + if (!$this->data) { + return; + } + Box::box($this->doFlush(...)); + } + + private function doFlush(): void + { + $fp = fsockopen('udp://' . $this->host, $this->port, $errno, $errstr, 1.0); + + if (!$fp) { + return; + } + + foreach ($this->data as $line) { + fwrite($fp, $this->prefix . $line); + } + + fclose($fp); + + $this->data = []; + } + + /** + * Given a key/value map of metric tags, builds them into a + * DogStatsD tag string and returns the string. + */ + private function buildTagString(array $tags): string + { + $results = []; + + foreach ($tags as $key => $value) { + $results[] = sprintf('%s:%s', $key, $value); + } + + $tagString = implode(',', $results); + + if (\strlen($tagString)) { + $tagString = sprintf('|#%s', $tagString); + } + + return $tagString; + } +} diff --git a/src/Metrics/Collector/GaugeableCollectorInterface.php b/src/Metrics/Collector/GaugeableCollectorInterface.php new file mode 100644 index 0000000..fac7a00 --- /dev/null +++ b/src/Metrics/Collector/GaugeableCollectorInterface.php @@ -0,0 +1,18 @@ +push($variable, $value); + } + + public function increment(string $variable, array $tags = []): void + { + $this->push($variable, 1); + } + + public function decrement(string $variable, array $tags = []): void + { + $this->push($variable, -1); + } + + public function timing(string $variable, int $time, array $tags = []): void + { + $this->push($variable, $time); + } + + public function flush(): void + { + if (!$this->data) { + return; + } + + Box::box($this->doFlush(...)); + } + + public function push(string $variable, int|float $value, ?int $time = null): void + { + $this->data[] = sprintf( + \is_float($value) ? "%s %.18f %d\n" : "%s %d %d\n", + $variable, + $value, + $time ?: time() + ); + } + + private function doFlush(): void + { + $fp = fsockopen($this->protocol . '://' . $this->host, $this->port); + + if (!$fp) { + return; + } + + foreach ($this->data as $line) { + fwrite($fp, (string) $line); + } + + fclose($fp); + + $this->data = []; + } +} diff --git a/src/Metrics/Collector/InMemory.php b/src/Metrics/Collector/InMemory.php new file mode 100644 index 0000000..a054792 --- /dev/null +++ b/src/Metrics/Collector/InMemory.php @@ -0,0 +1,86 @@ +incrementData[$variable] ??= 0; + $this->incrementData[$variable] += $value; + } + + public function increment(string $variable, array $tags = []): void + { + $this->measure($variable, 1); + } + + public function decrement(string $variable, array $tags = []): void + { + $this->measure($variable, -1); + } + + public function timing(string $variable, int $time, array $tags = []): void + { + $this->timingData[$variable] ??= 0; + $this->timingData[$variable] = $time; + } + + public function gauge(string $variable, string|int $value, array $tags = []): void + { + if (\is_int($value)) { + $this->gaugeData[$variable] = $value; + + return; + } + + $sign = substr($value, 0, 1); + if (!\in_array($sign, ['-', '+'], true)) { + throw new \InvalidArgumentException('Gauge value must be an integer or a string starting with + or -.'); + } + $this->gaugeData[$variable] ??= 0; + $this->gaugeData[$variable] += (int) $value; + } + + public function flush(): void + { + $this->timingData = []; + $this->gaugeData = []; + $this->incrementData = []; + } + + public function getMeasure(string $variable): int + { + return $this->incrementData[$variable] ?? 0; + } + + public function getGauge(string $variable): int + { + return $this->gaugeData[$variable] ?? 0; + } + + public function getTiming(string $variable): int + { + return $this->timingData[$variable] ?? 0; + } +} diff --git a/src/Metrics/Collector/InfluxDbV1.php b/src/Metrics/Collector/InfluxDbV1.php new file mode 100644 index 0000000..2330927 --- /dev/null +++ b/src/Metrics/Collector/InfluxDbV1.php @@ -0,0 +1,64 @@ +data[] = [$variable, $value, $tags]; + } + + public function increment(string $variable, array $tags = []): void + { + $this->data[] = [$variable, 1, $tags]; + } + + public function decrement(string $variable, array $tags = []): void + { + $this->data[] = [$variable, -1, $tags]; + } + + public function timing(string $variable, int $time, array $tags = []): void + { + $this->data[] = [$variable, $time, $tags]; + } + + public function flush(): void + { + $points = []; + foreach ($this->data as $data) { + $points[] = new Point( + $data[0], + $data[1], + $this->tags + $data[2], + ); + } + + try { + $this->database->writePoints($points, Database::PRECISION_SECONDS); + } catch (Exception) { + } + + $this->data = []; + } +} diff --git a/src/Metrics/Collector/Logger.php b/src/Metrics/Collector/Logger.php new file mode 100644 index 0000000..2f923b5 --- /dev/null +++ b/src/Metrics/Collector/Logger.php @@ -0,0 +1,50 @@ +logger->debug(sprintf('measure:%s:%s', $variable, $value)); + } + + public function increment(string $variable, array $tags = []): void + { + $this->logger->debug('increment:' . $variable); + } + + public function decrement(string $variable, array $tags = []): void + { + $this->logger->debug('decrement:' . $variable); + } + + public function timing(string $variable, int $time, array $tags = []): void + { + $this->logger->debug(sprintf('timing:%s:%s', $variable, $time)); + } + + public function gauge(string $variable, string|int $value, array $tags = []): void + { + $this->logger->debug(sprintf('gauge:%s:%s', $variable, $value)); + } + + public function flush(): void + { + $this->logger->debug('flush'); + } +} diff --git a/src/Metrics/Collector/NullCollector.php b/src/Metrics/Collector/NullCollector.php new file mode 100644 index 0000000..51560f0 --- /dev/null +++ b/src/Metrics/Collector/NullCollector.php @@ -0,0 +1,37 @@ +gauge($variable, $value, $tags); + } + + public function increment(string $variable, array $tags = []): void + { + $this->counters[] = ['variable' => $variable, 'value' => 1, 'tags' => $tags]; + } + + public function decrement(string $variable, array $tags = []): void + { + $this->counters[] = ['variable' => $variable, 'value' => -1, 'tags' => $tags]; + } + + public function timing(string $variable, int $time, array $tags = []): void + { + $this->gauge($variable, $time, $tags); + } + + public function gauge(string $variable, string|int $value, array $tags = []): void + { + $this->gauges[] = ['variable' => $variable, 'value' => $value, 'tags' => $tags]; + } + + public function flush(): void + { + try { + foreach ($this->counters as $counter) { + $variable = $this->normalizeVariable($counter['variable']); + $labels = $this->tags + $counter['tags']; + $this + ->registry + ->getOrRegisterCounter($this->namespace, $variable, '', $labels) + ->incBy($counter['value'], $labels) + ; + } + + foreach ($this->gauges as $gauge) { + $variable = $this->normalizeVariable($gauge['variable']); + $labels = $this->tags + $gauge['tags']; + $this + ->registry + ->getOrRegisterGauge($this->namespace, $variable, '', $labels) + ->set($gauge['value'], $labels) + ; + } + } catch (\Exception) { + } + + $this->counters = $this->gauges = []; + } + + private function normalizeVariable(string $variable): string + { + return str_replace(['.', ':'], ['_', '_'], $variable); + } +} diff --git a/src/Metrics/Collector/StatsD.php b/src/Metrics/Collector/StatsD.php new file mode 100644 index 0000000..4c80244 --- /dev/null +++ b/src/Metrics/Collector/StatsD.php @@ -0,0 +1,78 @@ +data[] = sprintf('%s:%s|ms', $variable, $time); + } + + public function increment(string $variable, array $tags = []): void + { + $this->data[] = $variable . ':1|c'; + } + + public function decrement(string $variable, array $tags = []): void + { + $this->data[] = $variable . ':-1|c'; + } + + public function measure(string $variable, int $value, array $tags = []): void + { + $this->data[] = sprintf('%s:%s|c', $variable, $value); + } + + public function gauge(string $variable, string|int $value, array $tags = []): void + { + $this->data[] = sprintf('%s:%s|g', $variable, $value); + } + + public function flush(): void + { + if (!$this->data) { + return; + } + + Box::box($this->doFlush(...)); + } + + private function doFlush(): void + { + $fp = fsockopen('udp://' . $this->host, $this->port, $errno, $errstr, 1.0); + + if (!$fp) { + return; + } + + foreach ($this->data as $line) { + fwrite($fp, $this->prefix . $line); + } + + fclose($fp); + + $this->data = []; + } +} diff --git a/src/Metrics/Collector/Telegraf.php b/src/Metrics/Collector/Telegraf.php new file mode 100644 index 0000000..dbc9422 --- /dev/null +++ b/src/Metrics/Collector/Telegraf.php @@ -0,0 +1,89 @@ +tags = http_build_query($tags, '', ','); + $this->tags = \strlen($this->tags) > 0 ? ',' . $this->tags : $this->tags; + } + + public function measure(string $variable, int $value, array $tags = []): void + { + $this->data[] = sprintf('%s%s:%s|c', $variable, $this->tags, $value); + } + + public function increment(string $variable, array $tags = []): void + { + $this->data[] = $variable . $this->tags . ':1|c'; + } + + public function decrement(string $variable, array $tags = []): void + { + $this->data[] = $variable . $this->tags . ':-1|c'; + } + + public function timing(string $variable, int $time, array $tags = []): void + { + $this->data[] = sprintf('%s%s:%s|ms', $variable, $this->tags, $time); + } + + public function gauge(string $variable, string|int $value, array $tags = []): void + { + $this->data[] = sprintf('%s%s:%s|g', $variable, $this->tags, $value); + } + + public function set(string $variable, string $value): void + { + $this->data[] = sprintf('%s%s:%s|s', $variable, $this->tags, $value); + } + + public function flush(): void + { + if (!$this->data) { + return; + } + + Box::box($this->doFlush(...)); + } + + private function doFlush(): void + { + $fp = fsockopen('udp://' . $this->host, $this->port, $errno, $errstr, 1.0); + + if (!$fp) { + return; + } + + foreach ($this->data as $line) { + fwrite($fp, $this->prefix . $line); + } + + fclose($fp); + + $this->data = []; + } +} diff --git a/src/Metrics/Factory.php b/src/Metrics/Factory.php new file mode 100644 index 0000000..32680a8 --- /dev/null +++ b/src/Metrics/Factory.php @@ -0,0 +1,146 @@ + null); + + return $callable(); + } catch (\Throwable) { + // ignore + } finally { + restore_error_handler(); + } + + return null; + } +} diff --git a/src/MetricsBundle/BeberleiMetricsBundle.php b/src/MetricsBundle/BeberleiMetricsBundle.php new file mode 100644 index 0000000..7a8844a --- /dev/null +++ b/src/MetricsBundle/BeberleiMetricsBundle.php @@ -0,0 +1,16 @@ +getConfiguration($configs, $container) ?? throw new \LogicException('Expected configuration to be set'); + $config = $this->processConfiguration($configuration, $configs); + + $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); + $loader->load('metrics.xml'); + + if (!$config['collectors']) { + $config['collectors']['null'] = [ + 'type' => 'null', + ]; + } + foreach ($config['collectors'] as $name => $colConfig) { + $definition = $this->createCollector($container, $name, $colConfig['type'], $colConfig); + $container->setDefinition('beberlei_metrics.collector.' . $name, $definition); + $container->registerAliasForArgument('beberlei_metrics.collector.' . $name, CollectorInterface::class, $name); + } + + if ($config['default']) { + if (!$container->hasDefinition('beberlei_metrics.collector.' . $config['default'])) { + throw new InvalidArgumentException(sprintf('The default collector "%s" does not exist.', $config['default'])); + } + $name = $config['default']; + } elseif (1 === \count($config['collectors'])) { + $name = key($config['collectors']); + } else { + throw new InvalidArgumentException('No default collector is configured and there is more than one collector. Please define a default collector'); + } + + $container->setAlias(CollectorInterface::class, 'beberlei_metrics.collector.' . $name); + } + + private function createCollector(ContainerBuilder $container, string $name, string $type, array $config): ChildDefinition + { + $definition = new ChildDefinition('beberlei_metrics.collector_proto.' . $config['type']); + + // Theses listeners should be as late as possible + $definition->addTag('kernel.event_listener', ['method' => 'flush', 'priority' => -1024, 'event' => 'kernel.terminate']); + $definition->addTag('kernel.event_listener', ['method' => 'flush', 'priority' => -1024, 'event' => 'console.terminate']); + $definition->addTag(CollectorInterface::class); + $definition->addTag('kernel.reset', ['method' => 'flush']); + + $tags = $config['tags'] ?? []; + + switch ($type) { + case 'influxdb_v1': + if (!class_exists(\InfluxDB\Client::class)) { + throw new \LogicException('The "influxdb/influxdb-php" package is required to use the "influxdb" collector.'); + } + + if ($config['service']) { + $database = new Reference($config['service']); + } else { + $database = new ChildDefinition('beberlei_metrics.collector_proto.influxdb_v1.database'); + $database->replaceArgument('$dsn', sprintf('influxdb://%s:%s@%s:%s/%s', + $config['username'], + $config['password'], + $config['host'], + $config['port'] ?? 8086, + $config['database'], + )); + } + + $definition->replaceArgument('$database', $database); + $definition->replaceArgument('$tags', $tags); + + return $definition; + case 'prometheus': + if (!class_exists(CollectorRegistry::class)) { + throw new \LogicException('The "promphp/prometheus_client_php" package is required to use the "prometheus" collector.'); + } + + if ($config['service']) { + $registryId = $config['service']; + } else { + $container->setDefinition( + $registryId = 'beberlei_metrics.collector.' . $name . '.prometheus.registry', + new ChildDefinition('beberlei_metrics.collector_proto.prometheus.registry'), + ); + + if (!$container->hasAlias(CollectorRegistry::class)) { + $container->setAlias(CollectorRegistry::class, $registryId); + } + } + + $definition->replaceArgument('$registry', new Reference($registryId)); + $definition->replaceArgument('$namespace', $config['namespace']); + $definition->replaceArgument('$tags', $tags); + + return $definition; + case 'graphite': + $definition->replaceArgument('$host', $config['host']); + $definition->replaceArgument('$port', $config['port'] ?? 2003); + $definition->replaceArgument('$protocol', $config['protocol'] ?? 'tcp'); + + return $definition; + case 'statsd': + case 'dogstatsd': + $definition->replaceArgument('$host', $config['host']); + $definition->replaceArgument('$port', $config['port'] ?? 8125); + $definition->replaceArgument('$prefix', $config['prefix']); + + return $definition; + case 'telegraf': + $definition->replaceArgument('$host', $config['host']); + $definition->replaceArgument('$port', $config['port'] ?? 8125); + $definition->replaceArgument('$prefix', $config['prefix']); + $definition->replaceArgument('$tags', $tags); + + return $definition; + case 'doctrine_dbal': + $ref = $config['connection'] ? sprintf('doctrine.dbal.%s_connection', $config['connection']) : 'database_connection'; + $definition->replaceArgument('$conn', new Reference($ref)); + + return $definition; + case 'logger': + case 'memory': + case 'null': + return $definition; + default: + throw new \InvalidArgumentException(sprintf('The type "%s" is not supported.', $type)); + } + } +} diff --git a/src/MetricsBundle/DependencyInjection/Configuration.php b/src/MetricsBundle/DependencyInjection/Configuration.php new file mode 100644 index 0000000..21bab64 --- /dev/null +++ b/src/MetricsBundle/DependencyInjection/Configuration.php @@ -0,0 +1,68 @@ +getRootNode() + ->children() + ->scalarNode('default') + ->defaultNull() + ->end() + ->arrayNode('collectors') + ->useAttributeAsKey('name') + ->prototype('array') + ->children() + ->enumNode('type') + ->values(BeberleiMetricsExtension::TYPES) + ->isRequired() + ->cannotBeEmpty() + ->end() + ->scalarNode('host')->defaultValue('localhost')->end() + ->scalarNode('protocol')->defaultNull()->end() + ->integerNode('port')->defaultNull()->end() + ->scalarNode('username')->defaultValue('')->end() + ->scalarNode('password')->defaultValue('')->end() + ->scalarNode('prefix')->defaultValue('')->end() + ->scalarNode('service')->defaultNull()->end() + ->arrayNode('tags') + ->defaultValue([]) + ->prototype('scalar')->end() + ->end() + // Doctrine DBAL stuff + ->scalarNode('connection')->defaultNull()->end() + // Prom stuff + ->scalarNode('prometheus_collector_registry')->defaultNull()->info('It must to contain service id for Prometheus\\CollectorRegistry class instance.')->end() + ->scalarNode('namespace')->defaultValue('')->end() + // InfluxDB stuff + ->scalarNode('database')->defaultValue('')->end() + ->end() + ->validate() + ->ifTrue(static fn ($v): bool => 'influxdb' === $v['type'] && empty($v['database'])) + ->thenInvalid('The "database" has to be specified to use a InfluxDB') + ->end() + ->end() + ->end() + ->end() + ->end() + ; + } +} diff --git a/src/MetricsBundle/Resources/config/metrics.xml b/src/MetricsBundle/Resources/config/metrics.xml new file mode 100644 index 0000000..145d5b6 --- /dev/null +++ b/src/MetricsBundle/Resources/config/metrics.xml @@ -0,0 +1,60 @@ + + + + + + + + should be defined the extension + + + should be defined the extension + should be defined the extension + should be defined the extension + + + + should be defined the extension + + + should be defined the extension + + + + + + + + + + + + + + should be defined the extension + should be defined the extension + should be defined the extension + + + should be defined the extension + should be defined the extension + should be defined the extension + + + should be defined the extension + should be defined the extension + should be defined the extension + + + should be defined the extension + should be defined the extension + should be defined the extension + should be defined the extension + + + + + + diff --git a/src/Beberlei/Metrics/Tests/Collector/InMemoryTest.php b/tests/Metrics/Collector/InMemoryTest.php similarity index 74% rename from src/Beberlei/Metrics/Tests/Collector/InMemoryTest.php rename to tests/Metrics/Collector/InMemoryTest.php index 66e1c3f..d3f6da2 100644 --- a/src/Beberlei/Metrics/Tests/Collector/InMemoryTest.php +++ b/tests/Metrics/Collector/InMemoryTest.php @@ -1,14 +1,10 @@ collector = new InMemory(); } - public function testIncrement() + public function testIncrement(): void { $this->collector->increment(self::VARIABLE_A); $this->collector->increment(self::VARIABLE_A); @@ -40,7 +36,7 @@ public function testIncrement() $this->assertEquals(1, $this->collector->getMeasure(self::VARIABLE_B)); } - public function testDecrement() + public function testDecrement(): void { $this->collector->increment(self::VARIABLE_A); $this->collector->increment(self::VARIABLE_A); @@ -53,7 +49,7 @@ public function testDecrement() $this->assertEquals(-2, $this->collector->getMeasure(self::VARIABLE_B)); } - public function testTiming() + public function testTiming(): void { $this->collector->timing(self::VARIABLE_A, 123); @@ -64,7 +60,7 @@ public function testTiming() $this->assertEquals(112, $this->collector->getTiming(self::VARIABLE_B)); } - public function testMeasure() + public function testMeasure(): void { $this->collector->measure(self::VARIABLE_A, 2); $this->collector->measure(self::VARIABLE_A, -5); @@ -76,7 +72,7 @@ public function testMeasure() $this->assertEquals(123, $this->collector->getMeasure(self::VARIABLE_B)); } - public function testSettingGauge() + public function testSettingGauge(): void { $this->collector->gauge(self::VARIABLE_A, 2); $this->collector->gauge(self::VARIABLE_A, 5); @@ -88,28 +84,28 @@ public function testSettingGauge() $this->assertEquals(0, $this->collector->getGauge(self::VARIABLE_B)); } - public function testIncrementingGauge() + public function testIncrementingGauge(): void { - $this->collector->gauge(self::VARIABLE_A, '10'); + $this->collector->gauge(self::VARIABLE_A, 10); $this->collector->gauge(self::VARIABLE_A, '+2'); $this->collector->gauge(self::VARIABLE_A, '-3'); $this->assertEquals(9, $this->collector->getGauge(self::VARIABLE_A)); } - public function testSettingGaugeToNegativeValue() + public function testSettingGaugeToNegativeValue(): void { - $this->collector->gauge(self::VARIABLE_A, 1); //sets to 1 - $this->collector->gauge(self::VARIABLE_A, 2); //sets to 2 - $this->collector->gauge(self::VARIABLE_A, -5); //decreases by 5 + $this->collector->gauge(self::VARIABLE_A, 1); // sets to 1 + $this->collector->gauge(self::VARIABLE_A, 2); // sets to 2 + $this->collector->gauge(self::VARIABLE_A, '-5'); // decreases by 5 $this->assertEquals(-3, $this->collector->getGauge(self::VARIABLE_A)); $this->collector->gauge(self::VARIABLE_A, 0); - $this->collector->gauge(self::VARIABLE_A, -5); + $this->collector->gauge(self::VARIABLE_A, '-5'); $this->assertEquals(-5, $this->collector->getGauge(self::VARIABLE_A)); } - public function testTypesOfMetricsAreSeparate() + public function testTypesOfMetricsAreSeparate(): void { $this->collector->increment(self::VARIABLE_A); $this->collector->gauge(self::VARIABLE_A, 2); @@ -120,7 +116,7 @@ public function testTypesOfMetricsAreSeparate() $this->assertEquals(3, $this->collector->getTiming(self::VARIABLE_A)); } - public function testFlushClearsData() + public function testFlushClearsData(): void { $this->collector->increment(self::VARIABLE_A); $this->collector->gauge(self::VARIABLE_A, 2); diff --git a/tests/Metrics/Collector/InfluxDbV1Test.php b/tests/Metrics/Collector/InfluxDbV1Test.php new file mode 100644 index 0000000..2690f31 --- /dev/null +++ b/tests/Metrics/Collector/InfluxDbV1Test.php @@ -0,0 +1,168 @@ +database = $this->getMockBuilder(Database::class) + ->disableOriginalConstructor() + ->getMock() + ; + } + + public function testCollectIncrement(): void + { + $collector = $this->createCollector([]); + + $this->database->expects($this->once()) + ->method('writePoints') + ->with($this->isType('array')) + ; + + $collector->increment('series-name'); + $collector->flush(); + } + + public function testCollectDecrement(): void + { + $collector = $this->createCollector([]); + + $this->database->expects($this->once()) + ->method('writePoints') + ->with($this->callback(function ($arg0) { + $this->assertIsArray($arg0); + $this->assertCount(1, $arg0); + $this->assertArrayHasKey(0, $arg0); + $point = $arg0[0]; + $this->assertInstanceOf(Point::class, $point); + $this->assertSame('series-name', $point->getMeasurement()); + $this->assertSame(['value' => '-1i'], $point->getFields()); + $this->assertSame([], $point->getTags()); + + return true; + })) + ; + + $collector->decrement('series-name'); + $collector->flush(); + } + + public function testCollectTiming(): void + { + $collector = $this->createCollector([]); + + $this->database->expects($this->once()) + ->method('writePoints') + ->with($this->callback(function ($arg0) { + $this->assertIsArray($arg0); + $this->assertCount(1, $arg0); + $this->assertArrayHasKey(0, $arg0); + $point = $arg0[0]; + $this->assertInstanceOf(Point::class, $point); + $this->assertSame('series-name', $point->getMeasurement()); + $this->assertSame(['value' => '47i'], $point->getFields()); + $this->assertSame([], $point->getTags()); + + return true; + })) + ; + + $collector->timing('series-name', 47); + $collector->flush(); + } + + public function testCollectMeasure(): void + { + $collector = $this->createCollector([]); + + $this->database->expects($this->once()) + ->method('writePoints') + ->with($this->callback(function ($arg0) { + $this->assertIsArray($arg0); + $this->assertCount(1, $arg0); + $this->assertArrayHasKey(0, $arg0); + $point = $arg0[0]; + $this->assertInstanceOf(Point::class, $point); + $this->assertSame('series-name', $point->getMeasurement()); + $this->assertSame(['value' => '47i'], $point->getFields()); + $this->assertSame([], $point->getTags()); + + return true; + })) + ; + + $collector->measure('series-name', 47); + $collector->flush(); + } + + public function testCollectMeasureWithTags(): void + { + $expectedTags = ['dc' => 'west', 'node' => 'nemesis101']; + $collector = $this->createCollector($expectedTags); + + $this->database->expects($this->once()) + ->method('writePoints') + ->with($this->callback(function ($arg0) use ($expectedTags) { + $this->assertIsArray($arg0); + $this->assertCount(1, $arg0); + $this->assertArrayHasKey(0, $arg0); + $point = $arg0[0]; + $this->assertInstanceOf(Point::class, $point); + $this->assertSame('series-name', $point->getMeasurement()); + $this->assertSame(['value' => '47i'], $point->getFields()); + $this->assertSame($expectedTags, $point->getTags()); + + return true; + })) + ; + + $collector->measure('series-name', 47); + $collector->flush(); + } + + public function testCollectMeasureWithTagsMerged(): void + { + $collector = $this->createCollector(['dc' => 'west', 'node' => 'nemesis101']); + + $this->database->expects($this->once()) + ->method('writePoints') + ->with($this->callback(function ($arg0) { + $this->assertIsArray($arg0); + $this->assertCount(1, $arg0); + $this->assertArrayHasKey(0, $arg0); + $point = $arg0[0]; + $this->assertInstanceOf(Point::class, $point); + $this->assertSame('series-name', $point->getMeasurement()); + $this->assertSame(['value' => '47i'], $point->getFields()); + $this->assertSame(['dc' => 'west', 'node' => 'nemesis101', 'foo' => 'bar'], $point->getTags()); + + return true; + })) + ; + + $collector->measure('series-name', 47, ['foo' => 'bar']); + $collector->flush(); + } + + private function createCollector(array $tags): InfluxDbV1 + { + return new InfluxDbV1($this->database, $tags); + } +} diff --git a/tests/Metrics/Collector/PrometheusTest.php b/tests/Metrics/Collector/PrometheusTest.php new file mode 100644 index 0000000..2bfe99a --- /dev/null +++ b/tests/Metrics/Collector/PrometheusTest.php @@ -0,0 +1,174 @@ +registry = new CollectorRegistry(new InMemory(), false); + } + + public function testMeasure(): void + { + $expectedVariableValue = 123; + $labels = []; + + $collector = $this->createCollector($labels); + $collector->measure(self::TEST_VARIABLE_NAME, $expectedVariableValue); + $collector->flush(); + + $data = $this->registry->getMetricFamilySamples()[0]->getSamples()[0]; + $this->assertSame(self::TEST_NAMESPACE . '_' . self::TEST_VARIABLE_NAME, $data->getName()); + $this->assertSame((string) $expectedVariableValue, $data->getValue()); + $this->assertSame($labels, $data->getLabelValues()); + } + + public function testMeasureWithTags(): void + { + $expectedVariableValue = 123; + $labels = ['tag1' => 'value1', 'tag2' => 'value2']; + + $collector = $this->createCollector($labels); + $collector->measure(self::TEST_VARIABLE_NAME, $expectedVariableValue); + $collector->flush(); + + $data = $this->registry->getMetricFamilySamples()[0]->getSamples()[0]; + $this->assertSame(self::TEST_NAMESPACE . '_' . self::TEST_VARIABLE_NAME, $data->getName()); + $this->assertSame((string) $expectedVariableValue, $data->getValue()); + $this->assertSame($labels, $data->getLabelValues()); + } + + public function testIncrement(): void + { + $labels = []; + + $collector = $this->createCollector($labels); + $collector->increment(self::TEST_VARIABLE_NAME); + $collector->flush(); + + $data = $this->registry->getMetricFamilySamples()[0]->getSamples()[0]; + $this->assertSame(self::TEST_NAMESPACE . '_' . self::TEST_VARIABLE_NAME, $data->getName()); + $this->assertSame('1', $data->getValue()); + $this->assertSame($labels, $data->getLabelValues()); + } + + public function testIncrementWithTags(): void + { + $labels = ['value1', 'value2']; + + $collector = $this->createCollector($labels); + $collector->increment(self::TEST_VARIABLE_NAME); + $collector->flush(); + + $data = $this->registry->getMetricFamilySamples()[0]->getSamples()[0]; + $this->assertSame(self::TEST_NAMESPACE . '_' . self::TEST_VARIABLE_NAME, $data->getName()); + $this->assertSame('1', $data->getValue()); + $this->assertSame($labels, $data->getLabelValues()); + } + + public function testDecrement(): void + { + $labels = []; + + $collector = $this->createCollector($labels); + $collector->decrement(self::TEST_VARIABLE_NAME); + $collector->flush(); + + $data = $this->registry->getMetricFamilySamples()[0]->getSamples()[0]; + $this->assertSame(self::TEST_NAMESPACE . '_' . self::TEST_VARIABLE_NAME, $data->getName()); + $this->assertSame('-1', $data->getValue()); + $this->assertSame($labels, $data->getLabelValues()); + } + + public function testDecrementWithTags(): void + { + $labels = ['value1', 'value2']; + + $collector = $this->createCollector($labels); + $collector->decrement(self::TEST_VARIABLE_NAME); + $collector->flush(); + + $data = $this->registry->getMetricFamilySamples()[0]->getSamples()[0]; + $this->assertSame(self::TEST_NAMESPACE . '_' . self::TEST_VARIABLE_NAME, $data->getName()); + $this->assertSame('-1', $data->getValue()); + $this->assertSame($labels, $data->getLabelValues()); + } + + public function testTiming(): void + { + $expectedVariableValue = 123; + $labels = []; + + $collector = $this->createCollector($labels); + $collector->timing(self::TEST_VARIABLE_NAME, $expectedVariableValue); + $collector->flush(); + + $data = $this->registry->getMetricFamilySamples()[0]->getSamples()[0]; + $this->assertSame(self::TEST_NAMESPACE . '_' . self::TEST_VARIABLE_NAME, $data->getName()); + $this->assertSame((string) $expectedVariableValue, $data->getValue()); + $this->assertSame($labels, $data->getLabelValues()); + } + + public function testTimingWithTags(): void + { + $expectedVariableValue = 123; + $labels = ['tag1' => 'value1', 'tag2' => 'value2']; + + $collector = $this->createCollector($labels); + $collector->timing(self::TEST_VARIABLE_NAME, $expectedVariableValue); + $collector->flush(); + + $data = $this->registry->getMetricFamilySamples()[0]->getSamples()[0]; + $this->assertSame(self::TEST_NAMESPACE . '_' . self::TEST_VARIABLE_NAME, $data->getName()); + $this->assertSame((string) $expectedVariableValue, $data->getValue()); + $this->assertSame($labels, $data->getLabelValues()); + } + + /** + * Method flush must to reset value of field `data`. + */ + public function testFlushWhenCallsTwiceWithDifferentData(): void + { + $firstExpectedVariableValue = 123; + + $collector = $this->createCollector([]); + $collector->timing(self::TEST_VARIABLE_NAME, $firstExpectedVariableValue); + $collector->flush(); + + $data = $this->registry->getMetricFamilySamples()[0]->getSamples()[0]; + $this->assertSame(self::TEST_NAMESPACE . '_' . self::TEST_VARIABLE_NAME, $data->getName()); + $this->assertSame((string) $firstExpectedVariableValue, $data->getValue()); + + $secondExpectedVariableValue = 321; + + $collector->timing(self::TEST_VARIABLE_NAME, $secondExpectedVariableValue); + $collector->flush(); + + $data = $this->registry->getMetricFamilySamples()[0]->getSamples()[0]; + $this->assertSame(self::TEST_NAMESPACE . '_' . self::TEST_VARIABLE_NAME, $data->getName()); + $this->assertSame((string) $secondExpectedVariableValue, $data->getValue()); + } + + private function createCollector(array $tags): Prometheus + { + return new Prometheus($this->registry, self::TEST_NAMESPACE, $tags); + } +} diff --git a/tests/Metrics/FactoryTest.php b/tests/Metrics/FactoryTest.php new file mode 100644 index 0000000..6c08fc4 --- /dev/null +++ b/tests/Metrics/FactoryTest.php @@ -0,0 +1,89 @@ + 'localhost', 'port' => 1234, 'prefix' => 'prefix']]; + yield [StatsD::class, 'statsd', ['host' => 'localhost', 'port' => 1234]]; + yield [StatsD::class, 'statsd', ['host' => 'localhost']]; + yield [DogStatsD::class, 'dogstatsd']; + yield [DogStatsD::class, 'dogstatsd', ['host' => 'localhost', 'port' => 1234, 'prefix' => 'prefix']]; + yield [DogStatsD::class, 'dogstatsd', ['host' => 'localhost', 'port' => 1234]]; + yield [DogStatsD::class, 'dogstatsd', ['host' => 'localhost']]; + yield [Graphite::class, 'graphite']; + yield [Graphite::class, 'graphite', ['host' => 'localhost', 'port' => 1234]]; + yield [DoctrineDBAL::class, 'doctrine_dbal', ['connection' => $this->getMockBuilder(Connection::class)->disableOriginalConstructor()->getMock()]]; + yield [Logger::class, 'logger', ['logger' => new NullLogger()]]; + yield [NullCollector::class, 'null']; + yield [InfluxDbV1::class, 'influxdb_v1', ['database' => $this->getMockBuilder(Database::class)->disableOriginalConstructor()->getMock()]]; + yield [Prometheus::class, 'prometheus', ['collector_registry' => $this->getMockBuilder(CollectorRegistry::class)->disableOriginalConstructor()->getMock()]]; + yield [Prometheus::class, 'prometheus', ['collector_registry' => $this->getMockBuilder(CollectorRegistry::class)->disableOriginalConstructor()->getMock(), 'namespace' => 'some_namespace']]; + } + + /** + * @dataProvider getCreateValidMetricTests + */ + public function testCreateValidMetric(string $expectedClass, string $type, array $options = []): void + { + $this->assertInstanceOf($expectedClass, Factory::create($type, $options)); + } + + public function getCreateThrowExceptionIfOptionsAreInvalidTests(): iterable + { + yield ['You should specified a host if you specified a port.', 'statsd', ['port' => '1234']]; + yield ['You should specified a host and a port if you specified a prefix.', 'statsd', ['prefix' => 'prefix']]; + yield ['You should specified a host and a port if you specified a prefix.', 'statsd', ['port' => '1234', 'prefix' => 'prefix']]; + yield ['You should specified a host and a port if you specified a prefix.', 'statsd', ['hostname' => 'foobar.com', 'prefix' => 'prefix']]; + yield ['You should specified a host if you specified a port.', 'dogstatsd', ['port' => '1234']]; + yield ['You should specified a host and a port if you specified a prefix.', 'dogstatsd', ['prefix' => 'prefix']]; + yield ['You should specified a host and a port if you specified a prefix.', 'dogstatsd', ['port' => '1234', 'prefix' => 'prefix']]; + yield ['You should specified a host and a port if you specified a prefix.', 'dogstatsd', ['hostname' => 'foobar.com', 'prefix' => 'prefix']]; + yield ['You should specified a host if you specified a port.', 'graphite', ['port' => '1234']]; + yield ['connection is required for Doctrine DBAL collector.', 'doctrine_dbal']; + yield ['Missing "logger" key with logger service.', 'logger']; + yield ['Missing "database" key for InfluxDB collector.', 'influxdb_v1']; + yield ['Missing "collector_registry" key for Prometheus collector.', 'prometheus']; + } + + /** + * @dataProvider getCreateThrowExceptionIfOptionsAreInvalidTests + */ + public function testCreateThrowExceptionIfOptionsAreInvalid(string $expectedMessage, string $type, array $options = []): void + { + try { + Factory::create($type, $options); + + $this->fail('An expected exception (MetricsException) has not been raised.'); + } catch (\Exception $exception) { + $this->assertInstanceOf(MetricsException::class, $exception); + $this->assertSame($expectedMessage, $exception->getMessage()); + } + } +} diff --git a/tests/MetricsBundle/DependencyInjection/BeberleiMetricsExtensionTest.php b/tests/MetricsBundle/DependencyInjection/BeberleiMetricsExtensionTest.php new file mode 100644 index 0000000..6497d98 --- /dev/null +++ b/tests/MetricsBundle/DependencyInjection/BeberleiMetricsExtensionTest.php @@ -0,0 +1,232 @@ +createContainer(['default' => 'simple', 'collectors' => ['simple' => ['type' => 'graphite'], 'full' => ['type' => 'graphite', 'host' => 'graphite.localhost', 'port' => 1234, 'protocol' => 'udp']]], ['beberlei_metrics.collector.simple', 'beberlei_metrics.collector.full']); + + $collector = $container->get('beberlei_metrics.collector.simple'); + $this->assertInstanceOf(Graphite::class, $collector); + $this->assertSame('tcp', $this->getProperty($collector, 'protocol')); + $this->assertSame('localhost', $this->getProperty($collector, 'host')); + $this->assertSame(2003, $this->getProperty($collector, 'port')); + + $collector = $container->get('beberlei_metrics.collector.full'); + $this->assertInstanceOf(Graphite::class, $collector); + $this->assertSame('udp', $this->getProperty($collector, 'protocol')); + $this->assertSame('graphite.localhost', $this->getProperty($collector, 'host')); + $this->assertSame(1234, $this->getProperty($collector, 'port')); + } + + public function testWithLogger(): void + { + $container = $this->createContainer(['collectors' => ['logger' => ['type' => 'logger']]], ['beberlei_metrics.collector.logger']); + + $this->assertInstanceOf(Logger::class, $container->get('beberlei_metrics.collector.logger')); + } + + public function testWithNullCollector(): void + { + $container = $this->createContainer(['collectors' => ['null' => ['type' => 'null']]], ['beberlei_metrics.collector.null']); + + $this->assertInstanceOf(NullCollector::class, $container->get('beberlei_metrics.collector.null')); + } + + public function testWithStatsD(): void + { + $container = $this->createContainer(['default' => 'simple', 'collectors' => ['simple' => ['type' => 'statsd'], 'full' => ['type' => 'statsd', 'host' => 'statsd.localhost', 'port' => 1234, 'prefix' => 'application.com.symfony.']]], ['beberlei_metrics.collector.simple', 'beberlei_metrics.collector.full']); + + $collector = $container->get('beberlei_metrics.collector.simple'); + $this->assertInstanceOf(StatsD::class, $collector); + $this->assertSame('localhost', $this->getProperty($collector, 'host')); + $this->assertSame(8125, $this->getProperty($collector, 'port')); + $this->assertSame('', $this->getProperty($collector, 'prefix')); + + $collector = $container->get('beberlei_metrics.collector.full'); + $this->assertInstanceOf(StatsD::class, $collector); + $this->assertSame('statsd.localhost', $this->getProperty($collector, 'host')); + $this->assertSame(1234, $this->getProperty($collector, 'port')); + $this->assertSame('application.com.symfony.', $this->getProperty($collector, 'prefix')); + } + + public function testWithDogStatsD(): void + { + $container = $this->createContainer(['default' => 'simple', 'collectors' => ['simple' => ['type' => 'dogstatsd'], 'full' => ['type' => 'dogstatsd', 'host' => 'dogstatsd.localhost', 'port' => 1234, 'prefix' => 'application.com.symfony.']]], ['beberlei_metrics.collector.simple', 'beberlei_metrics.collector.full']); + + $collector = $container->get('beberlei_metrics.collector.simple'); + $this->assertInstanceOf(DogStatsD::class, $collector); + $this->assertSame('localhost', $this->getProperty($collector, 'host')); + $this->assertSame(8125, $this->getProperty($collector, 'port')); + $this->assertSame('', $this->getProperty($collector, 'prefix')); + + $collector = $container->get('beberlei_metrics.collector.full'); + $this->assertInstanceOf(DogStatsD::class, $collector); + $this->assertSame('dogstatsd.localhost', $this->getProperty($collector, 'host')); + $this->assertSame(1234, $this->getProperty($collector, 'port')); + $this->assertSame('application.com.symfony.', $this->getProperty($collector, 'prefix')); + } + + public function testWithTelegraf(): void + { + $expectedTags = ['string_tag' => 'first_value', 'int_tag' => 123]; + + $container = $this->createContainer(['default' => 'simple', 'collectors' => ['simple' => ['type' => 'telegraf'], 'full' => ['type' => 'telegraf', 'host' => 'telegraf.localhost', 'port' => 1234, 'prefix' => 'application.com.symfony.', 'tags' => $expectedTags]]], ['beberlei_metrics.collector.simple', 'beberlei_metrics.collector.full']); + + $collector = $container->get('beberlei_metrics.collector.simple'); + $this->assertInstanceOf(Telegraf::class, $collector); + $this->assertSame('localhost', $this->getProperty($collector, 'host')); + $this->assertSame(8125, $this->getProperty($collector, 'port')); + $this->assertSame('', $this->getProperty($collector, 'prefix')); + + $collector = $container->get('beberlei_metrics.collector.full'); + $this->assertInstanceOf(Telegraf::class, $collector); + $this->assertSame('telegraf.localhost', $this->getProperty($collector, 'host')); + $this->assertSame(1234, $this->getProperty($collector, 'port')); + $this->assertSame('application.com.symfony.', $this->getProperty($collector, 'prefix')); + + $this->assertEquals(',string_tag=first_value,int_tag=123', $this->getProperty($collector, 'tags')); + } + + public function testWithInfluxDB(): void + { + $container = $this->createContainer(['collectors' => ['influxdb' => ['type' => 'influxdb_v1', 'database' => 'foobar']]], ['beberlei_metrics.collector.influxdb']); + + $collector = $container->get('beberlei_metrics.collector.influxdb'); + $this->assertInstanceOf(InfluxDbV1::class, $collector); + } + + public function testWithInfluxDBAndWithTags(): void + { + $expectedTags = ['string_tag' => 'first_value', 'int_tag' => 123]; + + $container = $this->createContainer(['collectors' => ['influxdb' => ['type' => 'influxdb_v1', 'database' => 'foobar', 'tags' => $expectedTags]]], ['beberlei_metrics.collector.influxdb']); + + $collector = $container->get('beberlei_metrics.collector.influxdb'); + $this->assertInstanceOf(InfluxDbV1::class, $collector); + $this->assertEquals($expectedTags, $this->getProperty($collector, 'tags')); + } + + public function testWithPrometheus(): void + { + $prometheusCollectorRegistryMock = $this->getMockBuilder(CollectorRegistry::class) + ->disableOriginalConstructor() + ->getMock() + ; + + $container = $this->createContainer(['collectors' => ['prometheus' => ['type' => 'prometheus', 'service' => 'prometheus_collector_registry_mock']]], ['beberlei_metrics.collector.prometheus'], ['prometheus_collector_registry_mock' => $prometheusCollectorRegistryMock]); + + $collector = $container->get('beberlei_metrics.collector.prometheus'); + $this->assertInstanceOf(Prometheus::class, $collector); + $this->assertSame($prometheusCollectorRegistryMock, $this->getProperty($collector, 'registry')); + $this->assertSame('', $this->getProperty($collector, 'namespace')); + } + + public function testWithInMemory(): void + { + $container = $this->createContainer(['collectors' => ['memory' => ['type' => 'memory']]], ['beberlei_metrics.collector.memory']); + $collector = $container->get('beberlei_metrics.collector.memory'); + $this->assertInstanceOf(InMemory::class, $collector); + } + + public function testWithPrometheusAndWithNamespace(): void + { + $expectedNamespace = 'some_namespace'; + + $prometheusCollectorRegistryMock = $this->getMockBuilder(CollectorRegistry::class) + ->disableOriginalConstructor() + ->getMock() + ; + + $container = $this->createContainer(['collectors' => ['prometheus' => ['type' => 'prometheus', 'service' => 'prometheus_collector_registry_mock', 'namespace' => $expectedNamespace]]], ['beberlei_metrics.collector.prometheus'], ['prometheus_collector_registry_mock' => $prometheusCollectorRegistryMock]); + + $collector = $container->get('beberlei_metrics.collector.prometheus'); + $this->assertInstanceOf(Prometheus::class, $collector); + $this->assertSame($prometheusCollectorRegistryMock, $this->getProperty($collector, 'registry')); + $this->assertSame($expectedNamespace, $this->getProperty($collector, 'namespace')); + } + + public function testWithPrometheusAndWithTags(): void + { + $expectedTags = ['string_tag' => 'first_value', 'int_tag' => 123]; + + $prometheusCollectorRegistryMock = $this->getMockBuilder(CollectorRegistry::class) + ->disableOriginalConstructor() + ->getMock() + ; + + $container = $this->createContainer(['collectors' => ['prometheus' => ['type' => 'prometheus', 'service' => 'prometheus_collector_registry_mock', 'tags' => $expectedTags]]], ['beberlei_metrics.collector.prometheus'], ['prometheus_collector_registry_mock' => $prometheusCollectorRegistryMock]); + + $collector = $container->get('beberlei_metrics.collector.prometheus'); + $this->assertInstanceOf(Prometheus::class, $collector); + $this->assertEquals($expectedTags, $this->getProperty($collector, 'tags')); + } + + public function testValidationWhenTypeIsPrometheusAndPrometheusCollectorRegistryIsNotSpecified(): void + { + $container = $this->createContainer(['collectors' => ['prometheus' => ['type' => 'prometheus']]], ['beberlei_metrics.collector.prometheus']); + + $collector = $container->get('beberlei_metrics.collector.prometheus'); + $this->assertInstanceOf(Prometheus::class, $collector); + } + + public function testWithInvalid(): void + { + $this->expectException(InvalidConfigurationException::class); + $this->expectExceptionMessageMatches('/^The value "invalid" is not allowed for path "beberlei_metrics.collectors.invalid.type". Permissible values: /'); + $this->createContainer(['collectors' => ['invalid' => ['type' => 'invalid']]]); + } + + private function createContainer($configs, array $publicServices = [], array $additionalServices = []): ContainerBuilder + { + $container = new ContainerBuilder(); + + $extension = new BeberleiMetricsExtension(); + $extension->load([$configs], $container); + // Needed for logger collector + $container->setDefinition('logger', new Definition(NullLogger::class)); + + foreach ($additionalServices as $serviceId => $additionalService) { + $container->set($serviceId, $additionalService); + } + + foreach ($publicServices as $serviceId) { + $container->getDefinition($serviceId)->setPublic(true); + } + + $container->compile(); + + return $container; + } + + private function getProperty(?object $object, string $property): mixed + { + return (new \ReflectionProperty($object::class, $property))->getValue($object); + } +} diff --git a/tools/php-cs-fixer/.gitignore b/tools/php-cs-fixer/.gitignore new file mode 100644 index 0000000..57872d0 --- /dev/null +++ b/tools/php-cs-fixer/.gitignore @@ -0,0 +1 @@ +/vendor/ diff --git a/tools/php-cs-fixer/composer.json b/tools/php-cs-fixer/composer.json new file mode 100644 index 0000000..b0ac7ff --- /dev/null +++ b/tools/php-cs-fixer/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "friendsofphp/php-cs-fixer": "^3.46" + } +} diff --git a/tools/php-cs-fixer/composer.lock b/tools/php-cs-fixer/composer.lock new file mode 100644 index 0000000..811d41a --- /dev/null +++ b/tools/php-cs-fixer/composer.lock @@ -0,0 +1,1823 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "df2cc2d63945b734c8edd007837e47ad", + "packages": [ + { + "name": "composer/pcre", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/00104306927c7a0919b4ced2aaa6782c1e61a3c9", + "reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9", + "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.1.1" + }, + "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": "2023-10-11T07:11:09+00:00" + }, + { + "name": "composer/semver", + "version": "3.4.0", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32", + "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32", + "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": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.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": "2023-08-31T09:50:34+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": "friendsofphp/php-cs-fixer", + "version": "v3.49.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", + "reference": "8742f7aa6f72a399688b65e4f58992c2d4681fc2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/8742f7aa6f72a399688b65e4f58992c2d4681fc2", + "reference": "8742f7aa6f72a399688b65e4f58992c2d4681fc2", + "shasum": "" + }, + "require": { + "composer/semver": "^3.4", + "composer/xdebug-handler": "^3.0.3", + "ext-filter": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0", + "sebastian/diff": "^4.0 || ^5.0", + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/event-dispatcher": "^5.4 || ^6.0 || ^7.0", + "symfony/filesystem": "^5.4 || ^6.0 || ^7.0", + "symfony/finder": "^5.4 || ^6.0 || ^7.0", + "symfony/options-resolver": "^5.4 || ^6.0 || ^7.0", + "symfony/polyfill-mbstring": "^1.28", + "symfony/polyfill-php80": "^1.28", + "symfony/polyfill-php81": "^1.28", + "symfony/process": "^5.4 || ^6.0 || ^7.0", + "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0" + }, + "require-dev": { + "facile-it/paraunit": "^1.3 || ^2.0", + "justinrainbow/json-schema": "^5.2", + "keradus/cli-executor": "^2.1", + "mikey179/vfsstream": "^1.6.11", + "php-coveralls/php-coveralls": "^2.7", + "php-cs-fixer/accessible-object": "^1.1", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.4", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.4", + "phpunit/phpunit": "^9.6 || ^10.5.5", + "symfony/yaml": "^5.4 || ^6.0 || ^7.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", + "keywords": [ + "Static code analysis", + "fixer", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.49.0" + }, + "funding": [ + { + "url": "https://github.com/keradus", + "type": "github" + } + ], + "time": "2024-02-02T00:41:40+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "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": "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": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.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/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + }, + { + "name": "sebastian/diff", + "version": "5.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "fbf413a49e54f6b9b17e12d900ac7f6101591b7f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/fbf413a49e54f6b9b17e12d900ac7f6101591b7f", + "reference": "fbf413a49e54f6b9b17e12d900ac7f6101591b7f", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/5.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-22T10:55:06+00:00" + }, + { + "name": "symfony/console", + "version": "v7.0.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "c5010d50f1ee4b25cfa0201d9915cf1b14071456" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/c5010d50f1ee4b25cfa0201d9915cf1b14071456", + "reference": "c5010d50f1ee4b25cfa0201d9915cf1b14071456", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^6.4|^7.0" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "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": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v7.0.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": "2024-01-23T15:02:46+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "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": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.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": "2023-05-23T14:45:45+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v7.0.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "834c28d533dd0636f910909d01b9ff45cc094b5e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/834c28d533dd0636f910909d01b9ff45cc094b5e", + "reference": "834c28d533dd0636f910909d01b9ff45cc094b5e", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/error-handler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "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": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v7.0.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": "2024-01-23T15:02:46+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "a76aed96a42d2b521153fb382d418e30d18b59df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/a76aed96a42d2b521153fb382d418e30d18b59df", + "reference": "a76aed96a42d2b521153fb382d418e30d18b59df", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "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 dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.4.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": "2023-05-23T14:45:45+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v7.0.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "2890e3a825bc0c0558526c04499c13f83e1b6b12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/2890e3a825bc0c0558526c04499c13f83e1b6b12", + "reference": "2890e3a825bc0c0558526c04499c13f83e1b6b12", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "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": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v7.0.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": "2024-01-23T15:02:46+00:00" + }, + { + "name": "symfony/finder", + "version": "v7.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "6e5688d69f7cfc4ed4a511e96007e06c2d34ce56" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/6e5688d69f7cfc4ed4a511e96007e06c2d34ce56", + "reference": "6e5688d69f7cfc4ed4a511e96007e06c2d34ce56", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/filesystem": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "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": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v7.0.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": "2023-10-31T17:59:56+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v7.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "700ff4096e346f54cb628ea650767c8130f1001f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/700ff4096e346f54cb628ea650767c8130f1001f", + "reference": "700ff4096e346f54cb628ea650767c8130f1001f", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "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": "Provides an improved replacement for the array_replace PHP function", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v7.0.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": "2023-08-08T10:20:21+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4", + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.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": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/32a9da87d7b3245e09ac426c83d334ae9f06f80f", + "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "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": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.29.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": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "bc45c394692b948b4d383a08d7753968bed9a83d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/bc45c394692b948b4d383a08d7753968bed9a83d", + "reference": "bc45c394692b948b4d383a08d7753968bed9a83d", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "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": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.29.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": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "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": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.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": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "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.29.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": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "c565ad1e63f30e7477fc40738343c62b40bc672d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/c565ad1e63f30e7477fc40738343c62b40bc672d", + "reference": "c565ad1e63f30e7477fc40738343c62b40bc672d", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "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": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.29.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": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/process", + "version": "v7.0.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "937a195147e0c27b2759ade834169ed006d0bc74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/937a195147e0c27b2759ade834169ed006d0bc74", + "reference": "937a195147e0c27b2759ade834169ed006d0bc74", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "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": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v7.0.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": "2024-01-23T15:02:46+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.4.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "fe07cbc8d837f60caf7018068e350cc5163681a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/fe07cbc8d837f60caf7018068e350cc5163681a0", + "reference": "fe07cbc8d837f60caf7018068e350cc5163681a0", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-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.4.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": "2023-12-26T14:02:43+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v7.0.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "983900d6fddf2b0cbaacacbbad07610854bd8112" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/983900d6fddf2b0cbaacacbbad07610854bd8112", + "reference": "983900d6fddf2b0cbaacacbbad07610854bd8112", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/service-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "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": "Provides a way to profile code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/stopwatch/tree/v7.0.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": "2024-01-23T15:02:46+00:00" + }, + { + "name": "symfony/string", + "version": "v7.0.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "524aac4a280b90a4420d8d6a040718d0586505ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/524aac4a280b90a4420d8d6a040718d0586505ac", + "reference": "524aac4a280b90a4420d8d6a040718d0586505ac", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "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.5" + }, + "require-dev": { + "symfony/error-handler": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.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/v7.0.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": "2024-01-29T15:41:16+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.6.0" +} diff --git a/tools/phpstan/.gitignore b/tools/phpstan/.gitignore new file mode 100644 index 0000000..57872d0 --- /dev/null +++ b/tools/phpstan/.gitignore @@ -0,0 +1 @@ +/vendor/ diff --git a/tools/phpstan/composer.json b/tools/phpstan/composer.json new file mode 100644 index 0000000..9cb4ed5 --- /dev/null +++ b/tools/phpstan/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "phpstan/phpstan": "^1.10.55" + } +} diff --git a/tools/phpstan/composer.lock b/tools/phpstan/composer.lock new file mode 100644 index 0000000..84c7c5f --- /dev/null +++ b/tools/phpstan/composer.lock @@ -0,0 +1,81 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "0df3d24799f35a5d623d94f4e4b6744e", + "packages": [ + { + "name": "phpstan/phpstan", + "version": "1.10.55", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "9a88f9d18ddf4cf54c922fbeac16c4cb164c5949" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9a88f9d18ddf4cf54c922fbeac16c4cb164c5949", + "reference": "9a88f9d18ddf4cf54c922fbeac16c4cb164c5949", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2024-01-08T12:32:40+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.6.0" +}